diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /lib/api | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'lib/api')
47 files changed, 502 insertions, 141 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index ada0da28749..0598f03c7ab 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -30,7 +30,7 @@ module API ] allow_access_with_scope :api - allow_access_with_scope :read_api, if: -> (request) { request.get? } + allow_access_with_scope :read_api, if: -> (request) { request.get? || request.head? } prefix :api version 'v3', using: :path do @@ -68,6 +68,10 @@ module API set_peek_enabled_for_current_request end + after do + Gitlab::UsageDataCounters::VSCodeExtensionActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user) + end + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } @@ -123,13 +127,32 @@ module API format :json formatter :json, Gitlab::Json::GrapeFormatter + content_type :json, 'application/json' + # Remove the `text/plain+deprecated` with `api_always_use_application_json` feature flag # There is a small chance some users depend on the old behavior. # We this change under a feature flag to see if affects GitLab.com users. - if Gitlab::Database.cached_table_exists?('features') && Feature.enabled?(:api_json_content_type) - content_type :json, 'application/json' - else - content_type :txt, 'text/plain' + # The `+deprecated` is added to distinguish content type + # as defined by `API::API` vs ex. `API::Repositories` + content_type :txt, 'text/plain+deprecated' + + before do + # the feature flag workaround is only for `.txt` + api_format = env[Grape::Env::API_FORMAT] + next unless api_format == :txt + + # get all defined content-types for the endpoint + api_endpoint = env[Grape::Env::API_ENDPOINT] + content_types = api_endpoint&.namespace_stackable_with_hash(:content_types).to_h + + # Only overwrite `text/plain+deprecated` + if content_types[api_format] == 'text/plain+deprecated' + if Feature.enabled?(:api_always_use_application_json) + content_type 'application/json' + else + content_type 'text/plain' + end + end end # Ensure the namespace is right, otherwise we might load Grape::API::Helpers @@ -249,6 +272,8 @@ module API mount ::API::Release::Links mount ::API::RemoteMirrors mount ::API::Repositories + mount ::API::ResourceAccessTokens + mount ::API::RubygemPackages mount ::API::Search mount ::API::Services mount ::API::Settings @@ -294,4 +319,4 @@ module API end end -API::API.prepend_if_ee('::EE::API::API') +API::API.prepend_ee_mod diff --git a/lib/api/applications.rb b/lib/api/applications.rb index 8b14e16b495..b883f83cc19 100644 --- a/lib/api/applications.rb +++ b/lib/api/applications.rb @@ -8,15 +8,6 @@ module API feature_category :authentication_and_authorization resource :applications do - helpers do - def validate_redirect_uri(value) - uri = ::URI.parse(value) - !uri.is_a?(URI::HTTP) || uri.host - rescue URI::InvalidURIError - false - end - end - desc 'Create a new application' do detail 'This feature was introduced in GitLab 10.5' success Entities::ApplicationWithSecret @@ -30,13 +21,6 @@ module API desc: 'Application will be used where the client secret is confidential' end post do - # Validate that host in uri is specified - # Please remove it when https://github.com/doorkeeper-gem/doorkeeper/pull/1440 is merged - # and the doorkeeper gem version is bumped - unless validate_redirect_uri(declared_params[:redirect_uri]) - render_api_error!({ redirect_uri: ["must be an absolute URI."] }, :bad_request) - end - application = Doorkeeper::Application.new(declared_params) if application.save diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 1b36e27f6c9..fa75d012613 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -121,6 +121,7 @@ module API end params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + optional :include_retried, type: Boolean, default: false, desc: 'Includes retried jobs' use :optional_scope use :pagination end diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb index 6c8b3a1ba4a..1796d51324f 100644 --- a/lib/api/concerns/packages/conan_endpoints.rb +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -72,6 +72,7 @@ module API namespace 'users' do format :txt + content_type :txt, 'text/plain' desc 'Authenticate user against conan CLI' do detail 'This feature was introduced in GitLab 12.2' diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index e3cacc4132f..f138f400601 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -8,12 +8,14 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do + require_packages_enabled! + not_found! unless ::Feature.enabled?(:debian_packages, user_group) authorize_read_package!(user_group) end - namespace ':id/-/packages/debian' do + namespace ':id/packages/debian' do include DebianPackageEndpoints end end diff --git a/lib/api/debian_package_endpoints.rb b/lib/api/debian_package_endpoints.rb index c95c75b7e5c..e7689b3feff 100644 --- a/lib/api/debian_package_endpoints.rb +++ b/lib/api/debian_package_endpoints.rb @@ -32,6 +32,7 @@ module API helpers ::API::Helpers::Packages::BasicAuthHelpers format :txt + content_type :txt, 'text/plain' rescue_from ArgumentError do |e| render_api_error!(e.message, 400) @@ -50,33 +51,33 @@ module API end namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do - # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/Release.gpg + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg desc 'The Release file signature' do detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true get 'Release.gpg' do not_found! end - # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/Release + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release desc 'The unsigned Release file' do detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true get 'Release' do # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 'TODO Release' end - # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/InRelease + # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease desc 'The signed Release file' do detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true get 'InRelease' do not_found! end @@ -87,12 +88,12 @@ module API end namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do - # GET {projects|groups}/:id/-/packages/debian/dists/*distribution/:component/binary-:architecture/Packages + # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages desc 'The binary files index' do detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true get 'Packages' do # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 'TODO Packages' @@ -107,7 +108,7 @@ module API end namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do - # GET {projects|groups}/:id/-/packages/debian/pool/:component/:letter/:source_package/:file_name + # GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name params do requires :file_name, type: String, desc: 'The Debian File Name' end @@ -115,7 +116,7 @@ module API detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true get ':file_name', requirements: FILE_NAME_REQUIREMENTS do # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 'TODO File' diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index f8129c18dff..8c0db42a448 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -8,27 +8,29 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do + require_packages_enabled! + not_found! unless ::Feature.enabled?(:debian_packages, user_project) authorize_read_package! end - namespace ':id/-/packages/debian' do + namespace ':id/packages/debian' do include DebianPackageEndpoints params do requires :file_name, type: String, desc: 'The file name' end - namespace 'incoming/:file_name', requirements: FILE_NAME_REQUIREMENTS do + namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE - # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name + # 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)' end - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true put do authorize_upload!(authorized_user_project) bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:debian_max_file_size, params[:file].size) @@ -42,8 +44,8 @@ module API forbidden! end - # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name/authorize - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + # PUT {projects|groups}/:id/packages/debian/:file_name/authorize + 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!( subject: authorized_user_project, diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index 5fab590eb4e..30ec4e52b2a 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -28,8 +28,6 @@ module API use :pagination end get 'deploy_tokens' do - service_unavailable! unless Feature.enabled?(:deploy_tokens_api, default_enabled: true) - authenticated_as_admin! present paginate(DeployToken.all), with: Entities::DeployToken @@ -39,10 +37,6 @@ module API requires :id, type: String, desc: 'The ID of a project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - service_unavailable! unless Feature.enabled?(:deploy_tokens_api, user_project, default_enabled: true) - end - params do use :pagination end @@ -102,10 +96,6 @@ module API requires :id, type: String, desc: 'The ID of a group' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - service_unavailable! unless Feature.enabled?(:deploy_tokens_api, user_group, default_enabled: true) - end - params do use :pagination end diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 5346fcf03c9..d0c842bb19d 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -36,7 +36,7 @@ module API get ':id/deployments' do authorize! :read_deployment, user_project - deployments = DeploymentsFinder.new(user_project, params).execute + deployments = DeploymentsFinder.new(params.merge(project: user_project)).execute present paginate(deployments), with: Entities::Deployment end diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb index e9572a8d430..2468c1f9b18 100644 --- a/lib/api/entities/application_setting.rb +++ b/lib/api/entities/application_setting.rb @@ -31,6 +31,7 @@ module API expose :password_authentication_enabled_for_web, as: :password_authentication_enabled expose :password_authentication_enabled_for_web, as: :signin_enabled expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services + expose :asset_proxy_allowlist, as: :asset_proxy_whitelist end end end diff --git a/lib/api/entities/ci/job.rb b/lib/api/entities/ci/job.rb index 7fe1a802e24..76487ed01dc 100644 --- a/lib/api/entities/ci/job.rb +++ b/lib/api/entities/ci/job.rb @@ -9,6 +9,9 @@ module API expose :job_artifacts, as: :artifacts, using: ::API::Entities::Ci::JobArtifact expose :runner, with: ::API::Entities::Runner expose :artifacts_expire_at + expose :tag_list do |job| + job.tags.map(&:name).sort + end end end end diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 7f1b5b87725..88c84c494e2 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -27,7 +27,7 @@ module API expose(:downvotes) { |merge_request, options| issuable_metadata.downvotes } expose :author, :assignees, :assignee, using: Entities::UserBasic - expose :reviewers, if: -> (merge_request, _) { merge_request.allows_reviewers? }, using: Entities::UserBasic + expose :reviewers, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :labels do |merge_request, options| if options[:with_labels_details] diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb index 4aa5c9b7236..248a86751d2 100644 --- a/lib/api/entities/user.rb +++ b/lib/api/entities/user.rb @@ -6,9 +6,16 @@ module API include UsersHelper expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title + expose :bot?, as: :bot expose :work_information do |user| work_information(user) end + expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user| + user.followers.count + end + expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user| + user.followees.count + end end end end diff --git a/lib/api/entities/user_status.rb b/lib/api/entities/user_status.rb index 1d5cc27e5ef..ef4772f60c6 100644 --- a/lib/api/entities/user_status.rb +++ b/lib/api/entities/user_status.rb @@ -9,6 +9,7 @@ module API expose :message_html do |entity| MarkupHelper.markdown_field(entity, :message) end + expose :clear_status_at end end end diff --git a/lib/api/events.rb b/lib/api/events.rb index 233c62b5389..db5ed7b7e6e 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -6,7 +6,7 @@ module API include APIGuard helpers ::API::Helpers::EventsHelpers - allow_access_with_scope :read_user, if: -> (request) { request.get? } + allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } feature_category :users diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 167531fdaec..24d726f4a41 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -7,6 +7,8 @@ module API file_name: API::NO_SLASH_URL_PART_REGEX }.freeze + ALLOWED_STATUSES = %w[default hidden].freeze + feature_category :package_registry before do @@ -35,6 +37,7 @@ module API requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' end put 'authorize' do @@ -49,6 +52,7 @@ module API requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true requires :package_version, type: String, desc: 'Package version', regexp: Gitlab::Regex.generic_package_version_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true + optional :status, type: String, values: ALLOWED_STATUSES, desc: 'Package status' requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' end diff --git a/lib/api/go_proxy.rb b/lib/api/go_proxy.rb index 2d978019f2a..ea30f17522e 100755 --- a/lib/api/go_proxy.rb +++ b/lib/api/go_proxy.rb @@ -30,20 +30,6 @@ module API str.gsub(/![[:alpha:]]/) { |s| s[1..].upcase } end - def find_project!(id) - # based on API::Helpers::Packages::BasicAuthHelpers#authorized_project_find! - - project = find_project(id) - - return project if project && can?(current_user, :read_project, project) - - if current_user - not_found!('Project') - else - unauthorized! - end - end - def find_module not_found! unless Feature.enabled?(:go_proxy, user_project) @@ -74,7 +60,7 @@ module API requires :id, type: String, desc: 'The ID of a project' requires :module_name, type: String, desc: 'Module name', coerce_with: ->(val) { CGI.unescape(val) } end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, authenticate_non_public: true resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do before do authorize_read_package! diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb index 7fbf4445116..bea538441ee 100644 --- a/lib/api/group_labels.rb +++ b/lib/api/group_labels.rb @@ -12,7 +12,7 @@ module API params do requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :groups, requirements: ::API::Labels::LABEL_ENDPOINT_REQUIREMENTS do desc 'Get all labels of the group' do detail 'This feature was added in GitLab 11.8' success Entities::GroupLabel diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb index d482f4d0585..ab4e91ff925 100644 --- a/lib/api/group_packages.rb +++ b/lib/api/group_packages.rb @@ -33,12 +33,14 @@ module API desc: 'Return packages with this name' optional :include_versionless, type: Boolean, desc: 'Returns packages without a version' + optional :status, type: String, values: Packages::Package.statuses.keys, + desc: 'Return packages with specified status' end get ':id/packages' do packages = Packages::GroupPackagesFinder.new( current_user, user_group, - declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless) + declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless, :status) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 79af9c37378..0abb21c9831 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -119,11 +119,10 @@ module API def find_project!(id) project = find_project(id) - if can?(current_user, :read_project, project) - project - else - not_found!('Project') - end + return project if can?(current_user, :read_project, project) + return unauthorized! if authenticate_non_public? + + not_found!('Project') end # rubocop: disable CodeReuse/ActiveRecord @@ -139,11 +138,10 @@ module API def find_group!(id) group = find_group(id) - if can?(current_user, :read_group, group) - group - else - not_found!('Group') - end + return group if can?(current_user, :read_group, group) + return unauthorized! if authenticate_non_public? + + not_found!('Group') end def check_namespace_access(namespace) @@ -657,6 +655,10 @@ module API Gitlab::Shell.secret_token end + def authenticate_non_public? + route_authentication_setting[:authenticate_non_public] && !current_user + end + def send_git_blob(repository, blob) env['api.format'] = :txt content_type 'text/plain' diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 12b0a053e79..9a1ff2ba8ce 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -124,7 +124,7 @@ module API repository: repository.gitaly_repository.to_h, address: Gitlab::GitalyClient.address(repository.shard), token: Gitlab::GitalyClient.token(repository.shard), - features: Feature::Gitaly.server_feature_flags + features: Feature::Gitaly.server_feature_flags(repository.project) } end end diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 8aed578905e..2de077b5a3b 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -# rubocop:disable GitlabSecurity/PublicSend - module API module Helpers module MembersHelpers diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 6798c4d284b..71a18524104 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -138,7 +138,7 @@ module API parent = noteable_parent(noteable) ::Discussions::ResolveService.new(parent, current_user, one_or_more_discussions: discussion).execute else - discussion.unresolve! + ::Discussions::UnresolveService.new(discussion, current_user).execute end present discussion, with: Entities::Discussion diff --git a/lib/api/helpers/rate_limiter.rb b/lib/api/helpers/rate_limiter.rb index 5a531b5324a..3a16aef6a74 100644 --- a/lib/api/helpers/rate_limiter.rb +++ b/lib/api/helpers/rate_limiter.rb @@ -3,8 +3,8 @@ module API module Helpers module RateLimiter - def check_rate_limit!(key, scope) - if rate_limiter.throttled?(key, scope: scope) + def check_rate_limit!(key, scope, users_allowlist = nil) + if rate_limiter.throttled?(key, scope: scope, users_allowlist: users_allowlist) log_request(key) render_exceeded_limit_error! end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 12bb6e77c3e..a3fee49cd8f 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -52,7 +52,9 @@ module API actor.update_last_used_at! check_result = begin - access_check!(actor, params) + Gitlab::Auth::CurrentUserMode.bypass_session!(actor.user&.id) do + access_check!(actor, params) + end rescue Gitlab::GitAccess::ForbiddenError => e # The return code needs to be 401. If we return 403 # the custom message we return won't be shown to the user @@ -114,6 +116,10 @@ module API 'Could not find a user for the given key' unless actor.user end + + def two_factor_otp_check + { success: false, message: 'Feature is not available' } + end end namespace 'internal' do @@ -276,6 +282,11 @@ module API present response, with: Entities::InternalPostReceive::Response end + # This endpoint was added in https://gitlab.com/gitlab-org/gitlab/-/issues/212308 + # It was added with the plan to be used by GitLab PAM module but we + # decided to pursue a different approach, so it's currently not used. + # We might revive the PAM module though as it provides better user + # flow. post '/two_factor_config', feature_category: :authentication_and_authorization do status 200 @@ -301,28 +312,7 @@ module API post '/two_factor_otp_check', feature_category: :authentication_and_authorization do status 200 - break { success: false, message: 'Feature flag is disabled' } unless Feature.enabled?(:two_factor_for_cli) - - actor.update_last_used_at! - user = actor.user - - error_message = validate_actor_key(actor, params[:key_id]) - - break { success: false, message: error_message } if error_message - - break { success: false, message: 'Deploy keys cannot be used for Two Factor' } if actor.key.is_a?(DeployKey) - - break { success: false, message: 'Two-factor authentication is not enabled for this user' } unless user.two_factor_enabled? - - otp_validation_result = ::Users::ValidateOtpService.new(user).execute(params.fetch(:otp_attempt)) - - if otp_validation_result[:status] == :success - ::Gitlab::Auth::Otp::SessionEnforcer.new(actor.key).update_session - - { success: true } - else - { success: false, message: 'Invalid OTP' } - end + two_factor_otp_check end end end diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 73723a96401..87ad79d601f 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -52,6 +52,8 @@ module API def check_agent_token forbidden! unless agent_token + + forbidden! unless Gitlab::Kas.included_in_gitlab_com_rollout?(agent.project) end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 73e2163248d..ea09174f03a 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -231,7 +231,7 @@ module API post ':id/issues' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42320') - check_rate_limit! :issues_create, [current_user, :issues_create] + check_rate_limit! :issues_create, [current_user] authorize! :create_issue, user_project diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index e14a4a5e680..390dbc892e2 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -45,7 +45,7 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) - builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, pipeline: :project) + builds = builds.preload(:user, :job_artifacts_archive, :job_artifacts, :runner, :tags, pipeline: :project) present paginate(builds), with: Entities::Ci::Job end # rubocop: enable CodeReuse/ActiveRecord @@ -82,7 +82,8 @@ module API content_type 'text/plain' env['api.format'] = :binary - trace = build.trace.raw + # The trace can be nil bu body method expects a string as an argument. + trace = build.trace.raw || '' body trace end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index c9f29865664..aa3746dae42 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -9,10 +9,14 @@ module API feature_category :issue_tracking + LABEL_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( + name: API::NO_SLASH_URL_PART_REGEX, + label_id: API::NO_SLASH_URL_PART_REGEX) + params do requires :id, type: String, desc: 'The ID of a project' end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :projects, requirements: LABEL_ENDPOINT_REQUIREMENTS do desc 'Get all labels of the project' do success Entities::ProjectLabel end diff --git a/lib/api/members.rb b/lib/api/members.rb index 9bea74e2ce9..42f608102b3 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -137,12 +137,14 @@ module API authorize_admin_source!(source_type, source) member = source_members(source).find_by!(user_id: params[:user_id]) - updated_member = - ::Members::UpdateService - .new(current_user, declared_params(include_missing: false)) - .execute(member) - if updated_member.valid? + result = ::Members::UpdateService + .new(current_user, declared_params(include_missing: false)) + .execute(member) + + updated_member = result[:member] + + if result[:status] == :success present_members updated_member else render_validation_error!(updated_member) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 142ecd0dc1e..5051c1a5529 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -26,6 +26,7 @@ module API %i[ assignee_id assignee_ids + reviewer_ids description labels add_labels @@ -160,7 +161,8 @@ module API 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: 'The array of user IDs to assign issue' + 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' @@ -368,7 +370,7 @@ module API with: Entities::MergeRequestChanges, current_user: current_user, project: user_project, - access_raw_diffs: params.fetch(:access_raw_diffs, false) + access_raw_diffs: to_boolean(params.fetch(:access_raw_diffs, false)) end desc 'Get the merge request pipelines' do diff --git a/lib/api/notes.rb b/lib/api/notes.rb index d249431b2f8..418efe3d1a7 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -4,6 +4,7 @@ module API class Notes < ::API::Base include PaginationParams helpers ::API::Helpers::NotesHelpers + helpers Helpers::RateLimiter before { authenticate! } @@ -72,6 +73,9 @@ module API optional :created_at, type: String, desc: 'The creation date of the note' end post ":id/#{noteables_str}/:noteable_id/notes", feature_category: feature_category do + allowlist = + Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist + check_rate_limit! :notes_create, [current_user], allowlist noteable = find_noteable(noteable_type, params[:noteable_id]) opts = { diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb index e373f051b24..a80de06d6b0 100644 --- a/lib/api/nuget_group_packages.rb +++ b/lib/api/nuget_group_packages.rb @@ -18,7 +18,7 @@ module API default_format :json authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) .sent_through(:http_basic_auth) end diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index 2146f4d4b78..e071b6bd68f 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -20,7 +20,7 @@ module API default_format :json authenticate_with do |accept| - accept.token_types(:personal_access_token, :deploy_token, :job_token) + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) .sent_through(:http_basic_auth) end diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 32636662987..0fdaa4b2656 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -32,11 +32,13 @@ module API desc: 'Return packages with this name' optional :include_versionless, type: Boolean, desc: 'Returns packages without a version' + optional :status, type: String, values: Packages::Package.statuses.keys, + desc: 'Return packages with specified status' end get ':id/packages' do packages = ::Packages::PackagesFinder.new( user_project, - declared_params.slice(:order_by, :sort, :package_type, :package_name, :include_versionless) + declared_params.slice(:order_by, :sort, :package_type, :package_name, :include_versionless, :status) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index 19244ed697f..fdfdc244cbe 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -26,9 +26,7 @@ module API use :pagination end get ':id/templates/:type' do - templates = TemplateFinder - .build(params[:type], user_project) - .execute + templates = TemplateFinder.all_template_names_array(user_project, params[:type]) present paginate(::Kaminari.paginate_array(templates)), with: Entities::TemplatesList end @@ -38,16 +36,22 @@ module API end params do requires :name, type: String, desc: 'The name of the template' - + optional :source_template_project_id, type: Integer, + desc: 'The project id where a given template is being stored. This is useful when multiple templates from different projects have the same name' optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses' optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses' end get ':id/templates/:type/:name', requirements: TEMPLATE_NAMES_ENDPOINT_REQUIREMENTS do begin - template = TemplateFinder - .build(params[:type], user_project, name: params[:name]) - .execute + template = TemplateFinder.build( + params[:type], + user_project, + { + name: params[:name], + source_template_project_id: params[:source_template_project_id] + } + ).execute rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError not_found!('Template') end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2d09ad01757..fca68c3606b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -295,6 +295,8 @@ module API 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 :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork' end post ':id/fork', feature_category: :source_code_management do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284') diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 8af8ffc3b63..eaedd53aedb 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -170,6 +170,65 @@ module API not_found!("Merge Base") end end + + desc 'Generates a changelog section for a release' do + detail 'This feature was introduced in GitLab 13.9' + end + params do + requires :version, + type: String, + regexp: Gitlab::Regex.unbounded_semver_regex, + desc: 'The version of the release, using the semantic versioning format' + + optional :from, + type: String, + desc: 'The first commit in the range of commits to use for the changelog' + + requires :to, + type: String, + desc: 'The last commit in the range of commits to use for the changelog' + + optional :date, + type: DateTime, + desc: 'The date and time of the release' + + optional :branch, + type: String, + desc: 'The branch to commit the changelog changes to' + + optional :trailer, + type: String, + desc: 'The Git trailer to use for determining if commits are to be included in the changelog', + default: ::Repositories::ChangelogService::DEFAULT_TRAILER + + optional :file, + type: String, + desc: 'The file to commit the changelog changes to', + default: ::Repositories::ChangelogService::DEFAULT_FILE + + optional :message, + type: String, + desc: 'The commit message to use when committing the changelog' + end + post ':id/repository/changelog' do + branch = params[:branch] || user_project.default_branch_or_master + access = Gitlab::UserAccess.new(current_user, container: user_project) + + unless access.can_push_to_branch?(branch) + forbidden!("You are not allowed to commit a changelog on this branch") + end + + service = ::Repositories::ChangelogService.new( + user_project, + current_user, + **declared_params(include_missing: false) + ) + + service.execute + status(200) + rescue Gitlab::Changelog::Error => ex + render_api_error!("Failed to generate the changelog: #{ex.message}", 422) + end end end end diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb new file mode 100644 index 00000000000..66948f9eaf3 --- /dev/null +++ b/lib/api/resource_access_tokens.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module API + class ResourceAccessTokens < ::API::Base + include PaginationParams + + before { authenticate! } + + feature_category :authentication_and_authorization + + %w[project].each do |source_type| + resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get list of all access tokens for the specified resource' do + detail 'This feature was introduced in GitLab 13.9.' + end + params do + requires :id, type: String, desc: "The #{source_type} ID" + end + get ":id/access_tokens" do + resource = find_source(source_type, params[:id]) + + next unauthorized! unless has_permission_to_read?(resource) + + tokens = PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).execute + + present paginate(tokens), with: Entities::PersonalAccessToken + end + + desc 'Revoke a resource access token' do + detail 'This feature was introduced in GitLab 13.9.' + end + params do + requires :id, type: String, desc: "The #{source_type} ID" + requires :token_id, type: String, desc: "The ID of the token" + end + delete ':id/access_tokens/:token_id' do + resource = find_source(source_type, params[:id]) + token = find_token(resource, params[:token_id]) + + if token.nil? + next not_found!("Could not find #{source_type} access token with token_id: #{params[:token_id]}") + end + + service = ::ResourceAccessTokens::RevokeService.new( + current_user, + resource, + token + ).execute + + service.success? ? no_content! : bad_request!(service.message) + end + + desc 'Create a resource access token' do + detail 'This feature was introduced in GitLab 13.9.' + end + params do + requires :id, type: String, desc: "The #{source_type} ID" + requires :name, type: String, desc: "Resource access token name" + requires :scopes, type: Array[String], desc: "The permissions of the token" + optional :expires_at, type: Date, desc: "The expiration date of the token" + end + post ':id/access_tokens' do + resource = find_source(source_type, params[:id]) + + token_response = ::ResourceAccessTokens::CreateService.new( + current_user, + resource, + declared_params + ).execute + + if token_response.success? + present token_response.payload[:access_token], with: Entities::PersonalAccessToken + else + bad_request!(token_response.message) + end + end + end + end + + helpers do + def find_source(source_type, id) + public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend + end + + def find_token(resource, token_id) + PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).find_by_id(token_id) + end + + def has_permission_to_read?(resource) + can?(current_user, :project_bot_access, resource) || can?(current_user, :admin_resource_access_tokens, resource) + end + end + end +end diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb new file mode 100644 index 00000000000..7819aab879c --- /dev/null +++ b/lib/api/rubygem_packages.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +### +# API endpoints for the RubyGem package registry +module API + class RubygemPackages < ::API::Base + include ::API::Helpers::Authentication + helpers ::API::Helpers::PackagesHelpers + + feature_category :package_registry + + # The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" + # Updating the version should require a GitLab API version change. + MARSHAL_VERSION = '4.8' + + FILE_NAME_REQUIREMENTS = { + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + + content_type :binary, 'application/octet-stream' + + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_token) + end + + before do + require_packages_enabled! + authenticate! + 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' + 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' + end + params do + requires :file_name, type: String, desc: 'Spec file name' + end + get ":file_name", requirements: FILE_NAME_REQUIREMENTS do + # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299267 + not_found! + end + + desc 'Download the gemspec file' do + detail 'This feature was introduced in GitLab 13.9' + end + params do + requires :file_name, type: String, desc: 'Gemspec file name' + 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 + not_found! + end + + desc 'Download the .gem package' do + detail 'This feature was introduced in GitLab 13.9' + end + params do + requires :file_name, type: String, desc: 'Package file name' + end + get "gems/:file_name", requirements: FILE_NAME_REQUIREMENTS do + # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299283 + not_found! + end + + namespace 'api/v1' do + desc 'Authorize a gem upload from workhorse' do + detail 'This feature was introduced in GitLab 13.9' + end + post 'gems/authorize' do + # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 + not_found! + end + + desc 'Upload a gem' do + detail 'This feature was introduced in GitLab 13.9' + end + post 'gems' do + # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 + not_found! + end + + desc 'Fetch a list of dependencies' do + detail 'This feature was introduced in GitLab 13.9' + end + params do + optional :gems, type: String, desc: 'Comma delimited gem names' + end + get 'dependencies' do + # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299282 + not_found! + end + end + end + end + end +end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index f329a94adf2..e7ee8b08d87 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -10,8 +10,7 @@ module API helpers do def current_settings - @current_setting ||= - (ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults) + @current_setting ||= ApplicationSetting.find_or_create_without_cache end def filter_attributes_using_license(attrs) @@ -42,7 +41,8 @@ module API optional :asset_proxy_enabled, type: Boolean, desc: 'Enable proxying of assets' optional :asset_proxy_url, type: String, desc: 'URL of the asset proxy server' optional :asset_proxy_secret_key, type: String, desc: 'Shared secret with the asset proxy server' - optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.' + optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Deprecated: Use :asset_proxy_allowlist instead. Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.' + optional :asset_proxy_allowlist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically allowed.' optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts" optional :default_ci_config_path, type: String, desc: 'The instance default CI configuration path for new projects' @@ -211,6 +211,11 @@ module API attrs[:abuse_notification_email] = attrs.delete(:admin_notification_email) end + # support legacy names, can be removed in v5 + if attrs.has_key?(:asset_proxy_whitelist) + attrs[:asset_proxy_allowlist] = attrs.delete(:asset_proxy_whitelist) + end + # since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0 attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled) diff --git a/lib/api/snippet_repository_storage_moves.rb b/lib/api/snippet_repository_storage_moves.rb index 1a5b41eb1ec..84dbc03ba33 100644 --- a/lib/api/snippet_repository_storage_moves.rb +++ b/lib/api/snippet_repository_storage_moves.rb @@ -58,9 +58,14 @@ module API resource :snippets do helpers do def user_snippet - Snippet.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord + @user_snippet ||= Snippet.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord end end + + before do + not_found!('Snippet') unless user_snippet + end + desc 'Get a list of all snippets repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' success Entities::SnippetRepositoryStorageMove diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 914bab52929..87dc1358a51 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -6,6 +6,9 @@ module API before { authenticate! } + SUBSCRIBE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( + subscribable_id: API::NO_SLASH_URL_PART_REGEX) + subscribables = [ { type: 'merge_requests', @@ -44,7 +47,7 @@ module API requires :id, type: String, desc: "The #{source_type} ID" requires :subscribable_id, type: String, desc: 'The ID of a resource' end - resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource source_type.pluralize, requirements: SUBSCRIBE_ENDPOINT_REQUIREMENTS do desc 'Subscribe to a resource' do success subscribable[:entity] end diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb index a024d6de874..7921700e365 100644 --- a/lib/api/suggestions.rb +++ b/lib/api/suggestions.rb @@ -12,12 +12,13 @@ module API end params do requires :id, type: String, desc: 'The suggestion ID' + optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end put ':id/apply' do suggestion = Suggestion.find_by_id(params[:id]) if suggestion - apply_suggestions(suggestion, current_user) + apply_suggestions(suggestion, current_user, params[:commit_message]) else render_api_error!(_('Suggestion is not applicable as the suggestion was not found.'), :not_found) end @@ -28,6 +29,7 @@ module API end params do requires :ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: "An array of suggestion ID's" + optional :commit_message, type: String, desc: "A custom commit message to use instead of the default generated message or the project's default message" end put 'batch_apply' do ids = params[:ids] @@ -35,7 +37,7 @@ module API suggestions = Suggestion.id_in(ids) if suggestions.size == ids.length - apply_suggestions(suggestions, current_user) + apply_suggestions(suggestions, current_user, params[:commit_message]) else render_api_error!(_('Suggestions are not applicable as one or more suggestions were not found.'), :not_found) end @@ -43,10 +45,10 @@ module API end helpers do - def apply_suggestions(suggestions, current_user) + def apply_suggestions(suggestions, current_user, message) authorize_suggestions(*suggestions) - result = ::Suggestions::ApplyService.new(current_user, *suggestions).execute + result = ::Suggestions::ApplyService.new(current_user, *suggestions, message: message).execute if result[:status] == :success present suggestions, with: Entities::Suggestion, current_user: current_user diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb index 39730da1251..71395086ac2 100644 --- a/lib/api/support/git_access_actor.rb +++ b/lib/api/support/git_access_actor.rb @@ -37,6 +37,8 @@ module API end def update_last_used_at! + return if Feature.enabled?(:disable_ssh_key_used_tracking) + key&.update_last_used_at end diff --git a/lib/api/users.rb b/lib/api/users.rb index cee09f60a2b..f91e3c34ef2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -6,7 +6,7 @@ module API include APIGuard include Helpers::CustomAttributes - allow_access_with_scope :read_user, if: -> (request) { request.get? } + allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } feature_category :users, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] @@ -159,6 +159,68 @@ module API present user.status || {}, with: Entities::UserStatus end + desc 'Follow a user' do + success Entities::User + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/follow', feature_category: :users do + user = find_user(params[:id]) + not_found!('User') unless user + + if current_user.follow(user) + present user, with: Entities::UserBasic + else + not_modified! + end + end + + desc 'Unfollow a user' do + success Entities::User + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/unfollow', feature_category: :users do + user = find_user(params[:id]) + not_found!('User') unless user + + if current_user.unfollow(user) + present user, with: Entities::UserBasic + else + not_modified! + end + end + + desc 'Get the users who follow a user' do + success Entities::UserBasic + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + use :pagination + end + get ':id/following', feature_category: :users do + user = find_user(params[:id]) + not_found!('User') unless user && can?(current_user, :read_user_profile, user) + + present paginate(user.followees), with: Entities::UserBasic + end + + desc 'Get the followers of a user' do + success Entities::UserBasic + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + use :pagination + end + get ':id/followers', feature_category: :users do + user = find_user(params[:id]) + not_found!('User') unless user && can?(current_user, :read_user_profile, user) + + present paginate(user.followers), with: Entities::UserBasic + end + desc 'Create a user. Available only for admins.' do success Entities::UserWithAdmin end @@ -1004,11 +1066,15 @@ module API optional :emoji, type: String, desc: "The emoji to set on the status" optional :message, type: String, desc: "The status message to set" optional :availability, type: String, desc: "The availability of user to set" + optional :clear_status_after, type: String, desc: "Automatically clear emoji, message and availability fields after a certain time", values: UserStatus::CLEAR_STATUS_QUICK_OPTIONS.keys end put "status", feature_category: :users do forbidden! unless can?(current_user, :update_user_status, current_user) - if ::Users::SetStatusService.new(current_user, declared_params).execute + update_params = declared_params + update_params.delete(:clear_status_after) if Feature.disabled?(:clear_status_with_quick_options, current_user, default_enabled: :yaml) + + if ::Users::SetStatusService.new(current_user, update_params).execute present current_user.status, with: Entities::UserStatus else render_validation_error!(current_user.status) diff --git a/lib/api/version.rb b/lib/api/version.rb index f8072658cc6..86eb34ca589 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -5,7 +5,7 @@ module API helpers ::API::Helpers::GraphqlHelpers include APIGuard - allow_access_with_scope :read_user, if: -> (request) { request.get? } + allow_access_with_scope :read_user, if: -> (request) { request.get? || request.head? } before { authenticate! } |