diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 14:22:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 14:22:11 +0000 |
commit | 0c872e02b2c822e3397515ec324051ff540f0cd5 (patch) | |
tree | ce2fb6ce7030e4dad0f4118d21ab6453e5938cdd /lib/api | |
parent | f7e05a6853b12f02911494c4b3fe53d9540d74fc (diff) | |
download | gitlab-ce-0c872e02b2c822e3397515ec324051ff540f0cd5.tar.gz |
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'lib/api')
128 files changed, 2466 insertions, 706 deletions
diff --git a/lib/api/admin/batched_background_migrations.rb b/lib/api/admin/batched_background_migrations.rb index e8cc08a23be..7e612b5b66a 100644 --- a/lib/api/admin/batched_background_migrations.rb +++ b/lib/api/admin/batched_background_migrations.rb @@ -12,7 +12,15 @@ module API namespace 'admin' do resources 'batched_background_migrations/:id' do - desc 'Retrieve a batched background migration' + desc 'Retrieve a batched background migration' do + success ::API::Entities::BatchedBackgroundMigration + failure [ + { code: 401, message: '401 Unauthorized' }, + { code: 403, message: '403 Forbidden' }, + { code: 404, message: '404 Not found' } + ] + tags %w[batched_background_migrations] + end params do optional :database, type: String, @@ -31,7 +39,15 @@ module API end resources 'batched_background_migrations' do - desc 'Get the list of the batched background migrations' + desc 'Get the list of batched background migrations' do + success ::API::Entities::BatchedBackgroundMigration + failure [ + { code: 401, message: '401 Unauthorized' }, + { code: 403, message: '403 Forbidden' } + ] + is_array true + tags %w[batched_background_migrations] + end params do optional :database, type: String, @@ -48,7 +64,16 @@ module API end resources 'batched_background_migrations/:id/resume' do - desc 'Resume a batched background migration' + desc 'Resume a batched background migration' do + success ::API::Entities::BatchedBackgroundMigration + failure [ + { code: 401, message: '401 Unauthorized' }, + { code: 403, message: '403 Forbidden' }, + { code: 404, message: '404 Not found' }, + { code: 422, message: 'You can resume only `paused` batched background migrations.' } + ] + tags %w[batched_background_migrations] + end params do optional :database, type: String, @@ -73,7 +98,16 @@ module API end resources 'batched_background_migrations/:id/pause' do - desc 'Pause a batched background migration' + desc 'Pause a batched background migration' do + success ::API::Entities::BatchedBackgroundMigration + failure [ + { code: 401, message: '401 Unauthorized' }, + { code: 403, message: '403 Forbidden' }, + { code: 404, message: '404 Not found' }, + { code: 422, message: 'You can pause only `active` batched background migrations.' } + ] + tags %w[batched_background_migrations] + end params do optional :database, type: String, diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb index 49b41b44a18..5ef56d3326f 100644 --- a/lib/api/admin/plan_limits.rb +++ b/lib/api/admin/plan_limits.rb @@ -70,6 +70,8 @@ module API optional :terraform_module_max_file_size, type: Integer, desc: 'Maximum Terraform Module package file size in bytes' optional :storage_size_limit, type: Integer, desc: 'Maximum storage size for the root namespace in megabytes' + optional :pipeline_hierarchy_size, type: Integer, + desc: "Maximum number of downstream pipelines in a pipeline's hierarchy tree" end put "application/plan_limits" do params = declared_params(include_missing: false) diff --git a/lib/api/alert_management_alerts.rb b/lib/api/alert_management_alerts.rb index f57b7d00c81..9e28ee049d0 100644 --- a/lib/api/alert_management_alerts.rb +++ b/lib/api/alert_management_alerts.rb @@ -6,12 +6,21 @@ module API urgency :low params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' - requires :alert_iid, type: Integer, desc: 'The IID of the Alert' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', + documentation: { example: 17 } + requires :alert_iid, type: Integer, desc: 'The IID of the Alert', + documentation: { example: 23 } end resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/alert_management_alerts/:alert_iid/metric_images' do + desc 'Workhorse authorize metric image file upload' do + success code: 200 + failure [ + { code: 403, message: 'Forbidden' } + ] + tags %w[alert_management] + end post 'authorize' do authorize!(:upload_alert_management_metric_image, find_project_alert(request.params[:alert_iid])) @@ -29,13 +38,20 @@ module API end desc 'Upload a metric image for an alert' do - success Entities::MetricImage + consumes ['multipart/form-data'] + success code: 200, model: Entities::MetricImage + failure [ + { code: 403, message: 'Forbidden' } + ] + tags %w[alert_management] end params do requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded', documentation: { type: 'file' } - optional :url, type: String, desc: 'The url to view more metric info' - optional :url_text, type: String, desc: 'A description of the image or URL' + optional :url, type: String, desc: 'The url to view more metric info', + documentation: { example: 'https://example.com/metric' } + optional :url_text, type: String, desc: 'A description of the image or URL', + documentation: { example: 'An example metric' } end post do require_gitlab_workhorse! @@ -61,7 +77,14 @@ module API end end - desc 'Metric Images for alert' + desc 'Metric Images for alert' do + success code: 200, model: Entities::MetricImage + is_array true + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[alert_management] + end get do alert = find_project_alert(params[:alert_iid]) @@ -73,12 +96,21 @@ module API end desc 'Update a metric image for an alert' do - success Entities::MetricImage + consumes ['multipart/form-data'] + success code: 200, model: Entities::MetricImage + failure [ + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[alert_management] end params do - requires :metric_image_id, type: Integer, desc: 'The ID of metric image' - optional :url, type: String, desc: 'The url to view more metric info' - optional :url_text, type: String, desc: 'A description of the image or URL' + requires :metric_image_id, type: Integer, desc: 'The ID of metric image', + documentation: { example: 42 } + optional :url, type: String, desc: 'The url to view more metric info', + documentation: { example: 'https://example.com/metric' } + optional :url_text, type: String, desc: 'A description of the image or URL', + documentation: { example: 'An example metric' } end put ':metric_image_id' do alert = find_project_alert(params[:alert_iid]) @@ -97,10 +129,16 @@ module API end desc 'Remove a metric image for an alert' do - success Entities::MetricImage + success code: 204, model: Entities::MetricImage + failure [ + { code: 403, message: 'Forbidden' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[alert_management] end params do - requires :metric_image_id, type: Integer, desc: 'The ID of metric image' + requires :metric_image_id, type: Integer, desc: 'The ID of metric image', + documentation: { example: 42 } end delete ':metric_image_id' do alert = find_project_alert(params[:alert_iid]) diff --git a/lib/api/api.rb b/lib/api/api.rb index ffb0cdf8991..b23b11d0c29 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -171,9 +171,11 @@ module API namespace do # Keep in alphabetical order mount ::API::AccessRequests + mount ::API::Admin::BatchedBackgroundMigrations mount ::API::Admin::Ci::Variables mount ::API::Admin::InstanceClusters mount ::API::Admin::PlanLimits + mount ::API::AlertManagementAlerts mount ::API::Appearance mount ::API::Applications mount ::API::Avatar @@ -181,10 +183,13 @@ module API mount ::API::Branches mount ::API::BroadcastMessages mount ::API::BulkImports + mount ::API::Ci::JobArtifacts + mount ::API::Groups mount ::API::Ci::Jobs mount ::API::Ci::ResourceGroups mount ::API::Ci::Runner mount ::API::Ci::Runners + mount ::API::Ci::SecureFiles mount ::API::Ci::Pipelines mount ::API::Ci::PipelineSchedules mount ::API::Ci::Triggers @@ -193,6 +198,13 @@ module API mount ::API::Clusters::Agents mount ::API::Commits mount ::API::CommitStatuses + mount ::API::ComposerPackages + mount ::API::ConanInstancePackages + mount ::API::ConanProjectPackages + mount ::API::ContainerRegistryEvent + mount ::API::ContainerRepositories + mount ::API::DebianGroupPackages + mount ::API::DebianProjectPackages mount ::API::DependencyProxy mount ::API::DeployKeys mount ::API::DeployTokens @@ -200,54 +212,75 @@ module API mount ::API::Environments mount ::API::ErrorTracking::ClientKeys mount ::API::ErrorTracking::ProjectSettings + mount ::API::Events mount ::API::FeatureFlags mount ::API::FeatureFlagsUserLists mount ::API::Features mount ::API::Files mount ::API::FreezePeriods + mount ::API::GenericPackages mount ::API::Geo mount ::API::GoProxy mount ::API::GroupAvatar mount ::API::GroupClusters mount ::API::GroupContainerRepositories + mount ::API::GroupDebianDistributions mount ::API::GroupExport mount ::API::GroupImport mount ::API::GroupPackages mount ::API::GroupVariables + mount ::API::HelmPackages mount ::API::ImportBitbucketServer mount ::API::ImportGithub mount ::API::Integrations + mount ::API::Integrations::JiraConnect::Subscriptions mount ::API::Invitations mount ::API::IssueLinks mount ::API::Keys mount ::API::Lint mount ::API::Markdown + mount ::API::MavenPackages + mount ::API::Members mount ::API::MergeRequestApprovals + mount ::API::MergeRequests mount ::API::MergeRequestDiffs mount ::API::Metadata mount ::API::Metrics::Dashboard::Annotations mount ::API::Metrics::UserStarredDashboards + mount ::API::Namespaces + mount ::API::NpmInstancePackages + mount ::API::NpmProjectPackages + mount ::API::NugetGroupPackages + mount ::API::NugetProjectPackages mount ::API::PackageFiles + mount ::API::Pages mount ::API::PersonalAccessTokens::SelfInformation mount ::API::PersonalAccessTokens mount ::API::ProjectClusters + mount ::API::ProjectContainerRepositories + mount ::API::ProjectDebianDistributions mount ::API::ProjectEvents mount ::API::ProjectExport mount ::API::ProjectHooks mount ::API::ProjectImport + mount ::API::ProjectPackages mount ::API::ProjectRepositoryStorageMoves mount ::API::ProjectSnippets mount ::API::ProjectSnapshots mount ::API::ProjectStatistics mount ::API::ProjectTemplates + mount ::API::Projects mount ::API::ProtectedBranches mount ::API::ProtectedTags + mount ::API::PypiPackages mount ::API::Releases mount ::API::Release::Links mount ::API::RemoteMirrors mount ::API::Repositories mount ::API::ResourceAccessTokens mount ::API::ResourceMilestoneEvents + mount ::API::RpmProjectPackages + mount ::API::RubygemPackages mount ::API::Snippets mount ::API::SnippetRepositoryStorageMoves mount ::API::Statistics @@ -260,6 +293,9 @@ module API mount ::API::Terraform::StateVersion mount ::API::Topics mount ::API::Unleash + mount ::API::UsageData + mount ::API::UsageDataNonSqlMetrics + mount ::API::UsageDataQueries mount ::API::UserCounts mount ::API::Wikis @@ -267,57 +303,27 @@ module API end # Keep in alphabetical order - mount ::API::Admin::BatchedBackgroundMigrations mount ::API::Admin::Sidekiq - mount ::API::AlertManagementAlerts mount ::API::AwardEmoji mount ::API::Boards - mount ::API::Ci::JobArtifacts + mount ::API::Ci::Pipelines + mount ::API::Ci::PipelineSchedules mount ::API::Ci::SecureFiles - mount ::API::ComposerPackages - mount ::API::ConanInstancePackages - mount ::API::ConanProjectPackages - mount ::API::ContainerRegistryEvent - mount ::API::ContainerRepositories - mount ::API::DebianGroupPackages - mount ::API::DebianProjectPackages mount ::API::Discussions mount ::API::ErrorTracking::Collector - mount ::API::Events - mount ::API::GenericPackages mount ::API::GroupBoards - mount ::API::GroupDebianDistributions mount ::API::GroupLabels mount ::API::GroupMilestones - mount ::API::Groups - mount ::API::HelmPackages - mount ::API::Integrations::JiraConnect::Subscriptions mount ::API::Issues mount ::API::Labels - mount ::API::MavenPackages - mount ::API::Members - mount ::API::MergeRequests - mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings - mount ::API::NpmInstancePackages - mount ::API::NpmProjectPackages - mount ::API::NugetGroupPackages - mount ::API::NugetProjectPackages - mount ::API::Pages mount ::API::PagesDomains - mount ::API::ProjectContainerRepositories - mount ::API::ProjectDebianDistributions mount ::API::ProjectEvents mount ::API::ProjectMilestones - mount ::API::ProjectPackages - mount ::API::Projects mount ::API::ProtectedTags - mount ::API::PypiPackages mount ::API::ResourceLabelEvents mount ::API::ResourceStateEvents - mount ::API::RpmProjectPackages - mount ::API::RubygemPackages mount ::API::Search mount ::API::Settings mount ::API::SidekiqMetrics @@ -327,7 +333,6 @@ module API mount ::API::Todos mount ::API::UsageData mount ::API::UsageDataNonSqlMetrics - mount ::API::UsageDataQueries mount ::API::Users mount ::API::Ml::Mlflow end diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index 69f1521ef2a..2cef1b27504 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -26,6 +26,7 @@ module API end params do optional :title, type: String, desc: 'Instance title on the sign in / sign up page' + optional :short_title, type: String, desc: 'Short title for Progressive Web App' optional :description, type: String, desc: 'Markdown text shown on the sign in / sign up page' # 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 diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index e419a025508..f7a39db7249 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -80,7 +80,7 @@ module API delete "#{endpoint}/:award_id", feature_category: awardable_params[:feature_category] do award = awardable.award_emoji.find(params[:award_id]) - unauthorized! unless award.user == current_user || current_user&.admin? + unauthorized! unless award.user == current_user || current_user&.can_admin_all_resources? destroy_conditionally!(award) end diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 352ad04c982..3788f5bec41 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -16,18 +16,25 @@ module API end end - prepend_mod_with('API::Ci::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule - params do requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the artifacts archive from a job' do detail 'This feature was introduced in GitLab 8.10' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref_name, type: String, desc: 'The ref from repository' - requires :job, type: String, desc: 'The name for the job' + requires :ref_name, type: String, + desc: 'Branch or tag name in repository. `HEAD` or `SHA` references are not supported.' + requires :job, type: String, desc: 'The name of the job.' + optional :job_token, type: String, + desc: 'To be used with triggers for multi-project pipelines, ' \ + 'available only on Premium and Ultimate tiers.' end route_setting :authentication, job_token_allowed: true get ':id/jobs/artifacts/:ref_name/download', @@ -43,11 +50,21 @@ module API desc 'Download a specific file from artifacts archive from a ref' do detail 'This feature was introduced in GitLab 11.5' + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do - requires :ref_name, type: String, desc: 'The ref from repository' - requires :job, type: String, desc: 'The name for the job' - requires :artifact_path, type: String, desc: 'Artifact path' + requires :ref_name, type: String, + desc: 'Branch or tag name in repository. `HEAD` or `SHA` references are not supported.' + requires :job, type: String, desc: 'The name of the job.' + requires :artifact_path, type: String, desc: 'Path to a file inside the artifacts archive.' + optional :job_token, type: String, + desc: 'To be used with triggers for multi-project pipelines, ' \ + 'available only on Premium and Ultimate tiers.' end route_setting :authentication, job_token_allowed: true get ':id/jobs/artifacts/:ref_name/raw/*artifact_path', @@ -69,9 +86,17 @@ module API desc 'Download the artifacts archive from a job' do detail 'This feature was introduced in GitLab 8.5' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do requires :job_id, type: Integer, desc: 'The ID of a job' + optional :job_token, type: String, + desc: 'To be used with triggers for multi-project pipelines, ' \ + 'available only on Premium and Ultimate tiers.' end route_setting :authentication, job_token_allowed: true get ':id/jobs/:job_id/artifacts', urgency: :low do @@ -85,10 +110,19 @@ module API desc 'Download a specific file from artifacts archive' do detail 'This feature was introduced in GitLab 10.0' + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do requires :job_id, type: Integer, desc: 'The ID of a job' - requires :artifact_path, type: String, desc: 'Artifact path' + requires :artifact_path, type: String, desc: 'Path to a file inside the artifacts archive.' + optional :job_token, type: String, + desc: 'To be used with triggers for multi-project pipelines, ' \ + 'available only on Premium and Ultimate tiers.' end route_setting :authentication, job_token_allowed: true get ':id/jobs/:job_id/artifacts/*artifact_path', urgency: :low, format: false do @@ -113,6 +147,11 @@ module API desc 'Keep the artifacts to prevent them from being deleted' do success ::API::Entities::Ci::Job + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] end params do requires :job_id, type: Integer, desc: 'The ID of a job' @@ -132,6 +171,12 @@ module API desc 'Delete the artifacts files from a job' do detail 'This feature was introduced in GitLab 11.9' + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 409, message: 'Conflict' } + ] end params do requires :job_id, type: Integer, desc: 'The ID of a job' @@ -148,7 +193,14 @@ module API status :no_content end - desc 'Expire the artifacts files from a project' + desc 'Expire the artifacts files from a project' do + success code: 202 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 409, message: 'Conflict' } + ] + end delete ':id/artifacts' do authorize_destroy_artifacts! @@ -162,3 +214,5 @@ module API end end end + +API::Ci::JobArtifacts.prepend_mod diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 9e41e1c0d8f..bb57a717f7c 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -49,13 +49,15 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/jobs', urgency: :low, feature_category: :continuous_integration do + check_rate_limit!(:jobs_index, scope: current_user) if enforce_jobs_api_rate_limits(@project) + authorize_read_builds! builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) - builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project) - present paginate(builds), with: Entities::Ci::Job + + present paginate(builds, without_count: true), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord @@ -255,16 +257,19 @@ module API pipeline = current_authenticated_job.pipeline project = current_authenticated_job.project - agent_authorizations = ::Clusters::AgentAuthorizationsFinder.new(project).execute project_groups = project.group&.self_and_ancestor_ids&.map { |id| { id: id } } || [] user_access_level = project.team.max_member_access(current_user.id) roles_in_project = Gitlab::Access.sym_options_with_owner .select { |_role, role_access_level| role_access_level <= user_access_level } .map(&:first) - environment = if persisted_environment = current_authenticated_job.actual_persisted_environment - { tier: persisted_environment.tier, slug: persisted_environment.slug } - end + persisted_environment = current_authenticated_job.actual_persisted_environment + environment = { tier: persisted_environment.tier, slug: persisted_environment.slug } if persisted_environment + + agent_authorizations = ::Clusters::Agents::FilterAuthorizationsService.new( + ::Clusters::AgentAuthorizationsFinder.new(project).execute, + environment: persisted_environment&.name + ).execute # See https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/kubernetes_ci_access.md#apiv4joballowed_agents-api { diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index c7d1887638a..b073eb49bf1 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -229,15 +229,17 @@ module API params do requires :id, type: Integer, desc: %q(Job's ID) optional :token, type: String, desc: %q(Job's authentication token) + optional :debug_trace, type: Boolean, desc: %q(Enable or Disable the debug trace) end patch '/:id/trace', urgency: :low, feature_category: :continuous_integration do job = authenticate_job!(heartbeat_runner: true) error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range') content_range = request.headers['Content-Range'] + debug_trace = Gitlab::Utils.to_boolean(params[:debug_trace]) result = ::Ci::AppendBuildTraceService - .new(job, content_range: content_range) + .new(job, content_range: content_range, debug_trace: debug_trace) .execute(request.body.read) if result.status == 403 @@ -256,7 +258,7 @@ module API header 'X-GitLab-Trace-Update-Interval', job.trace.update_interval.to_s end - desc 'Authorize artifacts uploading for job' do + desc 'Authorize uploading job artifact' do http_codes [[200, 'Upload allowed'], [403, 'Forbidden'], [405, 'Artifacts support not enabled'], @@ -270,7 +272,7 @@ module API # In current runner, filesize parameter would be empty here. This is because archive is streamed by runner, # so the archive size is not known ahead of time. Streaming is done to not use additional I/O on # Runner to first save, and then send via Network. - optional :filesize, type: Integer, desc: %q(Artifacts filesize) + optional :filesize, type: Integer, desc: %q(Size of artifact file) optional :artifact_type, type: String, desc: %q(The type of artifact), default: 'archive', values: ::Ci::JobArtifact.file_types.keys @@ -292,7 +294,7 @@ module API end end - desc 'Upload artifacts for job' do + desc 'Upload a job artifact' do success Entities::Ci::JobRequest::Response http_codes [[201, 'Artifact uploaded'], [400, 'Bad request'], @@ -304,7 +306,7 @@ module API requires :id, type: Integer, desc: %q(Job's ID) requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: %(The artifact file to store (generated by Multipart middleware)), documentation: { type: 'file' } optional :token, type: String, desc: %q(Job's authentication token) - optional :expire_in, type: String, desc: %q(Specify when artifacts should expire) + optional :expire_in, type: String, desc: %q(Specify when artifact should expire) optional :artifact_type, type: String, desc: %q(The type of artifact), default: 'archive', values: ::Ci::JobArtifact.file_types.keys optional :artifact_format, type: String, desc: %q(The format of artifact), @@ -333,7 +335,7 @@ module API end desc 'Download the artifacts file for job' do - http_codes [[200, 'Upload allowed'], + http_codes [[200, 'Download allowed'], [401, 'Unauthorized'], [403, 'Forbidden'], [404, 'Artifact not found']] diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 988c3f4f566..4a6c58b4987 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -58,19 +58,19 @@ module API end def authenticate_show_runner!(runner) - return if runner.instance_type? || current_user.admin? + return if runner.instance_type? || current_user.can_read_all_resources? forbidden!("No access granted") unless can?(current_user, :read_runner, runner) end def authenticate_update_runner!(runner) - return if current_user.admin? + return if current_user.can_admin_all_resources? forbidden!("No access granted") unless can?(current_user, :update_runner, runner) end def authenticate_delete_runner!(runner) - return if current_user.admin? + return if current_user.can_admin_all_resources? forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) @@ -79,14 +79,14 @@ module API def authenticate_enable_runner!(runner) forbidden!("Runner is a group runner") if runner.group_type? - return if current_user.admin? + return if current_user.can_admin_all_resources? forbidden!("Runner is locked") if runner.locked? forbidden!("No access granted") unless can?(current_user, :assign_runner, runner) end def authenticate_list_runners_jobs!(runner) - return if current_user.admin? + return if current_user.can_read_all_resources? forbidden!("No access granted") unless can?(current_user, :read_builds, runner) end diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index dd628a3413f..6483abcc74e 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -7,7 +7,6 @@ module API before do authenticate! - feature_flag_enabled? authorize! :read_secure_files, user_project end @@ -16,11 +15,15 @@ module API default_format :json params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the + authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'List all Secure Files for a Project' + desc 'Get list of secure files in a project' do + success Entities::Ci::SecureFile + tags %w[secure_files] + end params do use :pagination end @@ -30,9 +33,13 @@ module API present paginate(secure_files), with: Entities::Ci::SecureFile end - desc 'Get an individual Secure File' + desc 'Get the details of a specific secure file in a project' do + success Entities::Ci::SecureFile + tags %w[secure_files] + failure [{ code: 404, message: '404 Not found' }] + end params do - requires :id, type: Integer, desc: 'The Secure File ID' + requires :id, type: Integer, desc: 'The ID of a secure file' end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true @@ -41,7 +48,10 @@ module API present secure_file, with: Entities::Ci::SecureFile end - desc 'Download a Secure File' + desc 'Download secure file' do + failure [{ code: 404, message: '404 Not found' }] + tags %w[secure_files] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true get ':id/secure_files/:secure_file_id/download' do secure_file = user_project.secure_files.find(params[:secure_file_id]) @@ -58,10 +68,15 @@ module API authorize! :admin_secure_files, user_project end - desc 'Upload a Secure File' + desc 'Create a secure file' do + success Entities::Ci::SecureFile + tags %w[secure_files] + failure [{ code: 400, message: '400 Bad Request' }] + end params do - requires :name, type: String, desc: 'The name of the file' - requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file to be uploaded', documentation: { type: 'file' } + requires :name, type: String, desc: 'The name of the file being uploaded. The filename must be unique within + the project' + requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The secure file being uploaded', documentation: { type: 'file' } end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true post ':id/secure_files' do @@ -74,17 +89,17 @@ module API file_too_large! unless secure_file.file.size < ::Ci::SecureFile::FILE_SIZE_LIMIT.to_i if secure_file.save - if Feature.enabled?(:secure_files_metadata_parsers, user_project) - ::Ci::ParseSecureFileMetadataWorker.perform_async(secure_file.id) # rubocop:disable CodeReuse/Worker - end - + ::Ci::ParseSecureFileMetadataWorker.perform_async(secure_file.id) # rubocop:disable CodeReuse/Worker present secure_file, with: Entities::Ci::SecureFile else render_validation_error!(secure_file) end end - desc 'Delete an individual Secure File' + desc 'Remove a secure file' do + tags %w[secure_files] + failure [{ code: 404, message: '404 Not found' }] + end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true delete ':id/secure_files/:secure_file_id' do secure_file = user_project.secure_files.find(params[:secure_file_id]) @@ -97,10 +112,6 @@ module API end helpers do - def feature_flag_enabled? - service_unavailable! unless Feature.enabled?(:ci_secure_files, user_project) - end - def read_only_feature_flag_enabled? service_unavailable! if Feature.enabled?(:ci_secure_files_read_only, user_project, type: :ops) end diff --git a/lib/api/clusters/agent_tokens.rb b/lib/api/clusters/agent_tokens.rb index f65ae465b3d..68eef21903d 100644 --- a/lib/api/clusters/agent_tokens.rb +++ b/lib/api/clusters/agent_tokens.rb @@ -27,7 +27,8 @@ module API use :pagination end get do - agent_tokens = ::Clusters::AgentTokensFinder.new(user_project, current_user, params[:agent_id]).execute + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) + agent_tokens = ::Clusters::AgentTokensFinder.new(agent, current_user).execute present paginate(agent_tokens), with: Entities::Clusters::AgentTokenBasic end @@ -42,8 +43,7 @@ module API end get ':token_id' do agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) - - token = agent.agent_tokens.find(params[:token_id]) + token = ::Clusters::AgentTokensFinder.new(agent, current_user).find(params[:token_id]) present token, with: Entities::Clusters::AgentToken end @@ -84,8 +84,7 @@ module API authorize! :admin_cluster, user_project agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) - - token = agent.agent_tokens.find(params[:token_id]) + token = ::Clusters::AgentTokensFinder.new(agent, current_user).find(params[:token_id]) # Skipping explicit error handling and relying on exceptions token.revoked! diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 954b572c9b1..531235dc9b2 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -110,13 +110,23 @@ module API authorize! :update_pipeline, pipeline + # rubocop: disable Performance/ActiveRecordSubtransactionMethods + stage = pipeline.stages.safe_find_or_create_by!(name: 'external') do |stage| + stage.position = GenericCommitStatus::EXTERNAL_STAGE_IDX + stage.project = pipeline.project + end + # rubocop: enable Performance/ActiveRecordSubtransactionMethods + status = GenericCommitStatus.running_or_pending.find_or_initialize_by( project: user_project, pipeline: pipeline, name: name, ref: ref, user: current_user, - protected: user_project.protected_for?(ref) + protected: user_project.protected_for?(ref), + ci_stage: stage, + stage_idx: stage.position, + stage: 'external' ) updatable_optional_attributes = %w[target_url description coverage] @@ -152,7 +162,7 @@ module API def all_matching_pipelines pipelines = user_project.ci_pipelines.newest_first(sha: commit.sha) pipelines = pipelines.for_ref(params[:ref]) if params[:ref] - pipelines = pipelines.for_id(params[:pipeline_id]) if params[:pipeline_id] + pipelines = pipelines.id_in(params[:pipeline_id]) if params[:pipeline_id] pipelines end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 63a13b83a9b..ad2bbf90917 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -24,6 +24,26 @@ module API forbidden!("You are not allowed to push into this branch") end end + + def track_commit_events + return unless find_user_from_warden + + Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count + Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user, project: user_project) + namespace = user_project.namespace + + return unless Feature.enabled?(:route_hll_to_snowplow_phase3, namespace) + + Gitlab::Tracking.event( + 'API::Commits', + :commit, + project: user_project, + namespace: namespace, + user: current_user, + label: 'counts.web_ide_commits', + context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: 'counts.web_ide_commits').to_context] + ) + end end params do @@ -204,10 +224,7 @@ module API if result[:status] == :success commit_detail = user_project.repository.commit(result[:result]) - if find_user_from_warden - Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count - Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user, project: user_project) - end + track_commit_events present commit_detail, with: Entities::CommitDetail, include_stats: params[:stats], current_user: current_user else diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index d9806fa37d1..3819e6d236d 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -61,32 +61,56 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of a group' end resource :group, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do + after_validation do user_group end - desc 'Composer packages endpoint at group level' + desc 'Composer packages endpoint at group level' do + detail 'This feature was introduced in GitLab 13.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/packages', urgency: :low do presenter.root end - desc 'Composer packages endpoint at group level for packages list' + desc 'Composer packages endpoint at group level for packages list' do + detail 'This feature was introduced in GitLab 13.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end params do - requires :sha, type: String, desc: 'Shasum of current json' + requires :sha, type: String, desc: 'Shasum of current json', documentation: { example: '673594f85a55fe3c0eb45df7bd2fa9d95a1601ab' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p/:sha', urgency: :low do presenter.provider end - desc 'Composer v2 packages p2 endpoint at group level for package versions metadata' + desc 'Composer v2 packages p2 endpoint at group level for package versions metadata' do + detail 'This feature was introduced in GitLab 13.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end params do - requires :package_name, type: String, file_path: true, desc: 'The Composer package name' + requires :package_name, type: String, file_path: true, desc: 'The Composer package name', documentation: { example: 'my-composer-package' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true, urgency: :low do @@ -95,9 +119,17 @@ module API presenter.package_versions end - desc 'Composer packages endpoint at group level for package versions metadata' + desc 'Composer packages endpoint at group level for package versions metadata' do + detail 'This feature was introduced in GitLab 12.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end params do - requires :package_name, type: String, file_path: true, desc: 'The Composer package name' + requires :package_name, type: String, file_path: true, desc: 'The Composer package name', documentation: { example: 'my-composer-package' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true, urgency: :low do @@ -109,17 +141,27 @@ module API end params do - requires :id, type: Integer, desc: 'The ID of a project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of a project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Composer packages endpoint for registering packages' namespace ':id/packages/composer' do route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true + desc 'Composer packages endpoint for registering packages' do + detail 'This feature was introduced in GitLab 13.1' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end params do - optional :branch, type: String, desc: 'The name of the branch' - optional :tag, type: String, desc: 'The name of the tag' + optional :branch, type: String, desc: 'The name of the branch', documentation: { example: 'release' } + optional :tag, type: String, desc: 'The name of the tag', documentation: { example: 'v1.0.0' } exactly_one_of :tag, :branch end post urgency: :low do @@ -142,15 +184,25 @@ module API created! end + desc 'Composer package endpoint to download a package archive' do + detail 'This feature was introduced in GitLab 13.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[composer_packages] + end params do - requires :sha, type: String, desc: 'Shasum of current json' - requires :package_name, type: String, file_path: true, desc: 'The Composer package name' + requires :sha, type: String, desc: 'Shasum of current json', documentation: { example: '673594f85a55fe3c0eb45df7bd2fa9d95a1601ab' } + requires :package_name, type: String, file_path: true, desc: 'The Composer package name', documentation: { example: 'my-composer-package' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get 'archives/*package_name', urgency: :default do - authorize_read_package!(authorized_user_project) + project = authorized_user_project(action: :read_package) - package = authorized_user_project + package = project .packages .composer .with_name(params[:package_name]) @@ -160,10 +212,10 @@ module API not_found! unless metadata - track_package_event('pull_package', :composer, project: authorized_user_project, namespace: authorized_user_project.namespace) + track_package_event('pull_package', :composer, project: project, namespace: project.namespace) package.touch_last_downloaded_at - send_git_archive authorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true + send_git_archive project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true end end end diff --git a/lib/api/conan_project_packages.rb b/lib/api/conan_project_packages.rb index 636b5dca5ed..e282443e85c 100644 --- a/lib/api/conan_project_packages.rb +++ b/lib/api/conan_project_packages.rb @@ -4,7 +4,7 @@ module API class ConanProjectPackages < ::API::Base params do - requires :id, type: Integer, desc: 'The ID of a project', regexp: %r{\A[1-9]\d*\z} + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb index fdbffb1689b..e65e8f8710c 100644 --- a/lib/api/concerns/packages/conan_endpoints.rb +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -53,6 +53,11 @@ module API desc 'Ping the Conan API' do detail 'This feature was introduced in GitLab 12.2' + success code: 200 + failure [ + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -63,10 +68,15 @@ module API desc 'Search for packages' do detail 'This feature was introduced in GitLab 12.4' + success code: 200 + failure [ + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do - requires :q, type: String, desc: 'Search query' + requires :q, type: String, desc: 'Search query', documentation: { example: 'Hello*' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -86,6 +96,12 @@ module API desc 'Authenticate user against conan CLI' do detail 'This feature was introduced in GitLab 12.2' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -98,6 +114,12 @@ module API desc 'Check for valid user credentials per conan CLI' do detail 'This feature was introduced in GitLab 12.4' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -109,10 +131,10 @@ module API end params do - requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' - requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel' + requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name', documentation: { example: 'my-package' } + requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version', documentation: { example: '1.0' } + requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username', documentation: { example: 'my-group+my-project' } + requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel', documentation: { example: 'stable' } end namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do after_validation do @@ -122,14 +144,21 @@ module API # Get the snapshot # # the snapshot is a hash of { filename: md5 hash } - # md5 hash is the has of that file. This hash is used to diff the files existing on the client + # md5 hash is the hash of that file. This hash is used to diff the files existing on the client # to determine which client files need to be uploaded if no recipe exists the snapshot is empty desc 'Package Snapshot' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanPackageSnapshot + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' + requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -149,6 +178,13 @@ module API desc 'Recipe Snapshot' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeSnapshot + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -168,9 +204,16 @@ module API # where the url is the download url for the file desc 'Package Digest' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' + requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -181,6 +224,13 @@ module API desc 'Recipe Digest' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -197,10 +247,17 @@ module API # where the url is the download url for the file desc 'Package Download Urls' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanPackageManifest + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' + requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -211,6 +268,13 @@ module API desc 'Recipe Download Urls' do detail 'This feature was introduced in GitLab 12.5' + success code: 200, model: ::API::Entities::ConanPackage::ConanRecipeManifest + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -228,10 +292,17 @@ module API # where the url is the upload url for the file that the conan client will use desc 'Package Upload Urls' do detail 'This feature was introduced in GitLab 12.4' + success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' + requires :conan_package_reference, type: String, desc: 'Conan package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' } end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -245,6 +316,13 @@ module API desc 'Recipe Upload Urls' do detail 'This feature was introduced in GitLab 12.4' + success code: 200, model: ::API::Entities::ConanPackage::ConanUploadUrls + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -258,6 +336,13 @@ module API desc 'Delete Package' do detail 'This feature was introduced in GitLab 12.5' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -272,11 +357,11 @@ module API end params do - requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' - requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel' - requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision' + requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name', documentation: { example: 'my-package' } + requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version', documentation: { example: '1.0' } + requires :package_username, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package username', documentation: { example: 'my-group+my-project' } + requires :package_channel, type: String, regexp: CONAN_REVISION_USER_CHANNEL_REGEX, desc: 'Package channel', documentation: { example: 'stable' } + requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision', documentation: { example: '0' } end namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do before do @@ -288,12 +373,19 @@ module API end params do - requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES + requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES, documentation: { example: 'conanfile.py' } end namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do desc 'Download recipe files' do detail 'This feature was introduced in GitLab 12.6' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -304,6 +396,14 @@ module API desc 'Upload recipe package files' do detail 'This feature was introduced in GitLab 12.6' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do @@ -318,6 +418,14 @@ module API desc 'Workhorse authorize the conan recipe file' do detail 'This feature was introduced in GitLab 12.6' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -328,13 +436,19 @@ module API end params do - requires :conan_package_reference, type: String, desc: 'Conan Package ID' - requires :package_revision, type: String, desc: 'Conan Package Revision' - requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES + requires :conan_package_reference, type: String, desc: 'Conan Package ID', documentation: { example: '103f6067a947f366ef91fc1b7da351c588d1827f' } + requires :package_revision, type: String, desc: 'Conan Package Revision', documentation: { example: '0' } + requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES, documentation: { example: 'conaninfo.txt' } end namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do desc 'Download package files' do detail 'This feature was introduced in GitLab 12.5' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -345,6 +459,14 @@ module API desc 'Workhorse authorize the conan package file' do detail 'This feature was introduced in GitLab 12.6' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true @@ -355,6 +477,14 @@ module API desc 'Upload package files' do detail 'This feature was introduced in GitLab 12.6' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[conan_packages] end params do diff --git a/lib/api/concerns/packages/debian_distribution_endpoints.rb b/lib/api/concerns/packages/debian_distribution_endpoints.rb index 380966136df..76b996f2301 100644 --- a/lib/api/concerns/packages/debian_distribution_endpoints.rb +++ b/lib/api/concerns/packages/debian_distribution_endpoints.rb @@ -25,21 +25,23 @@ module API namespace 'debian_distributions' do helpers do params :optional_distribution_params do - optional :suite, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Suite' - optional :origin, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Origin' - optional :label, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Label' - optional :version, type: String, regexp: Gitlab::Regex.debian_version_regex, desc: 'The Debian Version' - optional :description, type: String, desc: 'The Debian Description' - optional :valid_time_duration_seconds, type: Integer, desc: 'The duration before the Release file should be considered expired by the client' + optional :suite, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Suite', documentation: { example: 'unstable' } + optional :origin, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Origin', documentation: { example: 'Grep' } + optional :label, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Label', documentation: { example: 'grep.be' } + optional :version, type: String, regexp: Gitlab::Regex.debian_version_regex, desc: 'The Debian Version', documentation: { example: '12' } + optional :description, type: String, desc: 'The Debian Description', documentation: { example: 'My description' } + optional :valid_time_duration_seconds, type: Integer, desc: 'The duration before the Release file should be considered expired by the client', documentation: { example: 604800 } optional :components, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, regexp: Gitlab::Regex.debian_component_regex, - desc: 'The list of Components' + desc: 'The list of Components', + documentation: { example: 'main' } optional :architectures, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, regexp: Gitlab::Regex.debian_architecture_regex, - desc: 'The list of Architectures' + desc: 'The list of Architectures', + documentation: { example: 'amd64' } end end @@ -63,11 +65,18 @@ module API # POST {projects|groups}/:id/debian_distributions desc 'Create a Debian Distribution' do detail 'This feature was introduced in 14.0' - success ::API::Entities::Packages::Debian::Distribution + success code: 201, model: ::API::Entities::Packages::Debian::Distribution + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } use :optional_distribution_params end post '/' do @@ -87,12 +96,18 @@ module API # GET {projects|groups}/:id/debian_distributions desc 'Get a list of Debian Distributions' do detail 'This feature was introduced in 14.0' - success ::API::Entities::Packages::Debian::Distribution + success code: 200, model: ::API::Entities::Packages::Debian::Distribution + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do use :pagination - optional :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + optional :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } use :optional_distribution_params end get '/' do @@ -107,11 +122,17 @@ module API # GET {projects|groups}/:id/debian_distributions/:codename desc 'Get a Debian Distribution' do detail 'This feature was introduced in 14.0' - success ::API::Entities::Packages::Debian::Distribution + success code: 200, model: ::API::Entities::Packages::Debian::Distribution + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } end get '/:codename' do authorize_read_package!(project_or_group) @@ -122,11 +143,17 @@ module API # GET {projects|groups}/:id/debian_distributions/:codename/key desc 'Get a Debian Distribution Key' do detail 'This feature was introduced in 14.4' - success ::API::Entities::Packages::Debian::Distribution + success code: 200, model: ::API::Entities::Packages::Debian::Distribution + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } end get '/:codename/key.asc' do authorize_read_package!(project_or_group) @@ -141,11 +168,18 @@ module API # PUT {projects|groups}/:id/debian_distributions/:codename desc 'Update a Debian Distribution' do detail 'This feature was introduced in 14.0' - success ::API::Entities::Packages::Debian::Distribution + success code: 200, model: ::API::Entities::Packages::Debian::Distribution + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } use :optional_distribution_params end put '/:codename' do @@ -165,10 +199,18 @@ module API # DELETE {projects|groups}/:id/debian_distributions/:codename desc 'Delete a Debian Distribution' do detail 'This feature was introduced in 14.0' + success code: 202 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_distribution] end params do - requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename' + requires :codename, type: String, regexp: Gitlab::Regex.debian_distribution_regex, desc: 'The Debian Codename', documentation: { example: 'unstable' } 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 2883944a745..842250d351b 100644 --- a/lib/api/concerns/packages/debian_package_endpoints.rb +++ b/lib/api/concerns/packages/debian_package_endpoints.rb @@ -24,11 +24,11 @@ module API helpers do params :shared_package_file_params do - requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex - requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' - requires :package_name, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex - requires :package_version, type: String, desc: 'The Debian Source Package Version', regexp: Gitlab::Regex.debian_version_regex - requires :file_name, type: String, desc: 'The Debian File Name' + requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex, documentation: { example: 'my-distro' } + requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)', documentation: { example: 'a' } + requires :package_name, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex, documentation: { example: 'my-pkg' } + requires :package_version, type: String, desc: 'The Debian Source Package Version', regexp: Gitlab::Regex.debian_version_regex, documentation: { example: '1.0.0' } + requires :file_name, type: String, desc: 'The Debian File Name', documentation: { example: 'example_1.0.0~alpha2_amd64.deb' } end def distribution_from!(container) @@ -79,7 +79,7 @@ module API content_type :txt, 'text/plain' params do - requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex + requires :distribution, type: String, desc: 'The Debian Codename or Suite', regexp: Gitlab::Regex.debian_distribution_regex, documentation: { example: 'my-distro' } end namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do @@ -87,6 +87,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files desc 'The Release file signature' do 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 route_setting :authentication, authenticate_non_public: true @@ -98,6 +106,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files desc 'The unsigned Release file' do 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 route_setting :authentication, authenticate_non_public: true @@ -109,6 +125,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files desc 'The signed Release file' do 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 route_setting :authentication, authenticate_non_public: true @@ -117,12 +141,12 @@ module API end params do - requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex, documentation: { example: 'main' } end namespace ':component', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do params do - requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex + requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex, documentation: { example: 'binary-amd64' } end namespace 'debian-installer/binary-:architecture' do @@ -130,6 +154,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices desc 'The installer (udeb) binary files index' do detail 'This feature was introduced in GitLab 15.4' + 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 route_setting :authentication, authenticate_non_public: true @@ -141,6 +173,14 @@ module API # https://wiki.debian.org/DebianRepository/Format?action=show&redirect=RepositoryFormat#indices_acquisition_via_hashsums_.28by-hash.29 desc 'The installer (udeb) binary files index by hash' do detail 'This feature was introduced in GitLab 15.4' + 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 route_setting :authentication, authenticate_non_public: true @@ -154,6 +194,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Sources.22_Indices desc 'The source files index' do detail 'This feature was introduced in GitLab 15.4' + 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 route_setting :authentication, authenticate_non_public: true @@ -165,6 +213,14 @@ module API # https://wiki.debian.org/DebianRepository/Format?action=show&redirect=RepositoryFormat#indices_acquisition_via_hashsums_.28by-hash.29 desc 'The source files index by hash' do detail 'This feature was introduced in GitLab 15.4' + 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 route_setting :authentication, authenticate_non_public: true @@ -174,7 +230,7 @@ module API end params do - requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex + requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex, documentation: { example: 'binary-amd64' } end namespace 'binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do @@ -182,6 +238,14 @@ module API # https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices desc 'The binary files index' do 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 route_setting :authentication, authenticate_non_public: true @@ -193,6 +257,14 @@ module API # https://wiki.debian.org/DebianRepository/Format?action=show&redirect=RepositoryFormat#indices_acquisition_via_hashsums_.28by-hash.29 desc 'The binary files index by hash' do detail 'This feature was introduced in GitLab 15.4' + 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 route_setting :authentication, authenticate_non_public: true diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index 4cc680068b6..f26b3a1d8c2 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -26,13 +26,39 @@ module API authenticate_non_get! end + helpers do + def redirect_or_present_audit_report + redirect_registry_request( + forward_to_registry: true, + package_type: :npm, + path: options[:path][0], + body: Gitlab::Json.dump(request.POST), + target: project_or_nil, + method: route.request_method + ) do + authorize_read_package!(project) + + status :ok + present [] + end + end + end + params do requires :package_name, type: String, desc: 'Package name' end namespace '-/package/*package_name' do desc 'Get all tags for a given an NPM package' do detail 'This feature was introduced in GitLab 12.7' - success ::API::Entities::NpmPackageTag + success [ + { code: 200, model: ::API::Entities::NpmPackageTag } + ] + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end get 'dist-tags', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do package_name = params[:package_name] @@ -56,6 +82,14 @@ module API namespace 'dist-tags/:tag', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do desc 'Create or Update the given tag for the given NPM package and version' do detail 'This feature was introduced in GitLab 12.7' + success code: 204 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end put format: false do package_name = params[:package_name] @@ -79,6 +113,14 @@ module API desc 'Deletes the given tag' do detail 'This feature was introduced in GitLab 12.7' + success code: 204 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end delete format: false do package_name = params[:package_name] @@ -104,6 +146,16 @@ module API desc 'NPM registry metadata endpoint' do detail 'This feature was introduced in GitLab 11.8' + success [ + { code: 200, model: ::API::Entities::NpmPackage, message: 'Ok' }, + { code: 302, message: 'Found (redirect)' } + ] + failure [ + { code: 400, message: 'Bad Request' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end params do requires :package_name, type: String, desc: 'Package name' @@ -130,6 +182,44 @@ module API with: ::API::Entities::NpmPackage end end + + desc 'NPM registry bulk advisory endpoint' do + detail 'This feature was introduced in GitLab 15.6' + success [ + { code: 200, message: 'Ok' }, + { code: 307, message: 'Temporary Redirect' } + ] + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[npm_packages] + end + route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true + post '-/npm/v1/security/advisories/bulk' do + redirect_or_present_audit_report + end + + desc 'NPM registry quick audit endpoint' do + detail 'This feature was introduced in GitLab 15.6' + success [ + { code: 200, message: 'Ok' }, + { code: 307, message: 'Temporary Redirect' } + ] + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[npm_packages] + end + route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true + post '-/npm/v1/security/audits/quick' do + redirect_or_present_audit_report + end end end end diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb index e0328e488c6..31ecb529c3c 100644 --- a/lib/api/concerns/packages/nuget_endpoints.rb +++ b/lib/api/concerns/packages/nuget_endpoints.rb @@ -55,6 +55,13 @@ module API # https://docs.microsoft.com/en-us/nuget/api/service-index desc 'The NuGet Service Index' do detail 'This feature was introduced in GitLab 12.6' + success code: 200, model: ::API::Entities::Nuget::ServiceIndex + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end get 'index', format: :json, urgency: :default do authorize_read_package!(project_or_group) @@ -67,7 +74,7 @@ module API # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource params do - requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX + requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'MyNuGetPkg' } end namespace '/metadata/*package_name' do after_validation do @@ -76,6 +83,13 @@ module API desc 'The NuGet Metadata Service - Package name level' do detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackagesMetadata + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end get 'index', format: :json, urgency: :low do present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])), @@ -84,9 +98,16 @@ module API desc 'The NuGet Metadata Service - Package name and version level' do detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackageMetadata + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX + requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.0.0' } end get '*package_version', format: :json, urgency: :low do present ::Packages::Nuget::PackageMetadataPresenter.new(find_package(params[:package_name], params[:package_version])), @@ -96,9 +117,9 @@ module API # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource params do - optional :q, type: String, desc: 'The search term' - optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX - optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX + optional :q, type: String, desc: 'The search term', documentation: { example: 'MyNuGet' } + optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX, documentation: { example: 1 } + optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX, documentation: { example: 1 } optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true end namespace '/query' do @@ -108,6 +129,13 @@ module API desc 'The NuGet Search Service' do detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::SearchResults + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end get format: :json, urgency: :low do search_options = { diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb index 9acf2fca1b3..9e59401ddf6 100644 --- a/lib/api/container_registry_event.rb +++ b/lib/api/container_registry_event.rb @@ -26,15 +26,21 @@ module API desc 'Receives notifications from the container registry when an operation occurs' do detail 'This feature was introduced in GitLab 12.10' consumes [:json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON] + success code: 200, message: 'Success' + failure [ + { code: 401, message: 'Invalid Token' } + ] + tags %w[container_registry_event] end params do requires :events, type: Array, desc: 'Event notifications' do requires :action, type: String, desc: 'The action to perform, `push`, `delete`', values: %w[push delete].freeze optional :target, type: Hash, desc: 'The target of the action' do - optional :tag, type: String, desc: 'The target tag' - optional :repository, type: String, desc: 'The target repository' - optional :digest, type: String, desc: 'Unique identifier for target image manifest' + optional :tag, type: String, desc: 'The target tag', documentation: { example: 'latest' } + optional :repository, type: String, desc: 'The target repository', documentation: { example: 'group/p1' } + optional :digest, type: String, desc: 'Unique identifier for target image manifest', + documentation: { example: 'imagedigest' } end end end diff --git a/lib/api/container_repositories.rb b/lib/api/container_repositories.rb index f2dd1fa21fd..b6b5fe10332 100644 --- a/lib/api/container_repositories.rb +++ b/lib/api/container_repositories.rb @@ -14,12 +14,17 @@ module API namespace 'registry' do params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], desc: 'The ID of the repository' end resource :repositories, requirements: { id: /[0-9]*/ } do desc 'Get a container repository' do detail 'This feature was introduced in GitLab 13.6.' success Entities::ContainerRegistry::Repository + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Repository Not Found' } + ] + tags %w[container_registry] end params do optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index 0962d749558..105a0955912 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -30,7 +30,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], desc: 'The group ID or full group path.' end namespace ':id/-/packages/debian' do @@ -42,8 +42,15 @@ module API use :shared_package_file_params end - desc 'The package' do + desc 'Download Debian package' do detail 'This feature was introduced in GitLab 14.2' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_packages] end route_setting :authentication, authenticate_non_public: true diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index df3b6e774ae..23a542e4183 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -45,8 +45,15 @@ module API use :shared_package_file_params end - desc 'The package' do + desc 'Download Debian package' do detail 'This feature was introduced in GitLab 14.2' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_packages] end route_setting :authentication, authenticate_non_public: true @@ -55,13 +62,25 @@ module API end params do - requires :file_name, type: String, desc: 'The file name' + requires :file_name, type: String, desc: 'The file name', documentation: { example: 'example_1.0.0~alpha2_amd64.deb' } end namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do format :txt content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + desc 'Upload Debian package' do + detail 'This feature was introduced in GitLab 14.0' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + 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' } @@ -91,6 +110,16 @@ module API end # PUT {projects|groups}/:id/packages/debian/:file_name/authorize + desc 'Authorize Debian package upload' do + detail 'This feature was introduced in GitLab 13.5' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[debian_packages] + 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/deployments.rb b/lib/api/deployments.rb index 141f089b5e1..3a0eea677b8 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -50,6 +50,14 @@ module API type: DateTime, desc: 'Return deployments updated before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + optional :finished_after, + type: DateTime, + desc: 'Return deployments finished after the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + + optional :finished_before, + type: DateTime, + desc: 'Return deployments finished before the specified date. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`)' + optional :environment, type: String, desc: 'The name of the environment to filter deployments by' @@ -64,7 +72,7 @@ module API authorize! :read_deployment, user_project deployments = - DeploymentsFinder.new(params.merge(project: user_project)) + DeploymentsFinder.new(declared_params(include_missing: false).merge(project: user_project)) .execute.with_api_entity_associations present paginate(deployments), with: Entities::Deployment diff --git a/lib/api/entities/appearance.rb b/lib/api/entities/appearance.rb index a09faf55f48..94a39568393 100644 --- a/lib/api/entities/appearance.rb +++ b/lib/api/entities/appearance.rb @@ -4,6 +4,7 @@ module API module Entities class Appearance < Grape::Entity expose :title + expose :short_title expose :description expose :logo do |appearance, options| diff --git a/lib/api/entities/basic_success.rb b/lib/api/entities/basic_success.rb new file mode 100644 index 00000000000..37388f56221 --- /dev/null +++ b/lib/api/entities/basic_success.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + # Simple representation for endpoints that returns a trivial success response. + class BasicSuccess < Grape::Entity + expose :success, documentation: { type: 'boolean' } do + true + end + end + end +end diff --git a/lib/api/entities/batched_background_migration.rb b/lib/api/entities/batched_background_migration.rb index eba17ff98f4..08e4681e0aa 100644 --- a/lib/api/entities/batched_background_migration.rb +++ b/lib/api/entities/batched_background_migration.rb @@ -3,12 +3,12 @@ module API module Entities class BatchedBackgroundMigration < Grape::Entity - expose :id - expose :job_class_name - expose :table_name - expose :status, &:status_name - expose :progress - expose :created_at + expose :id, documentation: { type: :string, example: "1234" } + expose :job_class_name, documentation: { type: :string, example: "CopyColumnUsingBackgroundMigrationJob" } + expose :table_name, documentation: { type: :string, example: "events" } + expose :status_name, as: :status, override: true, documentation: { type: :string, example: "active" } + expose :progress, documentation: { type: :float, example: 50 } + expose :created_at, documentation: { type: :dateTime, example: "2022-11-28T16:26:39+02:00" } end end end diff --git a/lib/api/entities/ci/job_request/hook.rb b/lib/api/entities/ci/job_request/hook.rb new file mode 100644 index 00000000000..2d155bb1c45 --- /dev/null +++ b/lib/api/entities/ci/job_request/hook.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + module JobRequest + class Hook < Grape::Entity + expose :name, :script + end + end + end + end +end diff --git a/lib/api/entities/ci/job_request/response.rb b/lib/api/entities/ci/job_request/response.rb index 9de415ebacb..cfdbeed79b6 100644 --- a/lib/api/entities/ci/job_request/response.rb +++ b/lib/api/entities/ci/job_request/response.rb @@ -23,6 +23,9 @@ module API expose :runner_variables, as: :variables expose :steps, using: Entities::Ci::JobRequest::Step + expose :runtime_hooks, as: :hooks, + using: Entities::Ci::JobRequest::Hook, + if: ->(job) { ::Feature.enabled?(:ci_hooks_pre_get_sources_script, job.project) } expose :image, using: Entities::Ci::JobRequest::Image expose :services, using: Entities::Ci::JobRequest::Service expose :artifacts, using: Entities::Ci::JobRequest::Artifacts diff --git a/lib/api/entities/ci/runner_details.rb b/lib/api/entities/ci/runner_details.rb index 9b1decca274..8aa134dc669 100644 --- a/lib/api/entities/ci/runner_details.rb +++ b/lib/api/entities/ci/runner_details.rb @@ -14,7 +14,7 @@ module API # rubocop: disable CodeReuse/ActiveRecord expose :projects, with: Entities::BasicProjectDetails do |runner, options| - if options[:current_user].admin? # rubocop: disable Cop/UserAdmin + if options[:current_user].can_read_all_resources? runner.projects else options[:current_user].authorized_projects.where(id: runner.runner_projects.pluck(:project_id)) @@ -23,7 +23,7 @@ module API # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord expose :groups, with: Entities::BasicGroupDetails do |runner, options| - if options[:current_user].admin? # rubocop: disable Cop/UserAdmin + if options[:current_user].can_read_all_resources? runner.groups else options[:current_user].authorized_groups.where(id: runner.runner_namespaces.pluck(:namespace_id)) diff --git a/lib/api/entities/ci/secure_file.rb b/lib/api/entities/ci/secure_file.rb index d957e4488fd..a234ada6f82 100644 --- a/lib/api/entities/ci/secure_file.rb +++ b/lib/api/entities/ci/secure_file.rb @@ -4,13 +4,14 @@ module API module Entities module Ci class SecureFile < Grape::Entity - expose :id - expose :name - expose :checksum - expose :checksum_algorithm - expose :created_at - expose :expires_at - expose :metadata + expose :id, documentation: { type: 'integer', example: 123 } + expose :name, documentation: { type: 'string', example: 'upload-keystore.jks' } + expose :checksum, +documentation: { type: 'string', example: '16630b189ab34b2e3504f4758e1054d2e478deda510b2b08cc0ef38d12e80aac' } + expose :checksum_algorithm, documentation: { type: 'string', example: 'sha256' } + 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" } } end end end diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb index 9430dd5e2a2..9c30c3c59ea 100644 --- a/lib/api/entities/commit_signature.rb +++ b/lib/api/entities/commit_signature.rb @@ -10,6 +10,8 @@ module API ::API::Entities::GpgCommitSignature.represent commit_signature(commit), options elsif commit.signature.is_a?(::CommitSignatures::X509CommitSignature) ::API::Entities::X509Signature.represent commit.signature, options + elsif commit.signature.is_a?(::CommitSignatures::SshSignature) + ::API::Entities::SshSignature.represent(commit.signature, options) end end diff --git a/lib/api/entities/conan_package/conan_package_manifest.rb b/lib/api/entities/conan_package/conan_package_manifest.rb index e6acfe1912f..70ab498c56a 100644 --- a/lib/api/entities/conan_package/conan_package_manifest.rb +++ b/lib/api/entities/conan_package/conan_package_manifest.rb @@ -4,7 +4,7 @@ module API module Entities module ConanPackage class ConanPackageManifest < Grape::Entity - expose :package_urls, merge: true + expose :package_urls, merge: true, documentation: { type: 'object', example: '{ "conan_package.tgz": "https://gitlab.example.com/api/v4/packages/conan/v1/files/my-package/1.0/my-group+my-project/stable/packages/103f6067a947f366ef91fc1b7da351c588d1827f/0/conan_package.tgz"' } end end end diff --git a/lib/api/entities/conan_package/conan_package_snapshot.rb b/lib/api/entities/conan_package/conan_package_snapshot.rb index d7fdda09b5a..5cf623c53df 100644 --- a/lib/api/entities/conan_package/conan_package_snapshot.rb +++ b/lib/api/entities/conan_package/conan_package_snapshot.rb @@ -4,7 +4,11 @@ module API module Entities module ConanPackage class ConanPackageSnapshot < Grape::Entity - expose :package_snapshot, merge: true + expose :package_snapshot, merge: true, + documentation: { + type: 'object', + example: '{ "conan_package.tgz": "749b29bdf72587081ca03ec033ee59dc" }' + } end end end diff --git a/lib/api/entities/conan_package/conan_recipe_manifest.rb b/lib/api/entities/conan_package/conan_recipe_manifest.rb index ecaa142cef9..0b29f0c5058 100644 --- a/lib/api/entities/conan_package/conan_recipe_manifest.rb +++ b/lib/api/entities/conan_package/conan_recipe_manifest.rb @@ -4,7 +4,7 @@ module API module Entities module ConanPackage class ConanRecipeManifest < Grape::Entity - expose :recipe_urls, merge: true + expose :recipe_urls, merge: true, documentation: { type: 'object', example: '{ "conan_sources.tgz": "https://gitlab.example.com/api/v4/packages/conan/v1/files/my-package/1.0/my-group+my-project/stable/0/export/conan_sources.tgz" }' } end end end diff --git a/lib/api/entities/conan_package/conan_recipe_snapshot.rb b/lib/api/entities/conan_package/conan_recipe_snapshot.rb index 09a60d23727..f9806e90816 100644 --- a/lib/api/entities/conan_package/conan_recipe_snapshot.rb +++ b/lib/api/entities/conan_package/conan_recipe_snapshot.rb @@ -4,7 +4,11 @@ module API module Entities module ConanPackage class ConanRecipeSnapshot < Grape::Entity - expose :recipe_snapshot, merge: true + expose :recipe_snapshot, merge: true, + documentation: { + type: 'object', + example: '{ "conan_sources.tgz": "eadf19b33f4c3c7e113faabf26e76277" }' + } end end end diff --git a/lib/api/entities/conan_package/conan_upload_urls.rb b/lib/api/entities/conan_package/conan_upload_urls.rb index c14963c87f5..fd5ea80068c 100644 --- a/lib/api/entities/conan_package/conan_upload_urls.rb +++ b/lib/api/entities/conan_package/conan_upload_urls.rb @@ -4,7 +4,7 @@ module API module Entities module ConanPackage class ConanUploadUrls < Grape::Entity - expose :upload_urls, merge: true + expose :upload_urls, merge: true, documentation: { type: 'object', example: '{ "conan_package.tgz": "https://gitlab.example.com/api/v4/packages/conan/v1/files/my-package/1.0/my-group+my-project/stable/0/package/103f6067a947f366ef91fc1b7da351c588d1827f/0/conan_package.tgz" }' } end end end diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index d12c8142e69..cadd45cb0eb 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -4,9 +4,9 @@ module API module Entities module ContainerRegistry class Tag < Grape::Entity - expose :name - expose :path - expose :location + expose :name, documentation: { type: 'string', example: 'latest' } + expose :path, documentation: { type: 'string', example: 'namespace1/project1/test_image_1:latest' } + expose :location, documentation: { type: 'string', example: 'registry.dev/namespace1/project1/test_image_1:latest' } end class Repository < Grape::Entity @@ -19,10 +19,11 @@ module API expose :location, documentation: { type: 'string', example: 'gitlab.example.com/group/project/releases' } expose :created_at, documentation: { type: 'dateTime', example: '2019-01-10T13:39:08.229Z' } expose :expiration_policy_started_at, as: :cleanup_policy_started_at, documentation: { type: 'dateTime', example: '2020-08-17T03:12:35.489Z' } - expose :tags_count, if: -> (_, options) { options[:tags_count] } + expose :tags_count, if: -> (_, options) { options[:tags_count] }, documentation: { type: 'integer', example: 3 } expose :tags, using: Tag, if: -> (_, options) { options[:tags] } - expose :delete_api_path, if: ->(object, options) { Ability.allowed?(options[:user], :admin_container_image, object) } - expose :size, if: -> (_, options) { options[:size] } + expose :delete_api_path, if: ->(object, options) { Ability.allowed?(options[:user], :admin_container_image, object) }, + documentation: { type: 'string', example: 'delete/api/path' } + expose :size, if: -> (_, options) { options[:size] }, documentation: { type: 'integer', example: 12345 } private @@ -32,11 +33,11 @@ module API end class TagDetails < Tag - expose :revision - expose :short_revision - expose :digest - expose :created_at - expose :total_size + expose :revision, documentation: { type: 'string', example: 'tagrevision' } + expose :short_revision, documentation: { type: 'string', example: 'shortrevison' } + expose :digest, documentation: { type: 'string', example: 'shadigest' } + expose :created_at, documentation: { type: 'dateTime', example: '2022-01-10T13:39:08.229Z' } + expose :total_size, documentation: { type: 'integer', example: 3 } end end end diff --git a/lib/api/entities/event.rb b/lib/api/entities/event.rb index f750d728e03..e81e89a8393 100644 --- a/lib/api/entities/event.rb +++ b/lib/api/entities/event.rb @@ -3,11 +3,15 @@ module API module Entities class Event < Grape::Entity - expose :id - expose :project_id, :action_name - expose :target_id, :target_iid, :target_type, :author_id - expose :target_title - expose :created_at + expose :id, documentation: { type: 'integer', example: 1 } + expose :project_id, documentation: { type: 'integer', example: 2 } + expose :action_name, documentation: { type: 'string', example: 'closed' } + expose :target_id, documentation: { type: 'integer', example: 160 } + expose :target_iid, documentation: { type: 'integer', example: 157 } + expose :target_type, documentation: { type: 'string', example: 'Issue' } + expose :author_id, documentation: { type: 'integer', example: 25 } + expose :target_title, documentation: { type: 'string', example: 'Public project search field' } + expose :created_at, documentation: { type: 'string', example: '2017-02-09T10:43:19.667Z' } expose :note, using: Entities::Note, if: ->(event, options) { event.note? } expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author } expose :wiki_page, using: Entities::WikiPageBasic, if: ->(event, _options) { event.wiki_page? } @@ -17,7 +21,7 @@ module API using: Entities::PushEventPayload, if: -> (event, _) { event.push_action? } - expose :author_username do |event, options| + expose :author_username, documentation: { type: 'string', example: 'root' } do |event, options| event.author&.username end end diff --git a/lib/api/entities/issuable_references.rb b/lib/api/entities/issuable_references.rb index 1bf078847cf..7b966b85800 100644 --- a/lib/api/entities/issuable_references.rb +++ b/lib/api/entities/issuable_references.rb @@ -3,15 +3,15 @@ module API module Entities class IssuableReferences < Grape::Entity - expose :short do |issuable| + expose :short, documentation: { type: "string", example: "&6" } do |issuable| issuable.to_reference end - expose :relative do |issuable, options| + expose :relative, documentation: { type: "string", example: "&6" } do |issuable, options| issuable.to_reference(options[:group] || options[:project]) end - expose :full do |issuable| + expose :full, documentation: { type: "string", example: "test&6" } do |issuable| issuable.to_reference(full: true) end end diff --git a/lib/api/entities/issuable_time_stats.rb b/lib/api/entities/issuable_time_stats.rb index f93b4651b1f..717d2282441 100644 --- a/lib/api/entities/issuable_time_stats.rb +++ b/lib/api/entities/issuable_time_stats.rb @@ -7,12 +7,12 @@ module API Gitlab::TimeTrackingFormatter.output(time_spent) end - expose :time_estimate - expose :total_time_spent - expose :human_time_estimate + expose :time_estimate, documentation: { type: 'integer', example: 12600 } + expose :total_time_spent, documentation: { type: 'integer', example: 3600 } + expose :human_time_estimate, documentation: { type: 'string', example: '3h 30m' } with_options(format_with: :time_tracking_formatter) do - expose :total_time_spent, as: :human_total_time_spent + expose :total_time_spent, as: :human_total_time_spent, documentation: { type: 'string', example: '1h' } end # rubocop: disable CodeReuse/ActiveRecord diff --git a/lib/api/entities/metric_image.rb b/lib/api/entities/metric_image.rb index fd5e3a62e40..3e4566832c9 100644 --- a/lib/api/entities/metric_image.rb +++ b/lib/api/entities/metric_image.rb @@ -3,7 +3,13 @@ module API module Entities class MetricImage < Grape::Entity - expose :id, :created_at, :filename, :file_path, :url, :url_text + expose :id, documentation: { type: 'integer', example: 23 } + expose :created_at, documentation: { type: 'dateTime', example: '2020-11-13T00:06:18.084Z' } + expose :filename, documentation: { type: 'string', example: 'file.png' } + expose :file_path, documentation: { type: 'string', + example: '/uploads/-/system/alert_metric_image/file/23/file.png' } + expose :url, documentation: { type: 'string', example: 'https://example.com/metric' } + expose :url_text, documentation: { type: 'string', example: 'An example metric' } end end end diff --git a/lib/api/entities/milestone.rb b/lib/api/entities/milestone.rb index b191210a234..ea73ade46cd 100644 --- a/lib/api/entities/milestone.rb +++ b/lib/api/entities/milestone.rb @@ -10,7 +10,7 @@ module API expose :state, :created_at, :updated_at expose :due_date expose :start_date - expose :expired?, as: :expired + expose :expired expose :web_url do |milestone, _options| Gitlab::UrlBuilder.build(milestone) diff --git a/lib/api/entities/ml/mlflow/experiment.rb b/lib/api/entities/ml/mlflow/experiment.rb index 54e0fe63985..51650c36d98 100644 --- a/lib/api/entities/ml/mlflow/experiment.rb +++ b/lib/api/entities/ml/mlflow/experiment.rb @@ -9,6 +9,7 @@ module API expose :name expose(:lifecycle_stage) { |experiment| experiment.deleted_on? ? 'deleted' : 'active' } expose(:artifact_location) { |experiment| 'not_implemented' } + expose :metadata, as: :tags, using: KeyValue end end end diff --git a/lib/api/entities/ml/mlflow/run_param.rb b/lib/api/entities/ml/mlflow/key_value.rb index 75fee738f8b..cf2c32f6f44 100644 --- a/lib/api/entities/ml/mlflow/run_param.rb +++ b/lib/api/entities/ml/mlflow/key_value.rb @@ -4,7 +4,7 @@ module API module Entities module Ml module Mlflow - class RunParam < Grape::Entity + class KeyValue < Grape::Entity expose :name, as: :key expose :value end diff --git a/lib/api/entities/ml/mlflow/run.rb b/lib/api/entities/ml/mlflow/run.rb index 8b16c67611f..01d85e8862b 100644 --- a/lib/api/entities/ml/mlflow/run.rb +++ b/lib/api/entities/ml/mlflow/run.rb @@ -9,7 +9,8 @@ module API expose :itself, using: RunInfo, as: :info expose :data do expose :metrics, using: Metric - expose :params, using: RunParam + expose :params, using: KeyValue + expose :metadata, as: :tags, using: KeyValue end end end diff --git a/lib/api/entities/namespace.rb b/lib/api/entities/namespace.rb index f11303d41a6..15bc7d158c4 100644 --- a/lib/api/entities/namespace.rb +++ b/lib/api/entities/namespace.rb @@ -3,7 +3,7 @@ module API module Entities class Namespace < Entities::NamespaceBasic - expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _| + expose :members_count_with_descendants, documentation: { type: 'integer', example: 5 }, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _| namespace.users_with_descendants.count end diff --git a/lib/api/entities/namespace_basic.rb b/lib/api/entities/namespace_basic.rb index 2b9dd0b5f4d..4264326cdc2 100644 --- a/lib/api/entities/namespace_basic.rb +++ b/lib/api/entities/namespace_basic.rb @@ -3,9 +3,15 @@ module API module Entities class NamespaceBasic < Grape::Entity - expose :id, :name, :path, :kind, :full_path, :parent_id, :avatar_url + expose :id, documentation: { type: 'integer', example: 2 } + expose :name, documentation: { type: 'string', example: 'project' } + expose :path, documentation: { type: 'string', example: 'my_project' } + expose :kind, documentation: { type: 'string', example: 'project' } + expose :full_path, documentation: { type: 'string', example: 'group/my_project' } + expose :parent_id, documentation: { type: 'integer', example: 1 } + expose :avatar_url, documentation: { type: 'string', example: 'https://example.com/avatar/12345' } - expose :web_url do |namespace| + expose :web_url, documentation: { type: 'string', example: 'https://example.com/group/my_project' } do |namespace| if namespace.user_namespace? Gitlab::Routing.url_helpers.user_url(namespace.owner) else diff --git a/lib/api/entities/namespace_existence.rb b/lib/api/entities/namespace_existence.rb index d93078ecdac..ac9511930ab 100644 --- a/lib/api/entities/namespace_existence.rb +++ b/lib/api/entities/namespace_existence.rb @@ -3,7 +3,8 @@ module API module Entities class NamespaceExistence < Grape::Entity - expose :exists, :suggests + expose :exists, documentation: { type: 'boolean' } + expose :suggests, documentation: { type: 'string', is_array: true, example: 'my-group1' } end end end diff --git a/lib/api/entities/npm_package.rb b/lib/api/entities/npm_package.rb index b094f3acdb6..ad864f86fd5 100644 --- a/lib/api/entities/npm_package.rb +++ b/lib/api/entities/npm_package.rb @@ -3,9 +3,19 @@ module API module Entities class NpmPackage < Grape::Entity - expose :name - expose :versions - expose :dist_tags, as: 'dist-tags' + expose :name, documentation: { type: 'string', example: 'my_package' } + expose :versions, + documentation: { + type: 'object', + example: '{ + "1.0.0": { + "name": "my_package", + "version": "1.0.0", + "dist": { "shasum": "12345", "tarball": "https://..." } + } + }' + } + expose :dist_tags, as: 'dist-tags', documentation: { type: 'object', example: '{ "latest":"1.0.1" }' } end end end diff --git a/lib/api/entities/npm_package_tag.rb b/lib/api/entities/npm_package_tag.rb index 7f458fa037f..0a20d18e917 100644 --- a/lib/api/entities/npm_package_tag.rb +++ b/lib/api/entities/npm_package_tag.rb @@ -3,7 +3,7 @@ module API module Entities class NpmPackageTag < Grape::Entity - expose :dist_tags, merge: true + expose :dist_tags, merge: true, documentation: { type: 'object', example: '{ "latest":"1.0.1" }' } end end end diff --git a/lib/api/entities/nuget/dependency.rb b/lib/api/entities/nuget/dependency.rb index b61c37f5882..adb11376cfa 100644 --- a/lib/api/entities/nuget/dependency.rb +++ b/lib/api/entities/nuget/dependency.rb @@ -4,10 +4,10 @@ module API module Entities module Nuget class Dependency < Grape::Entity - expose :id, as: :@id - expose :type, as: :@type - expose :name, as: :id - expose :range + expose :id, as: :@id, documentation: { type: 'string', example: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependency' } + expose :type, as: :@type, documentation: { type: 'string', example: 'PackageDependency' } + expose :name, as: :id, documentation: { type: 'string', example: 'Dependency' } + expose :range, documentation: { type: 'string', example: '2.0.0' } end end end diff --git a/lib/api/entities/nuget/dependency_group.rb b/lib/api/entities/nuget/dependency_group.rb index dcab9359fcf..8d943050cd8 100644 --- a/lib/api/entities/nuget/dependency_group.rb +++ b/lib/api/entities/nuget/dependency_group.rb @@ -4,10 +4,12 @@ module API module Entities module Nuget class DependencyGroup < Grape::Entity - expose :id, as: :@id - expose :type, as: :@type - expose :target_framework, as: :targetFramework, expose_nil: false - expose :dependencies, using: ::API::Entities::Nuget::Dependency + expose :id, as: :@id, documentation: { type: 'string', example: 'http://gitlab.com/Sandbox.App/1.0.0.json#dependencygroup' } + expose :type, as: :@type, documentation: { type: 'string', example: 'PackageDependencyGroup' } + expose :target_framework, as: :targetFramework, expose_nil: false, + documentation: { type: 'string', example: 'fwk test' } + expose :dependencies, using: ::API::Entities::Nuget::Dependency, + documentation: { is_array: true, type: 'API::Entities::Nuget::Dependency' } end end end diff --git a/lib/api/entities/nuget/metadatum.rb b/lib/api/entities/nuget/metadatum.rb index 87caef41a85..256b916cb64 100644 --- a/lib/api/entities/nuget/metadatum.rb +++ b/lib/api/entities/nuget/metadatum.rb @@ -4,9 +4,9 @@ module API module Entities module Nuget class Metadatum < Grape::Entity - expose :project_url, as: :projectUrl, expose_nil: false - expose :license_url, as: :licenseUrl, expose_nil: false - expose :icon_url, as: :iconUrl, expose_nil: false + expose :project_url, as: :projectUrl, expose_nil: false, documentation: { type: 'string', example: 'http://sandbox.com/project' } + expose :license_url, as: :licenseUrl, expose_nil: false, documentation: { type: 'string', example: 'http://sandbox.com/license' } + expose :icon_url, as: :iconUrl, expose_nil: false, documentation: { type: 'string', example: 'http://sandbox.com/icon' } end end end diff --git a/lib/api/entities/nuget/package_metadata.rb b/lib/api/entities/nuget/package_metadata.rb index e1c2a1ae161..1c94426bdd6 100644 --- a/lib/api/entities/nuget/package_metadata.rb +++ b/lib/api/entities/nuget/package_metadata.rb @@ -4,9 +4,10 @@ module API module Entities module Nuget class PackageMetadata < Grape::Entity - expose :json_url, as: :@id - expose :archive_url, as: :packageContent - expose :catalog_entry, as: :catalogEntry, using: ::API::Entities::Nuget::PackageMetadataCatalogEntry + expose :json_url, as: :@id, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/metadata/MyNuGetPkg/1.3.0.17.json' } + expose :archive_url, as: :packageContent, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/download/MyNuGetPkg/1.3.0.17/helloworld.1.3.0.17.nupkg' } + expose :catalog_entry, as: :catalogEntry, using: ::API::Entities::Nuget::PackageMetadataCatalogEntry, + documentation: { type: 'API::Entities::Nuget::PackageMetadataCatalogEntry' } end end end diff --git a/lib/api/entities/nuget/package_metadata_catalog_entry.rb b/lib/api/entities/nuget/package_metadata_catalog_entry.rb index 5533f857596..ce328c5a5ca 100644 --- a/lib/api/entities/nuget/package_metadata_catalog_entry.rb +++ b/lib/api/entities/nuget/package_metadata_catalog_entry.rb @@ -4,15 +4,17 @@ module API module Entities module Nuget class PackageMetadataCatalogEntry < Grape::Entity - expose :json_url, as: :@id - expose :authors - expose :dependency_groups, as: :dependencyGroups, using: ::API::Entities::Nuget::DependencyGroup - expose :package_name, as: :id - expose :package_version, as: :version - expose :tags - expose :archive_url, as: :packageContent - expose :summary - expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true + expose :json_url, as: :@id, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/metadata/MyNuGetPkg/1.3.0.17.json' } + expose :authors, documentation: { type: 'string', example: 'Author' } + expose :dependency_groups, as: :dependencyGroups, using: ::API::Entities::Nuget::DependencyGroup, + documentation: { is_array: true, type: 'API::Entities::Nuget::DependencyGroup' } + expose :package_name, as: :id, documentation: { type: 'string', example: 'MyNuGetPkg' } + expose :package_version, as: :version, documentation: { type: 'string', example: '1.3.0.17' } + expose :tags, documentation: { type: 'string', example: 'tag#1 tag#2' } + expose :archive_url, as: :packageContent, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/download/MyNuGetPkg/1.3.0.17/helloworld.1.3.0.17.nupkg' } + expose :summary, documentation: { type: 'string', example: 'Summary' } + expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true, + documentation: { type: 'API::Entities::Nuget::Metadatum' } end end end diff --git a/lib/api/entities/nuget/packages_metadata.rb b/lib/api/entities/nuget/packages_metadata.rb index 1cdf2491725..e556df0ce1f 100644 --- a/lib/api/entities/nuget/packages_metadata.rb +++ b/lib/api/entities/nuget/packages_metadata.rb @@ -4,8 +4,9 @@ module API module Entities module Nuget class PackagesMetadata < Grape::Entity - expose :count - expose :items, using: ::API::Entities::Nuget::PackagesMetadataItem + expose :count, documentation: { type: 'integer', example: 1 } + expose :items, using: ::API::Entities::Nuget::PackagesMetadataItem, + documentation: { is_array: true, type: 'API::Entities::Nuget::PackagesMetadataItem' } end end end diff --git a/lib/api/entities/nuget/packages_metadata_item.rb b/lib/api/entities/nuget/packages_metadata_item.rb index 84cc79166f3..420a4c3941c 100644 --- a/lib/api/entities/nuget/packages_metadata_item.rb +++ b/lib/api/entities/nuget/packages_metadata_item.rb @@ -4,11 +4,12 @@ module API module Entities module Nuget class PackagesMetadataItem < Grape::Entity - expose :json_url, as: :@id - expose :lower_version, as: :lower - expose :upper_version, as: :upper - expose :packages_count, as: :count - expose :packages, as: :items, using: ::API::Entities::Nuget::PackageMetadata + expose :json_url, as: :@id, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/metadata/MyNuGetPkg/1.3.0.17.json' } + expose :lower_version, as: :lower, documentation: { type: 'string', example: '1.3.0.17' } + expose :upper_version, as: :upper, documentation: { type: 'string', example: '1.3.0.17' } + expose :packages_count, as: :count, documentation: { type: 'integer', example: 1 } + expose :packages, as: :items, using: ::API::Entities::Nuget::PackageMetadata, + documentation: { is_array: true, type: 'API::Entities::Nuget::PackageMetadata' } end end end diff --git a/lib/api/entities/nuget/packages_versions.rb b/lib/api/entities/nuget/packages_versions.rb index 498c6970d5c..e0330300ca7 100644 --- a/lib/api/entities/nuget/packages_versions.rb +++ b/lib/api/entities/nuget/packages_versions.rb @@ -4,7 +4,7 @@ module API module Entities module Nuget class PackagesVersions < Grape::Entity - expose :versions + expose :versions, documentation: { type: 'string', is_array: true, example: '1.3.0.17' } end end end diff --git a/lib/api/entities/nuget/search_result.rb b/lib/api/entities/nuget/search_result.rb index 8e028cbad95..bb3698de30b 100644 --- a/lib/api/entities/nuget/search_result.rb +++ b/lib/api/entities/nuget/search_result.rb @@ -4,17 +4,18 @@ module API module Entities module Nuget class SearchResult < Grape::Entity - expose :type, as: :@type - expose :authors - expose :name, as: :id - expose :name, as: :title - expose :summary - expose :total_downloads, as: :totalDownloads - expose :verified - expose :version + expose :type, as: :@type, documentation: { type: 'string', example: 'Package' } + expose :authors, documentation: { type: 'string', example: 'Author' } + expose :name, as: :id, documentation: { type: 'string', example: 'MyNuGetPkg' } + expose :name, as: :title, documentation: { type: 'string', example: 'MyNuGetPkg' } + expose :summary, documentation: { type: 'string', example: 'Summary' } + expose :total_downloads, as: :totalDownloads, documentation: { type: 'integer', example: 1 } + expose :verified, documentation: { type: 'boolean' } + expose :version, documentation: { type: 'string', example: '1.3.0.17' } expose :versions, using: ::API::Entities::Nuget::SearchResultVersion - expose :tags - expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true + expose :tags, documentation: { type: 'string', example: 'tag#1 tag#2' } + expose :metadatum, using: ::API::Entities::Nuget::Metadatum, merge: true, + documentation: { is_array: true, type: 'API::Entities::Nuget::Metadatum' } end end end diff --git a/lib/api/entities/nuget/search_result_version.rb b/lib/api/entities/nuget/search_result_version.rb index 9032c964c44..fb8d8b75f83 100644 --- a/lib/api/entities/nuget/search_result_version.rb +++ b/lib/api/entities/nuget/search_result_version.rb @@ -4,9 +4,9 @@ module API module Entities module Nuget class SearchResultVersion < Grape::Entity - expose :json_url, as: :@id - expose :version - expose :downloads + expose :json_url, as: :@id, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/1/packages/nuget/metadata/MyNuGetPkg/1.3.0.17.json' } + expose :version, documentation: { type: 'string', example: '1.3.0.17' } + expose :downloads, documentation: { type: 'integer', example: 1 } end end end diff --git a/lib/api/entities/nuget/search_results.rb b/lib/api/entities/nuget/search_results.rb index 22a77dc7b6c..117904a1aff 100644 --- a/lib/api/entities/nuget/search_results.rb +++ b/lib/api/entities/nuget/search_results.rb @@ -4,8 +4,9 @@ module API module Entities module Nuget class SearchResults < Grape::Entity - expose :total_count, as: :totalHits - expose :data, using: ::API::Entities::Nuget::SearchResult + expose :total_count, as: :totalHits, documentation: { type: 'integer', example: 1 } + expose :data, using: ::API::Entities::Nuget::SearchResult, + documentation: { is_array: true, type: 'API::Entities::Nuget::SearchResult' } end end end diff --git a/lib/api/entities/nuget/service_index.rb b/lib/api/entities/nuget/service_index.rb index e57bd04adb9..4ab6c5ddc8b 100644 --- a/lib/api/entities/nuget/service_index.rb +++ b/lib/api/entities/nuget/service_index.rb @@ -4,8 +4,8 @@ module API module Entities module Nuget class ServiceIndex < Grape::Entity - expose :version - expose :resources + expose :version, documentation: { type: 'string', example: '1.3.0.17' } + expose :resources, documentation: { type: 'object', is_array: true, example: '{ "@id": "https://gitlab.com/api/v4/projects/1/packages/nuget/query", "@type": "SearchQueryService", "comment": "Filter and search for packages by keyword."}' } end end end diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index c92a4677220..ab6cc0fcb0a 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -26,7 +26,7 @@ module API expose :status, documentation: { type: 'string', example: 'default' } expose :_links do - expose :web_path do |package| + expose :web_path, if: ->(package) { package.default? } do |package| package_path(package) end diff --git a/lib/api/entities/packages/debian/distribution.rb b/lib/api/entities/packages/debian/distribution.rb index 97a3c479f40..a11f4337f38 100644 --- a/lib/api/entities/packages/debian/distribution.rb +++ b/lib/api/entities/packages/debian/distribution.rb @@ -5,17 +5,18 @@ module API module Packages module Debian class Distribution < Grape::Entity - expose :id - expose :codename - expose :suite - expose :origin - expose :label - expose :version - expose :description - expose :valid_time_duration_seconds + expose :id, documentation: { type: 'integer', example: 1 } + expose :codename, documentation: { type: 'string', example: 'unstable' } + expose :suite, documentation: { type: 'string', example: 'unstable' } + expose :origin, documentation: { type: 'string', example: 'Grep' } + expose :label, documentation: { type: 'string', example: 'grep.be' } + expose :version, documentation: { type: 'string', example: '12' } + expose :description, documentation: { type: 'string', example: 'My description' } + expose :valid_time_duration_seconds, documentation: { type: 'integer', example: 604800 } - expose :component_names, as: :components - expose :architecture_names, as: :architectures + expose :component_names, as: :components, documentation: { is_array: true, type: 'string', example: 'main' } + expose :architecture_names, as: :architectures, + documentation: { is_array: true, type: 'string', example: 'amd64' } end end end diff --git a/lib/api/entities/plan_limit.rb b/lib/api/entities/plan_limit.rb index 34018f03eb1..d69be0077f2 100644 --- a/lib/api/entities/plan_limit.rb +++ b/lib/api/entities/plan_limit.rb @@ -17,6 +17,7 @@ module API expose :maven_max_file_size, documentation: { type: 'integer', example: 3221225472 } expose :npm_max_file_size, documentation: { type: 'integer', example: 524288000 } expose :nuget_max_file_size, documentation: { type: 'integer', example: 524288000 } + expose :pipeline_hierarchy_size, documentation: { type: 'integer', example: 1000 } expose :pypi_max_file_size, documentation: { type: 'integer', example: 3221225472 } expose :terraform_module_max_file_size, documentation: { type: 'integer', example: 1073741824 } expose :storage_size_limit, documentation: { type: 'integer', example: 15000 } diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 1c1bafbf161..37be6903d8b 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -81,6 +81,10 @@ module API expose(:container_registry_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :container_registry) } expose(:security_and_compliance_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :security_and_compliance) } expose(:releases_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :releases) } + expose(:environments_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :environments) } + expose(:feature_flags_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :feature_flags) } + expose(:infrastructure_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :infrastructure) } + expose(:monitor_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :monitor) } expose :emails_disabled, documentation: { type: 'boolean' } expose :shared_runners_enabled, documentation: { type: 'boolean' } diff --git a/lib/api/entities/project_integration.rb b/lib/api/entities/project_integration.rb index 29bb60a19e5..f4709ce6dab 100644 --- a/lib/api/entities/project_integration.rb +++ b/lib/api/entities/project_integration.rb @@ -5,8 +5,8 @@ module API class ProjectIntegration < Entities::ProjectIntegrationBasic # Expose serialized properties expose :properties, documentation: { type: 'Hash', example: { "token" => "secr3t" } } do |integration, options| - integration.api_field_names.to_h do |name| - [name, integration.public_send(name)] # rubocop:disable GitlabSecurity/PublicSend + integration.api_field_names.index_with do |name| + integration.public_send(name) # rubocop:disable GitlabSecurity/PublicSend end end end diff --git a/lib/api/entities/push_event_payload.rb b/lib/api/entities/push_event_payload.rb index 6aad5f10177..2d8f0d9344c 100644 --- a/lib/api/entities/push_event_payload.rb +++ b/lib/api/entities/push_event_payload.rb @@ -3,8 +3,14 @@ module API module Entities class PushEventPayload < Grape::Entity - expose :commit_count, :action, :ref_type, :commit_from, :commit_to, :ref, - :commit_title, :ref_count + expose :commit_count, documentation: { type: 'integer', example: 1 } + expose :action, documentation: { type: 'string', example: 'pushed' } + expose :ref_type, documentation: { type: 'string', example: 'branch' } + expose :commit_from, documentation: { type: 'string', example: '50d4420237a9de7be1304607147aec22e4a14af7' } + expose :commit_to, documentation: { type: 'string', example: 'c5feabde2d8cd023215af4d2ceeb7a64839fc428' } + expose :ref, documentation: { type: 'string', example: 'master' } + expose :commit_title, documentation: { type: 'string', example: 'Add simple search to projects in public area' } + expose :ref_count, documentation: { type: 'integer', example: 1 } end end end diff --git a/lib/api/entities/ssh_key.rb b/lib/api/entities/ssh_key.rb index 3db10bb8ec2..37e8ad7b1f5 100644 --- a/lib/api/entities/ssh_key.rb +++ b/lib/api/entities/ssh_key.rb @@ -12,6 +12,7 @@ module API example: 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1256k6Yjz\ GGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCdd\ NaP0L+hM7zhFNzjFvpaMgJw0=' } + expose :usage_type, documentation: { type: 'string', example: 'auth' } end end end diff --git a/lib/api/entities/ssh_signature.rb b/lib/api/entities/ssh_signature.rb new file mode 100644 index 00000000000..dc3800c87c5 --- /dev/null +++ b/lib/api/entities/ssh_signature.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module API + module Entities + class SshSignature < Grape::Entity + expose :verification_status, documentation: { type: 'string', example: 'unverified' } + expose :key, using: 'API::Entities::SSHKey' + end + end +end diff --git a/lib/api/entities/tag_signature.rb b/lib/api/entities/tag_signature.rb new file mode 100644 index 00000000000..e75fd04109a --- /dev/null +++ b/lib/api/entities/tag_signature.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + class TagSignature < Grape::Entity + expose :signature_type, documentation: { type: 'string', example: 'PGP' } + + expose :signature, merge: true do |tag| + ::API::Entities::X509Signature.represent tag.signature if tag.signature_type == :X509 + end + end + end +end diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb index 5bbbb59f565..02dfdb68af9 100644 --- a/lib/api/entities/todo.rb +++ b/lib/api/entities/todo.rb @@ -32,6 +32,7 @@ module API def todo_target_url(todo) return design_todo_target_url(todo) if todo.for_design? + return todo.access_request_url if todo.member_access_requested? target_type = todo.target_type.gsub('::', '_').underscore target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url" diff --git a/lib/api/events.rb b/lib/api/events.rb index 0a0141484ef..d3e8892f3bc 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -15,8 +15,15 @@ module API desc "List currently authenticated user's events" do detail 'This feature was introduced in GitLab 9.3.' success Entities::Event + is_array true + failure [ + { code: 401, message: 'Unauthorized' } + ] end params do + optional :scope, type: String, + desc: 'Include all events across a user’s projects', + documentation: { example: 'all' } use :pagination use :event_filter_params use :sort_params @@ -32,12 +39,17 @@ module API end params do - requires :id, type: String, desc: 'The ID or Username of the user' + requires :id, type: String, desc: 'The ID or username of the user' end resource :users do desc 'Get the contribution events of a specified user' do detail 'This feature was introduced in GitLab 8.13.' success Entities::Event + tags %w[events] + is_array true + failure [ + { code: 404, message: 'Not found' } + ] end params do use :pagination diff --git a/lib/api/features.rb b/lib/api/features.rb index 6b6f5cbfb3f..9142591aebd 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -9,6 +9,8 @@ module API feature_category :feature_flags urgency :low + BadValueError = Class.new(StandardError) + # TODO: remove these helpers with feature flag set_feature_flag_service helpers do def gate_value(params) @@ -18,6 +20,8 @@ module API when '0', 'false' false else + raise BadValueError unless params[:value].match? /^\d+(\.\d+)?$/ + # https://github.com/jnunemaker/flipper/blob/master/lib/flipper/typecast.rb#L47 if params[:value].to_s.include?('.') params[:value].to_f @@ -153,7 +157,9 @@ module API present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet with: Entities::Feature, current_user: current_user end - rescue Feature::Target::UnknowTargetError => e + rescue BadValueError + bad_request!("Value must be boolean or numeric, got #{params[:value]}") + rescue Feature::Target::UnknownTargetError => e bad_request!(e.message) end diff --git a/lib/api/files.rb b/lib/api/files.rb index fa749299b9a..b02f1a8728b 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -172,14 +172,24 @@ module API desc: 'The url encoded path to the file.', documentation: { example: 'lib%2Fclass%2Erb' } optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false, documentation: { example: 'main' } + optional :lfs, type: Boolean, + desc: 'Retrieve binary data for a file that is an lfs pointer', + default: false end get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS, urgency: :low do assign_file_vars! - no_cache_headers - set_http_headers(blob_data) + if params[:lfs] && @blob.stored_externally? + lfs_object = LfsObject.find_by_oid(@blob.lfs_oid) + not_found! unless lfs_object&.project_allowed_access?(@project) + + present_carrierwave_file!(lfs_object.file) + else + no_cache_headers + set_http_headers(blob_data) - send_git_blob @repo, @blob + send_git_blob @repo, @blob + end end desc 'Get file metadata from repository' diff --git a/lib/api/freeze_periods.rb b/lib/api/freeze_periods.rb index 40f1be83028..abd8f4c0b94 100644 --- a/lib/api/freeze_periods.rb +++ b/lib/api/freeze_periods.rb @@ -34,7 +34,7 @@ module API get ":id/freeze_periods" do authorize! :read_freeze_period, user_project - freeze_periods = ::FreezePeriodsFinder.new(user_project, current_user).execute + freeze_periods = ::Ci::FreezePeriodsFinder.new(user_project, current_user).execute present paginate(freeze_periods), with: Entities::FreezePeriod, current_user: current_user end diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 3584f8d025a..da5b0930543 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -28,6 +28,13 @@ module API namespace ':package_name/*package_version/:file_name', requirements: GENERIC_PACKAGES_REQUIREMENTS do desc 'Workhorse authorize generic package file' do detail 'This feature was introduced in GitLab 13.5' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[generic_packages] end route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true @@ -47,6 +54,17 @@ module API desc 'Upload package file' do detail 'This feature was introduced in GitLab 13.5' + success [ + { code: 200 }, + { code: 201 } + ] + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[generic_packages] end params do @@ -88,6 +106,13 @@ module API desc 'Download package file' do detail 'This feature was introduced in GitLab 13.5' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[generic_packages] end params do diff --git a/lib/api/group_debian_distributions.rb b/lib/api/group_debian_distributions.rb index 1f43bb0e2b3..0364e2e7b56 100644 --- a/lib/api/group_debian_distributions.rb +++ b/lib/api/group_debian_distributions.rb @@ -3,7 +3,7 @@ module API class GroupDebianDistributions < ::API::Base params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group' end before do diff --git a/lib/api/groups.rb b/lib/api/groups.rb index ca99e30fbf7..23db10dbdbf 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -100,7 +100,7 @@ module API options = { with: serializer, current_user: current_user, - statistics: params[:statistics] && current_user&.admin? + statistics: params[:statistics] && current_user&.can_read_all_resources? } groups = groups.with_statistics if options[:statistics] @@ -186,7 +186,7 @@ module API end def check_subscription!(group) - render_api_error!("This group can't be removed because it is linked to a subscription.", :bad_request) if group.paid? + render_api_error!("This group can't be removed because it is linked to a subscription.", :bad_request) if group.prevent_delete? end end @@ -195,6 +195,8 @@ module API desc 'Get a groups list' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -207,6 +209,7 @@ module API desc 'Create a group. Available only for users who can create groups.' do success Entities::Group + tags %w[groups] end params do requires :name, type: String, desc: 'The name of the group' @@ -240,6 +243,7 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Update a group. Available only for users who can administrate groups.' do success Entities::Group + tags %w[groups] end params do optional :name, type: String, desc: 'The name of the group' @@ -265,6 +269,7 @@ module API desc 'Get a single group, with containing projects.' do success Entities::GroupDetail + tags %w[groups] end params do use :with_custom_attributes @@ -278,7 +283,9 @@ module API present_group_details(params, group, with_projects: params[:with_projects]) end - desc 'Remove a group.' + desc 'Remove a group.' do + tags %w[groups] + end delete ":id", feature_category: :subgroups, urgency: :low do group = find_group!(params[:id]) authorize! :admin_group, group @@ -289,6 +296,8 @@ module API desc 'Get a list of projects in this group.' do success Entities::Project + is_array true + tags %w[groups] end params do optional :archived, type: Boolean, desc: 'Limit by archived status' @@ -329,6 +338,8 @@ module API desc 'Get a list of shared projects in this group' do success Entities::Project + is_array true + tags %w[groups] end params do optional :archived, type: Boolean, desc: 'Limit by archived status' @@ -357,6 +368,8 @@ module API desc 'Get a list of subgroups in this group.' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -369,6 +382,8 @@ module API desc 'Get a list of descendant groups of this group.' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -382,6 +397,7 @@ module API desc 'Transfer a project to the group namespace. Available only for admin.' do success Entities::GroupDetail + tags %w[groups] end params do requires :project_id, type: String, desc: 'The ID or path of the project' @@ -400,7 +416,11 @@ module API end end - desc 'Get the groups to where the current group can be transferred to' + desc 'Get the groups to where the current group can be transferred to' do + success Entities::Group + is_array true + tags %w[groups] + end params do optional :search, type: String, desc: 'Return list of namespaces matching the search criteria' use :pagination @@ -415,7 +435,9 @@ module API present_groups params, groups, serializer: Entities::PublicGroupDetails end - desc 'Transfer a group to a new parent group or promote a subgroup to a root group' + desc 'Transfer a group to a new parent group or promote a subgroup to a root group' do + tags %w[groups] + end params do optional :group_id, type: Integer, @@ -440,6 +462,7 @@ module API desc 'Share a group with a group' do success Entities::GroupDetail + tags %w[groups] end params do requires :group_id, type: Integer, desc: 'The ID of the group to share' diff --git a/lib/api/helm_packages.rb b/lib/api/helm_packages.rb index fa2537bcfc4..8260d8a88f8 100644 --- a/lib/api/helm_packages.rb +++ b/lib/api/helm_packages.rb @@ -32,15 +32,21 @@ module API end params do - requires :id, type: String, desc: 'The ID or full path of a project' + requires :id, types: [Integer, String], desc: 'The ID or full path of a project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/helm', requirements: HELM_REQUIREMENTS do desc 'Download a chart index' do detail 'This feature was introduced in GitLab 14.0' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags %w[helm_packages] end params do - requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex + requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex, documentation: { example: 'stable' } end get ":channel/index.yaml" do @@ -56,10 +62,17 @@ module API desc 'Download a chart' do detail 'This feature was introduced in GitLab 14.0' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[helm_packages] end params do - requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex - requires :file_name, type: String, desc: 'Helm package file name' + requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex, documentation: { example: 'stable' } + requires :file_name, type: String, desc: 'Helm package file name', documentation: { example: 'mychart' } end get ":channel/charts/:file_name.tgz" do project = authorized_user_project(action: :read_package) @@ -67,16 +80,23 @@ module API package_file = Packages::Helm::PackageFilesFinder.new(project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent! - track_package_event('pull_package', :helm, project: project, namespace: project.namespace) + track_package_event('pull_package', :helm, project: project, namespace: project.namespace, property: 'i_package_helm_user') present_package_file!(package_file) end desc 'Authorize a chart upload from workhorse' do detail 'This feature was introduced in GitLab 14.0' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[helm_packages] end params do - requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex + requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex, documentation: { example: 'stable' } end post "api/:channel/charts/authorize" do authorize_workhorse!( @@ -88,9 +108,16 @@ module API desc 'Upload a chart' do detail 'This feature was introduced in GitLab 14.0' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[helm_packages] end params do - requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex + requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex, documentation: { example: 'stable' } requires :chart, type: ::API::Validations::Types::WorkhorseFile, desc: 'The chart file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end post "api/:channel/charts" do @@ -110,7 +137,8 @@ module API package, chart_params.merge(build: current_authenticated_job) ).execute - track_package_event('push_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace) + track_package_event('push_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace, + property: 'i_package_helm_user') ::Packages::Helm::ExtractionWorker.perform_async(params[:channel], chart_package_file.id) # rubocop:disable CodeReuse/Worker diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 75e7612bd5b..0b5a471ea12 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -167,6 +167,10 @@ module API current_authenticated_job.project == project end + def enforce_jobs_api_rate_limits(project) + ::Feature.enabled?(:ci_enforce_rate_limits_jobs_api, project) + end + # rubocop: disable CodeReuse/ActiveRecord def find_group(id) if id.to_s =~ INTEGER_ID_REGEX @@ -301,7 +305,7 @@ module API def authenticated_as_admin! authenticate! - forbidden! unless current_user.admin? + forbidden! unless current_user.can_admin_all_resources? end def authorize!(action, subject = :global, reason = nil) @@ -710,7 +714,7 @@ module API unauthorized! unless initial_current_user - unless initial_current_user.admin? + unless initial_current_user.can_admin_all_resources? forbidden!('Must be admin to use sudo') end diff --git a/lib/api/helpers/award_emoji.rb b/lib/api/helpers/award_emoji.rb index 3ea35381c97..f8417366ea4 100644 --- a/lib/api/helpers/award_emoji.rb +++ b/lib/api/helpers/award_emoji.rb @@ -7,7 +7,7 @@ module API [ { type: 'issue', resource: :projects, find_by: :iid, feature_category: :team_planning }, { type: 'merge_request', resource: :projects, find_by: :iid, feature_category: :code_review }, - { type: 'snippet', resource: :projects, find_by: :id, feature_category: :snippets } + { type: 'snippet', resource: :projects, find_by: :id, feature_category: :source_code_management } ] end @@ -18,18 +18,16 @@ module API # rubocop: disable CodeReuse/ActiveRecord def awardable @awardable ||= - begin - if params.include?(:note_id) - note_id = params.delete(:note_id) + if params.include?(:note_id) + note_id = params.delete(:note_id) - awardable.notes.find(note_id) - elsif params.include?(:issue_iid) - user_project.issues.find_by!(iid: params[:issue_iid]) - elsif params.include?(:merge_request_iid) - user_project.merge_requests.find_by!(iid: params[:merge_request_iid]) - elsif params.include?(:snippet_id) - user_project.snippets.find(params[:snippet_id]) - end + awardable.notes.find(note_id) + elsif params.include?(:issue_iid) + user_project.issues.find_by!(iid: params[:issue_iid]) + elsif params.include?(:merge_request_iid) + user_project.merge_requests.find_by!(iid: params[:merge_request_iid]) + elsif params.include?(:snippet_id) + user_project.snippets.find(params[:snippet_id]) end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb index c94199b17bc..182ada54a12 100644 --- a/lib/api/helpers/discussions_helpers.rb +++ b/lib/api/helpers/discussions_helpers.rb @@ -8,7 +8,7 @@ module API # extend it. { Issue => :team_planning, - Snippet => :snippets, + Snippet => :source_code_management, MergeRequest => :code_review, Commit => :code_review } diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 99273e81730..543449c0349 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -415,14 +415,6 @@ module API desc: 'The URL of the external wiki' } ], - 'flowdock' => [ - { - required: true, - name: :token, - type: String, - desc: 'Flowdock token' - } - ], 'hangouts-chat' => [ { required: true, @@ -893,7 +885,6 @@ module API ::Integrations::EmailsOnPush, ::Integrations::Ewm, ::Integrations::ExternalWiki, - ::Integrations::Flowdock, ::Integrations::HangoutsChat, ::Integrations::Harbor, ::Integrations::Irker, diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index eed9fa30d3c..ee3bb49c97f 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -11,100 +11,107 @@ module API params :ee_approval_params do end - params :merge_requests_negatable_params do - optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' - optional :author_username, type: String, desc: 'Return merge requests which are authored by the user with the given username' + params :merge_requests_negatable_params do |options| + optional :author_id, type: Integer, + desc: "#{options[:prefix]}Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`." + optional :author_username, type: String, + desc: "#{options[:prefix]}Returns merge requests created by the given `username`. Mutually exclusive with `author_id`." mutually_exclusive :author_id, :author_username - - optional :assignee_id, - types: [Integer, String], - integer_none_any: true, - desc: 'Return merge requests which are assigned to the user with the given ID' - optional :assignee_username, - type: Array[String], - check_assignees_count: true, - coerce_with: Validations::Validators::CheckAssigneesCount.coerce, - desc: 'Return merge requests which are assigned to the user with the given username' + optional :assignee_id, types: [Integer, String], + integer_none_any: true, + desc: "#{options[:prefix]}Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee." + optional :assignee_username, type: Array[String], + check_assignees_count: true, + coerce_with: Validations::Validators::CheckAssigneesCount.coerce, + desc: "#{options[:prefix]}Returns merge requests created by the given `username`. Mutually exclusive with `author_id`.", + documentation: { is_array: true } mutually_exclusive :assignee_id, :assignee_username - optional :reviewer_username, - type: String, - desc: 'Return merge requests which have the user as a reviewer with the given username' - - optional :labels, - type: Array[String], - coerce_with: Validations::Types::CommaSeparatedToArray.coerce, - desc: 'Comma-separated list of label names' - optional :milestone, type: String, desc: 'Return merge requests for a specific milestone' - optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' + optional :reviewer_username, type: String, + desc: "#{options[:prefix]}Returns merge requests which have the user as a reviewer with the given `username`. `None` returns merge requests with no reviewers. `Any` returns merge requests with any reviewer. Mutually exclusive with `reviewer_id`. Introduced in GitLab 13.8." + optional :labels, type: Array[String], + coerce_with: Validations::Types::CommaSeparatedToArray.coerce, + desc: "#{options[:prefix]}Returns merge requests matching a comma-separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. Predefined names are case-insensitive.", + documentation: { is_array: true } + optional :milestone, type: String, + desc: "#{options[:prefix]}Returns merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone." + optional :my_reaction_emoji, type: String, + desc: "#{options[:prefix]}Returns merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction." end params :merge_requests_base_params do - use :merge_requests_negatable_params - optional :reviewer_id, - types: [Integer, String], - integer_none_any: true, - desc: 'Return merge requests which have the user as a reviewer with the given ID' + use :merge_requests_negatable_params, prefix: '' + + optional :reviewer_id, types: [Integer, String], + integer_none_any: true, + desc: 'Returns merge requests which have the user as a reviewer with the given user `id`. `None` returns merge requests with no reviewers. `Any` returns merge requests with any reviewer. Mutually exclusive with `reviewer_username`.' mutually_exclusive :reviewer_id, :reviewer_username - optional :state, - type: String, - values: %w[opened closed locked merged all], - default: 'all', - desc: 'Return opened, closed, locked, merged, or all merge requests' - optional :order_by, - type: String, - values: Helpers::MergeRequestsHelpers.sort_options, - default: 'created_at', - desc: "Return merge requests ordered by #{Helpers::MergeRequestsHelpers.sort_options_help} fields." - optional :sort, - type: String, - values: %w[asc desc], - default: 'desc', - desc: 'Return merge requests sorted in `asc` or `desc` order.' - 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' - optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time' - optional :view, - type: String, - values: %w[simple], - desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' - - optional :scope, - type: String, - values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], - desc: 'Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' - optional :source_branch, type: String, desc: 'Return merge requests with the given source branch' - optional :source_project_id, type: Integer, desc: 'Return merge requests with the given source project id' - optional :target_branch, type: String, desc: 'Return merge requests with the given target branch' - optional :search, - type: String, - desc: 'Search merge requests for text present in the title, description, or any combination of these' - optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma' - optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title' - optional :not, type: Hash, desc: 'Parameters to negate' do - use :merge_requests_negatable_params - optional :reviewer_id, - types: Integer, - desc: 'Return merge requests which have the user as a reviewer with the given ID' + optional :state, type: String, + values: %w[opened closed locked merged all], + default: 'all', + desc: 'Returns `all` merge requests or just those that are `opened`, `closed`, `locked`, or `merged`.' + optional :order_by, type: String, + values: Helpers::MergeRequestsHelpers.sort_options, + default: 'created_at', + desc: "Returns merge requests ordered by #{Helpers::MergeRequestsHelpers.sort_options_help} fields. Introduced in GitLab 14.8." + optional :sort, type: String, + values: %w[asc desc], + default: 'desc', + desc: 'Returns merge requests sorted in `asc` or `desc` order.' + optional :with_labels_details, type: Boolean, + default: false, + desc: 'If `true`, response returns more details for each label in labels field: `:name`,`:color`, `:description`, `:description_html`, `:text_color`' + optional :with_merge_status_recheck, type: Boolean, + default: false, + desc: 'If `true`, this projection requests (but does not guarantee) that the `merge_status` field be recalculated asynchronously. Introduced in GitLab 13.0.' + optional :created_after, type: DateTime, + desc: 'Returns merge requests created on or after the given time. Expected in ISO 8601 format.', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :created_before, type: DateTime, + desc: 'Returns merge requests created on or before the given time. Expected in ISO 8601 format.', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :updated_after, type: DateTime, + desc: 'Returns merge requests updated on or after the given time. Expected in ISO 8601 format.', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :updated_before, type: DateTime, + desc: 'Returns merge requests updated on or before the given time. Expected in ISO 8601 format.', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :view, type: String, + values: %w[simple], + desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' + optional :scope, type: String, + values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + desc: 'Returns merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' + optional :source_branch, type: String, desc: 'Returns merge requests with the given source branch' + optional :source_project_id, type: Integer, desc: 'Returns merge requests with the given source project id' + optional :target_branch, type: String, desc: 'Returns merge requests with the given target branch' + optional :search, type: String, + desc: 'Search merge requests against their `title` and `description`.' + optional :in, type: String, + desc: 'Modify the scope of the search attribute. `title`, `description`, or a string joining them with comma.', + documentation: { example: 'title,description' } + optional :wip, type: String, + values: %w[yes no], + desc: 'Filter merge requests against their `wip` status. `yes` to return only draft merge requests, `no` to return non-draft merge requests.' + optional :not, type: Hash, desc: 'Returns merge requests that do not match the parameters supplied' do + use :merge_requests_negatable_params, prefix: '`<Negated>` ' + + optional :reviewer_id, types: Integer, + desc: '`<Negated>` Returns merge requests which have the user as a reviewer with the given user `id`. `None` returns merge requests with no reviewers. `Any` returns merge requests with any reviewer. Mutually exclusive with `reviewer_username`.' mutually_exclusive :reviewer_id, :reviewer_username end - - optional :deployed_before, - 'Return merge requests deployed before the given date/time' - optional :deployed_after, - 'Return merge requests deployed after the given date/time' - optional :environment, - 'Returns merge requests deployed to the given environment' + optional :deployed_before, desc: 'Returns merge requests deployed before the given date/time. Expected in ISO 8601 format.', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :deployed_after, desc: 'Returns merge requests deployed after the given date/time. Expected in ISO 8601 format', + documentation: { example: '2019-03-15T08:00:00Z' } + optional :environment, desc: 'Returns merge requests deployed to the given environment', + documentation: { example: '2019-03-15T08:00:00Z' } end params :optional_scope_param do - 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 merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' + optional :scope, type: String, + values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], + default: 'created_by_me', + desc: 'Returns merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`' end def handle_merge_request_errors!(merge_request) diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 45671b09be9..302dac4abf7 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -9,7 +9,7 @@ module API { Issue => :team_planning, MergeRequest => :code_review, - Snippet => :snippets + Snippet => :source_code_management } end @@ -90,7 +90,12 @@ module API params = finder_params_by_noteable_type_and_id(noteable_type, noteable_id) noteable = NotesFinder.new(current_user, params).target - noteable = nil unless can?(current_user, noteable_read_ability_name(noteable), noteable) + + # Checking `read_note` permission here, because API code does not seem to use NoteFinder to find notes, + # but rather pulls notes directly through notes association, so there is no chance to check read_note + # permission at service level. With WorkItem model we need to make sure that it has WorkItem::Widgets::Note + # available in order to access notes. + noteable = nil unless can_read_notes?(noteable) noteable || not_found!(noteable_type) end @@ -147,6 +152,13 @@ module API def disable_query_limiting Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/211538') end + + private + + def can_read_notes?(noteable) + Ability.allowed?(current_user, noteable_read_ability_name(noteable), noteable) && + Ability.allowed?(current_user, :read_note, noteable) + end end end end diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index a9d91895cfe..3ea558f3569 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -47,14 +47,14 @@ module API end def recipe_upload_urls - { upload_urls: file_names.select(&method(:recipe_file?)).to_h do |file_name| - [file_name, build_recipe_file_upload_url(file_name)] + { upload_urls: file_names.select(&method(:recipe_file?)).index_with do |file_name| + build_recipe_file_upload_url(file_name) end } end def package_upload_urls - { upload_urls: file_names.select(&method(:package_file?)).to_h do |file_name| - [file_name, build_package_file_upload_url(file_name)] + { upload_urls: file_names.select(&method(:package_file?)).index_with do |file_name| + build_package_file_upload_url(file_name) end } end @@ -128,7 +128,7 @@ module API strong_memoize(:project) do case package_scope when :project - find_project!(params[:id]) + user_project(action: :read_package) when :instance full_path = ::Packages::Conan::Metadatum.full_path_from(package_username: params[:package_username]) find_project!(full_path) diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb index 1ae863a5a25..4b0e63c8f3b 100644 --- a/lib/api/helpers/packages/dependency_proxy_helpers.rb +++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb @@ -19,7 +19,9 @@ module API def redirect_registry_request(forward_to_registry: false, package_type: nil, target: nil, **options) if forward_to_registry && redirect_registry_request_available?(package_type, target) && maven_forwarding_ff_enabled?(package_type, target) ::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward") - redirect(registry_url(package_type, options)) + redirect(registry_url(package_type, options), body: options[:body]) + # For the requests with POST methods we need to set status 307 in order to keep request's method + status :temporary_redirect if options[:method] == 'POST' else yield end @@ -32,7 +34,7 @@ module API case package_type when :npm - "#{base_url}#{options[:package_name]}" + "#{base_url}#{[options[:path], options[:package_name]].compact.join('/')}" when :pypi "#{base_url}#{options[:package_name]}/" when :maven diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb index 96a10d43401..8d913268405 100644 --- a/lib/api/helpers/packages_helpers.rb +++ b/lib/api/helpers/packages_helpers.rb @@ -78,10 +78,18 @@ module API end end - def track_package_event(event_name, scope, **args) - ::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute + def track_package_event(action, scope, **args) + ::Packages::CreateEventService.new(nil, current_user, event_name: action, scope: scope).execute category = args.delete(:category) || self.options[:for].name - ::Gitlab::Tracking.event(category, event_name.to_s, **args) + event_name = "i_package_#{scope}_user" + ::Gitlab::Tracking.event( + category, + action.to_s, + property: event_name, + label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly', + context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context], + **args + ) end def present_package_file!(package_file, supports_direct_download: true) diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index c95bf0f0c21..9d370176e62 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -37,6 +37,10 @@ module API 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`' optional :releases_access_level, type: String, values: %w(disabled private enabled), desc: 'Releases access level. One of `disabled`, `private` or `enabled`' + optional :environments_access_level, type: String, values: %w(disabled private enabled), desc: 'Environments access level. One of `disabled`, `private` or `enabled`' + optional :feature_flags_access_level, type: String, values: %w(disabled private enabled), desc: 'Feature flags access level. One of `disabled`, `private` or `enabled`' + optional :infrastructure_access_level, type: String, values: %w(disabled private enabled), desc: 'Infrastructure access level. One of `disabled`, `private` or `enabled`' + optional :monitor_access_level, type: String, values: %w(disabled private enabled), desc: 'Monitor access level. One of `disabled`, `private` or `enabled`' optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis' @@ -183,6 +187,10 @@ module API :mr_default_target_self, :enforce_auth_checks_on_uploads, :releases_access_level, + :environments_access_level, + :feature_flags_access_level, + :infrastructure_access_level, + :monitor_access_level, # TODO: remove in API v5, replaced by *_access_level :issues_enabled, diff --git a/lib/api/integrations/jira_connect/subscriptions.rb b/lib/api/integrations/jira_connect/subscriptions.rb index a6e931ba7bb..cc2199e0ef6 100644 --- a/lib/api/integrations/jira_connect/subscriptions.rb +++ b/lib/api/integrations/jira_connect/subscriptions.rb @@ -11,14 +11,22 @@ module API namespace :integrations do namespace :jira_connect do resource :subscriptions do - desc 'Subscribe a namespace to a JiraConnectInstallation' + desc 'Subscribe a namespace to a JiraConnectInstallation' do + detail 'Subscribes the namespace to the JiraConnectInstallation' + success ::API::Entities::BasicSuccess + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[jira_connect_subscriptions] + end params do requires :jwt, type: String, desc: 'JWT token for authorization with the Jira Connect installation' requires :namespace_path, type: String, desc: 'Path for the namespace that should be subscribed' end post do - not_found! unless Feature.enabled?(:jira_connect_oauth, current_user) - jwt = Atlassian::JiraConnect::Jwt::Symmetric.new(params[:jwt]) installation = JiraConnectInstallation.find_by_client_key(jwt.iss_claim) diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index c4464666020..dbd5c5f9db1 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -191,7 +191,7 @@ module API get '/authorized_keys', feature_category: :source_code_management, urgency: :high do fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256 - key = Key.find_by_fingerprint_sha256(fingerprint) + key = Key.auth.find_by_fingerprint_sha256(fingerprint) not_found!('Key') if key.nil? present key, with: Entities::SSHKey end diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index d06d1e9862a..777d5019a29 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -6,7 +6,6 @@ module API class Kubernetes < ::API::Base include Gitlab::Utils::StrongMemoize - feature_category :kubernetes_management before do check_feature_enabled authenticate_gitlab_kas_request! @@ -86,7 +85,7 @@ module API detail 'Retrieves agent info for the given token' end route_setting :authentication, cluster_agent_token_allowed: true - get '/agent_info', urgency: :low do + get '/agent_info', feature_category: :kubernetes_management, urgency: :low do project = agent.project status 200 @@ -104,7 +103,7 @@ module API detail 'Retrieves project info (if authorized)' end route_setting :authentication, cluster_agent_token_allowed: true - get '/project_info', urgency: :low do + get '/project_info', feature_category: :kubernetes_management, urgency: :low do project = find_project(params[:id]) not_found! unless agent_has_access_to_project?(project) @@ -127,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 '/' do + post '/', feature_category: :kubernetes_management do agent = ::Clusters::Agent.find(params[:agent_id]) ::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute @@ -147,10 +146,10 @@ module API end optional :unique_counters, type: Hash do - optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to' + optional :agent_users_using_ci_tunnel, type: Array[Integer], desc: 'An array of user ids that have interacted with CI Tunnel' end end - post '/' do + post '/', feature_category: :kubernetes_management do increment_count_events increment_unique_events diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 276560f3433..f348e20cc0b 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -35,6 +35,11 @@ module API context[:skip_project_check] = true end + # Disable comments in markdown for IE browsers because comments in IE + # could allow script execution. + browser = Browser.new(headers['User-Agent']) + context[:allow_comments] = !browser.ie? + present({ html: Banzai.render_and_post_process(params[:text], context) }, with: Entities::Markdown) end end diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 30cdaba76ba..411a53a481b 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -107,7 +107,7 @@ module API def fetch_package(file_name:, project: nil, group: nil) order_by_package_file = file_name.include?(::Packages::Maven::Metadata.filename) && - !params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM) + !params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM) ::Packages::Maven::PackageFinder.new( current_user, @@ -150,10 +150,17 @@ module API desc 'Download the maven package file at instance level' do detail 'This feature was introduced in GitLab 11.6' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[maven_packages] end params do - requires :path, type: String, desc: 'Package path' - requires :file_name, type: String, desc: 'Package file name' + requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, type: String, desc: 'Package file name', documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do @@ -190,14 +197,24 @@ module API desc 'Download the maven package file at a group level' do detail 'This feature was introduced in GitLab 11.7' + success [ + { code: 200 }, + { code: 302 } + ] + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[maven_packages] end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do - requires :path, type: String, desc: 'Package path' - requires :file_name, type: String, desc: 'Package file name' + requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, type: String, desc: 'Package file name', documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do @@ -225,10 +242,20 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Download the maven package file' do detail 'This feature was introduced in GitLab 11.3' + success [ + { code: 200 }, + { code: 302 } + ] + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[maven_packages] end params do - requires :path, type: String, desc: 'Package path' - requires :file_name, type: String, desc: 'Package file name' + requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, type: String, desc: 'Package file name', documentation: { example: 'mypkg-1.0-SNAPSHOT.jar' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do @@ -250,10 +277,18 @@ module API desc 'Workhorse authorize the maven package file upload' do detail 'This feature was introduced in GitLab 11.3' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[maven_packages] end params do - requires :path, type: String, desc: 'Package path' - requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex + requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex, documentation: { example: 'mypkg-1.0-SNAPSHOT.pom' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do @@ -266,10 +301,19 @@ module API desc 'Upload the maven package file' do detail 'This feature was introduced in GitLab 11.3' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' }, + { code: 422, message: 'Unprocessable Entity' } + ] + tags %w[maven_packages] end params do - requires :path, type: String, desc: 'Package path' - requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex + requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } + requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex, documentation: { example: 'mypkg-1.0-SNAPSHOT.pom' } requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true diff --git a/lib/api/members.rb b/lib/api/members.rb index f4e38207aca..76f4364106b 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -20,6 +20,8 @@ module API resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Gets a list of group or project members viewable by the authenticated user.' do success Entities::Member + is_array true + tags %w[members] end params do optional :query, type: String, desc: 'A query string to search for members' @@ -42,6 +44,8 @@ module API desc 'Gets a list of group or project members viewable by the authenticated user, including those who gained membership through ancestor group.' do success Entities::Member + is_array true + tags %w[members] end params do optional :query, type: String, desc: 'A query string to search for members' @@ -63,6 +67,7 @@ module API desc 'Gets a member of a group or project.' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the member' @@ -82,6 +87,7 @@ module API desc 'Gets a member of a group or project, including those who gained membership through ancestor group' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the member' @@ -101,6 +107,7 @@ module API desc 'Adds a member to a group or project.' do success Entities::Member + tags %w[members] end params do requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)' @@ -126,6 +133,7 @@ module API desc 'Updates a member of a group or project.' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the new member' @@ -153,7 +161,9 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Removes a user from a group or project.' + desc 'Removes a user from a group or project.' do + tags %w[members] + end params do requires :user_id, type: Integer, desc: 'The user ID of the member' optional :skip_subresources, type: Boolean, default: false, diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 7622ec717cc..35fdcfe3ab0 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -88,6 +88,24 @@ module API present_approval(merge_request) end + + desc 'Remove all merge request approvals' do + detail 'Clear all approvals of merge request. This feature was added in GitLab 15.4' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] + end + put 'reset_approvals', urgency: :low do + merge_request = find_project_merge_request(params[:merge_request_iid]) + + unauthorized! unless current_user.can?(:reset_merge_request_approvals, merge_request) + + merge_request.approvals.delete_all + + status :accepted + end end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bb2861aa221..a9572cf7ce6 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -134,7 +134,13 @@ module API resource :merge_requests do desc 'List merge requests' do + detail 'Get all merge requests the authenticated user has access to. By default it returns only merge requests created by the current user. To get all merge requests, use parameter `scope=all`.' success Entities::MergeRequestBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[merge_requests] end params do use :merge_requests_params @@ -150,16 +156,24 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, type: String, desc: 'The ID or URL-encoded path of the group owned by the authenticated user.' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get a list of group merge requests' do + desc 'List group merge requests' do + detail 'Get all merge requests for this group and its subgroups.' success Entities::MergeRequestBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[merge_requests] end params do use :merge_requests_params - optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects', - default: true + optional :non_archived, type: Boolean, + default: true, + desc: 'Returns merge requests from non archived projects only.' end get ":id/merge_requests", feature_category: :code_review, urgency: :low do validate_anonymous_search_access! if declared_params[:search].present? @@ -170,36 +184,62 @@ module API end params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project.' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do include TimeTrackingEndpoints helpers do params :optional_params do - optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request' - optional :assignee_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Comma-separated list of assignee ids' - optional :reviewer_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Comma-separated list of reviewer ids' - optional :description, type: String, desc: 'The description of the merge request' - optional :labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' - optional :add_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' - optional :remove_labels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' - optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' - 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 :assignee_id, type: Integer, desc: 'Assignee user ID.' + optional :assignee_ids, type: Array[Integer], + coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, + desc: 'The IDs of the users to assign the merge request to, as a comma-separated list. Set to 0 or provide an empty value to unassign all assignees.', + documentation: { is_array: true } + optional :reviewer_ids, type: Array[Integer], + coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, + desc: 'The IDs of the users to review the merge request, as a comma-separated list. Set to 0 or provide an empty value to unassign all reviewers.', + documentation: { is_array: true } + optional :description, type: String, desc: 'Description of the merge request. Limited to 1,048,576 characters.' + optional :labels, type: Array[String], + coerce_with: Validations::Types::CommaSeparatedToArray.coerce, + desc: 'Comma-separated label names for a merge request. Set to an empty string to unassign all labels.', + documentation: { is_array: true } + optional :add_labels, type: Array[String], + coerce_with: Validations::Types::CommaSeparatedToArray.coerce, + desc: 'Comma-separated label names to add to a merge request.', + documentation: { is_array: true } + optional :remove_labels, type: Array[String], + coerce_with: Validations::Types::CommaSeparatedToArray.coerce, + desc: 'Comma-separated label names to remove from a merge request.', + documentation: { is_array: true } + optional :milestone_id, type: Integer, desc: 'The global ID of a milestone to assign the merge reques to.' + optional :remove_source_branch, type: Boolean, desc: 'Flag indicating if a merge request should remove the 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' - optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' + optional :squash, type: Grape::API::Boolean, desc: 'Squash commits into a single commit when merging.' use :optional_params_ee end end - desc 'List merge requests' do + desc 'List project merge requests' do + detail 'Get all merge requests for this project.' success Entities::MergeRequestBasic + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[merge_requests] end params do use :merge_requests_params - optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of merge requests' + + optional :iids, type: Array[Integer], + coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, + desc: 'Returns the request having the given `iid`.', + documentation: { is_array: true } end get ":id/merge_requests", feature_category: :code_review, urgency: :low do authorize! :read_merge_request, user_project @@ -226,15 +266,24 @@ module API **options end - desc 'Create a merge request' do + desc 'Create merge request' do + detail 'Create a new merge request.' + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Unprocessable entity' } + ] success Entities::MergeRequest + tags %w[merge_requests] end params do - requires :title, type: String, desc: 'The title of the merge request' - requires :source_branch, type: String, desc: 'The source branch' - requires :target_branch, type: String, desc: 'The target branch' + requires :title, type: String, desc: 'The title of the merge request.' + requires :source_branch, type: String, desc: 'The source branch.' + requires :target_branch, type: String, desc: 'The target branch.' optional :target_project_id, type: Integer, - desc: 'The target project of the merge request defaults to the :id of the project' + desc: 'The target project of the merge request defaults to the :id of the project.' use :optional_params end post ":id/merge_requests", feature_category: :code_review, urgency: :low do @@ -253,9 +302,17 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Delete a merge request' + desc 'Delete a merge request' do + detail 'Only for administrators and project owners. Deletes the merge request in question. ' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 412, message: 'Precondition failed' } + ] + tags %w[merge_requests] + end params do - requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' + requires :merge_request_iid, type: Integer, desc: 'The internal ID of the merge request.' end delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review, urgency: :low do merge_request = find_project_merge_request(params[:merge_request_iid]) @@ -268,13 +325,19 @@ module API end params do - requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request' - optional :render_html, type: Boolean, desc: 'Returns the description and title rendered HTML' - optional :include_diverged_commits_count, type: Boolean, desc: 'Returns the commits count behind the target branch' - optional :include_rebase_in_progress, type: Boolean, desc: 'Returns whether a rebase operation is ongoing ' + requires :merge_request_iid, type: Integer, desc: 'The internal ID of the merge request.' + optional :render_html, type: Boolean, desc: 'If `true`, response includes rendered HTML for title and description.' + optional :include_diverged_commits_count, type: Boolean, desc: 'If `true`, response includes the commits behind the target branch.' + optional :include_rebase_in_progress, type: Boolean, desc: 'If `true`, response includes whether a rebase operation is in progress.' end - desc 'Get a single merge request' do + desc 'Get single merge request' do + detail 'Shows information about a single merge request. Note: the `changes_count` value in the response is a string, not an integer. This is because when an merge request has too many changes to display and store, it is capped at 1,000. In that case, the API returns the string `"1000+"` for the changes count.' + success Entities::MergeRequest + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -289,8 +352,13 @@ module API include_rebase_in_progress: params[:include_rebase_in_progress] end - desc 'Get the participants of a merge request' do + desc 'Get single merge request participants' do + detail 'Get a list of merge request participants.' success Entities::UserBasic + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -300,8 +368,13 @@ module API present paginate(participants), with: Entities::UserBasic end - desc 'Get the reviewers of a merge request' do + desc 'Get single merge request reviewers' do + detail 'Get a list of merge request reviewers.' success Entities::MergeRequestReviewer + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/reviewers', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -311,8 +384,13 @@ module API present paginate(reviewers), with: Entities::MergeRequestReviewer end - desc 'Get the commits of a merge request' do + desc 'Get single merge request commits' do + detail 'Get a list of merge request commits.' success Entities::Commit + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -324,8 +402,13 @@ module API present commits, with: Entities::Commit end - desc 'Get the context commits of a merge request' do + desc 'List merge request context commits' do + detail 'Get a list of merge request context commits.' success Entities::Commit + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review, urgency: :high do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -336,10 +419,20 @@ module API end params do - requires :commits, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, allow_blank: false, desc: 'List of context commits sha' - end - desc 'create context commits of merge request' do + requires :commits, type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + allow_blank: false, + desc: 'The context commits’ SHA.', + documentation: { is_array: true } + end + desc 'Create merge request context commits' do + detail 'Create a list of merge request context commits.' success Entities::Commit + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end post ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do commit_ids = params[:commits] @@ -363,9 +456,21 @@ module API end params do - requires :commits, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, allow_blank: false, desc: 'List of context commits sha' + requires :commits, type: Array[String], + coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, + allow_blank: false, + desc: 'The context commits’ SHA.', + documentation: { is_array: true } + end + desc 'Delete merge request context commits' do + detail 'Delete a list of merge request context commits.' + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end - desc 'remove context commits of merge request' delete ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do commit_ids = params[:commits] merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -382,8 +487,13 @@ module API status 204 end - desc 'Show the merge request changes' do + desc 'Get single merge request changes' do + detail 'Shows information about the merge request including its files and changes.' success Entities::MergeRequestChanges + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review, urgency: :low do merge_request = find_merge_request_with_access(params[:merge_request_iid]) @@ -395,17 +505,46 @@ module API access_raw_diffs: to_boolean(params.fetch(:access_raw_diffs, false)) end - desc 'Get the merge request pipelines' do + desc 'Get the merge request diffs' do + detail 'Get a list of merge request diffs.' + success Entities::Diff + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] + end + params do + use :pagination + end + get ':id/merge_requests/:merge_request_iid/diffs', feature_category: :code_review, urgency: :low do + merge_request = find_merge_request_with_access(params[:merge_request_iid]) + + present paginate(merge_request.merge_request_diff.paginated_diffs(params[:page], params[:per_page])).diffs, with: Entities::Diff + end + + desc 'Get single merge request pipelines' do + detail 'Get a list of merge request pipelines.' success Entities::Ci::PipelineBasic + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end get ':id/merge_requests/:merge_request_iid/pipelines', urgency: :low, feature_category: :continuous_integration do pipelines = merge_request_pipelines_with_access - present paginate(pipelines), with: Entities::Ci::PipelineBasic end - desc 'Create a pipeline for merge request' do + desc 'Create merge request pipeline' do + detail 'Create a new pipeline for a merge request. A pipeline created via this endpoint doesn’t run a regular branch/tag pipeline. It requires `.gitlab-ci.yml` to be configured with `only: [merge_requests]` to create jobs.' success ::API::Entities::Ci::Pipeline + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 405, message: 'Method not allowed' } + ] + tags %w[merge_requests] end post ':id/merge_requests/:merge_request_iid/pipelines', urgency: :low, feature_category: :continuous_integration do pipeline = ::MergeRequests::CreatePipelineService @@ -423,15 +562,25 @@ module API end end - desc 'Update a merge request' do + desc 'Update merge request' do + detail 'Updates an existing merge request. You can change the target branch, title, or even close the merge request.' success Entities::MergeRequest + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[merge_requests] end params do - optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' - optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' - optional :state_event, type: String, values: %w[close reopen], - desc: 'Status of the merge request' - optional :discussion_locked, type: Boolean, desc: 'Whether the MR discussion is locked' + optional :title, type: String, allow_blank: false, desc: 'The title of the merge request.' + optional :target_branch, type: String, allow_blank: false, desc: 'The target branch.' + optional :state_event, type: String, + values: %w[close reopen], + desc: 'New state (close/reopen).' + optional :discussion_locked, type: Boolean, + desc: 'Flag indicating if the merge request’s discussion is locked. If the discussion is locked only project members can add, edit or resolve comments.' use :optional_params at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of) @@ -456,17 +605,27 @@ module API end desc 'Merge a merge request' do + detail 'Accept and merge changes submitted with the merge request using this API.' success Entities::MergeRequest + failure [ + { code: 400, message: 'Bad request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 405, message: 'Method not allowed' }, + { code: 409, message: 'Conflict' }, + { code: 422, message: 'Unprocessable entity' } + ] + tags %w[merge_requests] end params do - optional :merge_commit_message, type: String, desc: 'Custom merge commit message' - optional :squash_commit_message, type: String, desc: 'Custom squash commit message' + optional :merge_commit_message, type: String, desc: 'Custom merge commit message.' + optional :squash_commit_message, type: String, desc: 'Custom squash commit message.' optional :should_remove_source_branch, type: Boolean, - desc: 'When true, the source branch will be deleted if possible' + desc: 'If `true`, removes the source branch.' optional :merge_when_pipeline_succeeds, type: Boolean, - desc: 'When true, this merge request will be merged when the pipeline succeeds' - optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' - optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' + desc: 'If `true`, the merge request is merged when the pipeline succeeds.' + optional :sha, type: String, desc: 'If present, then this SHA must match the HEAD of the source branch, otherwise the merge fails.' + optional :squash, type: Grape::API::Boolean, desc: 'If `true`, the commits are squashed into a single commit on merge.' end put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review, urgency: :low do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796') @@ -512,7 +671,13 @@ module API end end - desc 'Returns the up to date merge-ref HEAD commit' + desc 'Returns the up to date merge-ref HEAD commit' do + detail 'Returns the up to date merge-ref HEAD commit' + failure [ + { code: 400, message: 'Bad request' } + ] + tags %w[merge_requests] + end get ':id/merge_requests/:merge_request_iid/merge_ref', feature_category: :code_review do merge_request = find_project_merge_request(params[:merge_request_iid]) @@ -525,8 +690,16 @@ module API end end - desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do + desc 'Cancel Merge When Pipeline Succeeds' do + detail 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' success Entities::MergeRequest + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' }, + { code: 405, message: 'Method not allowed' }, + { code: 406, message: 'Not acceptable' } + ] + tags %w[merge_requests] end post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds', feature_category: :code_review do merge_request = find_project_merge_request(params[:merge_request_iid]) @@ -536,11 +709,17 @@ module API AutoMergeService.new(merge_request.target_project, current_user).cancel(merge_request) end - desc 'Rebase the merge request against its target branch' do - detail 'This feature was added in GitLab 11.6' + desc 'Rebase a merge request' do + detail 'Automatically rebase the `source_branch` of the merge request against its `target_branch`. This feature was added in GitLab 11.6' + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] + tags %w[merge_requests] end params do - optional :skip_ci, type: Boolean, desc: 'Do not create CI pipeline' + optional :skip_ci, type: Boolean, desc: 'Set to true to skip creating a CI pipeline.' end put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review, urgency: :low do merge_request = find_project_merge_request(params[:merge_request_iid]) @@ -554,22 +733,13 @@ module API rescue ::MergeRequest::RebaseLockTimeout => e render_api_error!(e.message, 409) end - - desc 'Remove merge request approvals' do - detail 'This feature was added in GitLab 15.4' - end - put ':id/merge_requests/:merge_request_iid/reset_approvals', feature_category: :code_review, urgency: :low do - merge_request = find_project_merge_request(params[:merge_request_iid]) - - unauthorized! unless current_user.bot? && merge_request.eligible_for_approval_by?(current_user) - - merge_request.approvals.delete_all - - status :accepted - end - - desc 'List issues that will be closed on merge' do + desc 'List issues that close on merge' do + detail 'Get all the issues that would be closed by merging the provided merge request.' success Entities::MRNote + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[merge_requests] end params do use :pagination diff --git a/lib/api/ml/mlflow.rb b/lib/api/ml/mlflow.rb index 56bfac1530e..54bbe0ee465 100644 --- a/lib/api/ml/mlflow.rb +++ b/lib/api/ml/mlflow.rb @@ -126,14 +126,31 @@ module API end params do requires :name, type: String, desc: 'Experiment name' + optional :tags, type: Array, desc: 'Tags with information about the experiment' optional :artifact_location, type: String, desc: 'This will be ignored' - optional :tags, type: Array, desc: 'This will be ignored' end post 'create', urgency: :low do - present experiment_repository.create!(params[:name]), with: Entities::Ml::Mlflow::NewExperiment + present experiment_repository.create!(params[:name], params[:tags]), + with: Entities::Ml::Mlflow::NewExperiment rescue ActiveRecord::RecordInvalid resource_already_exists! end + + desc 'Sets a tag for an experiment.' do + summary 'Sets a tag for an experiment. ' + + detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#set-experiment-tag' + end + params do + requires :experiment_id, type: String, desc: 'ID of the experiment.' + requires :key, type: String, desc: 'Name for the tag.' + requires :value, type: String, desc: 'Value for the tag.' + end + post 'set-experiment-tag', urgency: :low do + bad_request! unless experiment_repository.add_tag!(experiment, params[:key], params[:value]) + + {} + end end resource :runs do @@ -148,10 +165,10 @@ module API desc: 'Unix timestamp in milliseconds of when the run started.', default: 0 optional :user_id, type: String, desc: 'This will be ignored' - optional :tags, type: Array, desc: 'This will be ignored' + optional :tags, type: Array, desc: 'Tags are stored, but not displayed' end post 'create', urgency: :low do - present candidate_repository.create!(experiment, params[:start_time]), + present candidate_repository.create!(experiment, params[:start_time], params[:tags]), with: Entities::Ml::Mlflow::Run, packages_url: packages_url end @@ -229,6 +246,22 @@ module API {} end + desc 'Sets a tag for a run.' do + summary 'Sets a tag for a run. ' + + detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#set-tag' + end + params do + requires :run_id, type: String, desc: 'UUID of the run.' + requires :key, type: String, desc: 'Name for the tag.' + requires :value, type: String, desc: 'Value for the tag.' + end + post 'set-tag', urgency: :low do + bad_request! unless candidate_repository.add_tag!(candidate, params[:key], params[:value]) + + {} + end + desc 'Logs multiple parameters and metrics.' do summary 'Log a batch of metrics and params for a run. Validation errors will block the entire batch, '\ 'duplicate errors will be ignored.' @@ -251,6 +284,7 @@ module API post 'log-batch', urgency: :low do candidate_repository.add_metrics(candidate, params[:metrics]) candidate_repository.add_params(candidate, params[:params]) + candidate_repository.add_tags(candidate, params[:tags]) {} end diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index eeb66c86b3b..2b1007e715a 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -6,6 +6,8 @@ module API before { authenticate! } + NAMESPACES_TAGS = %w[namespaces].freeze + helpers do params :optional_list_params_ee do # EE::API::Namespaces would override this helper @@ -20,12 +22,18 @@ module API prepend_mod_with('API::Namespaces') # rubocop: disable Cop/InjectEnterpriseEditionModule resource :namespaces do - desc 'Get a namespaces list' do + desc 'List namespaces' do + detail 'Get a list of the namespaces of the authenticated user. If the user is an administrator, a list of all namespaces in the GitLab instance is shown.' success Entities::Namespace + failure [ + { code: 401, message: 'Unauthorized' } + ] + is_array true + tags NAMESPACES_TAGS end params do - optional :search, type: String, desc: "Search query for namespaces" - optional :owned_only, type: Boolean, desc: "Owned namespaces only" + optional :search, type: String, desc: 'Returns a list of namespaces the user is authorized to view based on the search criteria' + optional :owned_only, type: Boolean, desc: 'In GitLab 14.2 and later, returns a list of owned namespaces only' use :pagination use :optional_list_params_ee @@ -46,11 +54,17 @@ module API present paginate(namespaces), options.reverse_merge(custom_namespace_present_options) end - desc 'Get a namespace by ID' do + desc 'Get namespace by ID' do + detail 'Get a namespace by ID' success Entities::Namespace + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags NAMESPACES_TAGS end params do - requires :id, type: String, desc: "Namespace's ID or path" + requires :id, types: [String, Integer], desc: 'ID or URL-encoded path of the namespace' end get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups, urgency: :low do user_namespace = find_namespace!(params[:id]) @@ -58,12 +72,17 @@ module API present user_namespace, with: Entities::Namespace, current_user: current_user end - desc 'Get existence of a namespace including alternative suggestions' do + desc 'Get existence of a namespace' do + detail 'Get existence of a namespace by path. Suggests a new namespace path that does not already exist.' success Entities::NamespaceExistence + failure [ + { code: 401, message: 'Unauthorized' } + ] + tags NAMESPACES_TAGS end params do - requires :namespace, 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." + requires :namespace, 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 check_rate_limit!(:namespace_exists, scope: current_user) diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb index 494b493f5e0..f42ded5ac09 100644 --- a/lib/api/npm_project_packages.rb +++ b/lib/api/npm_project_packages.rb @@ -16,6 +16,12 @@ module API namespace 'projects/:id/packages/npm' do desc 'Download the NPM tarball' do detail 'This feature was introduced in GitLab 11.8' + success code: 200 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end params do requires :package_name, type: String, desc: 'Package name' @@ -33,13 +39,21 @@ module API package_file = ::Packages::PackageFileFinder .new(package, params[:file_name]).execute! - track_package_event('pull_package', package, category: 'API::NpmPackages', project: project, namespace: project.namespace) + track_package_event('pull_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace) present_package_file!(package_file) end desc 'Create NPM package' do detail 'This feature was introduced in GitLab 11.8' + success code: 200 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[npm_packages] end params do requires :package_name, type: String, desc: 'Package name' diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb index eb55e4cbf70..c93b24ee544 100644 --- a/lib/api/nuget_group_packages.rb +++ b/lib/api/nuget_group_packages.rb @@ -45,7 +45,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + requires :id, types: [Integer, String], desc: 'The group ID or full group path.', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index d549a8be035..aa517661791 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -39,18 +39,19 @@ module API end def project_or_group - authorized_user_project + authorized_user_project(action: :read_package) end def snowplow_gitlab_standard_context - { project: authorized_user_project, namespace: authorized_user_project.namespace } + { project: project_or_group, namespace: project_or_group.namespace } end def authorize_nuget_upload + project = project_or_group authorize_workhorse!( - subject: project_or_group, + subject: project, has_length: false, - maximum_size: project_or_group.actual_limits.nuget_max_file_size + maximum_size: project.actual_limits.nuget_max_file_size ) end @@ -67,8 +68,9 @@ module API end def upload_nuget_package_file(symbol_package: false) - authorize_upload!(project_or_group) - bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size) + project = project_or_group + authorize_upload!(project) + bad_request!('File is too large') if project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size) file_params = params.merge( file: params[:package], @@ -76,7 +78,7 @@ module API ) package = ::Packages::CreateTemporaryPackageService.new( - project_or_group, current_user, declared_params.merge(build: current_authenticated_job) + project, current_user, declared_params.merge(build: current_authenticated_job) ).execute(:nuget, name: temp_file_name(symbol_package)) package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)) @@ -100,6 +102,14 @@ module API # https://docs.microsoft.com/en-us/nuget/api/package-publish-resource desc 'The NuGet Package Publish endpoint' do detail 'This feature was introduced in GitLab 12.6' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end params do @@ -121,6 +131,17 @@ module API forbidden! end + + desc 'The NuGet Package Authorize endpoint' do + detail 'This feature was introduced in GitLab 14.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end put 'authorize', urgency: :low do authorize_nuget_upload end @@ -128,8 +149,15 @@ module API # https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource desc 'The NuGet Symbol Package Publish endpoint' do detail 'This feature was introduced in GitLab 14.1' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end - params do use :file_params end @@ -149,13 +177,24 @@ module API forbidden! end + + desc 'The NuGet Symbol Package Authorize endpoint' do + detail 'This feature was introduced in GitLab 14.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end put 'symbolpackage/authorize', urgency: :low do authorize_nuget_upload end # https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource params do - requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX + requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } end namespace '/download/*package_name' do after_validation do @@ -164,6 +203,13 @@ module API desc 'The NuGet Content Service - index request' do detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackagesVersions + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end get 'index', format: :json, urgency: :low do present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])), @@ -172,10 +218,17 @@ module API desc 'The NuGet Content Service - content request' do detail 'This feature was introduced in GitLab 12.8' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX - requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX + requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.3.0.17' } + requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } end get '*package_version/*package_filename', format: [:nupkg, :snupkg], urgency: :low do filename = "#{params[:package_filename]}.#{params[:format]}" diff --git a/lib/api/pages.rb b/lib/api/pages.rb index 7e230bd3c67..0cedf7d975f 100644 --- a/lib/api/pages.rb +++ b/lib/api/pages.rb @@ -10,11 +10,18 @@ module API end params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], + desc: 'The ID or URL-encoded path of the project owned by the authenticated user' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Unpublish pages' do - detail 'This feature was introduced in GitLab 12.6' + detail 'Remove pages. The user must have administrator access. This feature was introduced in GitLab 12.6' + success code: 204 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pages] end delete ':id/pages' do authorize! :remove_pages, user_project diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index c5add42decc..e5e6ccdf025 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -20,9 +20,15 @@ module API end route_setting :authentication, job_token_allowed: true, job_token_scope: :project resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get a project container repositories' do + desc 'List container repositories within a project' do detail 'This feature was introduced in GitLab 11.8.' success Entities::ContainerRegistry::Repository + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[container_registry] end params do use :pagination @@ -41,6 +47,13 @@ module API desc 'Delete repository' do detail 'This feature was introduced in GitLab 11.8.' + success status: :accepted, message: 'Success' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[container_registry] end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' @@ -49,18 +62,20 @@ module API authorize_admin_container_image! repository.delete_scheduled! - unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker) - DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) # rubocop:disable CodeReuse/Worker - end - track_package_event('delete_repository', :container, user: current_user, project: user_project, namespace: user_project.namespace) status :accepted end - desc 'Get a list of repositories tags' do + desc 'List tags of a repository' do detail 'This feature was introduced in GitLab 11.8.' success Entities::ContainerRegistry::Tag + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[container_registry] end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' @@ -77,6 +92,13 @@ module API desc 'Delete repository tags (in bulk)' do detail 'This feature was introduced in GitLab 11.8.' + success status: :accepted, message: 'Success' + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[container_registry] end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' @@ -104,9 +126,15 @@ module API status :accepted end - desc 'Get a details about repository tag' do + desc 'Get details about a repository tag' do detail 'This feature was introduced in GitLab 11.8.' success Entities::ContainerRegistry::TagDetails + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[container_registry] end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' @@ -121,6 +149,13 @@ module API desc 'Delete repository tag' do detail 'This feature was introduced in GitLab 11.8.' + success status: :ok, message: 'Success' + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[container_registry] end params do requires :repository_id, type: Integer, desc: 'The ID of the repository' diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index d09c481403f..158ba7465f4 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -17,9 +17,15 @@ module API requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get all project packages' do + desc 'Get a list of project packages' do detail 'This feature was introduced in GitLab 11.8' - success ::API::Entities::Package + success code: 200, model: ::API::Entities::Package + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Project Not Found' } + ] + is_array true + tags %w[project_packages] end params do use :pagination @@ -48,7 +54,12 @@ module API desc 'Get a single project package' do detail 'This feature was introduced in GitLab 11.9' - success ::API::Entities::Package + success code: 200, model: ::API::Entities::Package + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[project_packages] end params do requires :package_id, type: Integer, desc: 'The ID of a package' @@ -58,11 +69,19 @@ module API package = ::Packages::PackageFinder .new(user_project, params[:package_id]).execute + render_api_error!('Package not found', 404) unless package.default? + present package, with: ::API::Entities::Package, user: current_user, namespace: user_project.namespace end - desc 'Remove a package' do + desc 'Delete a project package' do detail 'This feature was introduced in GitLab 11.9' + success code: 204 + failure [ + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[project_packages] end params do requires :package_id, type: Integer, desc: 'The ID of a package' diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 93ffb23fea8..7ef722301ca 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -6,7 +6,7 @@ module API before { check_snippets_enabled } - feature_category :snippets + feature_category :source_code_management params do requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' diff --git a/lib/api/projects.rb b/lib/api/projects.rb index fc898c30a71..de39419b70b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -207,7 +207,10 @@ module API resource :users, requirements: API::USER_REQUIREMENTS do desc 'Get a user projects' do - success Entities::BasicProjectDetails + success code: 200, model: Entities::BasicProjectDetails + failure [{ code: 404, message: '404 User Not Found' }] + tags %w[projects] + is_array true end params do requires :user_id, type: String, desc: 'The ID or username of the user' @@ -225,7 +228,10 @@ module API end desc 'Get projects starred by a user' do - success Entities::BasicProjectDetails + success code: 200, model: Entities::BasicProjectDetails + failure [{ code: 404, message: '404 User Not Found' }] + tags %w[projects] + is_array true end params do requires :user_id, type: String, desc: 'The ID or username of the user' @@ -245,7 +251,9 @@ module API include CustomAttributesEndpoints desc 'Get a list of visible projects for authenticated user' do - success Entities::BasicProjectDetails + success code: 200, model: Entities::BasicProjectDetails + tags %w[projects] + is_array true end params do use :collection_params @@ -258,12 +266,18 @@ module API end desc 'Create new project' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 400, message: 'Bad request' } + ] + tags %w[projects] end params do - optional :name, type: String, desc: 'The name of the project' - optional :path, type: String, desc: 'The path of the repository' - optional :default_branch, type: String, desc: 'The default branch of the project' + optional :name, type: String, desc: 'The name of the project', documentation: { example: 'New Project' } + optional :path, type: String, desc: 'The path of the repository', documentation: { example: 'new_project' } + optional :default_branch, type: String, desc: 'The default branch of the project', documentation: { example: 'main' } at_least_one_of :name, :path use :optional_create_project_params use :create_params @@ -295,13 +309,19 @@ module API end desc 'Create new project for a specified user. Only available to admin users.' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 400, message: 'Bad request' } + ] + tags %w[projects] end params do - requires :name, type: String, desc: 'The name of the project' - requires :user_id, type: Integer, desc: 'The ID of a user' - optional :path, type: String, desc: 'The path of the repository' - optional :default_branch, type: String, desc: 'The default branch of the project' + requires :name, type: String, desc: 'The name of the project', documentation: { example: 'New Project' } + requires :user_id, type: Integer, desc: 'The ID of a user', documentation: { example: 1 } + optional :path, type: String, desc: 'The path of the repository', documentation: { example: 'new_project' } + optional :default_branch, type: String, desc: 'The default branch of the project', documentation: { example: 'main' } use :optional_project_params use :optional_create_project_params use :create_params @@ -339,7 +359,8 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a single project' do - success Entities::ProjectWithAccess + success code: 200, model: Entities::ProjectWithAccess + tags %w[projects] end params do use :statistics_params @@ -364,15 +385,21 @@ module API end desc 'Fork new project for the current user or provided namespace.' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' }, + { code: 409, message: 'Conflict' } + ] + tags %w[projects] end params do - optional :namespace, type: String, desc: '(deprecated) The ID or name of the namespace that the project will be forked into' - optional :namespace_id, type: Integer, desc: 'The ID of the namespace that the project will be forked into' - optional :namespace_path, type: String, desc: 'The path of the namespace that the project will be forked into' - optional :path, type: String, desc: 'The path that will be assigned to the fork' - optional :name, type: String, desc: 'The name that will be assigned to the fork' - optional :description, type: String, desc: 'The description that will be assigned to the fork' + optional :namespace, type: String, desc: '(deprecated) The ID or name of the namespace that the project will be forked into', documentation: { example: 'gitlab' } + optional :namespace_id, type: Integer, desc: 'The ID of the namespace that the project will be forked into', documentation: { example: 1 } + optional :namespace_path, type: String, desc: 'The path of the namespace that the project will be forked into', documentation: { example: 'new_path/gitlab' } + optional :path, type: String, desc: 'The path that will be assigned to the fork', documentation: { example: 'fork' } + optional :name, type: String, desc: 'The name that will be assigned to the fork', documentation: { example: 'Fork' } + optional :description, type: String, desc: 'The description that will be assigned to the fork', documentation: { example: 'Description' } optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork' optional :mr_default_target_self, type: Boolean, desc: 'Merge requests of this forked project targets itself by default' end @@ -410,7 +437,9 @@ module API end desc 'List forks of this project' do - success Entities::Project + success code: 200, model: Entities::Project + tags %w[projects] + is_array true end params do use :collection_params @@ -422,19 +451,30 @@ module API present_projects forks, request_scope: user_project end - desc 'Check pages access of this project' + desc 'Check pages access of this project' do + success code: 200 + failure [ + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] + end get ':id/pages_access', urgency: :low, feature_category: :pages do authorize! :read_pages_content, user_project unless user_project.public_pages? status 200 end desc 'Update an existing project' do - success Entities::Project + success code: 200, model: Entities::Project + failure [ + { code: 400, message: 'Bad request' }, + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end params do - optional :name, type: String, desc: 'The name of the project' - optional :default_branch, type: String, desc: 'The default branch of the project' - optional :path, type: String, desc: 'The path of the repository' + optional :name, type: String, desc: 'The name of the project', documentation: { example: 'project' } + optional :default_branch, type: String, desc: 'The default branch of the project', documentation: { example: 'main' } + optional :path, type: String, desc: 'The path of the repository', documentation: { example: 'group/project' } use :optional_project_params use :optional_update_params @@ -466,7 +506,11 @@ module API end desc 'Archive a project' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end post ':id/archive', feature_category: :projects do authorize!(:archive_project, user_project) @@ -477,7 +521,11 @@ module API end desc 'Unarchive a project' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end post ':id/unarchive', feature_category: :projects, urgency: :default do authorize!(:archive_project, user_project) @@ -488,7 +536,12 @@ module API end desc 'Star a project' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 304, message: 'Not modified' }, + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end post ':id/star', feature_category: :projects do if current_user.starred?(user_project) @@ -502,7 +555,12 @@ module API end desc 'Unstar a project' do - success Entities::Project + success code: 201, model: Entities::Project + failure [ + { code: 304, message: 'Not modified' }, + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end post ':id/unstar', feature_category: :projects do if current_user.starred?(user_project) @@ -516,10 +574,16 @@ module API end desc 'Get the users who starred a project' do - success Entities::UserBasic + success code: 200, model: Entities::UserBasic + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags %w[projects] end params do - optional :search, type: String, desc: 'Return list of users matching the search criteria' + optional :search, type: String, desc: 'Return list of users matching the search criteria', documentation: { example: 'user' } use :pagination end get ':id/starrers', feature_category: :projects do @@ -528,23 +592,44 @@ module API present paginate(starrers), with: Entities::UserStarsProject end - desc 'Get languages in project repository' + desc 'Get languages in project repository' do + success code: 200 + failure [ + { code: 404, message: 'Not found' } + ] + is_array true + tags %w[projects] + end get ':id/languages', feature_category: :source_code_management, urgency: :medium do ::Projects::RepositoryLanguagesService .new(user_project, current_user) .execute.to_h { |lang| [lang.name, lang.share] } end - desc 'Delete a project' + desc 'Delete a project' do + success code: 202 + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end delete ":id", feature_category: :projects do authorize! :remove_project, user_project delete_project(user_project) end - desc 'Mark this project as forked from another' + desc 'Mark this project as forked from another' do + success code: 201, model: Entities::Project + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end params do - requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from', documentation: { example: 'gitlab' } end post ":id/fork/:forked_from_id", feature_category: :source_code_management do authorize! :admin_project, user_project @@ -559,12 +644,20 @@ module API if result present_project user_project.reset, with: Entities::Project, current_user: current_user - else - render_api_error!("Project already forked", 409) if user_project.forked? + elsif user_project.forked? + render_api_error!("Project already forked", 409) end end - desc 'Remove a forked_from relationship' + desc 'Remove a forked_from relationship' do + success code: 204 + failure [ + { code: 304, message: 'Not modified' }, + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end delete ":id/fork", feature_category: :source_code_management do authorize! :remove_fork_project, user_project @@ -576,10 +669,16 @@ module API end desc 'Share the project with a group' do - success Entities::ProjectGroupLink + success code: 201, model: Entities::ProjectGroupLink + failure [ + { code: 400, message: 'Bad request' }, + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] end params do - requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_id, type: Integer, desc: 'The ID of a group', documentation: { example: 1 } requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level' optional :expires_at, type: Date, desc: 'Share expiration date' end @@ -601,6 +700,14 @@ module API end end + desc 'Remove a group share' do + success code: 204 + failure [ + { code: 400, message: 'Bad request' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end params do requires :group_id, type: Integer, desc: 'The ID of the group' end @@ -619,6 +726,12 @@ module API desc 'Import members from another project' do detail 'This feature was introduced in GitLab 14.2' + success code: 201 + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] end params do requires :project_id, type: Integer, desc: 'The ID of the source project to import the members from.' @@ -642,6 +755,11 @@ module API desc 'Workhorse authorize the file upload' do detail 'This feature was introduced in GitLab 13.11' + success code: 200 + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[projects] end post ':id/uploads/authorize', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned require_gitlab_workhorse! @@ -651,7 +769,13 @@ module API FileUploader.workhorse_authorize(has_length: false, maximum_size: project_attachment_size(user_project)) end - desc 'Upload a file' + desc 'Upload a file' do + success code: 201, model: Entities::ProjectUpload + failure [ + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end params do requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded', documentation: { type: 'file' } end @@ -666,10 +790,16 @@ module API end desc 'Get the users list of a project' do - success Entities::UserBasic + success code: 200, model: Entities::UserBasic + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags %w[projects] end params do - optional :search, type: String, desc: 'Return list of users matching the search criteria' + optional :search, type: String, desc: 'Return list of users matching the search criteria', documentation: { example: 'user' } optional :skip_users, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Filter out users with the specified IDs' use :pagination end @@ -683,10 +813,16 @@ module API end desc 'Get ancestor and shared groups for a project' do - success Entities::PublicGroupDetails + success code: 200, model: Entities::PublicGroupDetails + failure [ + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + is_array true + tags %w[projects] end params do - optional :search, type: String, desc: 'Return list of groups matching the search criteria' + optional :search, type: String, desc: 'Return list of groups matching the search criteria', documentation: { example: 'group' } optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list' optional :with_shared, type: Boolean, default: false, desc: 'Include shared groups' @@ -705,6 +841,13 @@ module API desc 'Start the housekeeping task for a project' do detail 'This feature was introduced in GitLab 9.0.' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Unauthenticated' }, + { code: 409, message: 'Conflict' } + ] + tags %w[projects] end post ':id/housekeeping', feature_category: :source_code_management do authorize_admin_project @@ -718,6 +861,12 @@ module API desc 'Start a task to recalculate repository size for a project' do detail 'This feature was introduced in GitLab 15.0.' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end post ':id/repository_size', feature_category: :source_code_management do authorize_admin_project @@ -727,9 +876,17 @@ module API ::Projects::UpdateStatisticsService.new(user_project, nil, statistics: [:repository_size, :lfs_objects_size]).execute end - desc 'Transfer a project to a new namespace' + desc 'Transfer a project to a new namespace' do + success code: 200, model: Entities::Project + failure [ + { code: 400, message: 'Bad request' }, + { code: 403, message: 'Unauthenticated' }, + { code: 404, message: 'Not found' } + ] + tags %w[projects] + end params do - requires :namespace, type: String, desc: 'The ID or path of the new namespace' + requires :namespace, type: String, desc: 'The ID or path of the new namespace', documentation: { example: 'gitlab' } end put ":id/transfer", feature_category: :projects do authorize! :change_namespace, user_project @@ -744,9 +901,16 @@ module API end end - desc 'Get the namespaces to where the project can be transferred' + desc 'Get the namespaces to where the project can be transferred' do + success code: 200, model: Entities::PublicGroupDetails + failure [ + { code: 403, message: 'Unauthenticated' } + ] + is_array true + tags %w[projects] + end params do - optional :search, type: String, desc: 'Return list of namespaces matching the search criteria' + optional :search, type: String, desc: 'Return list of namespaces matching the search criteria', documentation: { example: 'search' } use :pagination end get ":id/transfer_locations", feature_category: :projects do @@ -761,7 +925,11 @@ module API end desc 'Show the storage information' do - success Entities::ProjectRepositoryStorage + success code: 200, model: Entities::ProjectRepositoryStorage + failure [ + { code: 403, message: 'Unauthenticated' } + ] + tags %w[projects] end params do requires :id, type: String, desc: 'ID of a project' diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 6c649483da1..f9470ce1cb6 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -32,12 +32,12 @@ module API helpers do params :package_download do - requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true - requires :sha256, type: String, desc: 'The PyPi package sha256 check sum' + requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true, documentation: { example: 'my.pypi.package-0.0.1.tar.gz' } + requires :sha256, type: String, desc: 'The PyPi package sha256 check sum', documentation: { example: '5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff' } end params :package_name do - requires :package_name, type: String, file_path: true, desc: 'The PyPi package name' + requires :package_name, type: String, file_path: true, desc: 'The PyPi package name', documentation: { example: 'my.pypi.package' } end def present_simple_index(group_or_project) @@ -102,7 +102,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [Integer, String], desc: 'The ID or full path of the group.' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do after_validation do @@ -110,6 +110,16 @@ module API end namespace ':id/-/packages/pypi' do + desc 'Download a package file from a group' do + detail 'This feature was introduced in GitLab 13.12' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] + end params do use :package_download end @@ -123,13 +133,20 @@ module API package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute - track_package_event('pull_package', :pypi) + track_package_event('pull_package', :pypi, namespace: group, project: package.project) present_package_file!(package_file, supports_direct_download: true) end desc 'The PyPi Simple Group Index Endpoint' do detail 'This feature was introduced in GitLab 15.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end # An API entry point but returns an HTML file instead of JSON. @@ -141,6 +158,13 @@ module API desc 'The PyPi Simple Group Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -164,6 +188,13 @@ module API namespace ':id/packages/pypi' do desc 'The PyPi package download endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -185,6 +216,13 @@ module API desc 'The PyPi Simple Project Index Endpoint' do detail 'This feature was introduced in GitLab 15.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end # An API entry point but returns an HTML file instead of JSON. @@ -196,6 +234,13 @@ module API desc 'The PyPi Simple Project Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -211,15 +256,24 @@ module API desc 'The PyPi Package upload endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' }, + { code: 422, message: 'Unprocessable Entity' } + ] + tags %w[pypi_packages] end params do requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } - requires :name, type: String - requires :version, type: String - optional :requires_python, type: String - optional :md5_digest, type: String - optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex + requires :name, type: String, documentation: { example: 'my.pypi.package' } + requires :version, type: String, documentation: { example: '1.3.7' } + optional :requires_python, type: String, documentation: { example: '>=3.7' } + optional :md5_digest, type: String, documentation: { example: '900150983cd24fb0d6963f7d28e17f72' } + optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex, documentation: { example: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' } end route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth @@ -243,6 +297,17 @@ module API forbidden! end + desc 'Authorize the PyPi package upload from workhorse' do + detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] + end + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post 'authorize' do project = project!(action: :read_project) diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index c72f90dfdf3..0e83d086a6e 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -10,13 +10,13 @@ module API RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) - before { authorize! :read_release, user_project } + after_validation { authorize! :read_release, user_project } feature_category :release_orchestration urgency :low params do - requires :id, type: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' end resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do diff --git a/lib/api/rpm_project_packages.rb b/lib/api/rpm_project_packages.rb index 40b8d022c6c..f02d288982a 100644 --- a/lib/api/rpm_project_packages.rb +++ b/lib/api/rpm_project_packages.rb @@ -25,7 +25,16 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/rpm' do - desc 'Download repository metadata files' + desc 'Download repository metadata files' do + detail 'This feature was introduced in GitLab 15.7' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rpm_packages] + end params do requires :file_name, type: String, desc: 'Repository metadata file name' end @@ -40,7 +49,15 @@ module API present_carrierwave_file!(repository_file.file) end - desc 'Download RPM package files' + desc 'Download RPM package files' do + detail 'This feature was introduced in GitLab 15.7' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rpm_packages] + end params do requires :package_file_id, type: Integer, desc: 'RPM package file id' requires :file_name, type: String, desc: 'RPM package file name' @@ -56,7 +73,16 @@ module API not_found! end - desc 'Upload a RPM package' + desc 'Upload a RPM package' do + detail 'This feature was introduced in GitLab 15.7' + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rpm_packages] + end post do authorize_create_package!(authorized_user_project) @@ -64,6 +90,10 @@ module API bad_request!('File is too large') end + if Packages::Rpm::RepositoryFile.has_oversized_filelists?(project_id: authorized_user_project.id) + bad_request!('Repository packages limit exceeded') + end + track_package_event( 'push_package', :rpm, @@ -76,7 +106,15 @@ module API not_found! end - desc 'Authorize package upload from workhorse' + desc 'Authorize package upload from workhorse' do + detail 'This feature was introduced in GitLab 15.7' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rpm_packages] + end post 'authorize' do not_found! end diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb index 87cf1f66223..af0ceb1acfc 100644 --- a/lib/api/rubygem_packages.rb +++ b/lib/api/rubygem_packages.rb @@ -28,19 +28,27 @@ module API before do require_packages_enabled! authenticate_non_get! + end + + after_validation do not_found! unless Feature.enabled?(:rubygem_packages, user_project) end params do - requires :id, type: String, desc: 'The ID or full path of a project' + requires :id, types: [Integer, String], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/rubygems' do desc 'Download the spec index file' do detail 'This feature was introduced in GitLab 13.9' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rubygem_packages] end params do - requires :file_name, type: String, desc: 'Spec file name' + requires :file_name, type: String, desc: 'Spec file name', documentation: { type: 'file' } end get ":file_name", requirements: FILE_NAME_REQUIREMENTS do # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299267 @@ -49,9 +57,14 @@ module API desc 'Download the gemspec file' do detail 'This feature was introduced in GitLab 13.9' + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rubygem_packages] end params do - requires :file_name, type: String, desc: 'Gemspec file name' + requires :file_name, type: String, desc: 'Gemspec file name', documentation: { type: 'file' } end get "quick/Marshal.#{MARSHAL_VERSION}/:file_name", requirements: FILE_NAME_REQUIREMENTS do # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299284 @@ -60,9 +73,16 @@ module API desc 'Download the .gem package' do detail 'This feature was introduced in GitLab 13.9' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rubygem_packages] end params do - requires :file_name, type: String, desc: 'Package file name' + requires :file_name, type: String, desc: 'Package file name', documentation: { type: 'file' } end get "gems/:file_name", requirements: FILE_NAME_REQUIREMENTS do authorize_read_package!(user_project) @@ -80,6 +100,12 @@ module API namespace 'api/v1' do desc 'Authorize a gem upload from workhorse' do detail 'This feature was introduced in GitLab 13.9' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' } + ] + tags %w[rubygem_packages] end post 'gems/authorize' do authorize_workhorse!( @@ -91,6 +117,13 @@ module API desc 'Upload a gem' do detail 'This feature was introduced in GitLab 13.9' + success code: 201 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[rubygem_packages] end params do requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } @@ -133,6 +166,14 @@ module API desc 'Fetch a list of dependencies' do detail 'This feature was introduced in GitLab 13.9' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + is_array true + tags %w[rubygem_packages] end params do optional :gems, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma delimited gem names' diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 26b7e58bc7a..8b47604fe86 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -66,6 +66,7 @@ module API requires :eks_secret_access_key, type: String, desc: 'Secret access key for the EKS integration IAM user' end optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.' + optional :email_confirmation_setting, type: String, values: ApplicationSetting.email_confirmation_settings.keys, desc: "Email confirmation setting, possible values: `off`, `soft`, and `hard`" optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.' optional :gitpod_enabled, type: Boolean, desc: 'Enable Gitpod' given gitpod_enabled: ->(val) { val } do @@ -90,7 +91,7 @@ module API end optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.' optional :import_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, - values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator], + values: %w[github bitbucket bitbucket_server gitlab fogbugz git gitlab_project gitea manifest phabricator], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' optional :in_product_marketing_emails_enabled, type: Boolean, desc: 'By default, in-product marketing emails are enabled. To disable these emails, disable this option.' optional :invisible_captcha_enabled, type: Boolean, desc: 'Enable Invisible Captcha spam detection during signup.' @@ -100,6 +101,7 @@ module API optional :max_import_size, type: Integer, desc: 'Maximum import size in MB' optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB' optional :max_pages_custom_domains_per_project, type: Integer, desc: 'Maximum number of GitLab Pages custom domains per project' + optional :max_terraform_state_size_bytes, type: Integer, desc: "Maximum size in bytes of the Terraform state file. Set this to 0 for unlimited file size." 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' @@ -139,7 +141,6 @@ module API requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' end optional :restricted_visibility_levels, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' - optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up' optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.' optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects' given shared_runners_enabled: ->(val) { val } do @@ -184,6 +185,9 @@ 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 :bulk_import_enabled, type: Boolean, desc: 'Enable migrating GitLab groups and projects by direct transfer' Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 36698a220bd..104848206a3 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -5,7 +5,7 @@ module API class Snippets < ::API::Base include PaginationParams - feature_category :snippets + feature_category :source_code_management urgency :low resource :snippets do diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb index 16861a146ae..7a4e6f3e14c 100644 --- a/lib/api/support/git_access_actor.rb +++ b/lib/api/support/git_access_actor.rb @@ -16,7 +16,7 @@ module API def self.from_params(params) if params[:key_id] - new(key: Key.find_by_id(params[:key_id])) + new(key: Key.auth.find_by_id(params[:key_id])) elsif params[:user_id] new(user: UserFinder.new(params[:user_id]).find_by_id) elsif params[:username] diff --git a/lib/api/tags.rb b/lib/api/tags.rb index b412a17bc6f..4ddf22c726f 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -129,6 +129,24 @@ module API end end end + + desc "Get a tag's signature" do + success code: 200, model: Entities::TagSignature + tags %w[tags] + failure [ + { code: 404, message: 'Not found' } + ] + end + params do + requires :tag_name, type: String, desc: 'The name of the tag' + end + get ':id/repository/tags/:tag_name/signature', requirements: TAG_ENDPOINT_REQUIREMENTS, feature_category: :source_code_management do + tag = user_project.repository.find_tag(params[:tag_name]) + not_found! 'Tag' unless tag + not_found! 'Signature' unless tag.has_signature? + + present tag, with: Entities::TagSignature + end end end end diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index 577d011ebad..bdc9f975970 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -20,6 +20,8 @@ module API render_api_error!(e.message, 422) end + STATE_NAME_URI_REQUIREMENTS = { name: API::NO_SLASH_URL_PART_REGEX }.freeze + before do authenticate! authorize! :read_terraform_state, user_project @@ -45,7 +47,7 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - namespace ':id/terraform/state/:name' do + namespace ':id/terraform/state/:name', requirements: STATE_NAME_URI_REQUIREMENTS do params do requires :name, type: String, desc: 'The name of a Terraform state' optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID' @@ -55,6 +57,17 @@ module API def remote_state_handler ::Terraform::RemoteStateHandler.new(user_project, current_user, name: params[:name], lock_id: params[:ID]) end + + def not_found_for_dots? + Feature.disabled?(:allow_dots_on_tf_state_names) && params[:name].include?(".") + end + + # Change the state name to behave like before, https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105674 + # has been introduced. This behavior can be controlled via `allow_dots_on_tf_state_names` FF. + # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106861 + def legacy_state_name! + params[:name] = params[:name].split('.').first + end end desc 'Get a Terraform state by its name' do @@ -72,6 +85,8 @@ module API end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get do + legacy_state_name! if not_found_for_dots? + remote_state_handler.find_with_lock do |state| no_content! unless state.latest_file && state.latest_file.exists? @@ -88,17 +103,22 @@ module API ] failure [ { code: 403, message: 'Forbidden' }, - { code: 422, message: 'Validation failure' } + { code: 422, message: 'Validation failure' }, + { code: 413, message: 'Request Entity Too Large' } ] tags %w[terraform_state] end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post do authorize! :admin_terraform_state, user_project + legacy_state_name! if not_found_for_dots? data = request.body.read no_content! if data.empty? + max_state_size = Gitlab::CurrentSettings.max_terraform_state_size_bytes + file_too_large! if max_state_size > 0 && data.size > max_state_size + remote_state_handler.handle_with_lock do |state| state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial], build: current_authenticated_job) end @@ -120,6 +140,7 @@ module API route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth delete do authorize! :admin_terraform_state, user_project + legacy_state_name! if not_found_for_dots? remote_state_handler.find_with_lock do |state| ::Terraform::States::TriggerDestroyService.new(state, current_user: current_user).execute @@ -151,6 +172,8 @@ module API requires :Path, type: String, desc: 'Terraform path' end post '/lock' do + not_found! if not_found_for_dots? + authorize! :admin_terraform_state, user_project status_code = :ok @@ -194,6 +217,8 @@ module API optional :ID, type: String, limit: 255, desc: 'Terraform state lock ID' end delete '/lock' do + not_found! if not_found_for_dots? + authorize! :admin_terraform_state, user_project remote_state_handler.unlock! diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb index b8323304957..dd8ad2cc144 100644 --- a/lib/api/time_tracking_endpoints.rb +++ b/lib/api/time_tracking_endpoints.rb @@ -23,14 +23,12 @@ module API end def load_issuable - @issuable ||= begin - case issuable_name - when 'issue' - find_project_issue(params.delete(issuable_key)) - when 'merge_request' - find_project_merge_request(params.delete(issuable_key)) - end - end + @issuable ||= case issuable_name + when 'issue' + find_project_issue(params.delete(issuable_key)) + when 'merge_request' + find_project_merge_request(params.delete(issuable_key)) + end end def update_issuable(attrs) @@ -54,10 +52,18 @@ module API issuable_collection_name = issuable_name.pluralize issuable_key = "#{issuable_name}_iid".to_sym - desc "Set a time estimate for a project #{issuable_name}" + desc "Set a time estimate for a #{issuable_name}" do + detail " Sets an estimated time of work for this #{issuable_name}." + success Entities::IssuableTimeStats + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags [issuable_collection_name] + end params do - requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}" - requires :duration, type: String, desc: 'The duration to be parsed' + requires issuable_key, type: Integer, desc: "The internal ID of the #{issuable_name}." + requires :duration, type: String, desc: 'The duration in human format.', documentation: { example: '3h30m' } end post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do authorize! admin_issuable_key, load_issuable @@ -66,9 +72,17 @@ module API update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration))) end - desc "Reset the time estimate for a project #{issuable_name}" + desc "Reset the time estimate for a project #{issuable_name}" do + detail "Resets the estimated time for this #{issuable_name} to 0 seconds." + success Entities::IssuableTimeStats + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags [issuable_collection_name] + end params do - requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}" + requires issuable_key, type: Integer, desc: "The internal ID of the #{issuable_name}." end post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_time_estimate" do authorize! admin_issuable_key, load_issuable @@ -77,10 +91,18 @@ module API update_issuable(time_estimate: 0) end - desc "Add spent time for a project #{issuable_name}" + desc "Add spent time for a #{issuable_name}" do + detail "Adds spent time for this #{issuable_name}." + success Entities::IssuableTimeStats + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags [issuable_collection_name] + end params do - requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}" - requires :duration, type: String, desc: 'The duration to be parsed' + requires issuable_key, type: Integer, desc: "The internal ID of the #{issuable_name}." + requires :duration, type: String, desc: 'The duration in human format.' end post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do authorize! admin_issuable_key, load_issuable @@ -97,9 +119,17 @@ module API update_issuable(update_params) end - desc "Reset spent time for a project #{issuable_name}" + desc "Reset spent time for a #{issuable_name}" do + detail "Resets the total spent time for this #{issuable_name} to 0 seconds." + success Entities::IssuableTimeStats + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags [issuable_collection_name] + end params do - requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}" + requires issuable_key, type: Integer, desc: "The internal ID of the #{issuable_name}" end post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_spent_time" do authorize! admin_issuable_key, load_issuable @@ -108,9 +138,17 @@ module API update_issuable(spend_time: { duration: :reset, user_id: current_user.id }) end - desc "Show time stats for a project #{issuable_name}" + desc "Get time tracking stats" do + detail "Get time tracking stats" + success Entities::IssuableTimeStats + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Not found' } + ] + tags [issuable_collection_name] + end params do - requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}" + requires issuable_key, type: Integer, desc: "The internal ID of the #{issuable_name}" end get ":id/#{issuable_collection_name}/:#{issuable_key}/time_stats" do authorize! read_issuable_key, load_issuable diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb index 38ce4bd7f32..b2133dc743a 100644 --- a/lib/api/unleash.rb +++ b/lib/api/unleash.rb @@ -63,13 +63,9 @@ module API cache_context: -> (client) { client.unleash_api_cache_key } end - def project - @project ||= find_project(params[:project_id]) - end - def feature_flags_client strong_memoize(:feature_flags_client) do - client = Operations::FeatureFlagsClient.find_for_project_and_token(project, unleash_instance_id) + client = Operations::FeatureFlagsClient.find_for_project_and_token(params[:project_id], unleash_instance_id) client.unleash_app_name = unleash_app_name if client client end @@ -86,12 +82,6 @@ module API def authorize_by_unleash_instance_id! unauthorized! unless feature_flags_client end - - def feature_flags - return [] unless unleash_app_name.present? - - Operations::FeatureFlag.for_unleash_client(project, unleash_app_name) - end end end end diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb index 9e446aff605..3e2023d769f 100644 --- a/lib/api/usage_data.rb +++ b/lib/api/usage_data.rb @@ -12,11 +12,18 @@ module API forbidden!('Invalid CSRF token is provided') unless verified_request? end - desc 'Track usage data events' do + desc 'Track usage data event' do detail 'This feature was introduced in GitLab 13.4.' + success code: 200 + failure [ + { code: 403, message: 'Invalid CSRF token is provided' }, + { code: 404, message: 'Not found' } + ] + tags %w[usage_data] end params do - requires :event, type: String, desc: 'The event name that should be tracked' + requires :event, type: String, desc: 'The event name that should be tracked', + documentation: { example: 'i_quickactions_page' } end post 'increment_counter' do event_name = params[:event] @@ -26,8 +33,17 @@ module API status :ok end + desc 'Track usage data event for the current user' do + success code: 200 + failure [ + { code: 403, message: 'Invalid CSRF token is provided' }, + { code: 404, message: 'Not found' } + ] + tags %w[usage_data] + end params do - requires :event, type: String, desc: 'The event name that should be tracked' + requires :event, type: String, desc: 'The event name that should be tracked', + documentation: { example: 'i_quickactions_page' } end post 'increment_unique_users', urgency: :low do event_name = params[:event] @@ -39,6 +55,13 @@ module API desc 'Get a list of all metric definitions' do detail 'This feature was introduced in GitLab 13.11.' + success code: 200 + failure [ + { code: 403, message: 'Invalid CSRF token is provided' }, + { code: 404, message: 'Not found' } + ] + produces ['application/yaml'] + tags %w[usage_data metrics] end get 'metric_definitions', urgency: :low do content_type 'application/yaml' diff --git a/lib/api/usage_data_non_sql_metrics.rb b/lib/api/usage_data_non_sql_metrics.rb index 41f369a43b8..81f96a7958b 100644 --- a/lib/api/usage_data_non_sql_metrics.rb +++ b/lib/api/usage_data_non_sql_metrics.rb @@ -14,6 +14,12 @@ module API desc 'Get Non SQL usage ping metrics' do detail 'This feature was introduced in GitLab 13.11.' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] end get 'non_sql_metrics' do diff --git a/lib/api/usage_data_queries.rb b/lib/api/usage_data_queries.rb index fe972942111..8e85fca4ba9 100644 --- a/lib/api/usage_data_queries.rb +++ b/lib/api/usage_data_queries.rb @@ -14,6 +14,12 @@ module API desc 'Get raw SQL queries for usage data SQL metrics' do detail 'This feature was introduced in GitLab 13.11.' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] end get 'queries' do diff --git a/lib/api/users.rb b/lib/api/users.rb index 72c121bca03..d2d45c94291 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -133,7 +133,7 @@ module API get feature_category: :users, urgency: :low do authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present? - unless current_user&.admin? + unless current_user&.can_read_all_resources? params.except!(:created_after, :created_before, :order_by, :sort, :two_factor, :without_projects) end @@ -151,7 +151,7 @@ module API users = UsersFinder.new(current_user, params).execute users = reorder_users(users) - entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic + entity = current_user&.can_read_all_resources? ? Entities::UserWithAdmin : Entities::UserBasic if entity == Entities::UserWithAdmin users = users.preload(:identities, :u2f_registrations, :webauthn_registrations, :namespace, :followers, :followees, :user_preference) @@ -177,7 +177,7 @@ module API get ":id", feature_category: :users, urgency: :low do forbidden!('Not authorized!') unless current_user - unless current_user.admin? + unless current_user.can_read_all_resources? check_rate_limit!(:users_get_by_id, scope: current_user, users_allowlist: Gitlab::CurrentSettings.current_application_settings.users_get_by_id_limit_allowlist @@ -188,7 +188,7 @@ module API not_found!('User') unless user && can?(current_user, :read_user, user) - opts = { with: current_user.admin? ? Entities::UserDetailsWithAdmin : Entities::User, current_user: current_user } + opts = { with: current_user.can_read_all_resources? ? Entities::UserDetailsWithAdmin : Entities::User, current_user: current_user } user, opts = with_custom_attributes(user, opts) present user, opts @@ -333,12 +333,12 @@ module API not_found!('User') unless user conflict!('Email has already been taken') if params[:email] && - User.by_any_email(params[:email].downcase) - .where.not(id: user.id).exists? + User.by_any_email(params[:email].downcase) + .where.not(id: user.id).exists? conflict!('Username has already been taken') if params[:username] && - User.by_username(params[:username]) - .where.not(id: user.id).exists? + User.by_username(params[:username]) + .where.not(id: user.id).exists? user_params = declared_params(include_missing: false) admin_making_changes_for_another_user = (current_user != user) @@ -373,7 +373,8 @@ module API user = User.find_by_id(params[:id]) not_found!('User') unless user - forbidden!('Two-factor authentication for admins cannot be disabled via the API. Use the Rails console') if user.admin? + # We're disabling Cop/UserAdmin because it checks if the given user (not the current user) is an admin. + forbidden!('Two-factor authentication for admins cannot be disabled via the API. Use the Rails console') if user.admin? # rubocop:disable Cop/UserAdmin result = TwoFactor::DestroyService.new(current_user, user: user).execute @@ -437,6 +438,8 @@ module API requires :key, type: String, desc: 'The new SSH key' requires :title, type: String, desc: 'The title of the new SSH key' optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' + optional :usage_type, type: String, values: Key.usage_types.keys, default: 'auth_and_signing', + desc: 'Scope of usage for the SSH key' end # rubocop: disable CodeReuse/ActiveRecord post ":user_id/keys", feature_category: :authentication_and_authorization do @@ -1006,7 +1009,8 @@ module API end get feature_category: :users, urgency: :low do entity = - if current_user.admin? + # We're disabling Cop/UserAdmin because it checks if the given user is an admin. + if current_user.admin? # rubocop:disable Cop/UserAdmin Entities::UserWithAdmin else Entities::UserPublic @@ -1050,6 +1054,8 @@ module API requires :key, type: String, desc: 'The new SSH key' requires :title, type: String, desc: 'The title of the new SSH key' optional :expires_at, type: DateTime, desc: 'The expiration date of the SSH key in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ)' + optional :usage_type, type: String, values: Key.usage_types.keys, default: 'auth_and_signing', + desc: 'Scope of usage for the SSH key' end post "keys", feature_category: :authentication_and_authorization do key = ::Keys::CreateService.new(current_user, declared_params(include_missing: false)).execute diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index e4a26838746..db71e823b1d 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -78,13 +78,13 @@ module API # rubocop: enable CodeReuse/ActiveRecord def authorized_merge_requests - MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?) + MergeRequestsFinder.new(current_user, authorized_only: !current_user.can_read_all_resources?) .execute.with_jira_integration_associations end def authorized_merge_requests_for_project(project) MergeRequestsFinder - .new(current_user, authorized_only: !current_user.admin?, project_id: project.id) + .new(current_user, authorized_only: !current_user.can_read_all_resources?, project_id: project.id) .execute.with_jira_integration_associations end diff --git a/lib/api/validations/validators/array_none_any.rb b/lib/api/validations/validators/array_none_any.rb index 3732c1f575c..8c064eefbf2 100644 --- a/lib/api/validations/validators/array_none_any.rb +++ b/lib/api/validations/validators/array_none_any.rb @@ -8,7 +8,7 @@ module API value = params[attr_name] return if value.is_a?(Array) || - [IssuableFinder::Params::FILTER_NONE, IssuableFinder::Params::FILTER_ANY].include?(value.to_s.downcase) + [IssuableFinder::Params::FILTER_NONE, IssuableFinder::Params::FILTER_ANY].include?(value.to_s.downcase) raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], |