diff --git a/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml b/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml new file mode 100644 index 0000000000000000000000000000000000000000..304208909da02543e8a1ad795c1a52436c876ee5 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/ci_require_api_token_for_ci_lint.yml @@ -0,0 +1,10 @@ +--- +name: ci_require_api_token_for_ci_lint +description: Require API authentication for GET CI Lint endpoint +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209764 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209764 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/578076 +milestone: '18.6' +group: group::pipeline authoring +type: gitlab_com_derisk +default_enabled: false diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 241967e6c47a77a76b1766c4a650381bf9b4ef80..92751568685d016a9b5f32d10f870b8189ed216d 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -38,6 +38,10 @@ class Lint < ::API::Base not_found! 'Repository' if user_project.empty_repo? + if Feature.enabled?(:ci_require_api_token_for_ci_lint, user_project) && current_user && !::Current.token_info + unauthorized!("This endpoint requires an API authentication") + end + content_ref = params[:content_ref] || params[:sha] || user_project.repository.root_ref_sha dry_run_ref = params[:dry_run_ref] || params[:ref] || user_project.default_branch diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index eef7f5a14a4e8e7c9efa80abdcecb0b4cbb7a448..45b7cfc9cd25a51b2003e97a41f89f7ffa9086d8 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -268,6 +268,27 @@ it_behaves_like 'valid config with warnings' end + + context 'when authenticated user has no API token' do + before do + allow(::Current).to receive(:token_info).and_return(nil) + end + + it 'returns unauthorized error' do + ci_lint + + expect(response).to have_gitlab_http_status(:unauthorized) + expect(json_response['message']).to include('This endpoint requires an API authentication') + end + + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'valid config without warnings' + end + end end context 'when including a component' do @@ -578,6 +599,69 @@ end end end + + context 'when the project is public' do + let_it_be(:project) { create(:project, :repository, :public) } + + context 'with valid .gitlab-ci.yml content' do + let_it_be(:yaml_content) do + { test: { stage: 'test', script: 'echo 1' } }.to_yaml + end + + before_all do + project.repository.create_file( + project.creator, + '.gitlab-ci.yml', + yaml_content, + message: 'Automatically created .gitlab-ci.yml', + branch_name: 'master' + ) + end + + shared_examples 'passing validation' do + it 'passes validation' do + ci_lint + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['valid']).to eq(true) + expect(json_response['warnings']).to eq([]) + expect(json_response['errors']).to eq([]) + end + end + + context 'when unauthenticated' do + let_it_be(:api_user) { nil } + + it_behaves_like 'passing validation' + + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'passing validation' + end + end + + context 'when authenticated as project developer' do + let_it_be(:api_user) { create(:user) } + + before do + project.add_developer(api_user) + end + + it_behaves_like 'passing validation' + + context 'when the FF ci_require_api_token_for_ci_lint is disabled' do + before do + stub_feature_flags(ci_require_api_token_for_ci_lint: false) + end + + it_behaves_like 'passing validation' + end + end + end + end end describe 'POST /projects/:id/ci/lint' do