diff options
Diffstat (limited to 'lib/api')
53 files changed, 448 insertions, 188 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 5e449022676..4b7fe6bdc7a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -164,7 +164,7 @@ module API namespace do after do - ::Users::ActivityService.new(@current_user).execute + ::Users::ActivityService.new(author: @current_user, project: @project, namespace: @group).execute end # Mount endpoints to include in the OpenAPI V2 documentation here @@ -209,6 +209,7 @@ module API mount ::API::DeployKeys mount ::API::DeployTokens mount ::API::Deployments + mount ::API::DraftNotes mount ::API::Environments mount ::API::ErrorTracking::ClientKeys mount ::API::ErrorTracking::ProjectSettings diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index df550f12c0d..81a640d9a93 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -57,10 +57,7 @@ module API user = find_user_from_sources return unless user - if user.is_a?(User) && Gitlab::CurrentSettings.admin_mode - # Sessions are enforced to be unavailable for API calls, so ignore them for admin mode - Gitlab::Auth::CurrentUserMode.bypass_session!(user.id) - end + Gitlab::Auth::CurrentUserMode.bypass_session!(user.id) if bypass_session_for_admin_mode?(user) unless api_access_allowed?(user) forbidden!(api_access_denied_message(user)) @@ -85,6 +82,16 @@ module API private + def bypass_session_for_admin_mode?(user) + return user.is_a?(User) && Gitlab::CurrentSettings.admin_mode if Feature.disabled?(:admin_mode_for_api) + + return false unless Gitlab::CurrentSettings.admin_mode + return false unless user.is_a?(User) + + Gitlab::Session.with_session(current_request.session) { Gitlab::Auth::CurrentUserMode.new(user).admin_mode? } || + Gitlab::Auth::RequestAuthenticator.new(current_request).valid_access_token?(scopes: [:admin_mode]) + end + # An array of scopes that were registered (using `allow_access_with_scope`) # for the current endpoint class. It also returns scopes registered on # `API::API`, since these are meant to apply to all API routes. diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index 99278bdf8b0..f8e4f5d2ab2 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -26,13 +26,15 @@ module API end params do optional :title, type: String, desc: 'Instance title on the sign in / sign up page' - optional :pwa_short_name, type: String, desc: 'Optional, short name for Progressive Web App' optional :description, type: String, desc: 'Markdown text shown on the sign in / sign up page' + optional :pwa_name, type: String, desc: 'Name of the Progressive Web App' + optional :pwa_short_name, type: String, desc: 'Optional, short name for Progressive Web App' + optional :pwa_description, type: String, desc: 'An explanation of what the Progressive Web App does' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 - optional :logo, type: File, desc: 'Instance image used on the sign in / sign up page' # rubocop:disable Scalability/FileUploads - optional :pwa_icon, type: File, desc: 'Icon used for Progressive Web App' # rubocop:disable Scalability/FileUploads - optional :header_logo, type: File, desc: 'Instance image used for the main navigation bar' # rubocop:disable Scalability/FileUploads - optional :favicon, type: File, desc: 'Instance favicon in .ico/.png format' # rubocop:disable Scalability/FileUploads + optional :logo, type: File, desc: 'Instance image used on the sign in / sign up page' # rubocop:todo Scalability/FileUploads + optional :pwa_icon, type: File, desc: 'Icon used for Progressive Web App' # rubocop:todo Scalability/FileUploads + optional :header_logo, type: File, desc: 'Instance image used for the main navigation bar' # rubocop:todo Scalability/FileUploads + optional :favicon, type: File, desc: 'Instance favicon in .ico/.png format' # rubocop:todo Scalability/FileUploads optional :new_project_guidelines, type: String, desc: 'Markdown text shown on the new project page' optional :profile_image_guidelines, type: String, desc: 'Markdown text shown on the profile page below Public Avatar' optional :header_message, type: String, desc: 'Message within the system header bar' diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb index 0fb7a4cd435..addea780eb0 100644 --- a/lib/api/avatar.rb +++ b/lib/api/avatar.rb @@ -2,7 +2,7 @@ module API class Avatar < ::API::Base - feature_category :users + feature_category :user_profile urgency :medium resource :avatar do diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb index 6c07b15329e..e3dc9ea52cb 100644 --- a/lib/api/bulk_imports.rb +++ b/lib/api/bulk_imports.rb @@ -91,6 +91,8 @@ module API end end post do + check_rate_limit!(:bulk_import, scope: current_user) + params[:entities].each do |entity| if entity[:destination_name] entity[:destination_slug] ||= entity[:destination_name] diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index be4d82bc500..96f5265ce23 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -10,23 +10,32 @@ module API JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN' JOB_TOKEN_PARAM = :token + LEGACY_SYSTEM_XID = '<legacy>' def authenticate_runner! track_runner_authentication forbidden! unless current_runner - current_runner - .heartbeat(get_runner_details_from_request) + runner_details = get_runner_details_from_request + current_runner.heartbeat(runner_details) + current_runner_machine&.heartbeat(runner_details) end def get_runner_details_from_request return get_runner_ip unless params['info'].present? attributes_for_keys(%w(name version revision platform architecture executor), params['info']) + .merge(get_system_id_from_request) .merge(get_runner_config_from_request) .merge(get_runner_ip) end + def get_system_id_from_request + return { system_id: params[:system_id] } if params.include?(:system_id) + + {} + end + def get_runner_ip { ip_address: ip_address } end @@ -43,6 +52,15 @@ module API end end + def current_runner_machine + return if Feature.disabled?(:create_runner_machine) + + strong_memoize(:current_runner_machine) do + system_xid = params.fetch(:system_id, LEGACY_SYSTEM_XID) + current_runner&.ensure_machine(system_xid) { |m| m.contacted_at = Time.current } + end + end + def track_runner_authentication if current_runner metrics.increment_runner_authentication_success_counter(runner_type: current_runner.runner_type) @@ -78,6 +96,7 @@ module API # the heartbeat should be triggered. if heartbeat_runner job.runner&.heartbeat(get_runner_ip) + job.runner_machine&.heartbeat(get_runner_ip) end job diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index ed1c7dfbfa2..30d12864bf8 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -57,11 +57,7 @@ module API builds = filter_builds(builds, params[:scope]) builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project) - if Feature.enabled?(:jobs_api_keyset_pagination, user_project) - present paginate_with_strategies(builds, paginator_params: { without_count: true }), with: Entities::Ci::Job - else - present paginate(builds, without_count: true), with: Entities::Ci::Job - end + present paginate_with_strategies(builds, paginator_params: { without_count: true }), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 6b4394114df..1c81db39bb1 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -77,15 +77,18 @@ module API desc 'Validate authentication credentials' do summary "Verify authentication for a registered runner" + success Entities::Ci::RunnerRegistrationDetails http_codes [[200, 'Credentials are valid'], [403, 'Forbidden']] end params do requires :token, type: String, desc: %q(The runner's authentication token) + optional :system_id, type: String, desc: %q(The runner's system identifier) end post '/verify', urgency: :low, feature_category: :runner do authenticate_runner! status 200 - body "200" + + present current_runner, with: Entities::Ci::RunnerRegistrationDetails end desc 'Reset runner authentication token with current token' do @@ -115,6 +118,7 @@ module API end params do requires :token, type: String, desc: %q(Runner's authentication token) + optional :system_id, type: String, desc: %q(Runner's system identifier) optional :last_update, type: String, desc: %q(Runner's queue last_update token) optional :info, type: Hash, desc: %q(Runner's metadata) do optional :name, type: String, desc: %q(Runner's name) @@ -166,7 +170,7 @@ module API end new_update = current_runner.ensure_runner_queue_value - result = ::Ci::RegisterJobService.new(current_runner).execute(runner_params) + result = ::Ci::RegisterJobService.new(current_runner, current_runner_machine).execute(runner_params) if result.valid? if result.build_json @@ -192,7 +196,7 @@ module API [403, 'Forbidden']] end params do - requires :token, type: String, desc: %q(Runner's authentication token) + requires :token, type: String, desc: %q(Job token) requires :id, type: Integer, desc: %q(Job's ID) optional :state, type: String, desc: %q(Job's status: success, failed) optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 4a6c58b4987..f2f0f32261a 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -13,7 +13,7 @@ module API helpers do params :deprecated_filter_params do optional :scope, type: String, values: ::Ci::Runner::AVAILABLE_SCOPES, - desc: 'Deprecated: Use `type` or `status` instead. The scope of specific runners to return' + desc: 'Deprecated: Use `type` or `status` instead. The scope of runners to return' end params :filter_params do @@ -111,9 +111,9 @@ module API present paginate(runners), with: Entities::Ci::Runner end - desc 'Get all runners - shared and specific' do + desc 'Get all runners - shared and project' do summary 'List all runners' - detail 'Get a list of all runners in the GitLab instance (specific and shared). ' \ + detail 'Get a list of all runners in the GitLab instance (shared and project). ' \ 'Access is restricted to users with administrator access.' success Entities::Ci::Runner failure [[400, 'Scope contains invalid value'], [401, 'Unauthorized']] @@ -286,7 +286,7 @@ module API end desc 'Enable a runner in project' do - detail "Enable an available specific runner in the project." + detail "Enable an available project runner in the project." success Entities::Ci::Runner failure [[400, 'Bad Request'], [403, 'No access granted'], [403, 'Runner is a group runner'], [403, 'Runner is locked'], @@ -308,7 +308,7 @@ module API end desc "Disable project's runner" do - summary "Disable a specific runner from the project" + summary "Disable a project runner from the project" detail "It works only if the project isn't the only project associated with the specified runner. " \ "If so, an error is returned. Use the call to delete a runner instead." success Entities::Ci::Runner diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index 6483abcc74e..41faaf80c82 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -10,7 +10,7 @@ module API authorize! :read_secure_files, user_project end - feature_category :pipeline_authoring + feature_category :mobile_devops default_format :json diff --git a/lib/api/concerns/packages/debian_distribution_endpoints.rb b/lib/api/concerns/packages/debian_distribution_endpoints.rb index 6fe3f432edb..7aad3a56422 100644 --- a/lib/api/concerns/packages/debian_distribution_endpoints.rb +++ b/lib/api/concerns/packages/debian_distribution_endpoints.rb @@ -76,7 +76,7 @@ module API end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } use :optional_distribution_params end post '/' do @@ -107,7 +107,7 @@ module API params do use :pagination - optional :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + optional :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } use :optional_distribution_params end get '/' do @@ -132,7 +132,7 @@ module API end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } end get '/:codename' do authorize_read_package!(project_or_group) @@ -153,7 +153,7 @@ module API end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } end get '/:codename/key.asc' do authorize_read_package!(project_or_group) @@ -179,7 +179,7 @@ module API end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } use :optional_distribution_params end put '/:codename' do @@ -210,7 +210,7 @@ module API end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'sid' } use :optional_distribution_params end delete '/:codename' do diff --git a/lib/api/concerns/packages/debian_package_endpoints.rb b/lib/api/concerns/packages/debian_package_endpoints.rb index 181759a7f38..db31f2e35f1 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -44,6 +44,8 @@ module API end def present_index_file!(file_type) + not_found!("Format #{params[:format]} is not supported") unless params[:format].nil? + relation = "::Packages::Debian::#{project_or_group.class.name}ComponentFile".constantize relation = relation diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index 483d0dd9c90..67416e62f7d 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -6,10 +6,6 @@ module API project_id: %r{[0-9]+}.freeze ).freeze - before do - not_found! if Gitlab::FIPS.enabled? - end - resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do def project_or_group diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index 353f64b8dd1..21c0c219046 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -14,10 +14,6 @@ module API file_name: API::NO_SLASH_URL_PART_REGEX }.freeze - before do - not_found! if Gitlab::FIPS.enabled? - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do helpers do def project_or_group @@ -69,6 +65,7 @@ module API format :txt content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + # PUT {projects|groups}/:id/packages/debian/:file_name desc 'Upload Debian package' do detail 'This feature was introduced in GitLab 14.0' success code: 201 @@ -80,12 +77,14 @@ module API ] tags %w[debian_packages] end - - # PUT {projects|groups}/:id/packages/debian/:file_name params do requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } + optional :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex + given :distribution do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs and udebs can be directly added to a distribution' } + end end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true put do authorize_upload!(authorized_user_project) @@ -95,10 +94,18 @@ module API file: params['file'], file_name: params['file_name'], file_sha1: params['file.sha1'], - file_md5: params['file.md5'] + file_md5: params['file.md5'], + distribution: params['distribution'], + component: params['component'] } - package = ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute + package = if params[:distribution].present? + ::Packages::CreateTemporaryPackageService.new( + authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job) + ).execute(:debian, name: ::Packages::Debian::TEMPORARY_PACKAGE_NAME) + else + ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute + end ::Packages::Debian::CreatePackageFileService.new(package: package, current_user: current_user, params: file_params).execute @@ -114,12 +121,20 @@ module API detail 'This feature was introduced in GitLab 13.5' success code: 200 failure [ + { code: 400, message: 'Bad Request' }, { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, { code: 404, message: 'Not Found' } ] tags %w[debian_packages] end + params do + optional :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex + given :distribution do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :file_name, type: String, desc: 'The filename', regexp: { value: Gitlab::Regex.debian_direct_upload_filename_regex, message: 'Only debs and udebs can be directly added to a distribution' } + end + end route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true put 'authorize' do authorize_workhorse!( diff --git a/lib/api/draft_notes.rb b/lib/api/draft_notes.rb new file mode 100644 index 00000000000..842180652c4 --- /dev/null +++ b/lib/api/draft_notes.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module API + class DraftNotes < ::API::Base + before { authenticate! } + + urgency :low + + helpers do + def merge_request(params:) + strong_memoize(:merge_request) do + find_project_merge_request(params[:merge_request_iid]) + end + end + + def load_draft_notes(params:) + merge_request(params: params).draft_notes.authored_by(current_user) + end + + def get_draft_note(params:) + load_draft_notes(params: params).find(params[:draft_note_id]) + end + + def delete_draft_note(draft_note) + ::DraftNotes::DestroyService.new(user_project, current_user).execute(draft_note) + end + + def publish_draft_note(params:) + ::DraftNotes::PublishService + .new(merge_request(params: params), current_user) + .execute(get_draft_note(params: params)) + end + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc "Get a list of merge request draft notes" do + success Entities::DraftNote + is_array true + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :id, type: String, desc: "The ID of a project" + requires :merge_request_iid, type: Integer, desc: "The ID of a merge request" + end + get ":id/merge_requests/:merge_request_iid/draft_notes", feature_category: :code_review_workflow do + present load_draft_notes(params: params), with: Entities::DraftNote + end + + desc "Get a single draft note" do + success Entities::DraftNote + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :id, type: String, desc: "The ID of a project" + requires :merge_request_iid, type: Integer, desc: "The ID of a merge request" + requires :draft_note_id, type: Integer, desc: "The ID of a draft note" + end + get ":id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id", feature_category: :code_review_workflow do + draft_note = get_draft_note(params: params) + + if draft_note + present draft_note, with: Entities::DraftNote + else + not_found!("Draft Note") + end + end + + desc "Delete a draft note" do + success Entities::DraftNote + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :id, type: String, desc: "The ID of a project" + requires :merge_request_iid, type: Integer, desc: "The ID of a merge request" + requires :draft_note_id, type: Integer, desc: "The ID of a draft note" + end + delete( + ":id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id", + feature_category: :code_review_workflow) do + draft_note = get_draft_note(params: params) + + if draft_note + delete_draft_note(draft_note) + status 204 + body false + else + not_found!("Draft Note") + end + end + + desc "Publish a pending draft note" do + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + end + params do + requires :id, type: String, desc: "The ID of a project" + requires :merge_request_iid, type: Integer, desc: "The ID of a merge request" + requires :draft_note_id, type: Integer, desc: "The ID of a draft note" + end + put( + ":id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id/publish", + feature_category: :code_review_workflow) do + result = publish_draft_note(params: params) + + if result[:status] == :success + status 204 + body false + else + status 500 + end + end + end + end +end diff --git a/lib/api/entities/appearance.rb b/lib/api/entities/appearance.rb index cabdf68c23a..2825c84e01d 100644 --- a/lib/api/entities/appearance.rb +++ b/lib/api/entities/appearance.rb @@ -4,8 +4,10 @@ module API module Entities class Appearance < Grape::Entity expose :title - expose :pwa_short_name expose :description + expose :pwa_name + expose :pwa_short_name + expose :pwa_description expose :logo do |appearance, options| appearance.logo.url diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb index 3cbb8aad313..526dfba05f2 100644 --- a/lib/api/entities/ci/job_basic.rb +++ b/lib/api/entities/ci/job_basic.rb @@ -15,6 +15,7 @@ module API expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' } expose :started_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:30.733Z' } expose :finished_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' } + expose :erased_at, documentation: { type: 'dateTime', example: '2015-12-24T18:00:29.728Z' } expose :duration, documentation: { type: 'number', format: 'float', desc: 'Time spent running', example: 0.465 } expose :queued_duration, diff --git a/lib/api/entities/ci/secure_file.rb b/lib/api/entities/ci/secure_file.rb index a234ada6f82..bf8d2776db1 100644 --- a/lib/api/entities/ci/secure_file.rb +++ b/lib/api/entities/ci/secure_file.rb @@ -12,6 +12,7 @@ documentation: { type: 'string', example: '16630b189ab34b2e3504f4758e1054d2e478d expose :created_at, documentation: { type: 'dateTime', example: '2022-02-22T22:22:22.222Z' } expose :expires_at, documentation: { type: 'dateTime', example: '2022-09-21T14:56:00.000Z' } expose :metadata, documentation: { type: 'Hash', example: { "id" => "75949910542696343243264405377658443914" } } + expose :file_extension, documentation: { type: 'string', example: 'jks' } end end end diff --git a/lib/api/entities/draft_note.rb b/lib/api/entities/draft_note.rb new file mode 100644 index 00000000000..70b32bac502 --- /dev/null +++ b/lib/api/entities/draft_note.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module API + module Entities + class DraftNote < Grape::Entity + expose :id, documentation: { type: 'integer', example: 2 } + expose :author_id, documentation: { type: 'integer', example: 4 } + expose :merge_request_id, documentation: { type: 'integer', example: 52 } + expose :resolve_discussion, documentation: { type: 'boolean', example: true } + expose :discussion_id, documentation: { type: 'integer', example: 613 } + expose :note, documentation: { type: 'string', example: 'This is a note' } + expose :commit_id, documentation: { type: 'integer', example: 4 } + expose :line_code, documentation: { type: 'string', example: '1c497fbb3a46b78edf0_2_4' } + expose :position, documentation: { + type: 'Hash', + example: { + base_sha: "aa149113", + start_sha: "b3a0a8c4", + head_sha: "be3020c7", + old_path: "example.md", + new_path: "example.md", + position_type: "text", + old_line: 2, + new_line: 4, + line_range: { + start: { + line_code: "1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4", + type: nil, + old_line: 2, + new_line: 4 + }, + end: { + line_code: "1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4", + type: nil, + old_line: 2, + new_line: 4 + } + } + } + } do |note| + note.position.to_h + end + end + end +end diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb index 7630fd1e94e..56e942a0383 100644 --- a/lib/api/entities/issue.rb +++ b/lib/api/entities/issue.rb @@ -6,11 +6,11 @@ module API include ::API::Helpers::RelatedResourcesHelpers expose(:has_tasks) do |issue, _| - !issue.task_list_items.empty? + !issue.tasks? end expose :task_status, if: -> (issue, _) do - !issue.task_list_items.empty? + !issue.tasks? end expose :_links do diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 27f6e6ade06..16afc6c1f6a 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -91,6 +91,7 @@ module API end expose :squash + expose :squash_on_merge?, as: :squash_on_merge expose :task_completion_status expose :cannot_be_merged?, as: :has_conflicts expose :mergeable_discussions_state?, as: :blocking_discussions_resolved diff --git a/lib/api/entities/packages/debian/distribution.rb b/lib/api/entities/packages/debian/distribution.rb index a11f4337f38..82ee95c198b 100644 --- a/lib/api/entities/packages/debian/distribution.rb +++ b/lib/api/entities/packages/debian/distribution.rb @@ -6,7 +6,7 @@ module API module Debian class Distribution < Grape::Entity expose :id, documentation: { type: 'integer', example: 1 } - expose :codename, documentation: { type: 'string', example: 'unstable' } + expose :codename, documentation: { type: 'string', example: 'sid' } expose :suite, documentation: { type: 'string', example: 'unstable' } expose :origin, documentation: { type: 'string', example: 'Grep' } expose :label, documentation: { type: 'string', example: 'grep.be' } diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 37be6903d8b..fcb7ddb9567 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -88,6 +88,7 @@ module API expose :emails_disabled, documentation: { type: 'boolean' } expose :shared_runners_enabled, documentation: { type: 'boolean' } + expose :group_runners_enabled, documentation: { type: 'boolean' } expose :lfs_enabled?, as: :lfs_enabled, documentation: { type: 'boolean' } expose :creator_id, documentation: { type: 'integer', example: 1 } expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do diff --git a/lib/api/entities/project_with_access.rb b/lib/api/entities/project_with_access.rb index 9722b8806d4..47706165806 100644 --- a/lib/api/entities/project_with_access.rb +++ b/lib/api/entities/project_with_access.rb @@ -25,32 +25,10 @@ module API # rubocop: disable CodeReuse/ActiveRecord def self.preload_relation(projects_relation, options = {}) - if ::Feature.enabled?(:projects_preloader_fix) - super(projects_relation, options) - else - relation = super(projects_relation, options) - # use reselect to override the existing select and - # prevent an error `subquery has too many columns` - project_ids = relation.reselect('projects.id') - namespace_ids = relation.reselect(:namespace_id) - - options[:project_members] = options[:current_user] - .project_members - .where(source_id: project_ids) - .preload(:source, user: [notification_settings: :source]) - - options[:group_members] = options[:current_user] - .group_members - .where(source_id: namespace_ids) - .preload(:source, user: [notification_settings: :source]) - - relation - end + super(projects_relation, options) end def self.postload_relation(projects_relation, options = {}) - return unless ::Feature.enabled?(:projects_preloader_fix) - options[:project_members] = options[:current_user] .project_members .where(source_id: projects_relation.subquery(:id)) diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index c1a48a46d64..4e4a500718f 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -28,8 +28,13 @@ module API end expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_read_code? } expose :_links do - expose :self_url, as: :self, expose_nil: false + expose :closed_issues_url, expose_nil: false + expose :closed_merge_requests_url, expose_nil: false expose :edit_url, expose_nil: false + expose :merged_merge_requests_url, expose_nil: false + expose :opened_issues_url, expose_nil: false + expose :opened_merge_requests_url, expose_nil: false + expose :self_url, as: :self, expose_nil: false end private diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb index abf380e11d5..534510ec7e6 100644 --- a/lib/api/entities/releases/link.rb +++ b/lib/api/entities/releases/link.rb @@ -18,7 +18,7 @@ module API } do |link| ::Releases::LinkPresenter.new(link).direct_asset_url end - expose :external?, documentation: { type: 'boolean' }, as: :external + expose :external?, documentation: { type: 'boolean' }, as: :external # @deprecated expose :link_type, documentation: { type: 'string', example: 'other' } end end diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb index a86039b856a..884f0f75d7a 100644 --- a/lib/api/entities/user.rb +++ b/lib/api/entities/user.rb @@ -7,7 +7,7 @@ module API include TimeZoneHelper expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } - expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns + expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :discord, :website_url, :organization, :job_title, :pronouns expose :bot?, as: :bot expose :work_information do |user| work_information(user) diff --git a/lib/api/entities/wiki_page.rb b/lib/api/entities/wiki_page.rb index 07ef4a4a156..9d2a031cee8 100644 --- a/lib/api/entities/wiki_page.rb +++ b/lib/api/entities/wiki_page.rb @@ -25,3 +25,5 @@ module API end end end + +API::Entities::WikiPage.prepend_mod diff --git a/lib/api/events.rb b/lib/api/events.rb index d3e8892f3bc..5428692e3ec 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -8,7 +8,7 @@ module API allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } - feature_category :users + feature_category :user_profile urgency :low resource :events do diff --git a/lib/api/group_debian_distributions.rb b/lib/api/group_debian_distributions.rb index 8b6d4b8c4b2..9946199f7a8 100644 --- a/lib/api/group_debian_distributions.rb +++ b/lib/api/group_debian_distributions.rb @@ -6,10 +6,6 @@ module API requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group' end - before do - not_found! if Gitlab::FIPS.enabled? - end - resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do after_validation do require_packages_enabled! diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 38430aac455..aadcbe38b15 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -504,12 +504,12 @@ module API def render_validation_error!(model, status = 400) if model.errors.any? - render_api_error!(model_error_messages(model) || '400 Bad Request', status) + render_api_error!(model_errors(model).messages || '400 Bad Request', status) end end - def model_error_messages(model) - model.errors.messages + def model_errors(model) + model.errors end def render_api_error_with_reason!(status, message, reason) @@ -608,7 +608,7 @@ module API if file.file_storage? present_disk_file!(file.path, file.filename) elsif supports_direct_download && file.class.direct_download_enabled? - return redirect(signed_head_url(file)) if head_request_on_aws_file?(file) + return redirect(ObjectStorage::S3.signed_head_url(file)) if request.head? && file.fog_credentials[:provider] == 'AWS' redirect(cdn_fronted_url(file)) else @@ -691,15 +691,7 @@ module API {} end - def validate_anonymous_search_access! - return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops) - - unprocessable_entity!('User must be authenticated to use search') - end - def validate_search_rate_limit! - return unless Feature.enabled?(:rate_limit_issuable_searches) - if current_user check_rate_limit!(:search_rate_limit, scope: [current_user]) else @@ -709,19 +701,6 @@ module API private - def head_request_on_aws_file?(file) - request.head? && file.fog_credentials[:provider] == 'AWS' - end - - def signed_head_url(file) - fog_storage = ::Fog::Storage.new(file.fog_credentials) - fog_dir = fog_storage.directories.new(key: file.fog_directory) - fog_file = fog_dir.files.new(key: file.path) - expire_at = ::Fog::Time.now + file.fog_authenticated_url_expiration - - fog_file.collection.head_url(fog_file.key, expire_at) - end - # rubocop:disable Gitlab/ModuleWithInstanceVariables def initial_current_user return @initial_current_user if defined?(@initial_current_user) diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 56db6ee4c5c..816b8deb461 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -58,7 +58,9 @@ module API def log_user_activity(actor) commands = Gitlab::GitAccess::DOWNLOAD_COMMANDS - ::Users::ActivityService.new(actor).execute if commands.include?(params[:action]) + return unless commands.include?(params[:action]) + + ::Users::ActivityService.new(author: actor, namespace: project&.namespace, project: project).execute end def redis_ping diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index b0ea4388d9b..4b34a2bbe79 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -11,6 +11,9 @@ module API params :optional_state_filter_ee do end + params :optional_put_params_ee do + end + def find_source(source_type, id) public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend end @@ -98,6 +101,10 @@ module API user_id.present? end + def self.member_access_levels + Gitlab::Access.all_values + end + private def member_already_exists?(source, user_id) diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb index 1d35c316913..0fb3a19b8fd 100644 --- a/lib/api/helpers/packages_helpers.rb +++ b/lib/api/helpers/packages_helpers.rb @@ -86,7 +86,9 @@ module API end def track_package_event(action, scope, **args) - ::Packages::CreateEventService.new(nil, current_user, event_name: action, scope: scope).execute + service = ::Packages::CreateEventService.new(nil, current_user, event_name: action, scope: scope) + service.execute + category = args.delete(:category) || self.options[:for].name event_name = "i_package_#{scope}_user" ::Gitlab::Tracking.event( @@ -97,12 +99,40 @@ module API context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context], **args ) + + return unless Feature.enabled?(:route_hll_to_snowplow_phase3) + + if action.to_s == 'push_package' && service.originator_type == :deploy_token + track_snowplow_event("push_package_by_deploy_token", category, args) + elsif action.to_s == 'pull_package' && service.originator_type == :guest + track_snowplow_event("pull_package_by_guest", category, args) + end end def present_package_file!(package_file, supports_direct_download: true) package_file.package.touch_last_downloaded_at present_carrierwave_file!(package_file.file, supports_direct_download: supports_direct_download) end + + private + + def track_snowplow_event(action_name, category, args) + event_name = "i_package_#{action_name}" + key_path = "counts.package_events_i_package_#{action_name}" + service_ping_context = Gitlab::Tracking::ServicePingContext.new( + data_source: :redis, + key_path: key_path + ).to_context + + Gitlab::Tracking.event( + category, + action_name, + property: event_name, + label: key_path, + context: [service_ping_context], + **args + ) + end end end end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index c5636fa06de..2700ea90d59 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -32,7 +32,7 @@ module API optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`' optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`' optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`' - optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`' + optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`. Deprecated in GitLab 15.8, see https://gitlab.com/gitlab-org/gitlab/-/issues/385798.' optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`' optional :container_registry_access_level, type: String, values: %w(disabled private enabled), desc: 'Controls visibility of the container registry. One of `disabled`, `private` or `enabled`. `private` will make the container registry accessible only to project members (reporter role and above). `enabled` will make the container registry accessible to everyone who has access to the project. `disabled` will disable the container registry' optional :security_and_compliance_access_level, type: String, values: %w(disabled private enabled), desc: 'Security and compliance access level. One of `disabled`, `private` or `enabled`' @@ -48,6 +48,7 @@ module API optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters' optional :enforce_auth_checks_on_uploads, type: Boolean, desc: 'Enforce auth check on uploads' optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :group_runners_enabled, type: Boolean, desc: 'Flag indication if group runners are enabled for that project' optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diff threads on lines changed with a push' optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge' optional :container_registry_enabled, type: Boolean, desc: 'Deprecated: Use :container_registry_access_level instead. Flag indication if the container registry is enabled for that project' @@ -170,6 +171,7 @@ module API :security_and_compliance_access_level, :squash_option, :shared_runners_enabled, + :group_runners_enabled, :snippets_access_level, :tag_list, :topics, diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb index e80b89488a2..f97071d9a97 100644 --- a/lib/api/helpers/users_helpers.rb +++ b/lib/api/helpers/users_helpers.rb @@ -12,10 +12,14 @@ module API params :optional_index_params_ee do end - def model_error_messages(model) - super.tap do |error_messages| + def model_errors(model) + super.tap do |errors| # Remapping errors from nested associations. - error_messages[:bio] = error_messages.delete(:"user_detail.bio") if error_messages.has_key?(:"user_detail.bio") + next unless errors.has_key?(:"user_detail.bio") + + errors.delete(:"user_detail.bio").each do |message| + errors.add(:bio, message) + end end end diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 777d5019a29..5f12275b7a0 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -118,7 +118,7 @@ module API end end - namespace 'kubernetes/agent_configuration', urgency: :low do + namespace 'kubernetes/agent_configuration' do desc 'POST agent configuration' do detail 'Store configuration for an agent' end @@ -126,7 +126,7 @@ module API requires :agent_id, type: Integer, desc: 'ID of the configured Agent' requires :agent_config, type: JSON, desc: 'Configuration for the Agent' end - post '/', feature_category: :kubernetes_management do + post '/', feature_category: :kubernetes_management, urgency: :low do agent = ::Clusters::Agent.find(params[:agent_id]) ::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index 872dab26469..828f4b419ef 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -4,7 +4,7 @@ module API class Invitations < ::API::Base include PaginationParams - feature_category :users + feature_category :user_profile before { authenticate! } @@ -21,7 +21,7 @@ module API tags %w[invitations] end params do - requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)' + requires :access_level, type: Integer, values: ::API::Helpers::MembersHelpers.member_access_levels, desc: 'A valid access level (defaults: `30`, developer access level)' optional :email, type: Array[String], email_or_email_list: true, coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The email address to invite, or multiple emails separated by comma' optional :user_id, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The user ID of the new member or multiple IDs separated by commas.' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index 020b02248a0..4c79aa2587f 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -55,7 +55,7 @@ module API requires :target_project_id, types: [String, Integer], desc: 'The ID or URL-encoded path of a target project' requires :target_issue_iid, types: [String, Integer], desc: 'The internal ID of a target project’s issue' - optional :link_type, type: String, values: IssueLink.link_types.keys, + optional :link_type, type: String, values: IssueLink.available_link_types, desc: 'The type of the relation (“relates_to”, “blocks”, “is_blocked_by”),'\ 'defaults to “relates_to”)' end @@ -72,9 +72,7 @@ module API .execute if result[:status] == :success - issue_link = IssueLink.find_by!(source: source_issue, target: target_issue) - - present issue_link, with: Entities::IssueLink + present result[:created_references].first, with: Entities::IssueLink else render_api_error!(result[:message], result[:http_status]) end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 7b6306938cf..88e99b29587 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -115,7 +115,6 @@ module API end get '/issues_statistics' do authenticate! unless params[:scope] == 'all' - validate_anonymous_search_access! if params[:search].present? validate_search_rate_limit! if declared_params[:search].present? present issues_statistics, with: Grape::Presenters::Presenter @@ -134,7 +133,6 @@ module API end get do authenticate! unless params[:scope] == 'all' - validate_anonymous_search_access! if params[:search].present? validate_search_rate_limit! if declared_params[:search].present? issues = paginate(find_issues) @@ -174,7 +172,6 @@ module API optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true end get ":id/issues" do - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true)) @@ -194,7 +191,6 @@ module API use :issues_stats_params end get ":id/issues_statistics" do - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? present issues_statistics(group_id: user_group.id, include_subgroups: true), with: Grape::Presenters::Presenter @@ -214,7 +210,6 @@ module API use :issues_params end get ":id/issues" do - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? issues = paginate(find_issues(project_id: user_project.id)) @@ -234,7 +229,6 @@ module API use :issues_stats_params end get ":id/issues_statistics" do - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? present issues_statistics(project_id: user_project.id), with: Grape::Presenters::Presenter @@ -278,7 +272,7 @@ module API begin spam_params = ::Spam::SpamParams.new_from_request(request: request) - result = ::Issues::CreateService.new(project: user_project, + result = ::Issues::CreateService.new(container: user_project, current_user: current_user, params: issue_params, spam_params: spam_params).execute @@ -325,7 +319,7 @@ module API update_params = convert_parameters_from_legacy_format(update_params) spam_params = ::Spam::SpamParams.new_from_request(request: request) - issue = ::Issues::UpdateService.new(project: user_project, + issue = ::Issues::UpdateService.new(container: user_project, current_user: current_user, params: update_params, spam_params: spam_params).execute(issue) @@ -356,7 +350,7 @@ module API authorize! :update_issue, issue - if ::Issues::ReorderService.new(project: user_project, current_user: current_user, params: params).execute(issue) + if ::Issues::ReorderService.new(container: user_project, current_user: current_user, params: params).execute(issue) present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_api_error!({ error: 'Unprocessable Entity' }, 422) @@ -382,7 +376,7 @@ module API not_found!('Project') unless new_project begin - issue = ::Issues::MoveService.new(project: user_project, current_user: current_user).execute(issue, new_project) + issue = ::Issues::MoveService.new(container: user_project, current_user: current_user).execute(issue, new_project) present issue, with: Entities::Issue, current_user: current_user, project: user_project rescue ::Issues::MoveService::MoveError => error render_api_error!(error.message, 400) @@ -409,7 +403,7 @@ module API not_found!('Project') unless target_project begin - issue = ::Issues::CloneService.new(project: user_project, current_user: current_user) + issue = ::Issues::CloneService.new(container: user_project, current_user: current_user) .execute(issue, target_project, with_notes: params[:with_notes]) present issue, with: Entities::Issue, current_user: current_user, project: target_project rescue ::Issues::CloneService::CloneError => error @@ -430,7 +424,7 @@ module API authorize!(:destroy_issue, issue) destroy_conditionally!(issue) do |issue| - Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(issue) + Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(issue) end end # rubocop: enable CodeReuse/ActiveRecord @@ -444,9 +438,10 @@ module API get ':id/issues/:issue_iid/related_merge_requests' do issue = find_project_issue(params[:issue_iid]) - merge_requests = ::Issues::ReferencedMergeRequestsService.new(project: user_project, current_user: current_user) - .execute(issue) - .first + merge_requests = ::Issues::ReferencedMergeRequestsService + .new(container: user_project, current_user: current_user) + .execute(issue) + .first present paginate(::Kaminari.paginate_array(merge_requests)), with: Entities::MergeRequest, diff --git a/lib/api/members.rb b/lib/api/members.rb index 32c5227a939..1e640a6542a 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -139,6 +139,7 @@ module API requires :user_id, type: Integer, desc: 'The user ID of the new member' requires :access_level, type: Integer, desc: 'A valid access level' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' + use :optional_put_params_ee end # rubocop: disable CodeReuse/ActiveRecord put ":id/members/:user_id", feature_category: feature_category do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 25fbeca01dc..cd46b442b68 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -140,7 +140,6 @@ module API end get feature_category: :code_review_workflow, urgency: :low do authenticate! unless params[:scope] == 'all' - validate_anonymous_search_access! if params[:search].present? validate_search_rate_limit! if declared_params[:search].present? merge_requests = find_merge_requests @@ -169,7 +168,6 @@ module API desc: 'Returns merge requests from non archived projects only.' end get ":id/merge_requests", feature_category: :code_review_workflow, urgency: :low do - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) @@ -237,7 +235,6 @@ module API end get ":id/merge_requests", feature_category: :code_review_workflow, urgency: :low do authorize! :read_merge_request, user_project - validate_anonymous_search_access! if declared_params[:search].present? validate_search_rate_limit! if declared_params[:search].present? merge_requests = find_merge_requests(project_id: user_project.id) @@ -315,7 +312,7 @@ module API authorize!(:destroy_merge_request, merge_request) destroy_conditionally!(merge_request) do |merge_request| - Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(merge_request) + Issuable::DestroyService.new(container: user_project, current_user: current_user).execute(merge_request) end end @@ -627,8 +624,9 @@ module API merge_request = find_project_merge_request(params[:merge_request_iid]) - # Merge request can not be merged - # because user dont have permissions to push into target branch + # Merge request can not be merged because the user doesn't have + # permissions to push into target branch + # unauthorized! unless merge_request.can_be_merged_by?(current_user) merge_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds]) diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 2b1007e715a..c971f73ccbb 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -81,13 +81,13 @@ module API tags NAMESPACES_TAGS end params do - requires :namespace, type: String, desc: "Namespace’s path" + requires :id, type: String, desc: "Namespace’s path" optional :parent_id, type: Integer, desc: 'The ID of the parent namespace. If no ID is specified, only top-level namespaces are considered.' end - get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups, urgency: :low do + get ':id/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups, urgency: :low do check_rate_limit!(:namespace_exists, scope: current_user) - namespace_path = params[:namespace] + namespace_path = params[:id] existing_namespaces_within_the_parent = Namespace.without_project_namespaces.by_parent(params[:parent_id]) exists = existing_namespaces_within_the_parent.filter_by_path(namespace_path).exists? diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb index 15c1a78839f..db156186458 100644 --- a/lib/api/pages_domains.rb +++ b/lib/api/pages_domains.rb @@ -94,7 +94,7 @@ module API end params do requires :domain, type: String, desc: 'The domain' - # rubocop:disable Scalability/FileUploads + # rubocop:todo Scalability/FileUploads # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 optional :certificate, types: [File, String], desc: 'The certificate', as: :user_provided_certificate optional :key, types: [File, String], desc: 'The key', as: :user_provided_key @@ -122,7 +122,7 @@ module API desc 'Updates a pages domain' params do requires :domain, type: String, desc: 'The domain' - # rubocop:disable Scalability/FileUploads + # rubocop:todo Scalability/FileUploads # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 optional :certificate, types: [File, String], desc: 'The certificate', as: :user_provided_certificate optional :key, types: [File, String], desc: 'The key', as: :user_provided_key diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index e5e6ccdf025..0f4f1dc7fa6 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -12,7 +12,7 @@ module API before { authorize_read_container_images! } - feature_category :package_registry + feature_category :container_registry urgency :low params do diff --git a/lib/api/project_debian_distributions.rb b/lib/api/project_debian_distributions.rb index 856b4097b5a..fc94107f88d 100644 --- a/lib/api/project_debian_distributions.rb +++ b/lib/api/project_debian_distributions.rb @@ -6,10 +6,6 @@ module API requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end - before do - not_found! if Gitlab::FIPS.enabled? - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do after_validation do require_packages_enabled! diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb index d90ce32c354..3e651cdda44 100644 --- a/lib/api/project_events.rb +++ b/lib/api/project_events.rb @@ -6,7 +6,7 @@ module API include APIGuard helpers ::API::Helpers::EventsHelpers - feature_category :users + feature_category :user_profile # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357839 urgency :low diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5077f02fcc1..6eea56ea117 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -9,7 +9,11 @@ module API before { authenticate_non_get! } - feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key'] + feature_category :projects, %w[ + /projects/:id/custom_attributes + /projects/:id/custom_attributes/:key + /projects/:id/share_locations + ] PROJECT_ATTACHMENT_SIZE_EXEMPT = 1.gigabyte @@ -351,6 +355,20 @@ module API render_validation_error!(project) end end + + desc 'Returns group that can be shared with the given project' do + success Entities::Group + end + params do + requires :id, type: Integer, desc: 'The id of the project' + optional :search, type: String, desc: 'Return list of groups matching the search criteria' + end + get ':id/share_locations' do + groups = ::Groups::AcceptingProjectSharesFinder.new(current_user, user_project, declared_params(include_missing: false)).execute + + present_groups groups + end + # rubocop: enable CodeReuse/ActiveRecord end @@ -850,11 +868,23 @@ module API ] tags %w[projects] end + params do + optional :task, type: Symbol, default: :eager, values: %i[eager prune], desc: '`prune` to trigger manual prune of unreachable objects or `eager` to trigger eager housekeeping.' + end post ':id/housekeeping', feature_category: :source_code_management do authorize_admin_project begin - ::Repositories::HousekeepingService.new(user_project, :gc).execute + ::Repositories::HousekeepingService.new(user_project, params[:task]).execute do + ::Gitlab::Audit::Auditor.audit( + name: 'manually_trigger_housekeeping', + author: current_user, + scope: user_project, + target: user_project, + message: "Housekeeping task: #{params[:task]}", + created_at: DateTime.current + ) + end rescue ::Repositories::HousekeepingService::LeaseTaken => error conflict!(error.message) end diff --git a/lib/api/releases.rb b/lib/api/releases.rb index e69dc756551..ebf1c03e86b 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -246,8 +246,14 @@ module API optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, - desc: 'The title of each milestone the release is associated with. GitLab Premium customers can specify group milestones', - default: [] + desc: 'The title of each milestone the release is associated with. GitLab Premium customers can specify group milestones. Cannot be combined with `milestone_ids` parameter.' + + optional :milestone_ids, + type: Array[String, Integer], + coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, + desc: 'The ID of each milestone the release is associated with. GitLab Premium customers can specify group milestones. Cannot be combined with `milestones` parameter.' + + mutually_exclusive :milestones, :milestone_ids, message: 'Cannot specify milestones and milestone_ids at the same time' optional :released_at, type: DateTime, @@ -292,7 +298,14 @@ module API optional :milestones, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, - desc: 'The title of each milestone to associate with the release. GitLab Premium customers can specify group milestones. To remove all milestones from the release, specify `[]`' + desc: 'The title of each milestone to associate with the release. GitLab Premium customers can specify group milestones. Cannot be combined with `milestone_ids` parameter. To remove all milestones from the release, specify `[]`' + + optional :milestone_ids, + type: Array[String, Integer], + coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, + desc: 'The ID of each milestone the release is associated with. GitLab Premium customers can specify group milestones. Cannot be combined with `milestones` parameter. To remove all milestones from the release, specify `[]`' + + mutually_exclusive :milestones, :milestone_ids, message: 'Cannot specify milestones and milestone_ids at the same time' end route_setting :authentication, job_token_allowed: true put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 06b576a982b..8efb848eb57 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -191,8 +191,8 @@ module API optional :group_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for group runners, in seconds' optional :project_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for project runners, in seconds' optional :pipeline_limit_per_project_user_sha, type: Integer, desc: "Maximum number of pipeline creation requests allowed per minute per user and commit. Set to 0 for unlimited requests per minute." - optional :jira_connect_application_key, type: String, desc: "Application ID of the OAuth application that should be used to authenticate with the GitLab.com for Jira Cloud app" - optional :jira_connect_proxy_url, type: String, desc: "URL of the GitLab instance that should be used as a proxy for the GitLab.com for Jira Cloud app" + optional :jira_connect_application_key, type: String, desc: "Application ID of the OAuth application that should be used to authenticate with the GitLab for Jira Cloud app" + optional :jira_connect_proxy_url, type: String, desc: "URL of the GitLab instance that should be used as a proxy for the GitLab for Jira Cloud app" optional :bulk_import_enabled, type: Boolean, desc: 'Enable migrating GitLab groups and projects by direct transfer' optional :allow_runner_registration_token, type: Boolean, desc: 'Allow registering runners using a registration token' diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb index dd8ad2cc144..3534edc3831 100644 --- a/lib/api/time_tracking_endpoints.rb +++ b/lib/api/time_tracking_endpoints.rb @@ -35,7 +35,9 @@ module API custom_params = declared_params(include_missing: false) custom_params.merge!(attrs) - issuable = update_service.new(project: user_project, current_user: current_user, params: custom_params).execute(load_issuable) + issuable = update_service.new(**update_service.constructor_container_arg(user_project), + current_user: current_user, params: custom_params).execute(load_issuable) + if issuable.valid? present issuable, with: Entities::IssuableTimeStats else diff --git a/lib/api/users.rb b/lib/api/users.rb index a9b09596728..cc7eb63798a 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -8,7 +8,7 @@ module API allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } - feature_category :users, + feature_category :user_profile, %w[ /users/:id/custom_attributes /users/:id/custom_attributes/:key @@ -46,6 +46,7 @@ module API optional :skype, type: String, desc: 'The Skype username' optional :linkedin, type: String, desc: 'The LinkedIn username' optional :twitter, type: String, desc: 'The Twitter username' + optional :discord, type: String, desc: 'The Discord user ID' optional :website_url, type: String, desc: 'The website of the user' optional :organization, type: String, desc: 'The organization of the user' optional :projects_limit, type: Integer, desc: 'The number of projects a user can create' @@ -131,7 +132,7 @@ module API use :optional_index_params_ee end # rubocop: disable CodeReuse/ActiveRecord - get feature_category: :users, urgency: :low do + get feature_category: :user_profile, urgency: :low do authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present? unless current_user&.can_read_all_resources? @@ -175,7 +176,7 @@ module API use :with_custom_attributes end # rubocop: disable CodeReuse/ActiveRecord - get ":id", feature_category: :users, urgency: :low do + get ":id", feature_category: :user_profile, urgency: :low do forbidden!('Not authorized!') unless current_user unless current_user.can_read_all_resources? @@ -200,7 +201,7 @@ module API params do requires :user_id, type: String, desc: 'The ID or username of the user' end - get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users, urgency: :default do + get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :user_profile, urgency: :default do user = find_user(params[:user_id]) not_found!('User') unless user && can?(current_user, :read_user, user) @@ -214,7 +215,7 @@ module API params do requires :id, type: Integer, desc: 'The ID of the user' end - post ':id/follow', feature_category: :users do + post ':id/follow', feature_category: :user_profile do user = find_user(params[:id]) not_found!('User') unless user @@ -234,7 +235,7 @@ module API params do requires :id, type: Integer, desc: 'The ID of the user' end - post ':id/unfollow', feature_category: :users do + post ':id/unfollow', feature_category: :user_profile do user = find_user(params[:id]) not_found!('User') unless user @@ -252,7 +253,7 @@ module API requires :id, type: Integer, desc: 'The ID of the user' use :pagination end - get ':id/following', feature_category: :users do + get ':id/following', feature_category: :user_profile do forbidden!('Not authorized!') unless current_user user = find_user(params[:id]) @@ -268,7 +269,7 @@ module API requires :id, type: Integer, desc: 'The ID of the user' use :pagination end - get ':id/followers', feature_category: :users do + get ':id/followers', feature_category: :user_profile do forbidden!('Not authorized!') unless current_user user = find_user(params[:id]) @@ -291,7 +292,7 @@ module API optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set' use :optional_attributes end - post feature_category: :users do + post feature_category: :user_profile do authenticated_as_admin! params = declared_params(include_missing: false) @@ -333,7 +334,7 @@ module API use :optional_attributes end # rubocop: disable CodeReuse/ActiveRecord - put ":id", feature_category: :users do + put ":id", feature_category: :user_profile do authenticated_as_admin! user = User.find_by(id: params.delete(:id)) @@ -644,7 +645,7 @@ module API optional :skip_confirmation, type: Boolean, desc: 'Skip confirmation of email and assume it is verified' end # rubocop: disable CodeReuse/ActiveRecord - post ":id/emails", feature_category: :users do + post ":id/emails", feature_category: :user_profile do authenticated_as_admin! user = User.find_by(id: params.delete(:id)) @@ -668,7 +669,7 @@ module API use :pagination end # rubocop: disable CodeReuse/ActiveRecord - get ':id/emails', feature_category: :users do + get ':id/emails', feature_category: :user_profile do authenticated_as_admin! user = User.find_by(id: params[:id]) not_found!('User') unless user @@ -685,7 +686,7 @@ module API requires :email_id, type: Integer, desc: 'The ID of the email' end # rubocop: disable CodeReuse/ActiveRecord - delete ':id/emails/:email_id', feature_category: :users do + delete ':id/emails/:email_id', feature_category: :user_profile do authenticated_as_admin! user = User.find_by(id: params[:id]) not_found!('User') unless user @@ -707,7 +708,7 @@ module API optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions" end # rubocop: disable CodeReuse/ActiveRecord - delete ":id", feature_category: :users do + delete ":id", feature_category: :user_profile do authenticated_as_admin! user = User.find_by(id: params[:id]) @@ -883,7 +884,7 @@ module API optional :type, type: String, values: %w[Project Namespace] use :pagination end - get ":user_id/memberships", feature_category: :users, urgency: :high do + get ":user_id/memberships", feature_category: :user_profile, urgency: :high do authenticated_as_admin! user = find_user_by_id(params) @@ -1021,7 +1022,7 @@ module API desc 'Get the currently authenticated user' do success Entities::UserPublic end - get feature_category: :users, urgency: :low do + get feature_category: :user_profile, urgency: :low do entity = # We're disabling Cop/UserAdmin because it checks if the given user is an admin. if current_user.admin? # rubocop:disable Cop/UserAdmin @@ -1202,7 +1203,7 @@ module API params do use :pagination end - get "emails", feature_category: :users, urgency: :high do + get "emails", feature_category: :user_profile, urgency: :high do present paginate(current_user.emails), with: Entities::Email end @@ -1244,7 +1245,7 @@ module API optional :show_whitespace_in_diffs, type: Boolean, desc: 'Flag indicating the user sees whitespace changes in diffs' at_least_one_of :view_diffs_file_by_file, :show_whitespace_in_diffs end - put "preferences", feature_category: :users, urgency: :high do + put "preferences", feature_category: :user_profile, urgency: :high do authenticate! preferences = current_user.user_preference @@ -1263,7 +1264,7 @@ module API success Entities::UserPreferences detail 'This feature was introduced in GitLab 14.0.' end - get "preferences", feature_category: :users do + get "preferences", feature_category: :user_profile do present current_user.user_preference, with: Entities::UserPreferences end @@ -1274,7 +1275,7 @@ module API requires :email_id, type: Integer, desc: 'The ID of the email' end # rubocop: disable CodeReuse/ActiveRecord - get "emails/:email_id", feature_category: :users do + get "emails/:email_id", feature_category: :user_profile do email = current_user.emails.find_by(id: params[:email_id]) not_found!('Email') unless email @@ -1288,7 +1289,7 @@ module API params do requires :email, type: String, desc: 'The new email' end - post "emails", feature_category: :users do + post "emails", feature_category: :user_profile do email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute if email.errors.blank? @@ -1303,7 +1304,7 @@ module API requires :email_id, type: Integer, desc: 'The ID of the email' end # rubocop: disable CodeReuse/ActiveRecord - delete "emails/:email_id", feature_category: :users do + delete "emails/:email_id", feature_category: :user_profile do email = current_user.emails.find_by(id: params[:email_id]) not_found!('Email') unless email @@ -1319,7 +1320,7 @@ module API use :pagination end # rubocop: disable CodeReuse/ActiveRecord - get "activities", feature_category: :users do + get "activities", feature_category: :user_profile do authenticated_as_admin! activities = User @@ -1337,7 +1338,7 @@ module API params do use :set_user_status_params end - put "status", feature_category: :users do + put "status", feature_category: :user_profile do set_user_status(include_missing_params: true) end @@ -1348,7 +1349,7 @@ module API params do use :set_user_status_params end - patch "status", feature_category: :users do + patch "status", feature_category: :user_profile do if declared_params(include_missing: false).empty? status :ok @@ -1361,7 +1362,7 @@ module API desc 'get the status of the current user' do success Entities::UserStatus end - get 'status', feature_category: :users do + get 'status', feature_category: :user_profile do present current_user.status || {}, with: Entities::UserStatus end end diff --git a/lib/api/validations/validators/bulk_imports.rb b/lib/api/validations/validators/bulk_imports.rb index 8d49607f64c..4625f2f39cd 100644 --- a/lib/api/validations/validators/bulk_imports.rb +++ b/lib/api/validations/validators/bulk_imports.rb @@ -21,7 +21,7 @@ module API def validate_param!(attr_name, params) return if params[attr_name].blank? - unless params[attr_name] =~ Gitlab::Regex.bulk_import_namespace_path_regex # rubocop: disable Style/GuardClause + unless params[attr_name] =~ Gitlab::Regex.bulk_import_destination_namespace_path_regex # rubocop: disable Style/GuardClause raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], message: "cannot start with a dash or forward slash, or end with a period or forward slash. " \ @@ -34,7 +34,7 @@ module API class SourceFullPath < Grape::Validations::Base def validate_param!(attr_name, params) - unless params[attr_name] =~ Gitlab::Regex.bulk_import_namespace_path_regex # rubocop: disable Style/GuardClause + unless params[attr_name] =~ Gitlab::Regex.bulk_import_source_full_path_regex # rubocop: disable Style/GuardClause raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], message: "must be a relative path and not include protocol, sub-domain, or domain information. " \ |
