diff options
Diffstat (limited to 'lib/api')
50 files changed, 705 insertions, 143 deletions
diff --git a/lib/api/admin/ci/variables.rb b/lib/api/admin/ci/variables.rb new file mode 100644 index 00000000000..df731148bac --- /dev/null +++ b/lib/api/admin/ci/variables.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module API + module Admin + module Ci + class Variables < Grape::API + include PaginationParams + + before { authenticated_as_admin! } + + namespace 'admin' do + namespace 'ci' do + namespace 'variables' do + desc 'Get instance-level variables' do + success Entities::Variable + end + params do + use :pagination + end + get '/' do + variables = ::Ci::InstanceVariable.all + + present paginate(variables), with: Entities::Variable + end + + desc 'Get a specific variable from a group' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + get ':key' do + key = params[:key] + variable = ::Ci::InstanceVariable.find_by_key(key) + + break not_found!('InstanceVariable') unless variable + + present variable, with: Entities::Variable + end + + desc 'Create a new instance-level variable' do + success Entities::Variable + end + params do + requires :key, + type: String, + desc: 'The key of the variable' + + requires :value, + type: String, + desc: 'The value of the variable' + + optional :protected, + type: String, + desc: 'Whether the variable is protected' + + optional :masked, + type: String, + desc: 'Whether the variable is masked' + + optional :variable_type, + type: String, + values: ::Ci::InstanceVariable.variable_types.keys, + desc: 'The type of variable, must be one of env_var or file. Defaults to env_var' + end + post '/' do + variable_params = declared_params(include_missing: false) + + variable = ::Ci::InstanceVariable.new(variable_params) + + if variable.save + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Update an existing instance-variable' do + success Entities::Variable + end + params do + optional :key, + type: String, + desc: 'The key of the variable' + + optional :value, + type: String, + desc: 'The value of the variable' + + optional :protected, + type: String, + desc: 'Whether the variable is protected' + + optional :masked, + type: String, + desc: 'Whether the variable is masked' + + optional :variable_type, + type: String, + values: ::Ci::InstanceVariable.variable_types.keys, + desc: 'The type of variable, must be one of env_var or file' + end + put ':key' do + variable = ::Ci::InstanceVariable.find_by_key(params[:key]) + + break not_found!('InstanceVariable') unless variable + + variable_params = declared_params(include_missing: false).except(:key) + + if variable.update(variable_params) + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Delete an existing instance-level variable' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end + delete ':key' do + variable = ::Ci::InstanceVariable.find_by_key(params[:key]) + not_found!('InstanceVariable') unless variable + + variable.destroy + + no_content! + end + end + end + end + end + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index de9a3120d90..b8135539cda 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -24,7 +24,8 @@ module API Gitlab::GrapeLogging::Loggers::ExceptionLogger.new, Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new, Gitlab::GrapeLogging::Loggers::PerfLogger.new, - Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new + Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new, + Gitlab::GrapeLogging::Loggers::ContextLogger.new ] allow_access_with_scope :api @@ -97,6 +98,15 @@ module API handle_api_exception(exception) end + # This is a specific exception raised by `rack-timeout` gem when Puma + # requests surpass its timeout. Given it inherits from Exception, we + # should rescue it separately. For more info, see: + # - https://github.com/sharpstone/rack-timeout/blob/master/doc/exceptions.md + # - https://github.com/ruby-grape/grape#exception-handling + rescue_from Rack::Timeout::RequestTimeoutException do |exception| + handle_api_exception(exception) + end + format :json content_type :txt, "text/plain" @@ -111,6 +121,7 @@ module API # Keep in alphabetical order mount ::API::AccessRequests + mount ::API::Admin::Ci::Variables mount ::API::Admin::Sidekiq mount ::API::Appearance mount ::API::Applications @@ -131,6 +142,7 @@ module API mount ::API::Events mount ::API::Features mount ::API::Files + mount ::API::FreezePeriods mount ::API::GroupBoards mount ::API::GroupClusters mount ::API::GroupExport @@ -153,6 +165,7 @@ module API mount ::API::MergeRequestDiffs mount ::API::MergeRequests mount ::API::Metrics::Dashboard::Annotations + mount ::API::Metrics::UserStarredDashboards mount ::API::Namespaces mount ::API::Notes mount ::API::Discussions @@ -169,6 +182,7 @@ module API mount ::API::ProjectImport mount ::API::ProjectHooks mount ::API::ProjectMilestones + mount ::API::ProjectRepositoryStorageMoves mount ::API::Projects mount ::API::ProjectSnapshots mount ::API::ProjectSnippets diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 9dd2de5c7ba..c6557fce541 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -65,7 +65,8 @@ module API end def find_user_from_sources - find_user_from_access_token || + deploy_token_from_request || + find_user_from_access_token || find_user_from_job_token || find_user_from_warden end @@ -90,12 +91,16 @@ module API end def api_access_allowed?(user) - Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) + user_allowed_or_deploy_token?(user) && user.can?(:access_api) end def api_access_denied_message(user) Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message end + + def user_allowed_or_deploy_token?(user) + Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken) + end end class_methods do diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index a775102e87d..71a35bb4493 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -27,7 +27,8 @@ module API optional :logo, type: File, desc: 'Instance image used on the sign in / sign up page' # 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 :new_project_guidelines, type: String, desc: 'Markmarkdown text shown on the new project page' + 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' optional :footer_message, type: String, desc: 'Message within the system footer bar' optional :message_background_color, type: String, desc: 'Background color for the system header / footer bar' diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 999bf1627c1..081e8ffe4f0 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -8,6 +8,8 @@ module API BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX) + after_validation { content_type "application/json" } + before do require_repository_enabled! authorize! :download_code, user_project diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index f3a08ae970a..0fbbd96cf02 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -11,6 +11,8 @@ module API result_hash = Hashie::Mash.new result_hash[:read_registry] = scopes.include?('read_registry') result_hash[:write_registry] = scopes.include?('write_registry') + result_hash[:read_package_registry] = scopes.include?('read_package_registry') + result_hash[:write_package_registry] = scopes.include?('write_package_registry') result_hash[:read_repository] = scopes.include?('read_repository') result_hash end @@ -55,7 +57,7 @@ module API params do requires :name, type: String, desc: "New deploy token's name" requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end @@ -118,7 +120,7 @@ module API params do requires :name, type: String, desc: 'The name of the deploy token' requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", "write_registry", "read_package_registry", or "write_package_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end diff --git a/lib/api/entities/appearance.rb b/lib/api/entities/appearance.rb index c3cffc8d05c..a09faf55f48 100644 --- a/lib/api/entities/appearance.rb +++ b/lib/api/entities/appearance.rb @@ -19,6 +19,7 @@ module API end expose :new_project_guidelines + expose :profile_image_guidelines expose :header_message expose :footer_message expose :message_background_color diff --git a/lib/api/entities/branch.rb b/lib/api/entities/branch.rb index 1d5017ac702..f9d06082ad6 100644 --- a/lib/api/entities/branch.rb +++ b/lib/api/entities/branch.rb @@ -3,6 +3,8 @@ module API module Entities class Branch < Grape::Entity + include Gitlab::Routing + expose :name expose :commit, using: Entities::Commit do |repo_branch, options| @@ -36,6 +38,10 @@ module API expose :default do |repo_branch, options| options[:project].default_branch == repo_branch.name end + + expose :web_url do |repo_branch| + project_tree_url(options[:project], repo_branch.name) + end end end end diff --git a/lib/api/entities/design_management/design.rb b/lib/api/entities/design_management/design.rb new file mode 100644 index 00000000000..183fe06d8f1 --- /dev/null +++ b/lib/api/entities/design_management/design.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + module DesignManagement + class Design < Grape::Entity + expose :id + expose :project_id + expose :filename + expose :image_url do |design| + ::Gitlab::UrlBuilder.build(design) + end + end + end + end +end diff --git a/lib/api/entities/freeze_period.rb b/lib/api/entities/freeze_period.rb new file mode 100644 index 00000000000..9b5f08925db --- /dev/null +++ b/lib/api/entities/freeze_period.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + class FreezePeriod < Grape::Entity + expose :id + expose :freeze_start, :freeze_end, :cron_timezone + expose :created_at, :updated_at + end + end +end diff --git a/lib/api/entities/job_request/artifacts.rb b/lib/api/entities/job_request/artifacts.rb index c6871fdd875..0d27f5a9189 100644 --- a/lib/api/entities/job_request/artifacts.rb +++ b/lib/api/entities/job_request/artifacts.rb @@ -7,6 +7,7 @@ module API expose :name expose :untracked expose :paths + expose :exclude, expose_nil: false expose :when expose :expire_in expose :artifact_type diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 4610220e4f6..1a89a83a619 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -50,8 +50,10 @@ module API # use `MergeRequest#mergeable?` instead (boolean). # See https://gitlab.com/gitlab-org/gitlab-foss/issues/42344 for more # information. - expose :merge_status do |merge_request| - merge_request.check_mergeability(async: true) + # + # For list endpoints, we skip the recheck by default, since it's expensive + expose :merge_status do |merge_request, options| + merge_request.check_mergeability(async: true) unless options[:skip_merge_status_recheck] merge_request.public_merge_status end expose :diff_head_sha, as: :sha diff --git a/lib/api/entities/metrics/user_starred_dashboard.rb b/lib/api/entities/metrics/user_starred_dashboard.rb new file mode 100644 index 00000000000..d774160e3ea --- /dev/null +++ b/lib/api/entities/metrics/user_starred_dashboard.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Metrics + class UserStarredDashboard < Grape::Entity + expose :id, :dashboard_path, :user_id, :project_id + end + end + end +end diff --git a/lib/api/entities/personal_snippet.rb b/lib/api/entities/personal_snippet.rb index eb0266e61e6..03ab6c0809c 100644 --- a/lib/api/entities/personal_snippet.rb +++ b/lib/api/entities/personal_snippet.rb @@ -3,9 +3,6 @@ module API module Entities class PersonalSnippet < Snippet - expose :raw_url do |snippet| - Gitlab::UrlBuilder.build(snippet, raw: true) - end end end end diff --git a/lib/api/entities/project_repository_storage_move.rb b/lib/api/entities/project_repository_storage_move.rb new file mode 100644 index 00000000000..25643651a14 --- /dev/null +++ b/lib/api/entities/project_repository_storage_move.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class ProjectRepositoryStorageMove < Grape::Entity + expose :id + expose :created_at + expose :human_state_name, as: :state + expose :source_storage_name + expose :destination_storage_name + expose :project, using: Entities::ProjectIdentity + end + end +end diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index edcd9bc6505..99fa496d368 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -21,7 +21,6 @@ module API expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? } expose :commit_path, expose_nil: false expose :tag_path, expose_nil: false - expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? } expose :assets do expose :assets_count, as: :count do |release, _| @@ -32,7 +31,6 @@ module API expose :links, using: Entities::Releases::Link do |release, options| release.links.sorted end - expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? } end expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? } expose :_links do diff --git a/lib/api/entities/remote_mirror.rb b/lib/api/entities/remote_mirror.rb index 18d51726bab..87daef9a05c 100644 --- a/lib/api/entities/remote_mirror.rb +++ b/lib/api/entities/remote_mirror.rb @@ -12,9 +12,7 @@ module API expose :last_successful_update_at expose :last_error expose :only_protected_branches - expose :keep_divergent_refs, if: -> (mirror, _options) do - ::Feature.enabled?(:keep_divergent_refs, mirror.project) - end + expose :keep_divergent_refs end end end diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb index 2bb143253fe..1dd8543d595 100644 --- a/lib/api/entities/runner_details.rb +++ b/lib/api/entities/runner_details.rb @@ -11,9 +11,12 @@ module API expose :version, :revision, :platform, :architecture expose :contacted_at - # @deprecated in 12.10 https://gitlab.com/gitlab-org/gitlab/-/issues/214320 - # will be removed by 13.0 https://gitlab.com/gitlab-org/gitlab/-/issues/214322 - expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? } + # 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| diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb index 8a13b4746a9..19c89603cbc 100644 --- a/lib/api/entities/snippet.rb +++ b/lib/api/entities/snippet.rb @@ -3,14 +3,20 @@ module API module Entities class Snippet < Grape::Entity - expose :id, :title, :file_name, :description, :visibility + expose :id, :title, :description, :visibility expose :author, using: Entities::UserBasic expose :updated_at, :created_at expose :project_id expose :web_url do |snippet| Gitlab::UrlBuilder.build(snippet) end - expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.versioned_enabled_for?(options[:current_user]) } + expose :raw_url do |snippet| + Gitlab::UrlBuilder.build(snippet, raw: true) + end + expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.repository_exists? } + expose :file_name do |snippet| + snippet.file_name_on_repo || snippet.file_name + end end end end diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb index abfdde89bf1..0acbb4cb704 100644 --- a/lib/api/entities/todo.rb +++ b/lib/api/entities/todo.rb @@ -22,6 +22,7 @@ module API expose :body expose :state expose :created_at + expose :updated_at def todo_target_class(target_type) # false as second argument prevents looking up in module hierarchy @@ -30,6 +31,8 @@ module API end def todo_target_url(todo) + return design_todo_target_url(todo) if todo.for_design? + target_type = todo.target_type.underscore target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url" @@ -41,6 +44,16 @@ module API def todo_target_anchor(todo) "note_#{todo.note_id}" if todo.note_id? end + + def design_todo_target_url(todo) + design = todo.target + path_options = { + anchor: todo_target_anchor(todo), + vueroute: design.filename + } + + ::Gitlab::Routing.url_helpers.designs_project_issue_url(design.project, design.issue, path_options) + end end end end diff --git a/lib/api/entities/user_basic.rb b/lib/api/entities/user_basic.rb index e063aa42855..80f3ee7b502 100644 --- a/lib/api/entities/user_basic.rb +++ b/lib/api/entities/user_basic.rb @@ -18,3 +18,5 @@ module API end end end + +API::Entities::UserBasic.prepend_if_ee('EE::API::Entities::UserBasic') diff --git a/lib/api/entities/user_path.rb b/lib/api/entities/user_path.rb index 7d922b39654..3f007659813 100644 --- a/lib/api/entities/user_path.rb +++ b/lib/api/entities/user_path.rb @@ -12,3 +12,5 @@ module API end end end + +API::Entities::UserPath.prepend_if_ee('EE::API::Entities::UserPath') diff --git a/lib/api/features.rb b/lib/api/features.rb index 69b751e9bdb..f507919b055 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -16,6 +16,15 @@ module API end end + def gate_key(params) + case params[:key] + when 'percentage_of_actors' + :percentage_of_actors + else + :percentage_of_time + end + end + def gate_targets(params) Feature::Target.new(params).targets end @@ -40,15 +49,22 @@ module API end params do requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time' + optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`' optional :feature_group, type: String, desc: 'A Feature group name' optional :user, type: String, desc: 'A GitLab username' optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'" optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce' + + mutually_exclusive :key, :feature_group + mutually_exclusive :key, :user + mutually_exclusive :key, :group + mutually_exclusive :key, :project end post ':name' do feature = Feature.get(params[:name]) targets = gate_targets(params) value = gate_value(params) + key = gate_key(params) case value when true @@ -64,7 +80,11 @@ module API feature.disable end else - feature.enable_percentage_of_time(value) + if key == :percentage_of_actors + feature.enable_percentage_of_actors(value) + else + feature.enable_percentage_of_time(value) + end end present feature, with: Entities::Feature, current_user: current_user diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb new file mode 100644 index 00000000000..9c7e5a5832d --- /dev/null +++ b/lib/api/freeze_periods.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module API + class FreezePeriods < Grape::API + include PaginationParams + + before { authenticate! } + + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get project freeze periods' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::FreezePeriod + end + params do + use :pagination + end + + get ":id/freeze_periods" do + authorize! :read_freeze_period, user_project + + freeze_periods = ::FreezePeriodsFinder.new(user_project, current_user).execute + + present paginate(freeze_periods), with: Entities::FreezePeriod, current_user: current_user + end + + desc 'Get a single freeze period' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::FreezePeriod + end + params do + requires :freeze_period_id, type: Integer, desc: 'The ID of a project freeze period' + end + get ":id/freeze_periods/:freeze_period_id" do + authorize! :read_freeze_period, user_project + + present freeze_period, with: Entities::FreezePeriod, current_user: current_user + end + + desc 'Create a new freeze period' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::FreezePeriod + end + params do + requires :freeze_start, type: String, desc: 'Freeze Period start' + requires :freeze_end, type: String, desc: 'Freeze Period end' + optional :cron_timezone, type: String, desc: 'Timezone' + end + post ':id/freeze_periods' do + authorize! :create_freeze_period, user_project + + freeze_period_params = declared(params, include_parent_namespaces: false) + + freeze_period = user_project.freeze_periods.create(freeze_period_params) + + if freeze_period.persisted? + present freeze_period, with: Entities::FreezePeriod + else + render_validation_error!(freeze_period) + end + end + + desc 'Update a freeze period' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::FreezePeriod + end + params do + optional :freeze_start, type: String, desc: 'Freeze Period start' + optional :freeze_end, type: String, desc: 'Freeze Period end' + optional :cron_timezone, type: String, desc: 'Freeze Period Timezone' + end + put ':id/freeze_periods/:freeze_period_id' do + authorize! :update_freeze_period, user_project + + freeze_period_params = declared(params, include_parent_namespaces: false, include_missing: false) + + if freeze_period.update(freeze_period_params) + present freeze_period, with: Entities::FreezePeriod + else + render_validation_error!(freeze_period) + end + end + + desc 'Delete a freeze period' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::FreezePeriod + end + params do + requires :freeze_period_id, type: Integer, desc: 'Freeze Period ID' + end + delete ':id/freeze_periods/:freeze_period_id' do + authorize! :destroy_freeze_period, user_project + + destroy_conditionally!(freeze_period) + end + end + + helpers do + def freeze_period + @freeze_period ||= user_project.freeze_periods.find(params[:freeze_period_id]) + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index d375c35e8c0..353c8b4b242 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -60,18 +60,14 @@ module API .execute end - def find_group_projects(params) + def find_group_projects(params, finder_options) group = find_group!(params[:id]) - options = { - only_owned: !params[:with_shared], - include_subgroups: params[:include_subgroups] - } projects = GroupProjectsFinder.new( group: group, current_user: current_user, params: project_finder_params, - options: options + options: finder_options ).execute projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled] projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] @@ -80,11 +76,22 @@ module API paginate(projects) end + def present_projects(params, projects) + options = { + with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project, + current_user: current_user + } + + projects, options = with_custom_attributes(projects, options) + + present options[:with].prepare_relation(projects), options + end + def present_groups(params, groups) options = { with: Entities::Group, current_user: current_user, - statistics: params[:statistics] && current_user.admin? + statistics: params[:statistics] && current_user&.admin? } groups = groups.with_statistics if options[:statistics] @@ -226,16 +233,42 @@ module API use :optional_projects_params end get ":id/projects" do - projects = find_group_projects(params) - - options = { - with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project, - current_user: current_user + finder_options = { + only_owned: !params[:with_shared], + include_subgroups: params[:include_subgroups] } - projects, options = with_custom_attributes(projects, options) + projects = find_group_projects(params, finder_options) - present options[:with].prepare_relation(projects), options + present_projects(params, projects) + end + + desc 'Get a list of shared projects in this group' do + success Entities::Project + end + params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + optional :starred, type: Boolean, default: false, desc: 'Limit by starred status' + optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' + optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' + optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects' + + use :pagination + use :with_custom_attributes + end + get ":id/projects/shared" do + projects = find_group_projects(params, { only_shared: true }) + + present_projects(params, projects) end desc 'Get a list of subgroups in this group.' do diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 42b82aac1c4..c6f6dc255d4 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -11,6 +11,7 @@ module API SUDO_PARAM = :sudo API_USER_ENV = 'gitlab.api.user' API_EXCEPTION_ENV = 'gitlab.api.exception' + API_RESPONSE_STATUS_CODE = 'gitlab.api.response_status_code' def declared_params(options = {}) options = { include_parent_namespaces: false }.merge(options) @@ -178,6 +179,14 @@ module API end end + def find_tag!(tag_name) + if Gitlab::GitRefValidator.validate(tag_name) + user_project.repository.find_tag(tag_name) || not_found!('Tag') + else + render_api_error!('The tag refname is invalid', 400) + end + end + # rubocop: disable CodeReuse/ActiveRecord def find_project_issue(iid, project_id = nil) project = project_id ? find_project!(project_id) : user_project @@ -416,6 +425,11 @@ module API end def render_api_error!(message, status) + # grape-logging doesn't pass the status code, so this is a + # workaround for getting that information in the loggers: + # https://github.com/aserafin/grape_logging/issues/71 + env[API_RESPONSE_STATUS_CODE] = Rack::Utils.status_code(status) + error!({ 'message' => message }, status, header) end @@ -595,8 +609,8 @@ module API header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs)) end - def send_artifacts_entry(build, entry) - header(*Gitlab::Workhorse.send_artifacts_entry(build, entry)) + def send_artifacts_entry(file, entry) + header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) end # The Grape Error Middleware only has access to `env` but not `params` nor diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 31272c537a3..b05e82a541d 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -51,7 +51,7 @@ module API def parse_env return {} if params[:env].blank? - JSON.parse(params[:env]) + Gitlab::Json.parse(params[:env]) rescue JSON::ParserError {} end diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 73711a7e0ba..9dab2a88f0b 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -27,6 +27,7 @@ module API coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false + optional :with_merge_status_recheck, type: Boolean, desc: 'Request that stale merge statuses be rechecked asynchronously', default: false optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time' diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb index 6bebb4bfeac..823891d6fe7 100644 --- a/lib/api/helpers/pagination_strategies.rb +++ b/lib/api/helpers/pagination_strategies.rb @@ -3,19 +3,24 @@ module API module Helpers module PaginationStrategies - def paginate_with_strategies(relation) - paginator = paginator(relation) + def paginate_with_strategies(relation, request_scope) + paginator = paginator(relation, request_scope) yield(paginator.paginate(relation)).tap do |records, _| paginator.finalize(records) end end - def paginator(relation) - return Gitlab::Pagination::OffsetPagination.new(self) unless keyset_pagination_enabled? + def paginator(relation, request_scope = nil) + return keyset_paginator(relation) if keyset_pagination_enabled? - request_context = Gitlab::Pagination::Keyset::RequestContext.new(self) + offset_paginator(relation, request_scope) + end + + private + def keyset_paginator(relation) + request_context = Gitlab::Pagination::Keyset::RequestContext.new(self) unless Gitlab::Pagination::Keyset.available?(request_context, relation) return error!('Keyset pagination is not yet available for this type of request', 405) end @@ -23,11 +28,28 @@ module API Gitlab::Pagination::Keyset::Pager.new(request_context) end - private + def offset_paginator(relation, request_scope) + offset_limit = limit_for_scope(request_scope) + if Gitlab::Pagination::Keyset.available_for_type?(relation) && offset_limit_exceeded?(offset_limit) + return error!("Offset pagination has a maximum allowed offset of #{offset_limit} " \ + "for requests that return objects of type #{relation.klass}. " \ + "Remaining records can be retrieved using keyset pagination.", 405) + end + + Gitlab::Pagination::OffsetPagination.new(self) + end def keyset_pagination_enabled? params[:pagination] == 'keyset' end + + def limit_for_scope(scope) + (scope || Plan.default).actual_limits.offset_pagination_limit + end + + def offset_limit_exceeded?(offset_limit) + offset_limit.positive? && params[:page] * params[:per_page] > offset_limit + end end end end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 14c83114f32..5afdb34da97 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -31,6 +31,7 @@ module API optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`' optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' + optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis' optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push' optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge' diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb index de8cbe62106..936684ea1f8 100644 --- a/lib/api/helpers/search_helpers.rb +++ b/lib/api/helpers/search_helpers.rb @@ -5,7 +5,7 @@ module API module SearchHelpers def self.global_search_scopes # This is a separate method so that EE can redefine it. - %w(projects issues merge_requests milestones snippet_titles snippet_blobs users) + %w(projects issues merge_requests milestones snippet_titles users) end def self.group_search_scopes diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 4c44aca2de4..02e60ff5db5 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -724,6 +724,15 @@ module API desc: 'The Unify Circuit webhook. e.g. https://circuit.com/rest/v2/webhooks/incoming/…' }, chat_notification_events + ].flatten, + 'webex-teams' => [ + { + required: true, + name: :webhook, + type: String, + desc: 'The Webex Teams webhook. e.g. https://api.ciscospark.com/v1/webhooks/incoming/…' + }, + chat_notification_events ].flatten } end diff --git a/lib/api/helpers/snippets_helpers.rb b/lib/api/helpers/snippets_helpers.rb new file mode 100644 index 00000000000..20aeca6a9d3 --- /dev/null +++ b/lib/api/helpers/snippets_helpers.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module API + module Helpers + module SnippetsHelpers + def content_for(snippet) + if snippet.empty_repo? + snippet.content + else + blob = snippet.blobs.first + blob.load_all_data! + blob.data + end + end + end + end +end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 0d50a310b37..79c407b9581 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -30,10 +30,6 @@ module API project.http_url_to_repo end - def ee_post_receive_response_hook(response) - # Hook for EE to add messages - end - def check_allowed(params) # This is a separate method so that EE can alter its behaviour more # easily. @@ -73,13 +69,14 @@ module API } # Custom option for git-receive-pack command + if Feature.enabled?(:gitaly_upload_pack_filter, project, default_enabled: true) + payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true" + end + receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i + if receive_max_input_size > 0 payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" - - if Feature.enabled?(:gitaly_upload_pack_filter, project) - payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true" - end end response_with_status(**payload) @@ -116,10 +113,6 @@ module API # check_ip - optional, only in EE version, may limit access to # group resources based on its IP restrictions post "/allowed" do - if repo_type.snippet? && params[:protocol] != 'web' && Feature.disabled?(:version_snippets, actor.user) - break response_with_status(code: 401, success: false, message: 'Snippet git access is disabled.') - end - # It was moved to a separate method so that EE can alter its behaviour more # easily. check_allowed(params) @@ -216,8 +209,6 @@ module API response = PostReceiveService.new(actor.user, repository, project, params).execute - ee_post_receive_response_hook(response) - present response, with: Entities::InternalPostReceive::Response end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index f27afd0055f..be50c3e0381 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -95,6 +95,8 @@ module API use :issues_params optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], default: 'created_by_me', desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' + optional :non_archived, type: Boolean, default: true, + desc: 'Return issues from non archived projects' end get do authenticate! unless params[:scope] == 'all' diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 920938ad453..6a82256cc96 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -54,7 +54,7 @@ module API bad_request! unless path.valid? - send_artifacts_entry(build, path) + send_artifacts_entry(build.artifacts_file, path) end desc 'Download the artifacts archive from a job' do @@ -90,7 +90,7 @@ module API bad_request! unless path.valid? - send_artifacts_entry(build, path) + send_artifacts_entry(build.artifacts_file, path) end desc 'Keep the artifacts to prevent them from being deleted' do diff --git a/lib/api/members.rb b/lib/api/members.rb index 2e49b4be45c..37d4ca29b68 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -160,3 +160,5 @@ module API end end end + +API::Members.prepend_if_ee('EE::API::Members') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d45786cdd3d..ff4ad85115b 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -26,6 +26,8 @@ module API assignee_ids description labels + add_labels + remove_labels milestone_id remove_source_branch state_event @@ -91,6 +93,9 @@ module API 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 end options @@ -180,6 +185,8 @@ module API optional :assignee_ids, type: Array[Integer], desc: 'The array of user IDs to assign issue' optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' 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 :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' optional :allow_collaboration, type: Boolean, desc: 'Allow commits from members who can merge to the target branch' optional :allow_maintainer_to_push, type: Boolean, as: :allow_collaboration, desc: '[deprecated] See allow_collaboration' diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb index 691abac863a..c8ec4d29657 100644 --- a/lib/api/metrics/dashboard/annotations.rb +++ b/lib/api/metrics/dashboard/annotations.rb @@ -8,30 +8,37 @@ module API success Entities::Metrics::Dashboard::Annotation end - params do - requires :starting_at, type: DateTime, - desc: 'Date time indicating starting moment to which the annotation relates.' - optional :ending_at, type: DateTime, - desc: 'Date time indicating ending moment to which the annotation relates.' - requires :dashboard_path, type: String, - desc: 'The path to a file defining the dashboard on which the annotation should be added' - requires :description, type: String, desc: 'The description of the annotation' - end + ANNOTATIONS_SOURCES = [ + { class: ::Environment, resource: :environments, create_service_param_key: :environment }, + { class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster } + ].freeze + + ANNOTATIONS_SOURCES.each do |annotations_source| + resource annotations_source[:resource] do + params do + requires :starting_at, type: DateTime, + desc: 'Date time indicating starting moment to which the annotation relates.' + optional :ending_at, type: DateTime, + desc: 'Date time indicating ending moment to which the annotation relates.' + requires :dashboard_path, type: String, coerce_with: -> (val) { CGI.unescape(val) }, + desc: 'The path to a file defining the dashboard on which the annotation should be added' + requires :description, type: String, desc: 'The description of the annotation' + end - resource :environments do - post ':id/metrics_dashboard/annotations' do - environment = ::Environment.find(params[:id]) + post ':id/metrics_dashboard/annotations' do + annotations_source_object = annotations_source[:class].find(params[:id]) - not_found! unless Feature.enabled?(:metrics_dashboard_annotations, environment.project) + forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object) - forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, environment) + create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object) - result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, declared(params).merge(environment: environment)).execute + result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute - if result[:status] == :success - present result[:annotation], with: Entities::Metrics::Dashboard::Annotation - else - error!(result, 400) + if result[:status] == :success + present result[:annotation], with: Entities::Metrics::Dashboard::Annotation + else + error!(result, 400) + end end end end diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb new file mode 100644 index 00000000000..85fc0f33ed8 --- /dev/null +++ b/lib/api/metrics/user_starred_dashboards.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module API + module Metrics + class UserStarredDashboards < Grape::API + resource :projects do + desc 'Marks selected metrics dashboard as starred' do + success Entities::Metrics::UserStarredDashboard + end + + params do + requires :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) }, + desc: 'Url encoded path to a file defining the dashboard to which the star should be added' + end + + post ':id/metrics/user_starred_dashboards' do + result = ::Metrics::UsersStarredDashboards::CreateService.new(current_user, user_project, params[:dashboard_path]).execute + + if result.success? + present result.payload, with: Entities::Metrics::UserStarredDashboard + else + error!({ errors: result.message }, 400) + end + end + + desc 'Remove star from selected metrics dashboard' + + params do + optional :dashboard_path, type: String, allow_blank: false, coerce_with: ->(val) { CGI.unescape(val) }, + desc: 'Url encoded path to a file defining the dashboard from which the star should be removed' + end + + delete ':id/metrics/user_starred_dashboards' do + result = ::Metrics::UsersStarredDashboards::DeleteService.new(current_user, user_project, params[:dashboard_path]).execute + + if result.success? + status :ok + result.payload + else + status :bad_request + end + end + end + end + end +end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 06f8920b37c..c09bca26a41 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -108,6 +108,21 @@ module API present pipeline.variables, with: Entities::Variable end + desc 'Gets the test report for a given pipeline' do + detail 'This feature was introduced in GitLab 13.0. Disabled by default behind feature flag `junit_pipeline_view`' + success TestReportEntity + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + get ':id/pipelines/:pipeline_id/test_report' do + not_found! unless Feature.enabled?(:junit_pipeline_view, user_project) + + authorize! :read_build, pipeline + + present pipeline.test_reports, with: TestReportEntity + end + desc 'Deletes a pipeline' do detail 'This feature was introduced in GitLab 11.6' http_codes [[204, 'Pipeline was deleted'], [403, 'Forbidden']] diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb new file mode 100644 index 00000000000..1a63e984fbf --- /dev/null +++ b/lib/api/project_repository_storage_moves.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module API + class ProjectRepositoryStorageMoves < Grape::API + include PaginationParams + + before { authenticated_as_admin! } + + resource :project_repository_storage_moves do + desc 'Get a list of all project repository storage moves' do + detail 'This feature was introduced in GitLab 13.0.' + success Entities::ProjectRepositoryStorageMove + end + params do + use :pagination + end + get do + storage_moves = ProjectRepositoryStorageMove.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.0.' + success Entities::ProjectRepositoryStorageMove + end + get ':id' do + storage_move = ProjectRepositoryStorageMove.find(params[:id]) + + present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + end + end + end +end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index e8234a9285c..68f4a0dcb65 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -11,6 +11,7 @@ module API requires :id, type: String, desc: 'The ID of a project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + helpers Helpers::SnippetsHelpers helpers do def check_snippets_enabled forbidden! unless user_project.feature_available?(:snippets, current_user) @@ -54,30 +55,27 @@ module API success Entities::ProjectSnippet end params do - requires :title, type: String, desc: 'The title of the snippet' + requires :title, type: String, allow_blank: false, desc: 'The title of the snippet' requires :file_name, type: String, desc: 'The file name of the snippet' - optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")' - optional :content, type: String, allow_blank: false, desc: 'The content of the snippet' + requires :content, type: String, allow_blank: false, desc: 'The content of the snippet' optional :description, type: String, desc: 'The description of a snippet' requires :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the snippet' - mutually_exclusive :code, :content end post ":id/snippets" do authorize! :create_snippet, user_project snippet_params = declared_params(include_missing: false).merge(request: request, api: true) - snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present? service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute snippet = service_response.payload[:snippet] - render_spam_error! if snippet.spam? - - if snippet.persisted? + if service_response.success? present snippet, with: Entities::ProjectSnippet else - render_validation_error!(snippet) + render_spam_error! if snippet.spam? + + render_api_error!({ error: service_response.message }, service_response.http_status) end end @@ -86,16 +84,14 @@ module API end params do requires :snippet_id, type: Integer, desc: 'The ID of a project snippet' - optional :title, type: String, desc: 'The title of the snippet' + optional :title, type: String, allow_blank: false, desc: 'The title of the snippet' optional :file_name, type: String, desc: 'The file name of the snippet' - optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")' optional :content, type: String, allow_blank: false, desc: 'The content of the snippet' optional :description, type: String, desc: 'The description of a snippet' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the snippet' - at_least_one_of :title, :file_name, :code, :content, :visibility_level - mutually_exclusive :code, :content + at_least_one_of :title, :file_name, :content, :visibility_level end # rubocop: disable CodeReuse/ActiveRecord put ":id/snippets/:snippet_id" do @@ -107,17 +103,15 @@ module API snippet_params = declared_params(include_missing: false) .merge(request: request, api: true) - snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present? - service_response = ::Snippets::UpdateService.new(user_project, current_user, snippet_params).execute(snippet) snippet = service_response.payload[:snippet] - render_spam_error! if snippet.spam? - - if snippet.valid? + if service_response.success? present snippet, with: Entities::ProjectSnippet else - render_validation_error!(snippet) + render_spam_error! if snippet.spam? + + render_api_error!({ error: service_response.message }, service_response.http_status) end end # rubocop: enable CodeReuse/ActiveRecord @@ -155,7 +149,7 @@ module API env['api.format'] = :txt content_type 'text/plain' - present snippet.content + present content_for(snippet) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index 119902a189c..cfcc7f5212d 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -5,6 +5,10 @@ module API include PaginationParams TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze + # The regex is needed to ensure a period (e.g. agpl-3.0) + # isn't confused with a format type. We also need to allow encoded + # values (e.g. C%2B%2B for C++), so allow % and + as well. + TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(name: /[\w%.+-]+/) before { authenticate_non_get! } @@ -12,7 +16,7 @@ module API requires :id, type: String, desc: 'The ID of a project' requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses) of the template' end - resource :projects do + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of templates available to this project' do detail 'This endpoint was introduced in GitLab 11.4' end @@ -36,10 +40,8 @@ module API optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses' optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses' end - # The regex is needed to ensure a period (e.g. agpl-3.0) - # isn't confused with a format type. We also need to allow encoded - # values (e.g. C%2B%2B for C++), so allow % and + as well. - get ':id/templates/:type/:name', requirements: { name: /[\w%.+-]+/ } do + + get ':id/templates/:type/:name', requirements: TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS do template = TemplateFinder .build(params[:type], user_project, name: params[:name]) .execute diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ee0731a331f..732453cf1c4 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -95,7 +95,7 @@ module API projects = reorder_projects(projects) projects = apply_filters(projects) - records, options = paginate_with_strategies(projects) do |projects| + records, options = paginate_with_strategies(projects, options[:request_scope]) do |projects| projects, options = with_custom_attributes(projects, options) options = options.reverse_merge( @@ -313,7 +313,7 @@ module API get ':id/forks' do forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute - present_projects forks + present_projects forks, request_scope: user_project end desc 'Check pages access of this project' diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb index 7e484eb8885..0808541d3c7 100644 --- a/lib/api/remote_mirrors.rb +++ b/lib/api/remote_mirrors.rb @@ -34,7 +34,6 @@ module API end post ':id/remote_mirrors' do create_params = declared_params(include_missing: false) - create_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project) new_mirror = user_project.remote_mirrors.create(create_params) @@ -59,7 +58,6 @@ module API mirror_params = declared_params(include_missing: false) mirror_params[:id] = mirror_params.delete(:mirror_id) - mirror_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project) update_params = { remote_mirrors_attributes: mirror_params } diff --git a/lib/api/search.rb b/lib/api/search.rb index ed52a4fc8f2..3d2d4527e30 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -17,7 +17,6 @@ module API blobs: Entities::Blob, wiki_blobs: Entities::Blob, snippet_titles: Entities::Snippet, - snippet_blobs: Entities::Snippet, users: Entities::UserBasic }.freeze @@ -36,7 +35,7 @@ module API end def snippets? - %w(snippet_blobs snippet_titles).include?(params[:scope]).to_s + %w(snippet_titles).include?(params[:scope]).to_s end def entity diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 5362b3060c1..e3a8f0671ef 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -84,16 +84,7 @@ module API 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_pages_size, type: Integer, desc: 'Maximum size of pages in MB' - optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' - given metrics_enabled: ->(val) { val } do - requires :metrics_host, type: String, desc: 'The InfluxDB host' - requires :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.' - requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet' - requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open' - requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB' - requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds' - requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out' - end + 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 optional :password_authentication_enabled_for_web, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled @@ -153,6 +144,8 @@ module API optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain' optional :snowplow_app_id, type: String, desc: 'The Snowplow site name / application id' end + optional :issues_create_limit, type: Integer, desc: "Maximum number of issue creation requests allowed per minute per user. Set to 0 for unlimited requests per minute." + optional :raw_blob_request_limit, type: Integer, desc: "Maximum number of requests per minute for each raw path. Set to 0 for unlimited requests per minute." ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", @@ -192,6 +185,9 @@ module API attrs[:allow_local_requests_from_web_hooks_and_services] = attrs.delete(:allow_local_requests_from_hooks_and_services) end + # since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0 + attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled) + attrs = filter_attributes_using_license(attrs) if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 0aaab9a812f..be58b832f97 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -8,6 +8,7 @@ module API before { authenticate! } resource :snippets do + helpers Helpers::SnippetsHelpers helpers do def snippets_for_current_user SnippetsFinder.new(current_user, author: current_user).execute @@ -24,13 +25,13 @@ module API desc 'Get a snippets list for authenticated user' do detail 'This feature was introduced in GitLab 8.15.' - success Entities::PersonalSnippet + success Entities::Snippet end params do use :pagination end get do - present paginate(snippets_for_current_user), with: Entities::PersonalSnippet + present paginate(snippets_for_current_user), with: Entities::Snippet end desc 'List all public personal snippets current_user has access to' do @@ -64,9 +65,9 @@ module API success Entities::PersonalSnippet end params do - requires :title, type: String, desc: 'The title of a snippet' + requires :title, type: String, allow_blank: false, desc: 'The title of a snippet' requires :file_name, type: String, desc: 'The name of a snippet file' - requires :content, type: String, desc: 'The content of a snippet' + requires :content, type: String, allow_blank: false, desc: 'The content of a snippet' optional :description, type: String, desc: 'The description of a snippet' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, @@ -80,12 +81,12 @@ module API service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute snippet = service_response.payload[:snippet] - render_spam_error! if snippet.spam? - - if snippet.persisted? + if service_response.success? present snippet, with: Entities::PersonalSnippet else - render_validation_error!(snippet) + render_spam_error! if snippet.spam? + + render_api_error!({ error: service_response.message }, service_response.http_status) end end @@ -95,9 +96,9 @@ module API end params do requires :id, type: Integer, desc: 'The ID of a snippet' - optional :title, type: String, desc: 'The title of a snippet' + optional :title, type: String, allow_blank: false, desc: 'The title of a snippet' optional :file_name, type: String, desc: 'The name of a snippet file' - optional :content, type: String, desc: 'The content of a snippet' + optional :content, type: String, allow_blank: false, desc: 'The content of a snippet' optional :description, type: String, desc: 'The description of a snippet' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, @@ -114,12 +115,12 @@ module API service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet) snippet = service_response.payload[:snippet] - render_spam_error! if snippet.spam? - - if snippet.persisted? + if service_response.success? present snippet, with: Entities::PersonalSnippet else - render_validation_error!(snippet) + render_spam_error! if snippet.spam? + + render_api_error!({ error: service_response.message }, service_response.http_status) end end @@ -159,7 +160,7 @@ module API env['api.format'] = :txt content_type 'text/plain' header['Content-Disposition'] = 'attachment' - present snippet.content + present content_for(snippet) end desc 'Get the user agent details for a snippet' do diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index a2146406690..884e3019a2d 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -70,7 +70,7 @@ module API post ':id/wikis' do authorize! :create_wiki, user_project - page = WikiPages::CreateService.new(user_project, current_user, params).execute + page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute if page.valid? present page, with: Entities::WikiPage @@ -91,7 +91,7 @@ module API put ':id/wikis/:slug' do authorize! :create_wiki, user_project - page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page) + page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page) if page.valid? present page, with: Entities::WikiPage @@ -107,7 +107,7 @@ module API delete ':id/wikis/:slug' do authorize! :admin_wiki, user_project - WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page) + WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page) no_content! end @@ -123,9 +123,11 @@ module API post ":id/wikis/attachments" do authorize! :create_wiki, user_project - result = ::Wikis::CreateAttachmentService.new(user_project, - current_user, - commit_params(declared_params(include_missing: false))).execute + result = ::Wikis::CreateAttachmentService.new( + container: user_project, + current_user: current_user, + params: commit_params(declared_params(include_missing: false)) + ).execute if result[:status] == :success status(201) |