Module: API::Helpers

Includes:
Caching, Pagination, PaginationStrategies, Gitlab::Allowable, Gitlab::Ci::Artifacts::Logger, Gitlab::RackLoadBalancingHelpers, Gitlab::Utils, Gitlab::Utils::StrongMemoize
Defined in:
lib/api/helpers.rb,
lib/api/helpers/caching.rb,
lib/api/helpers/unidiff.rb,
lib/api/helpers/version.rb,
lib/api/helpers/open_api.rb,
lib/api/helpers/pagination.rb,
lib/api/helpers/award_emoji.rb,
lib/api/helpers/kas_helpers.rb,
lib/api/helpers/presentable.rb,
lib/api/helpers/blob_helpers.rb,
lib/api/helpers/packages/npm.rb,
lib/api/helpers/rate_limiter.rb,
lib/api/helpers/label_helpers.rb,
lib/api/helpers/notes_helpers.rb,
lib/api/helpers/users_helpers.rb,
lib/api/helpers/wikis_helpers.rb,
lib/api/helpers/authentication.rb,
lib/api/helpers/badges_helpers.rb,
lib/api/helpers/common_helpers.rb,
lib/api/helpers/events_helpers.rb,
lib/api/helpers/groups_helpers.rb,
lib/api/helpers/issues_helpers.rb,
lib/api/helpers/packages/maven.rb,
lib/api/helpers/packages/nuget.rb,
lib/api/helpers/search_helpers.rb,
lib/api/helpers/graphql_helpers.rb,
lib/api/helpers/headers_helpers.rb,
lib/api/helpers/members_helpers.rb,
lib/api/helpers/internal_helpers.rb,
lib/api/helpers/packages_helpers.rb,
lib/api/helpers/projects_helpers.rb,
lib/api/helpers/settings_helpers.rb,
lib/api/helpers/snippets_helpers.rb,
lib/api/helpers/custom_attributes.rb,
lib/api/helpers/variables_helpers.rb,
lib/api/helpers/web_hooks_helpers.rb,
lib/api/helpers/discussions_helpers.rb,
lib/api/helpers/file_upload_helpers.rb,
lib/api/helpers/integrations_helpers.rb,
lib/api/helpers/import_github_helpers.rb,
lib/api/helpers/pagination_strategies.rb,
lib/api/helpers/merge_requests_helpers.rb,
lib/api/helpers/protected_tags_helpers.rb,
lib/api/helpers/remote_mirrors_helpers.rb,
lib/api/helpers/performance_bar_helpers.rb,
lib/api/helpers/resource_events_helpers.rb,
lib/api/helpers/kubernetes/agent_helpers.rb,
lib/api/helpers/project_snapshots_helpers.rb,
lib/api/helpers/related_resources_helpers.rb,
lib/api/helpers/bulk_imports/audit_helpers.rb,
lib/api/helpers/container_registry_helpers.rb,
lib/api/helpers/packages/conan/api_helpers.rb,
lib/api/helpers/protected_branches_helpers.rb,
lib/api/helpers/snippets/http_response_map.rb,
lib/api/helpers/authz/postfiltering_helpers.rb,
lib/api/helpers/packages/basic_auth_helpers.rb,
lib/api/helpers/commits_body_uploader_helper.rb,
lib/api/helpers/personal_access_tokens_helpers.rb,
lib/api/helpers/packages_manager_clients_helpers.rb,
lib/api/helpers/packages/dependency_proxy_helpers.rb,
lib/api/helpers/packages/maven/basic_auth_helpers.rb,
lib/api/helpers/project_stats_refresh_conflicts_helpers.rb

Defined Under Namespace

Modules: Authentication, Authz, AwardEmoji, BadgesHelpers, BlobHelpers, BulkImports, Caching, CommitsBodyUploaderHelper, CommonHelpers, ContainerRegistryHelpers, CustomAttributes, DiscussionsHelpers, EventsHelpers, FileUploadHelpers, GraphqlHelpers, GroupsHelpers, HeadersHelpers, ImportGithubHelpers, IntegrationsHelpers, InternalHelpers, IssuesHelpers, KasHelpers, Kubernetes, LabelHelpers, MembersHelpers, MergeRequestsHelpers, NotesHelpers, OpenApi, Packages, PackagesHelpers, PackagesManagerClientsHelpers, Pagination, PaginationStrategies, PerformanceBarHelpers, PersonalAccessTokensHelpers, Presentable, ProjectSnapshotsHelpers, ProjectStatsRefreshConflictsHelpers, ProjectsHelpers, ProtectedBranchesHelpers, ProtectedTagsHelpers, RateLimiter, RelatedResourcesHelpers, RemoteMirrorsHelpers, ResourceEventsHelpers, SearchHelpers, SettingsHelpers, Snippets, SnippetsHelpers, Unidiff, UsersHelpers, VariablesHelpers, WebHooksHelpers, WikisHelpers Classes: Version

Constant Summary collapse

SUDO_HEADER =
"HTTP_SUDO"
GITLAB_SHARED_SECRET_HEADER =
"Gitlab-Shared-Secret"
SUDO_PARAM =
:sudo
API_USER_ENV =
'gitlab.api.user'
API_EXCEPTION_ENV =
'gitlab.api.exception'
API_RESPONSE_STATUS_CODE =
'gitlab.api.response_status_code'
INTEGER_ID_REGEX =
/^-?\d+$/
TOKEN_SCOPES_TO_AUDIT =

ai_workflows scope is used by Duo Workflow which is an AI automation tool, requests authenticated by token with this scope are audited to keep track of all actions done by Duo Workflow.

[:ai_workflows].freeze

Constants included from Caching

Caching::DEFAULT_CACHE_OPTIONS, Caching::PAGINATION_HEADERS

Constants included from Gitlab::Cache::Helpers

Gitlab::Cache::Helpers::DEFAULT_EXPIRY

Instance Method Summary collapse

Methods included from Gitlab::RackLoadBalancingHelpers

#load_balancer_stick_request

Methods included from Gitlab::Ci::Artifacts::Logger

#log_artifacts_context, #log_artifacts_filesize, #log_build_dependencies, log_created, log_deleted

Methods included from PaginationStrategies

#paginate_with_strategies, #paginator

Methods included from Pagination

#paginate

Methods included from Caching

#cache_action, #cache_action_if, #cache_action_unless, #present_cached

Methods included from Gitlab::Cache::Helpers

#cache, #render_cached

Methods included from Gitlab::Allowable

#can?, #can_all?, #can_any?

Instance Method Details

#accepted!(message = nil) ⇒ Object



619
620
621
# File 'lib/api/helpers.rb', line 619

def accepted!(message = nil)
  render_api_error!(message || '202 Accepted', 202)
end

#attributes_for_keys(keys, custom_params = nil) ⇒ Object



501
502
503
504
505
506
507
508
509
510
511
# File 'lib/api/helpers.rb', line 501

def attributes_for_keys(keys, custom_params = nil)
  params_hash = custom_params || params
  attrs = {}
  keys.each do |key|
    if params_hash[key].present? || (params_hash.key?(key) && params_hash[key] == false)
      attrs[key] = params_hash[key]
    end
  end
  permitted_attrs = ActionController::Parameters.new(attrs).permit!
  permitted_attrs.to_h
end

#authenticate!Object



361
362
363
# File 'lib/api/helpers.rb', line 361

def authenticate!
  unauthorized! unless current_user
end

#authenticate_by_gitlab_shell_or_workhorse_token!Object



373
374
375
376
377
# File 'lib/api/helpers.rb', line 373

def authenticate_by_gitlab_shell_or_workhorse_token!
  return require_gitlab_workhorse! unless Gitlab::Shell.header_set?(headers)

  authenticate_by_gitlab_shell_token!
end

#authenticate_by_gitlab_shell_token!Object



369
370
371
# File 'lib/api/helpers.rb', line 369

def authenticate_by_gitlab_shell_token!
  unauthorized! unless Gitlab::Shell.verify_api_request(headers)
end

#authenticate_non_get!Object



365
366
367
# File 'lib/api/helpers.rb', line 365

def authenticate_non_get!
  authenticate! unless %w[GET HEAD].include?(route.request_method)
end

#authenticated_as_admin!Object



384
385
386
387
# File 'lib/api/helpers.rb', line 384

def authenticated_as_admin!
  authenticate!
  forbidden! unless current_user.can_admin_all_resources?
end

#authenticated_with_can_read_all_resources!Object



379
380
381
382
# File 'lib/api/helpers.rb', line 379

def authenticated_with_can_read_all_resources!
  authenticate!
  forbidden! unless current_user.can_read_all_resources?
end

#authorize!(action, subject = :global, reason = nil) ⇒ Object



394
395
396
# File 'lib/api/helpers.rb', line 394

def authorize!(action, subject = :global, reason = nil)
  forbidden!(reason) unless can?(current_user, action, subject)
end

#authorize_admin_groupObject



422
423
424
# File 'lib/api/helpers.rb', line 422

def authorize_admin_group
  authorize! :admin_group, user_group
end

#authorize_admin_group_integrationsObject



418
419
420
# File 'lib/api/helpers.rb', line 418

def authorize_admin_group_integrations
  authorize! :admin_integrations, user_group
end

#authorize_admin_member_role_on_group!Object



426
427
428
# File 'lib/api/helpers.rb', line 426

def authorize_admin_member_role_on_group!
  authorize! :admin_member_role, user_group
end

#authorize_admin_member_role_on_instance!Object



430
431
432
# File 'lib/api/helpers.rb', line 430

def authorize_admin_member_role_on_instance!
  authorize! :admin_member_role
end

#authorize_admin_projectObject



410
411
412
# File 'lib/api/helpers.rb', line 410

def authorize_admin_project
  authorize! :admin_project, user_project
end

#authorize_admin_project_integrationsObject



414
415
416
# File 'lib/api/helpers.rb', line 414

def authorize_admin_project_integrations
  authorize! :admin_integrations, user_project
end

#authorize_admin_tagObject



406
407
408
# File 'lib/api/helpers.rb', line 406

def authorize_admin_tag
  authorize! :admin_tag, user_project
end

#authorize_any!(abilities, subject = :global, reason = nil) ⇒ Object



398
399
400
# File 'lib/api/helpers.rb', line 398

def authorize_any!(abilities, subject = :global, reason = nil)
  forbidden!(reason) unless can_any?(current_user, abilities, subject)
end

#authorize_cancel_builds!Object



458
459
460
# File 'lib/api/helpers.rb', line 458

def authorize_cancel_builds!
  authorize! :cancel_build, user_project
end

#authorize_delete_job_artifact!Object



454
455
456
# File 'lib/api/helpers.rb', line 454

def authorize_delete_job_artifact!
  authorize! :delete_job_artifact, user_project
end

#authorize_job_token_policies!(project) ⇒ Object



194
195
196
# File 'lib/api/helpers.rb', line 194

def authorize_job_token_policies!(project)
  forbidden!(job_token_policies_unauthorized_message(project)) unless job_token_policies_authorized?(project)
end

#authorize_push_projectObject



402
403
404
# File 'lib/api/helpers.rb', line 402

def authorize_push_project
  authorize! :push_code, user_project
end

#authorize_read_application_statistics!Object



389
390
391
392
# File 'lib/api/helpers.rb', line 389

def authorize_read_application_statistics!
  authenticate!
  forbidden! unless current_user.can?(:read_application_statistics)
end

#authorize_read_attestations!Object



434
435
436
# File 'lib/api/helpers.rb', line 434

def authorize_read_attestations!
  authorize! :read_attestation, user_project
end

#authorize_read_build_trace!(build) ⇒ Object



446
447
448
# File 'lib/api/helpers.rb', line 446

def authorize_read_build_trace!(build)
  authorize! :read_build_trace, build
end

#authorize_read_builds!Object



438
439
440
# File 'lib/api/helpers.rb', line 438

def authorize_read_builds!
  authorize! :read_build, user_project
end

#authorize_read_code!Object



442
443
444
# File 'lib/api/helpers.rb', line 442

def authorize_read_code!
  authorize! :read_code, user_project
end

#authorize_read_job_artifacts!(build) ⇒ Object



450
451
452
# File 'lib/api/helpers.rb', line 450

def authorize_read_job_artifacts!(build)
  authorize! :read_job_artifacts, build
end

#authorized_project_scope?(project) ⇒ Boolean

Returns:

  • (Boolean)


202
203
204
205
206
207
# File 'lib/api/helpers.rb', line 202

def authorized_project_scope?(project)
  return true unless job_token_authentication?
  return true unless route_authentication_setting[:job_token_scope] == :project

  current_authenticated_job.project == project
end

#available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true }) ⇒ Object



137
138
139
140
141
142
143
144
145
146
# File 'lib/api/helpers.rb', line 137

def available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true })
  if label_parent.is_a?(Project)
    params.delete(:only_group_labels)
    params[:project_id] = label_parent.id
  else
    params[:group_id] = label_parent.id
  end

  LabelsFinder.new(current_user, params).execute
end

#bad_request!(reason = nil) ⇒ Object



552
553
554
# File 'lib/api/helpers.rb', line 552

def bad_request!(reason = nil)
  render_api_error_with_reason!(400, '400 Bad request', reason)
end

#bad_request_missing_attribute!(attribute) ⇒ Object



556
557
558
# File 'lib/api/helpers.rb', line 556

def bad_request_missing_attribute!(attribute)
  bad_request!("\"#{attribute}\" not given")
end

#check_group_access(group) ⇒ Object



264
265
266
267
268
269
# File 'lib/api/helpers.rb', line 264

def check_group_access(group)
  return group if can?(current_user, :read_group, group)
  return unauthorized! if authenticate_non_public?

  not_found!('Group')
end

#check_namespace_access(namespace) ⇒ Object



271
272
273
274
275
# File 'lib/api/helpers.rb', line 271

def check_namespace_access(namespace)
  return namespace if can?(current_user, :read_namespace_via_membership, namespace)

  not_found!('Namespace')
end

#check_pipeline_access(pipeline) ⇒ Object



224
225
226
227
228
229
230
231
# File 'lib/api/helpers.rb', line 224

def check_pipeline_access(pipeline)
  return forbidden! unless authorized_project_scope?(pipeline&.project)

  return pipeline if can?(current_user, :read_pipeline, pipeline)
  return unauthorized! if authenticate_non_public?

  not_found!('Pipeline')
end

#check_sha_param!(params, merge_request) ⇒ Object



567
568
569
570
571
# File 'lib/api/helpers.rb', line 567

def check_sha_param!(params, merge_request)
  if params[:sha] && merge_request.diff_head_sha != params[:sha]
    render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409)
  end
end

#check_unmodified_since!(last_modified) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/api/helpers.rb', line 35

def check_unmodified_since!(last_modified)
  if_unmodified_since = begin
    Time.parse(headers['If-Unmodified-Since'])
  rescue StandardError
    nil
  end

  if if_unmodified_since && last_modified && last_modified > if_unmodified_since
    render_api_error!('412 Precondition Failed', 412)
  end
end

#conflict!(message = nil) ⇒ Object



589
590
591
# File 'lib/api/helpers.rb', line 589

def conflict!(message = nil)
  render_api_error!(message || '409 Conflict', 409)
end

#created!(message = nil) ⇒ Object



615
616
617
# File 'lib/api/helpers.rb', line 615

def created!(message = nil)
  render_api_error!(message || '201 Created', 201)
end

#current_authenticated_jobObject

Returns the job associated with the token provided for authentication, if any



68
69
70
71
72
73
74
# File 'lib/api/helpers.rb', line 68

def current_authenticated_job
  if try(:namespace_inheritable, :authentication)
    ci_build_from_namespace_inheritable
  else
    @current_authenticated_job # rubocop:disable Gitlab/ModuleWithInstanceVariables
  end
end

#current_userObject

rubocop:disable Gitlab/ModuleWithInstanceVariables We can’t rewrite this with StrongMemoize because sudo! would actually write to ‘@current_user`, and sudo? would immediately call current_user again which reads from `@current_user`. We should rewrite this in a way that using StrongMemoize is possible



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
# File 'lib/api/helpers.rb', line 81

def current_user
  return @current_user if defined?(@current_user)

  @current_user = initial_current_user

  Gitlab::I18n.locale = @current_user&.preferred_language

  sudo!

  unless sudo?
    token = validate_and_save_access_token!(scopes: scopes_registered_for_endpoint)

    if token
      result = ::Authz::Tokens::AuthorizeGranularScopesService.new(
        boundary: boundary_for_endpoint, permissions: permissions_for_endpoint, token: token
      ).execute

      raise Gitlab::Auth::GranularPermissionsError, result.message if result.error?
    end
  end

  save_current_user_in_env(@current_user) if @current_user

  if @current_user
    load_balancer_stick_request(::ApplicationRecord, :user, @current_user.id)
    audit_request_with_token_scope(@current_user)
  end

  @current_user
end

#declared_params(options = {}) ⇒ Object



30
31
32
33
# File 'lib/api/helpers.rb', line 30

def declared_params(options = {})
  options = { include_parent_namespaces: false }.merge(options)
  declared(params, options).to_h.symbolize_keys
end

#destroy_conditionally!(resource, last_updated: nil) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/api/helpers.rb', line 47

def destroy_conditionally!(resource, last_updated: nil)
  last_updated ||= resource.updated_at

  check_unmodified_since!(last_updated)

  status 204
  body false

  if block_given?
    yield resource
  else
    resource.destroy
  end
end

#file_too_large!(message = nil) ⇒ Object



597
598
599
# File 'lib/api/helpers.rb', line 597

def file_too_large!(message = nil)
  render_api_error!(message || '413 Request Entity Too Large', 413)
end

#filter_by_iid(items, iid) ⇒ Object



513
514
515
# File 'lib/api/helpers.rb', line 513

def filter_by_iid(items, iid)
  items.iid_in(iid)
end

#filter_by_search(items, text) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



523
524
525
# File 'lib/api/helpers.rb', line 523

def filter_by_search(items, text)
  items.search(text)
end

#filter_by_title(items, title) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



518
519
520
# File 'lib/api/helpers.rb', line 518

def filter_by_title(items, title)
  items.where(title: title)
end

#find_branch!(branch_name) ⇒ Object



307
308
309
310
311
312
313
# File 'lib/api/helpers.rb', line 307

def find_branch!(branch_name)
  if Gitlab::GitRefValidator.validate(branch_name)
    user_project.repository.find_branch(branch_name) || not_found!('Branch')
  else
    render_api_error!('The branch refname is invalid', 400)
  end
end

#find_build!(id) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



353
354
355
# File 'lib/api/helpers.rb', line 353

def find_build!(id)
  user_project.builds.find(id.to_i)
end

#find_group(id, organization: nil) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



239
240
241
242
243
244
245
246
247
# File 'lib/api/helpers.rb', line 239

def find_group(id, organization: nil)
  collection = organization.present? ? Group.in_organization(organization) : Group.all

  if INTEGER_ID_REGEX.match?(id.to_s)
    collection.find_by(id: id)
  else
    collection.find_by_full_path(id)
  end
end

#find_group!(id, organization: nil) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



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

def find_group!(id, organization: nil)
  group = find_group(id, organization: organization)
  # We need to ensure the namespace is in the context since
  # it's possible a method such as bypass_session! might log
  # a message before @group is set.
  ::Gitlab::ApplicationContext.push(namespace: group) if group
  check_group_access(group)
end

#find_group_by_full_path!(full_path) ⇒ Object



259
260
261
262
# File 'lib/api/helpers.rb', line 259

def find_group_by_full_path!(full_path)
  group = Group.find_by_full_path(full_path)
  check_group_access(group)
end

#find_job!(id) ⇒ Object



357
358
359
# File 'lib/api/helpers.rb', line 357

def find_job!(id)
  user_project.processables.find(id.to_i)
end

#find_merge_request_with_access(iid, access_level = :read_merge_request) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



346
347
348
349
350
# File 'lib/api/helpers.rb', line 346

def find_merge_request_with_access(iid, access_level = :read_merge_request)
  merge_request = user_project.merge_requests.find_by!(iid: iid)
  authorize! access_level, merge_request
  merge_request
end

#find_namespace(id) ⇒ Object

find_namespace returns the namespace regardless of user access level on the namespace rubocop: disable CodeReuse/ActiveRecord



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

def find_namespace(id)
  if INTEGER_ID_REGEX.match?(id.to_s)
    # We need to stick to an up-to-date replica or primary db here in order to properly observe the namespace
    # recently created by GitlabSubscriptions::Trials::UltimateCreateService.
    # See https://gitlab.com/gitlab-org/customers-gitlab-com/-/issues/9808
    ::Namespace.sticking.find_caught_up_replica(:namespace, id)

    Namespace.without_project_namespaces.find_by(id: id)
  else
    find_namespace_by_path(id)
  end
end

#find_namespace!(id) ⇒ Object

find_namespace! returns the namespace if the current user can read the given namespace Otherwise, returns a not_found! error



295
296
297
# File 'lib/api/helpers.rb', line 295

def find_namespace!(id)
  check_namespace_access(find_namespace(id))
end

#find_namespace_by_path(path) ⇒ Object



299
300
301
# File 'lib/api/helpers.rb', line 299

def find_namespace_by_path(path)
  Namespace.without_project_namespaces.find_by_full_path(path)
end

#find_namespace_by_path!(path) ⇒ Object



303
304
305
# File 'lib/api/helpers.rb', line 303

def find_namespace_by_path!(path)
  check_namespace_access(find_namespace_by_path(path))
end

#find_organization!(id) ⇒ Object



233
234
235
236
# File 'lib/api/helpers.rb', line 233

def find_organization!(id)
  organization = ::Organizations::Organization.find_by_id(id)
  check_organization_access(organization)
end

#find_pipeline(id) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



210
211
212
213
214
215
216
# File 'lib/api/helpers.rb', line 210

def find_pipeline(id)
  return unless id

  if INTEGER_ID_REGEX.match?(id.to_s)
    ::Ci::Pipeline.find_by(id: id)
  end
end

#find_pipeline!(id) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



219
220
221
222
# File 'lib/api/helpers.rb', line 219

def find_pipeline!(id)
  pipeline = find_pipeline(id)
  check_pipeline_access(pipeline)
end

#find_project(id) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



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

def find_project(id)
  return unless id

  projects = find_project_scopes

  if id.is_a?(Integer) || id =~ INTEGER_ID_REGEX
    projects.find_by(id: id)
  elsif id.include?("/")
    projects.find_by_full_path(id, follow_redirects: true)
  end
end

#find_project!(id) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/api/helpers.rb', line 171

def find_project!(id)
  project = find_project(id)

  return forbidden!("This project's CI/CD job token cannot be used to authenticate with the container registry of a different project.") unless authorized_project_scope?(project)
  return not_found!('Project') if project.nil?

  unless can?(current_user, read_project_ability, project)
    return unauthorized! if authenticate_non_public?

    return handle_job_token_failure!(project)
  end

  authorize_job_token_policies!(project) && return

  if project_moved?(id, project)
    return not_allowed!('Non GET methods are not allowed for moved projects') unless request.get?

    return redirect!(url_with_project_id(project))
  end

  project
end

#find_project_commit(id) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



341
342
343
# File 'lib/api/helpers.rb', line 341

def find_project_commit(id)
  user_project.commit_by(oid: id)
end

#find_project_issue(iid, project_id = nil) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



324
325
326
327
328
329
330
331
332
# File 'lib/api/helpers.rb', line 324

def find_project_issue(iid, project_id = nil)
  project = project_id ? find_project!(project_id) : user_project

  ::IssuesFinder.new(
    current_user,
    project_id: project.id,
    issue_types: ::WorkItems::TypesFilter.allowed_types_for_issues
  ).find_by!(iid: iid)
end

#find_project_merge_request(iid, project: user_project) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



336
337
338
# File 'lib/api/helpers.rb', line 336

def find_project_merge_request(iid, project: user_project)
  MergeRequestsFinder.new(current_user, project_id: project.id).find_by!(iid: iid)
end

#find_project_scopesObject

Can be overriden by API endpoints



167
168
169
# File 'lib/api/helpers.rb', line 167

def find_project_scopes
  Project.without_deleted.not_hidden
end

#find_tag!(tag_name) ⇒ Object



315
316
317
318
319
320
321
# File 'lib/api/helpers.rb', line 315

def find_tag!(tag_name)
  if Gitlab::GitRefValidator.validate(tag_name)
    user_project.repository.find_tag(tag_name) || not_found!('Tag')
  else
    render_api_error!('The tag refname is invalid', 400)
  end
end

#find_user(id) ⇒ Object



148
149
150
# File 'lib/api/helpers.rb', line 148

def find_user(id)
  UserFinder.new(id).find_by_id_or_username
end

#forbidden!(reason = nil) ⇒ Object

error helpers



548
549
550
# File 'lib/api/helpers.rb', line 548

def forbidden!(reason = nil)
  render_api_error_with_reason!(403, '403 Forbidden', reason)
end

#handle_api_exception(exception) ⇒ Object



661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
# File 'lib/api/helpers.rb', line 661

def handle_api_exception(exception)
  if report_exception?(exception)
    context_user = begin
      current_user
    rescue StandardError
      nil
    end
    define_params_for_grape_middleware
    Gitlab::ApplicationContext.push(user: context_user, remote_ip: request.ip)
    Gitlab::ErrorTracking.track_exception(exception)
  end

  # This is used with GrapeLogging::Loggers::ExceptionLogger
  env[API_EXCEPTION_ENV] = exception

  # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
  trace = exception.backtrace

  message = ["\n#{exception.class} (#{exception.message}):\n"]
  message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
  message << "  " << trace.join("\n  ")
  message = message.join

  API.logger.add Logger::FATAL, message

  response_message =
    if Rails.env.test?
      message
    else
      '500 Internal Server Error'
    end

  rack_response({ 'message' => response_message }.to_json, 500)
end

#increment_counter(event_name) ⇒ Object



774
775
776
777
778
# File 'lib/api/helpers.rb', line 774

def increment_counter(event_name)
  Gitlab::UsageDataCounters.count(event_name)
rescue StandardError => error
  Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
end

#increment_unique_values(event_name, values) ⇒ Object

Parameters:

  • event_name (String)

    the event name

  • values (Array|String)

    the values counted



782
783
784
785
786
787
788
# File 'lib/api/helpers.rb', line 782

def increment_unique_values(event_name, values)
  return unless values.present?

  Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: values)
rescue StandardError => error
  Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
end

#job_token_authentication?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/api/helpers.rb', line 62

def job_token_authentication?
  initial_current_user && @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
end

#loggerObject



26
27
28
# File 'lib/api/helpers.rb', line 26

def logger
  API.logger
end

#model_errors(model) ⇒ Object



633
634
635
# File 'lib/api/helpers.rb', line 633

def model_errors(model)
  model.errors
end

#no_content!(message = nil) ⇒ Object



611
612
613
# File 'lib/api/helpers.rb', line 611

def no_content!(message = nil)
  render_api_error!(message || '204 No Content', 204)
end

#not_acceptable!(message = nil) ⇒ Object



581
582
583
# File 'lib/api/helpers.rb', line 581

def not_acceptable!(message = nil)
  render_api_error!(message || '406 Not Acceptable', 406)
end

#not_allowed!(message = nil) ⇒ Object



577
578
579
# File 'lib/api/helpers.rb', line 577

def not_allowed!(message = nil)
  render_api_error!(message || '405 Method Not Allowed', :method_not_allowed)
end

#not_found!(resource = nil) ⇒ Object



560
561
562
563
564
565
# File 'lib/api/helpers.rb', line 560

def not_found!(resource = nil)
  message = ["404"]
  message << resource if resource
  message << "Not Found"
  render_api_error!(message.join(' '), 404)
end

#not_modified!(message = nil) ⇒ Object



607
608
609
# File 'lib/api/helpers.rb', line 607

def not_modified!(message = nil)
  render_api_error!(message || '304 Not Modified', 304)
end

#order_by_similarity?(allow_unauthorized: true) ⇒ Boolean

Returns:

  • (Boolean)


814
815
816
# File 'lib/api/helpers.rb', line 814

def order_by_similarity?(allow_unauthorized: true)
  params[:order_by] == 'similarity' && params[:search].present? && (allow_unauthorized || current_user.present?)
end

#order_options_with_tie_breaker(override_created_at: true) ⇒ Object



527
528
529
530
531
532
533
534
535
536
537
# File 'lib/api/helpers.rb', line 527

def order_options_with_tie_breaker(override_created_at: true)
  order_by = if params[:order_by] == 'created_at' && override_created_at
               'id'
             else
               params[:order_by]
             end

  order_options = { order_by => params[:sort] }
  order_options['id'] ||= params[:sort] || 'asc'
  order_options
end

#present_artifacts_file!(file, **args) ⇒ Object



727
728
729
730
731
# File 'lib/api/helpers.rb', line 727

def present_artifacts_file!(file, **args)
  log_artifacts_filesize(file&.model)

  present_carrierwave_file!(file, **args)
end

#present_carrierwave_file!(file, supports_direct_download: true, content_disposition: nil, content_type: nil, extra_response_headers: {}, extra_send_url_params: {}) ⇒ Object

Return back the given file depending on the object storage configuration. For disabled mode, the disk file is returned. For enabled mode, the response depends on the direct download support:

* direct download supported by the uploader class: a redirect to the file signed url is returned.
* direct download not supported: a workhorse send_url response is returned.

Params:



746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/api/helpers.rb', line 746

def present_carrierwave_file!(file, supports_direct_download: true, content_disposition: nil, content_type: nil, extra_response_headers: {}, extra_send_url_params: {})
  return not_found! unless file&.exists?

  if content_disposition
    response_disposition = ActionDispatch::Http::ContentDisposition.format(disposition: content_disposition, filename: file.filename)
  end

  if file.file_storage?
    present_disk_file!(file.path, file.filename, content_type: content_type, extra_response_headers: extra_response_headers)
  elsif supports_direct_download && file.direct_download_enabled?
    return redirect(ObjectStorage::S3.signed_head_url(file)) if request.head? && file.fog_credentials[:provider] == 'AWS'

    redirect_params = {}
    if content_disposition
      redirect_params[:query] = { 'response-content-disposition' => response_disposition, 'response-content-type' => content_type || file.content_type }
    end

    file_url = ObjectStorage::CDN::FileUrl.new(file: file, ip_address: ip_address, redirect_params: redirect_params)
    redirect(file_url.url)
  else
    response_headers = extra_response_headers.merge('Content-Type' => content_type, 'Content-Disposition' => response_disposition).compact_blank

    header(*Gitlab::Workhorse.send_url(file.url, response_headers: response_headers, **extra_send_url_params))
    status :ok
    body '' # to avoid an error from API::APIGuard::ResponseCoercerMiddleware
  end
end

#present_disk_file!(path, filename, content_type: nil, extra_response_headers: {}) ⇒ Object

file helpers



710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
# File 'lib/api/helpers.rb', line 710

def present_disk_file!(path, filename, content_type: nil, extra_response_headers: {})
  filename ||= File.basename(path)
  extra_response_headers.compact_blank.each { |k, v| header[k] = v }
  header['Content-Disposition'] = ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: filename)
  header['Content-Transfer-Encoding'] = 'binary'
  content_type(content_type || 'application/octet-stream')

  # Support download acceleration
  case headers['X-Sendfile-Type']
  when 'X-Sendfile'
    header['X-Sendfile'] = path
    body '' # to avoid an error from API::APIGuard::ResponseCoercerMiddleware
  else
    sendfile path
  end
end

#process_create_params(args) ⇒ Object



78
79
80
81
82
83
84
85
86
# File 'lib/api/helpers/snippets_helpers.rb', line 78

def process_create_params(args)
  args[:snippet_actions] = args.delete(:files)&.map do |file|
    file[:action] = :create
    file.symbolize_keys
  end
  args[:organization_id] = Current.organization.id

  args
end

#process_update_params(args) ⇒ Object



88
89
90
91
92
# File 'lib/api/helpers/snippets_helpers.rb', line 88

def process_update_params(args)
  args[:snippet_actions] = args.delete(:files)&.map(&:symbolize_keys)

  args
end

#project_finder_paramsObject

rubocop: enable CodeReuse/ActiveRecord



704
705
706
# File 'lib/api/helpers.rb', line 704

def project_finder_params
  project_finder_params_ce.merge(project_finder_params_ee)
end

#read_project_abilityObject



198
199
200
# File 'lib/api/helpers.rb', line 198

def read_project_ability
  :read_project
end

#redirect!(location_url) ⇒ Object

An error is raised to interrupt user’s request and redirect them to the right route. The error! helper behaves similarly, but it cannot be used because it formats the response message.



542
543
544
# File 'lib/api/helpers.rb', line 542

def redirect!(location_url)
  raise ::API::API::MovedPermanentlyError, location_url
end

#render_api_error!(message, status) ⇒ Object



643
644
645
# File 'lib/api/helpers.rb', line 643

def render_api_error!(message, status)
  render_structured_api_error!({ 'message' => message }, status)
end

#render_api_error_with_reason!(status, message, reason) ⇒ Object



637
638
639
640
641
# File 'lib/api/helpers.rb', line 637

def render_api_error_with_reason!(status, message, reason)
  message = [message]
  message << "- #{reason}" if reason
  render_api_error!(message.join(' '), status)
end

#render_structured_api_error!(hash, status) ⇒ Object



647
648
649
650
651
652
# File 'lib/api/helpers.rb', line 647

def render_structured_api_error!(hash, status)
  # Use this method instead of `render_api_error!` when you have additional top-level
  # hash entries in addition to 'message' which need to be passed to `#error!`
  set_status_code_in_env(status)
  error!(hash, status, header)
end

#render_validation_error!(models, status = 400) ⇒ Object



623
624
625
626
627
628
629
630
631
# File 'lib/api/helpers.rb', line 623

def render_validation_error!(models, status = 400)
  models = Array(models)

  errors = models.map { |m| model_errors(m) }.filter(&:present?)
  messages = errors.map(&:messages)
  messages = messages.count == 1 ? messages.first : messages.join(" ")

  render_api_error!(messages || '400 Bad Request', status) if errors.any?
end

#reorder_projects(projects) ⇒ Object

rubocop: disable CodeReuse/ActiveRecord



699
700
701
# File 'lib/api/helpers.rb', line 699

def reorder_projects(projects)
  projects.reorder(order_options_with_tie_breaker)
end

#require_gitlab_workhorse!Object



466
467
468
469
470
471
472
# File 'lib/api/helpers.rb', line 466

def require_gitlab_workhorse!
  verify_workhorse_api!

  unless env['HTTP_GITLAB_WORKHORSE'].present?
    forbidden!('Request should be executed via GitLab Workhorse')
  end
end

#require_pages_config_enabled!Object



486
487
488
# File 'lib/api/helpers.rb', line 486

def require_pages_config_enabled!
  not_found! unless Gitlab.config.pages.enabled
end

#require_pages_enabled!Object



482
483
484
# File 'lib/api/helpers.rb', line 482

def require_pages_enabled!
  not_found! unless ::Gitlab::Pages.enabled?
end

#require_repository_enabled!(subject = :global) ⇒ Object



462
463
464
# File 'lib/api/helpers.rb', line 462

def require_repository_enabled!(subject = :global)
  not_found!("Repository") unless user_project.feature_available?(:repository, current_user)
end

#required_attributes!(keys) ⇒ Object

Checks the occurrences of required attributes, each attribute must be present in the params hash or a Bad Request error is invoked.

Parameters:

keys (required) - A hash consisting of keys that must be present


495
496
497
498
499
# File 'lib/api/helpers.rb', line 495

def required_attributes!(keys)
  keys.each do |key|
    bad_request_missing_attribute!(key) unless params[key].present?
  end
end

#save_current_user_in_env(user) ⇒ Object



121
122
123
# File 'lib/api/helpers.rb', line 121

def save_current_user_in_env(user)
  env[API_USER_ENV] = { user_id: user.id, username: user.username }
end

#service_unavailable!(message = nil) ⇒ Object



585
586
587
# File 'lib/api/helpers.rb', line 585

def service_unavailable!(message = nil)
  render_api_error!(message || '503 Service Unavailable', 503)
end

#set_current_organization(user: current_user) ⇒ Object

rubocop:enable Gitlab/ModuleWithInstanceVariables



113
114
115
116
117
118
119
# File 'lib/api/helpers.rb', line 113

def set_current_organization(user: current_user)
  ::Current.organization = Gitlab::Current::Organization.new(
    params: {},
    user: user,
    rack_env: request.env
  ).organization
end

#set_status_code_in_env(status) ⇒ Object



654
655
656
657
658
659
# File 'lib/api/helpers.rb', line 654

def set_status_code_in_env(status)
  # grape-logging doesn't pass the status code, so this is a
  # workaround for getting that information in the loggers:
  # https://github.com/aserafin/grape_logging/issues/71
  env[API_RESPONSE_STATUS_CODE] = Rack::Utils.status_code(status)
end

#sudo?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/api/helpers.rb', line 125

def sudo?
  initial_current_user != current_user
end

#too_many_requests!(message = nil, retry_after: 1.minute) ⇒ Object



601
602
603
604
605
# File 'lib/api/helpers.rb', line 601

def too_many_requests!(message = nil, retry_after: 1.minute)
  header['Retry-After'] = retry_after.to_i if retry_after

  render_api_error!(message || '429 Too Many Requests', 429)
end

#track_event(event_name, user:, send_snowplow_event: true, namespace_id: nil, project_id: nil, additional_properties: {}) ⇒ Object



790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'lib/api/helpers.rb', line 790

def track_event(event_name, user:, send_snowplow_event: true, namespace_id: nil, project_id: nil, additional_properties: {})
  return unless user.present?

  namespace = Namespace.find(namespace_id) if namespace_id
  project = Project.find(project_id) if project_id

  Gitlab::InternalEvents.track_event(
    event_name,
    send_snowplow_event: send_snowplow_event,
    additional_properties: additional_properties,
    user: user,
    namespace: namespace,
    project: project
  )
rescue Gitlab::Tracking::EventValidator::UnknownEventError => e
  Gitlab::ErrorTracking.track_exception(e, event_name: event_name)

  # We want to keep the error silent on production to keep the behavior
  # consistent with StandardError rescue
  unprocessable_entity!(e.message) if Gitlab.dev_or_test_env?
rescue StandardError => e
  Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, event_name: event_name)
end

#unauthorized!(reason = nil) ⇒ Object



573
574
575
# File 'lib/api/helpers.rb', line 573

def unauthorized!(reason = nil)
  render_api_error_with_reason!(401, '401 Unauthorized', reason)
end

#unprocessable_entity!(message = nil) ⇒ Object



593
594
595
# File 'lib/api/helpers.rb', line 593

def unprocessable_entity!(message = nil)
  render_api_error!(message || '422 Unprocessable Entity', :unprocessable_entity)
end

#user_groupObject



129
130
131
# File 'lib/api/helpers.rb', line 129

def user_group
  @group ||= find_group!(params[:id])
end

#user_projectObject



133
134
135
# File 'lib/api/helpers.rb', line 133

def user_project
  @project ||= find_project!(params[:id])
end

#validate_params_for_multiple_files(snippet) ⇒ Object



94
95
96
97
98
99
100
# File 'lib/api/helpers/snippets_helpers.rb', line 94

def validate_params_for_multiple_files(snippet)
  return unless params[:content] || params[:file_name]

  if snippet.multiple_files?
    render_api_error!({ error: _('To update Snippets with multiple files, you must use the `files` parameter') }, 400)
  end
end

#verify_workhorse_api!Object



474
475
476
477
478
479
480
# File 'lib/api/helpers.rb', line 474

def verify_workhorse_api!
  Gitlab::Workhorse.verify_api_request!(request.headers)
rescue StandardError => e
  Gitlab::ErrorTracking.track_exception(e)

  forbidden!
end