diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md index 2081a979ab5d582047e4242b97aeb2c50e94de40..9b53828f9ecf8ece649cc9a8710847e5387eea42 100644 --- a/doc/user/packages/generic_packages/index.md +++ b/doc/user/packages/generic_packages/index.md @@ -163,20 +163,38 @@ GET /projects/:id/packages/generic/:package_name/:package_version/:file_name The file context is served in the response body. The response content type is `application/octet-stream`. +::Tabs + +:::TabTitle Personal access token + Example request that uses a personal access token: ```shell +# Header authentication curl --header "PRIVATE-TOKEN: <your_access_token>" \ "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt" + +# Basic authentication +curl --user "user:<your_access_token>" \ + "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt" ``` -Example request that uses HTTP Basic authentication: +:::TabTitle CI_JOB_TOKEN + +Example request that uses a `CI_JOB_TOKEN`: ```shell -curl --user "user:<your_access_token>" \ +# Header authentication +curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \ + "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt" + +# Basic authentication +curl --user "gitlab-ci-token:${CI_JOB_TOKEN}" \ "https://gitlab.example.com/api/v4/projects/24/packages/generic/my_package/0.0.1/file.txt" ``` +::EndTabs + ## Publish a generic package by using CI/CD To work with generic packages in [GitLab CI/CD](../../../ci/index.md), you can use diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 8d1e9e55f24ed2ea7148c3aae2ae3757c1efd591..561883922882a68b5df16fda81cd1e2b5e7d0960 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -119,7 +119,7 @@ class GenericPackages < ::API::Base requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true end - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true + route_setting :authentication, job_token_allowed: %i[request basic_auth], basic_auth_personal_access_token: true, deploy_token_allowed: true get do project = authorized_user_project(action: :read_package) diff --git a/lib/api/helpers/packages/maven/basic_auth_helpers.rb b/lib/api/helpers/packages/maven/basic_auth_helpers.rb index c9ef95adc33b20a6811120e4504c02e0a22de5f5..0b0082dc7af9265a86084a718beec93ad3230af3 100644 --- a/lib/api/helpers/packages/maven/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/maven/basic_auth_helpers.rb @@ -12,7 +12,7 @@ module BasicAuthHelpers # basic auth. override :find_user_from_job_token def find_user_from_job_token - super || find_user_from_basic_auth_job + super || find_user_from_job_token_basic_auth end end end diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index 319d1121ec8f04a3e82bcd64680f71f20525f46d..f753db5076b70e39722788329811f1932d644e2e 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -76,30 +76,11 @@ def find_user_from_bearer_token def find_user_from_job_token return unless route_authentication_setting[:job_token_allowed] - return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth - token = current_request.params[JOB_TOKEN_PARAM].presence || - current_request.params[RUNNER_JOB_TOKEN_PARAM].presence || - current_request.env[JOB_TOKEN_HEADER].presence - return unless token - - job = find_valid_running_job_by_token!(token.to_s) - @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables - - job.user - end - - def find_user_from_basic_auth_job - return unless has_basic_credentials?(current_request) + user = find_user_from_job_token_basic_auth if can_authenticate_job_token_basic_auth? + return user if user - login, password = user_name_and_password(current_request) - return unless login.present? && password.present? - return unless ::Gitlab::Auth::CI_JOB_USER == login - - job = find_valid_running_job_by_token!(password.to_s) - @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables - - job.user + find_user_from_job_token_query_params_or_header if can_authenticate_job_token_request? end def find_user_from_basic_auth_password @@ -322,6 +303,41 @@ def find_user_from_path_feed_token(token) user end + def can_authenticate_job_token_basic_auth? + setting = route_authentication_setting[:job_token_allowed] + Array.wrap(setting).include?(:basic_auth) + end + + def can_authenticate_job_token_request? + setting = route_authentication_setting[:job_token_allowed] + setting == true || Array.wrap(setting).include?(:request) + end + + def find_user_from_job_token_query_params_or_header + token = current_request.params[JOB_TOKEN_PARAM].presence || + current_request.params[RUNNER_JOB_TOKEN_PARAM].presence || + current_request.env[JOB_TOKEN_HEADER].presence + return unless token + + job = find_valid_running_job_by_token!(token.to_s) + @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables + + job.user + end + + def find_user_from_job_token_basic_auth + return unless has_basic_credentials?(current_request) + + login, password = user_name_and_password(current_request) + return unless login.present? && password.present? + return unless ::Gitlab::Auth::CI_JOB_USER == login + + job = find_valid_running_job_by_token!(password.to_s) + @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables + + job.user + end + def parsed_oauth_token Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods) end diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb index c33ae1ce9013d736cfeb44e5b0ae8331a9a8e149..da13d8e382f9aa49a39fd973783b480d5dedca1e 100644 --- a/lib/gitlab/auth/request_authenticator.rb +++ b/lib/gitlab/auth/request_authenticator.rb @@ -38,7 +38,7 @@ def find_sessionless_user(request_format) find_user_from_web_access_token(request_format, scopes: [:api, :read_api]) || find_user_from_feed_token(request_format) || find_user_from_static_object_token(request_format) || - find_user_from_basic_auth_job || + find_user_from_job_token_basic_auth || find_user_from_job_token || find_user_from_personal_access_token_for_api_or_git || find_user_for_git_or_lfs_request diff --git a/spec/lib/gitlab/auth/auth_finders_spec.rb b/spec/lib/gitlab/auth/auth_finders_spec.rb index 26e6cd3661585b9b708d1427f905b874ae0f3618..914d675452c1499302ec5b2b9d1af508c8454946 100644 --- a/spec/lib/gitlab/auth/auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/auth_finders_spec.rb @@ -786,8 +786,8 @@ def auth_header_with(token) end end - describe '#find_user_from_basic_auth_job' do - subject { find_user_from_basic_auth_job } + describe '#find_user_from_job_token_basic_auth' do + subject { find_user_from_job_token_basic_auth } context 'when the request does not have AUTHORIZATION header' do it { is_expected.to be_nil } @@ -1037,6 +1037,60 @@ def auth_header_with(token) end end + context 'for route_authentication_setting[job_token_allowed]' do + using RSpec::Parameterized::TableSyntax + + where(:route_setting, :expect_user_via_request, :expect_user_via_basic_auth) do + true | true | false + :request | true | false + [:request] | true | false + :basic_auth | false | true + [:basic_auth] | false | true + [:request, :basic_auth] | true | true + + # unexpected values + :foo | false | false + [:foo] | false | false + [:foo, :bar] | false | false + end + + with_them do + let(:route_authentication_setting) { { job_token_allowed: route_setting } } + + context 'when the token is in the headers' do + before do + set_header(described_class::JOB_TOKEN_HEADER, token) + end + + it { is_expected.to eq(expect_user_via_request ? user : nil) } + end + + context 'when the token is in the job_token param' do + before do + set_param(described_class::JOB_TOKEN_PARAM, token) + end + + it { is_expected.to eq(expect_user_via_request ? user : nil) } + end + + context 'when the token is in the token param' do + before do + set_param(described_class::RUNNER_JOB_TOKEN_PARAM, token) + end + + it { is_expected.to eq(expect_user_via_request ? user : nil) } + end + + context 'when the token is in basic auth header' do + before do + set_basic_auth_header(::Gitlab::Auth::CI_JOB_USER, token) + end + + it { is_expected.to eq(expect_user_via_basic_auth ? user : nil) } + end + end + end + context 'when route setting allows job_token' do let(:route_authentication_setting) { { job_token_allowed: true } } diff --git a/spec/requests/api/generic_packages_spec.rb b/spec/requests/api/generic_packages_spec.rb index cf9912ba906b98d63d8022f698737d9f707ce0ce..eadd0817fb65810d3a7dd978afee749aad873500 100644 --- a/spec/requests/api/generic_packages_spec.rb +++ b/spec/requests/api/generic_packages_spec.rb @@ -29,6 +29,8 @@ def auth_header personal_access_token_header when :job_token job_token_header + when :job_basic_auth + job_basic_auth_header when :invalid_personal_access_token personal_access_token_header('wrong token') when :invalid_job_token @@ -61,6 +63,10 @@ def job_token_header(value = nil) { Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER => value || ci_build.token } end + def job_basic_auth_header(value = nil) + basic_auth_header(Gitlab::Auth::CI_JOB_USER, value || ci_build.token) + end + def deploy_token_header(value) { Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => value } end @@ -564,12 +570,16 @@ def upload_file(params, request_headers, send_rewritten_field: true, package_nam 'PRIVATE' | :guest | false | :invalid_user_basic_auth | :unauthorized 'PRIVATE' | :anonymous | false | :none | :unauthorized 'PUBLIC' | :developer | true | :job_token | :success + 'PUBLIC' | :developer | true | :job_basic_auth | :success 'PUBLIC' | :developer | true | :invalid_job_token | :unauthorized 'PUBLIC' | :developer | false | :job_token | :success + 'PUBLIC' | :developer | false | :job_basic_auth | :success 'PUBLIC' | :developer | false | :invalid_job_token | :unauthorized 'PRIVATE' | :developer | true | :job_token | :success + 'PRIVATE' | :developer | true | :job_basic_auth | :success 'PRIVATE' | :developer | true | :invalid_job_token | :unauthorized 'PRIVATE' | :developer | false | :job_token | :not_found + 'PRIVATE' | :developer | false | :job_basic_auth | :not_found 'PRIVATE' | :developer | false | :invalid_job_token | :unauthorized end