diff options
Diffstat (limited to 'lib/api')
51 files changed, 448 insertions, 155 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index b8135539cda..fb67258f331 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -159,7 +159,6 @@ module API mount ::API::Keys mount ::API::Labels mount ::API::Lint - mount ::API::LsifData mount ::API::Markdown mount ::API::Members mount ::API::MergeRequestDiffs @@ -170,6 +169,7 @@ module API mount ::API::Notes mount ::API::Discussions mount ::API::ResourceLabelEvents + mount ::API::ResourceMilestoneEvents mount ::API::NotificationSettings mount ::API::Pages mount ::API::PagesDomains diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index e86bcc19b2b..11340e91aae 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -25,7 +25,8 @@ module API get "deploy_keys" do authenticated_as_admin! - present paginate(DeployKey.all), with: Entities::SSHKey + deploy_keys = DeployKey.all.preload_users + present paginate(deploy_keys), with: Entities::SSHKey end params do @@ -42,7 +43,7 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ":id/deploy_keys" do - keys = user_project.deploy_keys_projects.preload(:deploy_key) + keys = user_project.deploy_keys_projects.preload(deploy_key: [:user]) present paginate(keys), with: Entities::DeployKeysProject end diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 0dd1850e526..7b453ada41c 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -78,6 +78,8 @@ module API optional :line_range, type: Hash, desc: 'Multi-line start and end' do requires :start_line_code, type: String, desc: 'Start line code for multi-line note' requires :end_line_code, type: String, desc: 'End line code for multi-line note' + requires :start_line_type, type: String, desc: 'Start line type for multi-line note' + requires :end_line_type, type: String, desc: 'End line type for multi-line note' end end end diff --git a/lib/api/entities/bridge.rb b/lib/api/entities/bridge.rb new file mode 100644 index 00000000000..8f0ee69399a --- /dev/null +++ b/lib/api/entities/bridge.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class Bridge < Entities::JobBasic + expose :downstream_pipeline, with: Entities::PipelineBasic + end + end +end diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index 6250f35c7cb..cff627ab50a 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -16,6 +16,7 @@ module API expose :project_id expose :location expose :created_at + expose :tags_count, if: -> (_, options) { options[:tags_count] } expose :tags, using: Tag, if: -> (_, options) { options[:tags] } end diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb index e03047a6e75..93dc41da81d 100644 --- a/lib/api/entities/group_detail.rb +++ b/lib/api/entities/group_detail.rb @@ -3,6 +3,9 @@ module API module Entities class GroupDetail < Group + expose :shared_with_groups do |group, options| + SharedGroupWithGroup.represent(group.shared_with_group_links.public_or_visible_to_user(group, options[:current_user])) + end expose :runners_token, if: lambda { |group, options| options[:user_can_admin_group] } expose :projects, using: Entities::Project do |group, options| projects = GroupProjectsFinder.new( diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 1a89a83a619..1643f267938 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -6,19 +6,15 @@ module API expose :merged_by, using: Entities::UserBasic do |merge_request, _options| merge_request.metrics&.merged_by end - expose :merged_at do |merge_request, _options| merge_request.metrics&.merged_at end - expose :closed_by, using: Entities::UserBasic do |merge_request, _options| merge_request.metrics&.latest_closed_by end - expose :closed_at do |merge_request, _options| merge_request.metrics&.latest_closed_at end - expose :title_html, if: -> (_, options) { options[:render_html] } do |entity| MarkupHelper.markdown_field(entity, :title) end @@ -33,7 +29,6 @@ module API merge_request.assignee end expose :author, :assignees, using: Entities::UserBasic - expose :source_project_id, :target_project_id expose :labels do |merge_request, options| if options[:with_labels_details] @@ -85,11 +80,8 @@ module API end expose :squash - expose :task_completion_status - expose :cannot_be_merged?, as: :has_conflicts - expose :mergeable_discussions_state?, as: :blocking_discussions_resolved end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 39cd2d610e4..55a57501858 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -90,9 +90,10 @@ module API expose :build_coverage_regex expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) } expose :shared_with_groups do |project, options| - SharedGroup.represent(project.project_group_links, options) + SharedGroupWithProject.represent(project.project_group_links, options) end expose :only_allow_merge_if_pipeline_succeeds + expose :allow_merge_on_skipped_pipeline expose :request_access_enabled expose :only_allow_merge_if_all_discussions_are_resolved expose :remove_source_branch_after_merge @@ -119,6 +120,7 @@ module API # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 super(projects_relation).preload(:group) .preload(:ci_cd_settings) + .preload(:project_setting) .preload(:container_expiration_policy) .preload(:auto_devops) .preload(project_group_links: { group: :route }, diff --git a/lib/api/entities/releases/evidence.rb b/lib/api/entities/releases/evidence.rb index 25b2bf6bf6f..01603a71dbf 100644 --- a/lib/api/entities/releases/evidence.rb +++ b/lib/api/entities/releases/evidence.rb @@ -6,7 +6,7 @@ module API class Evidence < Grape::Entity include ::API::Helpers::Presentable - expose :summary_sha, as: :sha + expose :sha expose :filepath expose :collected_at end diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb index f4edb83bd58..654df2e2caf 100644 --- a/lib/api/entities/releases/link.rb +++ b/lib/api/entities/releases/link.rb @@ -9,6 +9,7 @@ module API expose :url expose :direct_asset_url expose :external?, as: :external + expose :link_type def direct_asset_url return object.url unless object.filepath diff --git a/lib/api/entities/resource_milestone_event.rb b/lib/api/entities/resource_milestone_event.rb new file mode 100644 index 00000000000..26dc6620cbe --- /dev/null +++ b/lib/api/entities/resource_milestone_event.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module API + module Entities + class ResourceMilestoneEvent < Grape::Entity + expose :id + expose :user, using: Entities::UserBasic + expose :created_at + expose :resource_type do |event, _options| + event.issuable.class.name + end + expose :resource_id do |event, _options| + event.issuable.id + end + expose :milestone, using: Entities::Milestone + expose :action + expose :state + end + end +end diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb index 1dd8543d595..0afe298ef64 100644 --- a/lib/api/entities/runner_details.rb +++ b/lib/api/entities/runner_details.rb @@ -11,13 +11,6 @@ module API expose :version, :revision, :platform, :architecture expose :contacted_at - # Will be removed: https://gitlab.com/gitlab-org/gitlab/-/issues/217105 - expose(:token, if: ->(runner, options) do - return false if ::Feature.enabled?(:hide_token_from_runners_api, default_enabled: true) - - options[:current_user].admin? || !runner.instance_type? - end) - # rubocop: disable CodeReuse/ActiveRecord expose :projects, with: Entities::BasicProjectDetails do |runner, options| if options[:current_user].admin? diff --git a/lib/api/entities/shared_group_with_group.rb b/lib/api/entities/shared_group_with_group.rb new file mode 100644 index 00000000000..1ca879182eb --- /dev/null +++ b/lib/api/entities/shared_group_with_group.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Entities + class SharedGroupWithGroup < Grape::Entity + expose :shared_with_group_id, as: :group_id + expose :group_name do |group_link| + group_link.shared_with_group.name + end + expose :group_full_path do |group_link| + group_link.shared_with_group.full_path + end + expose :group_access, as: :group_access_level + expose :expires_at + end + end +end diff --git a/lib/api/entities/shared_group.rb b/lib/api/entities/shared_group_with_project.rb index 862e73e07f0..d91bee31b04 100644 --- a/lib/api/entities/shared_group.rb +++ b/lib/api/entities/shared_group_with_project.rb @@ -2,7 +2,7 @@ module API module Entities - class SharedGroup < Grape::Entity + class SharedGroupWithProject < Grape::Entity expose :group_id expose :group_name do |group_link, options| group_link.group.name diff --git a/lib/api/entities/ssh_key.rb b/lib/api/entities/ssh_key.rb index aae216173c7..e1554730cb6 100644 --- a/lib/api/entities/ssh_key.rb +++ b/lib/api/entities/ssh_key.rb @@ -3,7 +3,8 @@ module API module Entities class SSHKey < Grape::Entity - expose :id, :title, :key, :created_at, :expires_at + expose :id, :title, :created_at, :expires_at + expose :publishable_key, as: :key end end end diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb index d3df12200ff..c225ade6eb6 100644 --- a/lib/api/entities/user_with_admin.rb +++ b/lib/api/entities/user_with_admin.rb @@ -4,8 +4,7 @@ module API module Entities class UserWithAdmin < UserPublic expose :admin?, as: :is_admin + expose :note end end end - -API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin', with_descendants: true) diff --git a/lib/api/features.rb b/lib/api/features.rb index f507919b055..3fb3fc92e42 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -61,7 +61,7 @@ module API mutually_exclusive :key, :project end post ':name' do - feature = Feature.get(params[:name]) + feature = Feature.get(params[:name]) # rubocop:disable Gitlab/AvoidFeatureGet targets = gate_targets(params) value = gate_value(params) key = gate_key(params) @@ -92,7 +92,7 @@ module API desc 'Remove the gate value for the given feature' delete ':name' do - Feature.get(params[:name]).remove + Feature.remove(params[:name]) no_content! end diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb index 7f95b411b36..d34317b5271 100644 --- a/lib/api/group_container_repositories.rb +++ b/lib/api/group_container_repositories.rb @@ -20,6 +20,7 @@ module API params do use :pagination optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' + optional :tags_count, type: Boolean, default: false, desc: 'Determines if the tags count should be included' end get ':id/registry/repositories' do repositories = ContainerRepositoriesFinder.new( @@ -28,7 +29,7 @@ module API track_event('list_repositories') - present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] + present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count] end end diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 8ca5dfa082e..d3010b6d147 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -2,6 +2,8 @@ module API class GroupExport < Grape::API + helpers Helpers::RateLimiter + before do not_found! unless Feature.enabled?(:group_import_export, user_group, default_enabled: true) @@ -16,6 +18,8 @@ module API detail 'This feature was introduced in GitLab 12.5.' end get ':id/export/download' do + check_rate_limit! :group_download_export, [current_user, user_group] + if user_group.export_file_exists? present_carrierwave_file!(user_group.export_file) else @@ -27,6 +31,8 @@ module API detail 'This feature was introduced in GitLab 12.5.' end post ':id/export' do + check_rate_limit! :group_export, [current_user] + export_service = ::Groups::ImportExport::ExportService.new(group: user_group, user: current_user) if export_service.async_execute diff --git a/lib/api/group_import.rb b/lib/api/group_import.rb index ec51c2f44c3..afcbc24d3ce 100644 --- a/lib/api/group_import.rb +++ b/lib/api/group_import.rb @@ -2,8 +2,6 @@ module API class GroupImport < Grape::API - MAXIMUM_FILE_SIZE = 50.megabytes.freeze - helpers Helpers::FileUploadHelpers helpers do @@ -40,7 +38,10 @@ module API status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - ImportExportUploader.workhorse_authorize(has_length: false, maximum_size: MAXIMUM_FILE_SIZE) + ImportExportUploader.workhorse_authorize( + has_length: false, + maximum_size: Gitlab::CurrentSettings.max_import_size.megabytes + ) end desc 'Create a new group import' do @@ -69,7 +70,7 @@ module API group = ::Groups::CreateService.new(current_user, group_params).execute if group.persisted? - GroupImportWorker.perform_async(current_user.id, group.id) # rubocop:disable CodeReuse/Worker + ::Groups::ImportExport::ImportService.new(group: group, user: current_user).async_execute accepted! else diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 353c8b4b242..6e07bb46721 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -23,13 +23,20 @@ module API optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user' + optional :top_level_only, type: Boolean, desc: 'Only include top level groups' use :pagination end # rubocop: disable CodeReuse/ActiveRecord def find_groups(params, parent_id = nil) find_params = params.slice(:all_available, :custom_attributes, :owned, :min_access_level) - find_params[:parent] = find_group!(parent_id) if parent_id + + find_params[:parent] = if params[:top_level_only] + [nil] + elsif parent_id + find_group!(parent_id) + end + find_params[:all_available] = find_params.fetch(:all_available, current_user&.can_read_all_resources?) @@ -144,6 +151,7 @@ module API end group = create_group + group.preload_shared_group_links if group.persisted? present group, with: Entities::GroupDetail, current_user: current_user @@ -168,6 +176,8 @@ module API end put ':id' do group = find_group!(params[:id]) + group.preload_shared_group_links + authorize! :admin_group, group if update_group(group) @@ -186,6 +196,7 @@ module API end get ":id" do group = find_group!(params[:id]) + group.preload_shared_group_links options = { with: params[:with_projects] ? Entities::GroupDetail : Entities::Group, @@ -292,6 +303,7 @@ module API post ":id/projects/:project_id", requirements: { project_id: /.+/ } do authenticated_as_admin! group = find_group!(params[:id]) + group.preload_shared_group_links project = find_project!(params[:project_id]) result = ::Projects::TransferService.new(project, current_user).execute(group) @@ -301,6 +313,49 @@ module API render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end end + + desc 'Share a group with a group' do + success Entities::GroupDetail + end + params do + requires :group_id, type: Integer, desc: 'The ID of the group to share' + requires :group_access, type: Integer, values: Gitlab::Access.all_values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end + post ":id/share" do + shared_group = find_group!(params[:id]) + shared_with_group = find_group!(params[:group_id]) + + group_link_create_params = { + shared_group_access: params[:group_access], + expires_at: params[:expires_at] + } + + result = ::Groups::GroupLinks::CreateService.new(shared_with_group, current_user, group_link_create_params).execute(shared_group) + shared_group.preload_shared_group_links + + if result[:status] == :success + present shared_group, with: Entities::GroupDetail, current_user: current_user + else + render_api_error!(result[:message], result[:http_status]) + end + end + + params do + requires :group_id, type: Integer, desc: 'The ID of the shared group' + end + # rubocop: disable CodeReuse/ActiveRecord + delete ":id/share/:group_id" do + shared_group = find_group!(params[:id]) + + link = shared_group.shared_with_group_links.find_by(shared_with_group_id: params[:group_id]) + not_found!('Group Link') unless link + + ::Groups::GroupLinks::DestroyService.new(shared_group, current_user).execute(link) + + no_content! + end + # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index c6f6dc255d4..bbdb45da3b1 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -437,7 +437,7 @@ module API if report_exception?(exception) define_params_for_grape_middleware Gitlab::ErrorTracking.with_context(current_user) do - Gitlab::ErrorTracking.track_exception(exception, params) + Gitlab::ErrorTracking.track_exception(exception) end end diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb index e272b13f3ae..638b31cc7ba 100644 --- a/lib/api/helpers/issues_helpers.rb +++ b/lib/api/helpers/issues_helpers.rb @@ -24,6 +24,8 @@ module API :discussion_locked, :due_date, :labels, + :add_labels, + :remove_labels, :milestone_id, :state_event, :title diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index c85a38fc18b..f88624ed63e 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -133,7 +133,7 @@ module API if resolved parent = noteable_parent(noteable) - ::Discussions::ResolveService.new(parent, current_user, merge_request: noteable).execute(discussion) + ::Discussions::ResolveService.new(parent, current_user, one_or_more_discussions: discussion).execute else discussion.unresolve! end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 5afdb34da97..8a115d42929 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -44,6 +44,7 @@ module API optional :public_builds, type: Boolean, desc: 'Perform public builds' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :allow_merge_on_skipped_pipeline, type: Boolean, desc: 'Allow to merge if pipeline is skipped' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' optional :tag_list, type: Array[String], desc: 'The list of tags for a project' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 @@ -92,6 +93,7 @@ module API def self.update_params_at_least_one_of [ + :allow_merge_on_skipped_pipeline, :autoclose_referenced_issues, :auto_devops_enabled, :auto_devops_deploy_strategy, diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 1f1253c8542..293d7ed9a6a 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -3,6 +3,8 @@ module API module Helpers module Runner + include Gitlab::Utils::StrongMemoize + prepend_if_ee('EE::API::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN' @@ -16,7 +18,7 @@ module API forbidden! unless current_runner current_runner - .update_cached_info(get_runner_details_from_request) + .heartbeat(get_runner_details_from_request) end def get_runner_details_from_request @@ -31,31 +33,35 @@ module API end def current_runner - @runner ||= ::Ci::Runner.find_by_token(params[:token].to_s) + strong_memoize(:current_runner) do + ::Ci::Runner.find_by_token(params[:token].to_s) + end end - def validate_job!(job) - not_found! unless job + def authenticate_job!(require_running: true) + job = current_job - yield if block_given? + not_found! unless job + forbidden! unless job_token_valid?(job) - project = job.project - forbidden!('Project has been deleted!') if project.nil? || project.pending_delete? + forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete? forbidden!('Job has been erased!') if job.erased? - end - def authenticate_job! - job = current_job + if require_running + job_forbidden!(job, 'Job is not running') unless job.running? + end - validate_job!(job) do - forbidden! unless job_token_valid?(job) + if Gitlab::Ci::Features.job_heartbeats_runner?(job.project) + job.runner&.heartbeat(get_runner_ip) end job end def current_job - @current_job ||= Ci::Build.find_by_id(params[:id]) + strong_memoize(:current_job) do + Ci::Build.find_by_id(params[:id]) + end end def job_token_valid?(job) diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 02e60ff5db5..3d6039cacaa 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -583,6 +583,18 @@ module API name: :api_url, type: String, desc: 'Prometheus API Base URL, like http://prometheus.example.com/' + }, + { + required: true, + name: :google_iap_audience_client_id, + type: String, + desc: 'Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)' + }, + { + required: true, + name: :google_iap_service_account_json, + type: String, + desc: 'Contents of the credentials.json file of your service account, like: { "type": "service_account", "project_id": ... }' } ], 'pushover' => [ diff --git a/lib/api/issues.rb b/lib/api/issues.rb index be50c3e0381..2374ac11f4a 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -5,7 +5,6 @@ module API include PaginationParams helpers Helpers::IssuesHelpers helpers Helpers::RateLimiter - helpers ::Gitlab::IssuableMetadata before { authenticate_non_get! } @@ -67,6 +66,8 @@ module API optional :assignee_id, type: Integer, desc: '[Deprecated] The ID of a user to assign issue' optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue' optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' + optional :add_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' + optional :remove_labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" @@ -106,7 +107,7 @@ module API with: Entities::Issue, with_labels_details: declared_params[:with_labels_details], current_user: current_user, - issuable_metadata: issuable_meta_data(issues, 'Issue', current_user), + issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data, include_subscribed: false } @@ -132,7 +133,7 @@ module API with: Entities::Issue, with_labels_details: declared_params[:with_labels_details], current_user: current_user, - issuable_metadata: issuable_meta_data(issues, 'Issue', current_user), + issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data, include_subscribed: false, group: user_group } @@ -169,7 +170,7 @@ module API with_labels_details: declared_params[:with_labels_details], current_user: current_user, project: user_project, - issuable_metadata: issuable_meta_data(issues, 'Issue', current_user), + issuable_metadata: Gitlab::IssuableMetadata.new(current_user, issues).data, include_subscribed: false } diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 59f0dbe8a9b..61a7fc107ef 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -70,6 +70,32 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Get pipeline bridge jobs' do + success Entities::Bridge + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + use :optional_scope + use :pagination + end + # rubocop: disable CodeReuse/ActiveRecord + get ':id/pipelines/:pipeline_id/bridges' do + authorize!(:read_build, user_project) + pipeline = user_project.ci_pipelines.find(params[:pipeline_id]) + authorize!(:read_pipeline, pipeline) + + bridges = pipeline.bridges + bridges = filter_builds(bridges, params[:scope]) + bridges = bridges.preload( + :metadata, + downstream_pipeline: [project: [:route, { namespace: :route }]], + project: [:namespace] + ) + + present paginate(bridges), with: Entities::Bridge + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Get a specific job of a project' do success Entities::Job end diff --git a/lib/api/lsif_data.rb b/lib/api/lsif_data.rb deleted file mode 100644 index a673ccb4af0..00000000000 --- a/lib/api/lsif_data.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module API - class LsifData < Grape::API - MAX_FILE_SIZE = 10.megabytes - - before do - not_found! if Feature.disabled?(:code_navigation, user_project) - end - - params do - requires :id, type: String, desc: 'The ID of a project' - requires :commit_id, type: String, desc: 'The ID of a commit' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - segment ':id/commits/:commit_id' do - params do - requires :paths, type: Array, desc: 'The paths of the files' - end - get 'lsif/info' do - authorize! :download_code, user_project - - artifact = - Ci::JobArtifact - .with_file_types(['lsif']) - .for_sha(params[:commit_id], @project.id) - .last - - not_found! unless artifact - authorize! :read_pipeline, artifact.job.pipeline - file_too_large! if artifact.file.cached_size > MAX_FILE_SIZE - - service = ::Projects::LsifDataService.new(artifact.file, @project, params[:commit_id]) - - params[:paths].to_h { |path| [path, service.execute(path)] } - end - end - end - end -end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index ff4ad85115b..773a451d3a8 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -8,7 +8,6 @@ module API before { authenticate_non_get! } - helpers ::Gitlab::IssuableMetadata helpers Helpers::MergeRequestsHelpers # EE::API::MergeRequests would override the following helpers @@ -92,10 +91,8 @@ module API if params[:view] == 'simple' options[:with] = Entities::MergeRequestSimple else - options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest', current_user) - if Feature.enabled?(:mr_list_api_skip_merge_status_recheck, default_enabled: true) - options[:skip_merge_status_recheck] = !declared_params[:with_merge_status_recheck] - end + options[:issuable_metadata] = Gitlab::IssuableMetadata.new(current_user, merge_requests).data + options[:skip_merge_status_recheck] = !declared_params[:with_merge_status_recheck] end options @@ -478,7 +475,7 @@ module API squash_commit_message: params[:squash_commit_message], should_remove_source_branch: params[:should_remove_source_branch], sha: params[:sha] || merge_request.diff_head_sha - ) + ).compact if immediately_mergeable ::MergeRequests::MergeService diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index 555fd98b451..2a0099018d9 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -21,6 +21,7 @@ module API params do use :pagination optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' + optional :tags_count, type: Boolean, default: false, desc: 'Determines if the tags count should be included' end get ':id/registry/repositories' do repositories = ContainerRepositoriesFinder.new( @@ -29,7 +30,7 @@ module API track_event( 'list_repositories') - present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] + present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count] end desc 'Delete repository' do @@ -69,11 +70,11 @@ module API end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' - optional :name_regex_delete, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' - optional :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' + optional :name_regex_delete, type: String, untrusted_regexp: true, desc: 'The tag name regexp to delete, specify .* to delete all' + optional :name_regex, type: String, untrusted_regexp: true, desc: 'The tag name regexp to delete, specify .* to delete all' # require either name_regex (deprecated) or name_regex_delete, it is ok to have both at_least_one_of :name_regex, :name_regex_delete - optional :name_regex_keep, type: String, desc: 'The tag name regexp to retain' + optional :name_regex_keep, type: String, untrusted_regexp: true, desc: 'The tag name regexp to retain' optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name' optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month' end diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 9fd9d13a20c..4b35f245b8c 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -25,7 +25,7 @@ module API detail 'This feature was introduced in GitLab 10.6.' end get ':id/export/download' do - check_rate_limit! :project_download_export, [current_user, :project_download_export, user_project] + check_rate_limit! :project_download_export, [current_user, user_project] if user_project.export_file_exists? present_carrierwave_file!(user_project.export_file) @@ -45,7 +45,7 @@ module API end end post ':id/export' do - check_rate_limit! :project_export, [current_user, :project_export, user_project] + check_rate_limit! :project_export, [current_user] project_export_params = declared_params(include_missing: false) after_export_params = project_export_params.delete(:upload) || {} diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 0e83686cab2..17d08d14a20 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -30,7 +30,10 @@ module API status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - ImportExportUploader.workhorse_authorize(has_length: false, maximum_size: MAXIMUM_FILE_SIZE) + ImportExportUploader.workhorse_authorize( + has_length: false, + maximum_size: Gitlab::CurrentSettings.max_import_size.megabytes + ) end params do diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index 1a63e984fbf..5de623102fb 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -24,11 +24,64 @@ module API detail 'This feature was introduced in GitLab 13.0.' success Entities::ProjectRepositoryStorageMove end - get ':id' do - storage_move = ProjectRepositoryStorageMove.find(params[:id]) + params do + requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' + end + get ':repository_storage_move_id' do + storage_move = ProjectRepositoryStorageMove.find(params[:repository_storage_move_id]) + + present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get a list of all project repository storage moves' do + detail 'This feature was introduced in GitLab 13.1.' + success Entities::ProjectRepositoryStorageMove + end + params do + use :pagination + end + get ':id/repository_storage_moves' do + storage_moves = user_project.repository_storage_moves.with_projects.order_created_at_desc + + present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user + end + + desc 'Get a project repository storage move' do + detail 'This feature was introduced in GitLab 13.1.' + success Entities::ProjectRepositoryStorageMove + end + params do + requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' + end + get ':id/repository_storage_moves/:repository_storage_move_id' do + storage_move = user_project.repository_storage_moves.find(params[:repository_storage_move_id]) present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user end + + desc 'Schedule a project repository storage move' do + detail 'This feature was introduced in GitLab 13.1.' + success Entities::ProjectRepositoryStorageMove + end + params do + requires :destination_storage_name, type: String, desc: 'The destination storage shard' + end + post ':id/repository_storage_moves' do + storage_move = user_project.repository_storage_moves.build( + declared_params.merge(source_storage_name: user_project.repository_storage) + ) + + if storage_move.schedule + present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + else + render_validation_error!(storage_move) + end + end end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index f305da681c4..e00fb61f478 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -502,7 +502,9 @@ module API link = user_project.project_group_links.find_by(group_id: params[:group_id]) not_found!('Group Link') unless link - destroy_conditionally!(link) + destroy_conditionally!(link) do + ::Projects::GroupLinks::DestroyService.new(user_project, current_user).execute(link) + end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index f72230c084c..07c27f39539 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -40,6 +40,7 @@ module API requires :name, type: String, desc: 'The name of the link' requires :url, type: String, desc: 'The URL of the link' optional :filepath, type: String, desc: 'The filepath of the link' + optional :link_type, type: String, desc: 'The link type' end post 'links' do authorize! :create_release, release @@ -75,6 +76,7 @@ module API optional :name, type: String, desc: 'The name of the link' optional :url, type: String, desc: 'The URL of the link' optional :filepath, type: String, desc: 'The filepath of the link' + optional :link_type, type: String, desc: 'The link type' at_least_one_of :name, :url end put do diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 95b3e90323c..a5bb1a44f1f 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -67,7 +67,6 @@ module API if result[:status] == :success log_release_created_audit_event(result[:release]) - create_evidence! present result[:release], with: Entities::Release, current_user: current_user else @@ -169,16 +168,6 @@ module API def log_release_milestones_updated_audit_event # This is a separate method so that EE can extend its behaviour end - - def create_evidence! - return if release.historical_release? - - if release.upcoming_release? - CreateEvidenceWorker.perform_at(release.released_at, release.id) # rubocop:disable CodeReuse/Worker - else - CreateEvidenceWorker.perform_async(release.id) # rubocop:disable CodeReuse/Worker - end - end end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 0b2df85f61f..bf4f08ce390 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -6,6 +6,8 @@ module API class Repositories < Grape::API include PaginationParams + helpers ::API::Helpers::HeadersHelpers + before { authorize! :download_code, user_project } params do @@ -67,6 +69,8 @@ module API get ':id/repository/blobs/:sha/raw' do assign_blob_vars! + no_cache_headers + send_git_blob @repo, @blob end diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb index f7f7c881f4a..1fa6898b92c 100644 --- a/lib/api/resource_label_events.rb +++ b/lib/api/resource_label_events.rb @@ -27,10 +27,9 @@ module API get ":id/#{eventables_str}/:eventable_id/resource_label_events" do eventable = find_noteable(eventable_type, params[:eventable_id]) - opts = { page: params[:page], per_page: params[:per_page] } - events = ResourceLabelEventFinder.new(current_user, eventable, opts).execute + events = eventable.resource_label_events.inc_relations - present paginate(events), with: Entities::ResourceLabelEvent + present ResourceLabelEvent.visible_to_user?(current_user, paginate(events)), with: Entities::ResourceLabelEvent end desc "Get a single #{eventable_type.to_s.downcase} resource label event" do diff --git a/lib/api/resource_milestone_events.rb b/lib/api/resource_milestone_events.rb new file mode 100644 index 00000000000..30ff5a9b4be --- /dev/null +++ b/lib/api/resource_milestone_events.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module API + class ResourceMilestoneEvents < Grape::API + include PaginationParams + helpers ::API::Helpers::NotesHelpers + + before { authenticate! } + + [Issue, MergeRequest].each do |eventable_type| + parent_type = eventable_type.parent_class.to_s.underscore + eventables_str = eventable_type.to_s.underscore.pluralize + + params do + requires :id, type: String, desc: "The ID of a #{parent_type}" + end + resource parent_type.pluralize.to_sym, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc "Get a list of #{eventable_type.to_s.downcase} resource milestone events" do + success Entities::ResourceMilestoneEvent + end + params do + requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' + use :pagination + end + + get ":id/#{eventables_str}/:eventable_id/resource_milestone_events" do + eventable = find_noteable(eventable_type, params[:eventable_id]) + + opts = { page: params[:page], per_page: params[:per_page] } + events = ResourceMilestoneEventFinder.new(current_user, eventable, opts).execute + + present paginate(events), with: Entities::ResourceMilestoneEvent + end + + desc "Get a single #{eventable_type.to_s.downcase} resource milestone event" do + success Entities::ResourceMilestoneEvent + end + params do + requires :event_id, type: String, desc: 'The ID of a resource milestone event' + requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' + end + get ":id/#{eventables_str}/:eventable_id/resource_milestone_events/:event_id" do + eventable = find_noteable(eventable_type, params[:eventable_id]) + + event = eventable.resource_milestone_events.find(params[:event_id]) + + not_found!('ResourceMilestoneEvent') unless can?(current_user, :read_milestone, event.milestone_parent) + + present event, with: Entities::ResourceMilestoneEvent + end + end + end + end +end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 9095aba7340..5f08ebe4a06 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -154,7 +154,6 @@ module API end put '/:id' do job = authenticate_job! - job_forbidden!(job, 'Job is not running') unless job.running? job.trace.set(params[:trace]) if params[:trace] @@ -182,7 +181,6 @@ module API end patch '/:id/trace' do job = authenticate_job! - job_forbidden!(job, 'Job is not running') unless job.running? error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') content_range = request.headers['Content-Range'] @@ -220,6 +218,8 @@ module API requires :id, type: Integer, desc: %q(Job's ID) optional :token, type: String, desc: %q(Job's authentication token) optional :filesize, type: Integer, desc: %q(Artifacts filesize) + optional :artifact_type, type: String, desc: %q(The type of artifact), + default: 'archive', values: Ci::JobArtifact.file_types.keys end post '/:id/artifacts/authorize' do not_allowed! unless Gitlab.config.artifacts.enabled @@ -227,18 +227,15 @@ module API Gitlab::Workhorse.verify_api_request!(headers) job = authenticate_job! - forbidden!('Job is not running') unless job.running? - max_size = max_artifacts_size(job) + service = Ci::AuthorizeJobArtifactService.new(job, params, max_size: max_artifacts_size(job)) - if params[:filesize] - file_size = params[:filesize].to_i - file_too_large! unless file_size < max_size - end + forbidden! if service.forbidden? + file_too_large! if service.too_large? status 200 content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size) + service.headers end desc 'Upload artifacts for job' do @@ -265,7 +262,6 @@ module API require_gitlab_workhorse! job = authenticate_job! - forbidden!('Job is not running!') unless job.running? artifacts = params[:file] metadata = params[:metadata] @@ -292,7 +288,7 @@ module API optional :direct_download, default: false, type: Boolean, desc: %q(Perform direct download from remote storage instead of proxying artifacts) end get '/:id/artifacts' do - job = authenticate_job! + job = authenticate_job!(require_running: false) present_carrierwave_file!(job.artifacts_file, supports_direct_download: params[:direct_download]) end diff --git a/lib/api/search.rb b/lib/api/search.rb index 3d2d4527e30..ac00d3682a0 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -20,6 +20,13 @@ module API users: Entities::UserBasic }.freeze + SCOPE_PRELOAD_METHOD = { + merge_requests: :with_api_entity_associations, + projects: :with_api_entity_associations, + issues: :with_api_entity_associations, + milestones: :with_api_entity_associations + }.freeze + def search(additional_params = {}) search_params = { scope: params[:scope], @@ -29,7 +36,9 @@ module API per_page: params[:per_page] }.merge(additional_params) - results = SearchService.new(current_user, search_params).search_objects + results = SearchService.new(current_user, search_params).search_objects(preload_method) + + Gitlab::UsageDataCounters::SearchCounter.count(:all_searches) paginate(results) end @@ -42,6 +51,10 @@ module API SCOPE_ENTITY[params[:scope].to_sym] end + def preload_method + SCOPE_PRELOAD_METHOD[params[:scope].to_sym] + end + def verify_search_scope!(resource:) # In EE we have additional validation requirements for searches. # Defining this method here as a noop allows us to easily extend it in diff --git a/lib/api/settings.rb b/lib/api/settings.rb index e3a8f0671ef..0bf5eed26b4 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -83,6 +83,7 @@ module API desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts" optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' + optional :max_import_size, type: Integer, desc: 'Maximum import size in MB' optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB' optional :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.' optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5 @@ -113,6 +114,7 @@ module API end optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects' + optional :repository_storages_weighted, type: Hash, desc: 'Storage paths for new projects with a weighted value between 0 and 100' optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -132,6 +134,10 @@ module API given sourcegraph_enabled: ->(val) { val } do requires :sourcegraph_url, type: String, desc: 'The configured Sourcegraph instance URL' end + optional :spam_check_endpoint_enabled, type: Boolean, desc: 'Enable Spam Check via external API endpoint' + given spam_check_endpoint_enabled: ->(val) { val } do + requires :spam_check_endpoint_url, type: String, desc: 'The URL of the external Spam Check service endpoint' + end optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins' diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb index d008d1b9e97..05aaa8a6f41 100644 --- a/lib/api/suggestions.rb +++ b/lib/api/suggestions.rb @@ -14,18 +14,51 @@ module API put ':id/apply' do suggestion = Suggestion.find_by_id(params[:id]) - not_found! unless suggestion - authorize! :apply_suggestion, suggestion + if suggestion + apply_suggestions(suggestion, current_user) + else + render_api_error!(_('Suggestion is not applicable as the suggestion was not found.'), :not_found) + end + end + + desc 'Apply multiple suggestion patches in the Merge Request where they were created' do + success Entities::Suggestion + end + params do + requires :ids, type: Array[String], desc: "An array of suggestion ID's" + end + put 'batch_apply' do + ids = params[:ids] + + suggestions = Suggestion.id_in(ids) - result = ::Suggestions::ApplyService.new(current_user).execute(suggestion) + if suggestions.size == ids.length + apply_suggestions(suggestions, current_user) + else + render_api_error!(_('Suggestions are not applicable as one or more suggestions were not found.'), :not_found) + end + end + end + + helpers do + def apply_suggestions(suggestions, current_user) + authorize_suggestions(*suggestions) + + result = ::Suggestions::ApplyService.new(current_user, *suggestions).execute if result[:status] == :success - present suggestion, with: Entities::Suggestion, current_user: current_user + present suggestions, with: Entities::Suggestion, current_user: current_user else - http_status = result[:http_status] || 400 + http_status = result[:http_status] || :bad_request render_api_error!(result[:message], http_status) end end + + def authorize_suggestions(*suggestions) + suggestions.each do |suggestion| + authorize! :apply_suggestion, suggestion + end + end end end end diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index 5141d1fd499..e7c9627c753 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -32,7 +32,7 @@ module API end desc 'Get a terraform state by its name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get do remote_state_handler.find_with_lock do |state| no_content! unless state.file.exists? @@ -44,7 +44,7 @@ module API end desc 'Add a new terraform state or update an existing one' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post do data = request.body.read no_content! if data.empty? @@ -57,7 +57,7 @@ module API end desc 'Delete a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth delete do remote_state_handler.handle_with_lock do |state| state.destroy! @@ -66,7 +66,7 @@ module API end desc 'Lock a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do requires :ID, type: String, limit: 255, desc: 'Terraform state lock ID' requires :Operation, type: String, desc: 'Terraform operation' @@ -103,7 +103,7 @@ module API end desc 'Unlock a terraform state of a certain name' - route_setting :authentication, basic_auth_personal_access_token: true + route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth params do optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID' end diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 02b8bb55274..e36ddf21277 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -6,8 +6,6 @@ module API before { authenticate! } - helpers ::Gitlab::IssuableMetadata - ISSUABLE_TYPES = { 'merge_requests' => ->(iid) { find_merge_request_with_access(iid) }, 'issues' => ->(iid) { find_project_issue(iid) } @@ -65,7 +63,7 @@ module API next unless collection targets = collection.map(&:target) - options[type] = { issuable_metadata: issuable_meta_data(targets, type, current_user) } + options[type] = { issuable_metadata: Gitlab::IssuableMetadata.new(current_user, targets).data } end end end @@ -91,16 +89,18 @@ module API requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end post ':id/mark_as_done' do - TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) todo = current_user.todos.find(params[:id]) + TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :api_done) + present todo, with: Entities::Todo, current_user: current_user end desc 'Mark all todos as done' post '/mark_as_done' do todos = find_todos - TodoService.new.mark_todos_as_done(todos, current_user) + + TodoService.new.resolve_todos(todos, current_user, resolved_by_action: :api_all_done) no_content! end diff --git a/lib/api/users.rb b/lib/api/users.rb index c986414c223..3d8ae09edf1 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -55,6 +55,7 @@ module API optional :theme_id, type: Integer, desc: 'The GitLab theme for the user' optional :color_scheme_id, type: Integer, desc: 'The color scheme for the file viewer' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' + optional :note, type: String, desc: 'Admin note for this user' all_or_none_of :extern_uid, :provider use :optional_params_ee @@ -254,6 +255,7 @@ module API requires :id, type: Integer, desc: 'The ID of the user' requires :key, type: String, desc: 'The new SSH key' requires :title, type: String, desc: 'The title of the new SSH key' + optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' end # rubocop: disable CodeReuse/ActiveRecord post ":id/keys" do @@ -262,9 +264,9 @@ module API user = User.find_by(id: params.delete(:id)) not_found!('User') unless user - key = user.keys.new(declared_params(include_missing: false)) + key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute - if key.save + if key.persisted? present key, with: Entities::SSHKey else render_validation_error!(key) @@ -283,7 +285,8 @@ module API user = find_user(params[:user_id]) not_found!('User') unless user && can?(current_user, :read_user, user) - present paginate(user.keys), with: Entities::SSHKey + keys = user.keys.preload_users + present paginate(keys), with: Entities::SSHKey end desc 'Delete an existing SSH key from a specified user. Available only for admins.' do @@ -303,7 +306,10 @@ module API key = user.keys.find_by(id: params[:key_id]) not_found!('Key') unless key - destroy_conditionally!(key) + destroy_conditionally!(key) do |key| + destroy_service = ::Keys::DestroyService.new(current_user) + destroy_service.execute(key) + end end # rubocop: enable CodeReuse/ActiveRecord @@ -695,7 +701,9 @@ module API use :pagination end get "keys" do - present paginate(current_user.keys), with: Entities::SSHKey + keys = current_user.keys.preload_users + + present paginate(keys), with: Entities::SSHKey end desc 'Get a single key owned by currently authenticated user' do @@ -719,6 +727,7 @@ module API params do requires :key, type: String, desc: 'The new SSH key' requires :title, type: String, desc: 'The title of the new SSH key' + optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' end post "keys" do key = current_user.keys.new(declared_params) diff --git a/lib/api/validations/validators/file_path.rb b/lib/api/validations/validators/file_path.rb index 93a20e5bf7d..fee71373170 100644 --- a/lib/api/validations/validators/file_path.rb +++ b/lib/api/validations/validators/file_path.rb @@ -8,7 +8,7 @@ module API path = params[attr_name] Gitlab::Utils.check_path_traversal!(path) - rescue StandardError + rescue ::Gitlab::Utils::PathTraversalAttackError raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "should be a valid file path" end diff --git a/lib/api/validations/validators/untrusted_regexp.rb b/lib/api/validations/validators/untrusted_regexp.rb new file mode 100644 index 00000000000..ec623684e67 --- /dev/null +++ b/lib/api/validations/validators/untrusted_regexp.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module API + module Validations + module Validators + class UntrustedRegexp < Grape::Validations::Base + def validate_param!(attr_name, params) + value = params[attr_name] + return unless value + + Gitlab::UntrustedRegexp.new(value) + rescue RegexpError => e + message = "is an invalid regexp: #{e.message}" + raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message + end + end + end + end +end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 884e3019a2d..c1bf3a64923 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -24,7 +24,7 @@ module API params :common_wiki_page_params do optional :format, type: String, - values: ProjectWiki::MARKUPS.values.map(&:to_s), + values: Wiki::MARKUPS.values.map(&:to_s), default: 'markdown', desc: 'Format of a wiki page. Available formats are markdown, rdoc, asciidoc and org' end |