diff --git a/config/feature_flags/development/only_positive_pagination_values.yml b/config/feature_flags/development/only_positive_pagination_values.yml new file mode 100644 index 0000000000000000000000000000000000000000..9347c628e65be54af133b5209bfb018a83a88238 --- /dev/null +++ b/config/feature_flags/development/only_positive_pagination_values.yml @@ -0,0 +1,8 @@ +--- +name: only_positive_pagination_values +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93571 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/369225 +milestone: '15.3' +type: development +group: group::source code +default_enabled: false diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 5fd9a8e32789f00e32df236ed53246184d0d8c18..221e044b24d0c85a29821f81852f33e81e47435f 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -10,6 +10,8 @@ class Commits < ::API::Base before do require_repository_enabled! authorize! :download_code, user_project + + verify_pagination_params! end helpers do diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb index 85ac50d5becb8be4956a730af3a0acd7de973293..bdb69d0ba44ad0eb424168cab6e54d637ab0b0e5 100644 --- a/lib/api/pagination_params.rb +++ b/lib/api/pagination_params.rb @@ -20,6 +20,26 @@ module PaginationParams optional :page, type: Integer, default: 1, desc: 'Current page number' optional :per_page, type: Integer, default: 20, desc: 'Number of items per page', except_values: [0] end + + def verify_pagination_params! + return if Feature.disabled?(:only_positive_pagination_values) + + page = begin + Integer(params[:page]) + rescue ArgumentError, TypeError + nil + end + + return render_structured_api_error!({ error: 'page does not have a valid value' }, 400) if page&.< 1 + + per_page = begin + Integer(params[:per_page]) + rescue ArgumentError, TypeError + nil + end + + return render_structured_api_error!({ error: 'per_page does not have a valid value' }, 400) if per_page&.< 1 + end end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 9ef845f06bf75b687026be20e92ecee294e59e01..7c7a167d71df7dcf5fd2d5f3a591757543e7010b 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -206,11 +206,13 @@ let(:page) { 1 } let(:per_page) { 5 } let(:ref_name) { 'master' } - let!(:request) do + let(:request) do get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user) end it 'returns correct headers' do + request + expect(response).to include_limited_pagination_headers expect(response.headers['Link']).to match(/page=1&per_page=5/) expect(response.headers['Link']).to match(/page=2&per_page=5/) @@ -218,6 +220,8 @@ context 'viewing the first page' do it 'returns the first 5 commits' do + request + commit = project.repository.commit expect(json_response.size).to eq(per_page) @@ -230,6 +234,8 @@ let(:page) { 3 } it 'returns the third 5 commits' do + request + commit = project.repository.commits('HEAD', limit: per_page, offset: (page - 1) * per_page).first expect(json_response.size).to eq(per_page) @@ -238,10 +244,55 @@ end end - context 'when per_page is 0' do - let(:per_page) { 0 } + context 'when pagination params are invalid' do + let_it_be(:project) { create(:project, :repository) } + + using RSpec::Parameterized::TableSyntax + + where(:page, :per_page, :error_message) do + 0 | nil | 'page does not have a valid value' + -1 | nil | 'page does not have a valid value' + 'a' | nil | 'page is invalid' + nil | 0 | 'per_page does not have a valid value' + nil | -1 | 'per_page does not have a valid value' + nil | 'a' | 'per_page is invalid' + end - it_behaves_like '400 response' + with_them do + it 'returns 400 response' do + request + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to eq(error_message) + end + end + + context 'when FF is off' do + before do + stub_feature_flags(only_positive_pagination_values: false) + end + + where(:page, :per_page, :error_message, :status) do + 0 | nil | nil | :success + -10 | nil | nil | :internal_server_error + 'a' | nil | 'page is invalid' | :bad_request + nil | 0 | 'per_page has a value not allowed' | :bad_request + nil | -1 | nil | :success + nil | 'a' | 'per_page is invalid' | :bad_request + end + + with_them do + it 'returns a response' do + request + + expect(response).to have_gitlab_http_status(status) + + if error_message + expect(json_response['error']).to eq(error_message) + end + end + end + end end end