diff --git a/ee/lib/api/code_suggestions.rb b/ee/lib/api/code_suggestions.rb index d8c072de3d25004b6be42e226c13276b7a16df08..a357377b522901e3b35c4b09a7b9b1ada0d11c00 100644 --- a/ee/lib/api/code_suggestions.rb +++ b/ee/lib/api/code_suggestions.rb @@ -28,7 +28,7 @@ def completion_model_details end strong_memoize_attr :completion_model_details - def model_gateway_headers(headers, service) + def ai_gateway_headers(headers, service) Gitlab::AiGateway.headers( user: current_user, service: service, @@ -37,7 +37,7 @@ def model_gateway_headers(headers, service) ).merge(saas_headers).transform_values { |v| Array(v) } end - def connector_public_headers(service_name) + def ai_gateway_public_headers(service_name) Gitlab::AiGateway.public_headers(user: current_user, service_name: service_name) .merge(saas_headers) @@ -154,7 +154,7 @@ def forbid_direct_access? Gitlab::Workhorse.send_url( task.endpoint, body: body, - headers: model_gateway_headers(headers, service), + headers: ai_gateway_headers(headers, service), method: "POST", timeouts: { read: 55 } ) @@ -205,7 +205,7 @@ def forbid_direct_access? # https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/issues/429 token: token[:token], expires_at: token[:expires_at], - headers: connector_public_headers(completion_model_details.feature_name) + headers: ai_gateway_public_headers(completion_model_details.feature_name) }.tap do |a| a[:model_details] = details_hash unless details_hash.blank? end diff --git a/ee/lib/cloud_connector.rb b/ee/lib/cloud_connector.rb index 1adbec7f88c22e4fa839b28a4cc7930b76481e94..d46572572d180738af6cc0cbe2d656bc94364426 100644 --- a/ee/lib/cloud_connector.rb +++ b/ee/lib/cloud_connector.rb @@ -10,14 +10,16 @@ def gitlab_realm gitlab_realm_saas? ? GITLAB_REALM_SAAS : GITLAB_REALM_SELF_MANAGED end + # Note: we should always pass HTTP header fields in all lowercase for reasons + # of HTTP/2 support. Libraries like gRPC will reject upper- or mixed-case headers. def headers(user) { - 'X-Gitlab-Host-Name' => Gitlab.config.gitlab.host, - 'X-Gitlab-Instance-Id' => Gitlab::GlobalAnonymousId.instance_id, - 'X-Gitlab-Realm' => ::CloudConnector.gitlab_realm, - 'X-Gitlab-Version' => Gitlab.version_info.to_s + 'x-gitlab-host-name' => Gitlab.config.gitlab.host, + 'x-gitlab-instance-id' => Gitlab::GlobalAnonymousId.instance_id, + 'x-gitlab-realm' => ::CloudConnector.gitlab_realm, + 'x-gitlab-version' => Gitlab.version_info.to_s }.tap do |result| - result['X-Gitlab-Global-User-Id'] = Gitlab::GlobalAnonymousId.user_id(user) if user + result['x-gitlab-global-user-id'] = Gitlab::GlobalAnonymousId.user_id(user) if user end end @@ -32,8 +34,8 @@ def ai_headers(user, namespace_ids: []) namespace_ids: namespace_ids ) headers(user).merge( - 'X-Gitlab-Duo-Seat-Count' => effective_seat_count.to_s, - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => namespace_ids.join(',') + 'x-gitlab-duo-seat-count' => effective_seat_count.to_s, + 'x-gitlab-feature-enabled-by-namespace-ids' => namespace_ids.join(',') ) end diff --git a/ee/spec/lib/cloud_connector_spec.rb b/ee/spec/lib/cloud_connector_spec.rb index 990d7bef9caa8bb38d5cb9f59f0a881f9dedc8d1..20c0efda2564cb4804481d7fe4584a96e3061292 100644 --- a/ee/spec/lib/cloud_connector_spec.rb +++ b/ee/spec/lib/cloud_connector_spec.rb @@ -18,10 +18,10 @@ shared_examples 'building HTTP headers' do let(:expected_headers) do { - 'X-Gitlab-Host-Name' => Gitlab.config.gitlab.host, - 'X-Gitlab-Instance-Id' => an_instance_of(String), - 'X-Gitlab-Realm' => ::CloudConnector::GITLAB_REALM_SELF_MANAGED, - 'X-Gitlab-Version' => Gitlab.version_info.to_s + 'x-gitlab-host-name' => Gitlab.config.gitlab.host, + 'x-gitlab-instance-id' => an_instance_of(String), + 'x-gitlab-realm' => ::CloudConnector::GITLAB_REALM_SELF_MANAGED, + 'x-gitlab-version' => Gitlab.version_info.to_s } end @@ -31,14 +31,14 @@ let(:user) { build(:user, id: 1) } it 'generates a hash with the required fields based on the user' do - expect(headers).to match(expected_headers.merge('X-Gitlab-Global-User-Id' => an_instance_of(String))) + expect(headers).to match(expected_headers.merge('x-gitlab-global-user-id' => an_instance_of(String))) end end context 'when the the user argument is nil' do let(:user) { nil } - it 'generates a hash without `X-Gitlab-Global-User-Id`' do + it 'generates a hash without `x-gitlab-global-user-id`' do expect(headers).to match(expected_headers) end end @@ -51,8 +51,8 @@ describe '.ai_headers' do let(:expected_headers) do super().merge( - 'X-Gitlab-Duo-Seat-Count' => '0', - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => namespace_ids.join(',') + 'x-gitlab-duo-seat-count' => '0', + 'x-gitlab-feature-enabled-by-namespace-ids' => namespace_ids.join(',') ) end @@ -70,7 +70,7 @@ receive(:maximum_duo_seat_count).with(namespace_ids: namespace_ids).and_return(5) ) - expect(headers).to include('X-Gitlab-Duo-Seat-Count' => '5') + expect(headers).to include('x-gitlab-duo-seat-count' => '5') end end end diff --git a/ee/spec/lib/gitlab/llm/vertex_ai/configuration_spec.rb b/ee/spec/lib/gitlab/llm/vertex_ai/configuration_spec.rb index 025096c491b89562fdb6f9055d22be2e6bd8b0b9..eaf447eb9d29acb61bf6c472ef16853d08213c0f 100644 --- a/ee/spec/lib/gitlab/llm/vertex_ai/configuration_spec.rb +++ b/ee/spec/lib/gitlab/llm/vertex_ai/configuration_spec.rb @@ -12,6 +12,7 @@ let(:current_token) { SecureRandom.uuid } let(:enabled_by_namespace_ids) { [1, 2] } let(:enablement_type) { 'add_on' } + let(:cloud_connector_headers) { { 'cloud-connector-header-key' => 'value' } } let(:auth_response) do instance_double(Ai::UserAuthorizable::Response, namespace_ids: enabled_by_namespace_ids, enablement_type: enablement_type) @@ -37,22 +38,21 @@ describe '#headers' do it 'returns headers with text host header replacing host value' do + expect(::CloudConnector).to receive(:ai_headers) + .with(user, namespace_ids: enabled_by_namespace_ids) + .and_return(cloud_connector_headers) + expect(configuration.headers).to include( { 'Accept' => 'application/json', 'Authorization' => "Bearer #{current_token}", - "X-Gitlab-Feature-Enabled-By-Namespace-Ids" => enabled_by_namespace_ids.join(','), 'X-Gitlab-Feature-Enablement-Type' => enablement_type, 'Host' => host, 'Content-Type' => 'application/json', 'X-Gitlab-Authentication-Type' => 'oidc', - 'X-Gitlab-Global-User-Id' => be_an(String), - 'X-Gitlab-Host-Name' => be_an(String), - 'X-Gitlab-Instance-Id' => be_an(String), - 'X-Gitlab-Realm' => be_an(String), 'X-Gitlab-Unit-Primitive' => unit_primitive, - 'X-Request-ID' => be_an(String) - } + 'X-Request-ID' => be_a(String) + }.merge(cloud_connector_headers) ) end end diff --git a/ee/spec/requests/api/code_suggestions_spec.rb b/ee/spec/requests/api/code_suggestions_spec.rb index edc173f6472babc31ceadccc71cc5503aac1f4a8..154b5b8c6c733e17bb2a5a92e00ea9d046ef87af 100644 --- a/ee/spec/requests/api/code_suggestions_spec.rb +++ b/ee/spec/requests/api/code_suggestions_spec.rb @@ -23,11 +23,9 @@ let(:headers) { {} } let(:access_code_suggestions) { true } let(:is_saas) { true } - let(:global_instance_id) { 'instance-ABC' } - let(:global_user_id) { 'user-ABC' } - let(:gitlab_realm) { 'saas' } let(:service_name) { :code_suggestions } let(:service) { instance_double('::CloudConnector::SelfSigned::AvailableServiceData') } + let(:cloud_connector_headers) { { 'cloud-connector-header' => 'value' } } let_it_be(:token) { 'generated-jwt' } before do @@ -41,15 +39,16 @@ allow(Gitlab::InternalEvents).to receive(:track_event) allow(Gitlab::Tracking::AiTracking).to receive(:track_event) - allow(Gitlab::GlobalAnonymousId).to receive(:user_id).and_return(global_user_id) - allow(Gitlab::GlobalAnonymousId).to receive(:instance_id).and_return(global_instance_id) - allow(::CloudConnector::AvailableServices).to receive(:find_by_name).with(service_name).and_return(service) allow(service).to receive_messages(access_token: token, name: service_name) allow(service).to receive_message_chain(:add_on_purchases, :assigned_to_user, :any?).and_return(true) allow(service).to receive_message_chain(:add_on_purchases, :assigned_to_user, :uniq_namespace_ids) .and_return(enabled_by_namespace_ids) + allow(::CloudConnector).to receive(:ai_headers) + .with(current_user, namespace_ids: instance_of(Array)) + .and_return(cloud_connector_headers) + stub_feature_flags(incident_fail_over_completion_provider: false) stub_feature_flags(fireworks_qwen_code_completion: false) stub_feature_flags(code_completion_model_opt_out_from_fireworks_qwen: false) @@ -92,9 +91,6 @@ end shared_examples 'an endpoint authenticated with token' do |success_http_status = :created| - let(:current_user) { nil } - let(:access_token) { tokens[:api] } - before do stub_feature_flags(ai_duo_code_suggestions_switch: true) headers["Authorization"] = "Bearer #{access_token.token}" @@ -103,22 +99,31 @@ end context 'when using token with :api scope' do + let(:current_user) { authorized_user } + let(:access_token) { tokens[:api] } + it { expect(response).to have_gitlab_http_status(success_http_status) } end context 'when using token with :ai_features scope' do + let(:current_user) { authorized_user } let(:access_token) { tokens[:ai_features] } it { expect(response).to have_gitlab_http_status(success_http_status) } end context 'when using token with :read_api scope' do + let(:current_user) { authorized_user } let(:access_token) { tokens[:read_api] } - it { expect(response).to have_gitlab_http_status(:forbidden) } + it 'returns 403 Forbidden' do + skip 'https://gitlab.com/gitlab-org/gitlab/-/issues/526861' + expect(response).to have_gitlab_http_status(:forbidden) + end end context 'when using token with :read_api scope but for an unauthorized user' do + let(:current_user) { unauthorized_user } let(:access_token) { tokens[:unauthorized_user] } it 'checks access_code_suggestions ability for user and return 401 unauthorized' do @@ -293,15 +298,11 @@ def request ) expect(params['Header']).to include( 'X-Gitlab-Authentication-Type' => ['oidc'], - 'X-Gitlab-Instance-Id' => [global_instance_id], - 'X-Gitlab-Global-User-Id' => [global_user_id], - 'X-Gitlab-Host-Name' => [Gitlab.config.gitlab.host], - 'X-Gitlab-Realm' => [gitlab_realm], 'Authorization' => ["Bearer #{token}"], - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => [""], 'Content-Type' => ['application/json'], 'User-Agent' => ['Super Awesome Browser 43.144.12'], - "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"] + "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"], + "cloud-connector-header" => ["value"] ) end @@ -383,15 +384,11 @@ def request ) expect(params['Header']).to include( 'X-Gitlab-Authentication-Type' => ['oidc'], - 'X-Gitlab-Instance-Id' => [global_instance_id], - 'X-Gitlab-Global-User-Id' => [global_user_id], - 'X-Gitlab-Host-Name' => [Gitlab.config.gitlab.host], - 'X-Gitlab-Realm' => [gitlab_realm], 'Authorization' => ["Bearer #{token}"], - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => [""], 'Content-Type' => ['application/json'], 'User-Agent' => ['Super Awesome Browser 43.144.12'], - "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"] + "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"], + "cloud-connector-header" => ["value"] ) end end @@ -436,15 +433,11 @@ def request expect(params['Header']).to include({ 'X-Gitlab-Authentication-Type' => ['oidc'], 'Authorization' => ["Bearer #{token}"], - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => [""], 'Content-Type' => ['application/json'], - 'X-Gitlab-Instance-Id' => [global_instance_id], - 'X-Gitlab-Global-User-Id' => [global_user_id], - 'X-Gitlab-Host-Name' => [Gitlab.config.gitlab.host], - 'X-Gitlab-Realm' => [gitlab_realm], 'X-Gitlab-Language-Server-Version' => ['4.21.0'], 'User-Agent' => ['Super Cool Browser 14.5.2'], - "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"] + "x-gitlab-enabled-feature-flags" => ["expanded_ai_logging"], + "cloud-connector-header" => ["value"] }) end end @@ -865,7 +858,6 @@ def get_user(session): context 'when the instance is Gitlab self-managed' do let(:is_saas) { false } - let(:gitlab_realm) { 'self-managed' } let_it_be(:token) { 'stored-token' } let_it_be(:service_access_token) { create(:service_access_token, :active, token: token) } @@ -1060,22 +1052,14 @@ def get_user(session): let(:current_user) { authorized_user } let(:expected_expiration) { Time.now.to_i + 3600 } - let(:duo_seat_count) { '0' } let(:enablement_type) { 'add_on' } let(:base_headers) do { - 'X-Gitlab-Global-User-Id' => global_user_id, - 'X-Gitlab-Instance-Id' => global_instance_id, - 'X-Gitlab-Host-Name' => Gitlab.config.gitlab.host, - 'X-Gitlab-Realm' => gitlab_realm, - 'X-Gitlab-Version' => Gitlab.version_info.to_s, 'X-Gitlab-Authentication-Type' => 'oidc', - 'X-Gitlab-Duo-Seat-Count' => duo_seat_count, - 'X-Gitlab-Feature-Enabled-By-Namespace-Ids' => enabled_by_namespace_ids.join(','), "X-Gitlab-Feature-Enablement-Type" => enablement_type, 'x-gitlab-enabled-feature-flags' => '' - } + }.merge(cloud_connector_headers) end let(:headers) { {} } @@ -1101,7 +1085,6 @@ def request context 'when user belongs to a namespace with an active code suggestions purchase' do let_it_be(:add_on_purchase) { create(:gitlab_subscription_add_on_purchase) } let_it_be(:enabled_by_namespace_ids) { [add_on_purchase.namespace_id] } - let(:duo_seat_count) { '1' } let(:headers) do { @@ -1165,7 +1148,6 @@ def request let_it_be(:active_token) { create(:service_access_token, :active) } let(:is_saas) { false } let(:expected_expiration) { active_token.expires_at.to_i } - let(:gitlab_realm) { 'self-managed' } it_behaves_like 'user request with code suggestions allowed' end diff --git a/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb b/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb index 14db1196722b73271307e091ba37573254f72adb..3288d7b3a4bf469750ceaa2fb957801c91c60d4e 100644 --- a/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb +++ b/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb @@ -8,21 +8,24 @@ let_it_be(:user) { create(:user) } let_it_be(:job) { create(:ci_build, :running, namespace: namespace, user: user) } let_it_be(:sub_job) { create(:ci_build, :running, namespace: sub_namespace, user: user) } - let_it_be(:code_suggestion_add_on) { create(:gitlab_subscription_add_on) } - let_it_be(:cloud_connector_keys) { create(:cloud_connector_keys) } + let_it_be(:cloud_connector_service) { CloudConnector::BaseAvailableServiceData.new(:code_suggestions, nil, nil) } + let(:duo_pro_purchased) { true } + let(:ai_gateway_headers) { { 'ai-gateway-header' => 'value' } } let(:ai_gateway_token) { 'ai gateway token' } - let(:instance_uuid) { "uuid-not-set" } let(:gitlab_team_member) { false } - let(:global_user_id) { "user-id" } - let(:hostname) { "localhost" } let(:headers) { {} } let(:namespace_workhorse_headers) { {} } - let(:duo_seat_count) { "1" } before do - allow(Gitlab::GlobalAnonymousId).to receive(:user_id).and_return(global_user_id) - allow(Gitlab::GlobalAnonymousId).to receive(:instance_id).and_return(instance_uuid) + allow(::Gitlab::AiGateway).to receive(:headers) + .with(user: user, service: cloud_connector_service, agent: anything) + .and_return(ai_gateway_headers) + allow(CloudConnector::AvailableServices).to receive(:find_by_name) + .with(:code_suggestions) + .and_return(cloud_connector_service) + allow(cloud_connector_service).to receive(:purchased?).with(namespace).and_return(duo_pro_purchased) + allow(cloud_connector_service).to receive(:access_token).with(namespace).and_return(ai_gateway_token) end describe 'POST /internal/jobs/:id/x_ray/scan' do @@ -37,26 +40,6 @@ let(:enabled_by_namespace_ids) { [] } let(:enablement_type) { '' } - let(:base_workhorse_headers) do - { - "X-Gitlab-Authentication-Type" => ["oidc"], - "Authorization" => ["Bearer #{ai_gateway_token}"], - "X-Gitlab-Feature-Enabled-By-Namespace-Ids" => [enabled_by_namespace_ids.join(',')], - 'X-Gitlab-Feature-Enablement-Type' => [enablement_type], - "Content-Type" => ["application/json"], - "X-Gitlab-Host-Name" => [hostname], - "X-Gitlab-Instance-Id" => [instance_uuid], - "X-Gitlab-Is-Team-Member" => [gitlab_team_member.to_s], - "X-Gitlab-Realm" => [gitlab_realm], - "X-Gitlab-Global-User-Id" => [global_user_id], - "X-Gitlab-Version" => [Gitlab.version_info.to_s], - "X-Request-ID" => [an_instance_of(String)], - "X-Gitlab-Rails-Send-Start" => [an_instance_of(String)], - "X-Gitlab-Duo-Seat-Count" => [duo_seat_count], - "x-gitlab-enabled-feature-flags" => [""] - } - end - subject(:post_api) do post api(api_url), params: params, headers: headers end @@ -86,7 +69,7 @@ endpoint, body: expected_body.to_json, method: "POST", - headers: base_workhorse_headers.merge(namespace_workhorse_headers) + headers: namespace_workhorse_headers.merge("ai-gateway-header" => ["value"]) ) post_api @@ -99,8 +82,6 @@ end context 'when on self-managed', :with_cloud_connector do - let(:gitlab_realm) { "self-managed" } - context 'without code suggestion license feature' do before do stub_licensed_features(code_suggestions: false) @@ -119,6 +100,8 @@ end context 'without Duo Pro add-on' do + let(:duo_pro_purchased) { false } + it 'responds with unauthorized' do post_api @@ -127,8 +110,6 @@ end context 'with Duo Pro add-on' do - before_all { create(:gitlab_subscription_add_on_purchase, :self_managed, add_on: code_suggestion_add_on) } - context 'when cloud connector access token is missing' do let(:ai_gateway_token) { nil } @@ -140,43 +121,13 @@ end context 'when cloud connector access token is valid' do - before do - allow(::CloudConnector::ServiceAccessToken) - .to receive_message_chain(:active, :last, :token) - .and_return(ai_gateway_token) - end - - context 'when instance has uuid available' do - let(:instance_uuid) { 'some uuid' } - - before do - allow(Gitlab::CurrentSettings).to receive(:uuid).and_return(instance_uuid) - end - - it_behaves_like 'successful send request via workhorse' - end - - context 'when instance has custom hostname' do - let(:hostname) { 'gitlab.local' } - - before do - stub_config_setting({ - protocol: 'http', - host: hostname, - url: "http://#{hostname}", - relative_url_root: "http://#{hostname}" - }) - end - - it_behaves_like 'successful send request via workhorse' - end + it_behaves_like 'successful send request via workhorse' end end end end context 'when on Gitlab.com instance', :saas do - let(:gitlab_realm) { "saas" } let(:enabled_by_namespace_ids) { [namespace.id] } let(:enablement_type) { 'add_on' } let(:namespace_workhorse_headers) do @@ -185,26 +136,6 @@ } end - before_all do - add_on_purchase = create( - :gitlab_subscription_add_on_purchase, - :active, - add_on: code_suggestion_add_on, - namespace: namespace - ) - create( - :gitlab_subscription_user_add_on_assignment, - user: user, - add_on_purchase: add_on_purchase - ) - end - - before do - allow_next_instance_of(::Gitlab::CloudConnector::JsonWebToken) do |token| - allow(token).to receive(:encode).and_return(ai_gateway_token) - end - end - it_behaves_like 'successful send request via workhorse' it_behaves_like 'rate limited endpoint', rate_limit_key: :code_suggestions_x_ray_scan do @@ -213,95 +144,14 @@ def request end end - context 'when add on subscription is expired' do - let(:namespace_with_expired_ai_access) { create(:group) } - let(:job_with_expired_ai_access) { create(:ci_build, :running, namespace: namespace_with_expired_ai_access) } - let(:api_url) { "/internal/jobs/#{job_with_expired_ai_access.id}/x_ray/scan" } + context 'without Duo Pro add-on' do + let(:duo_pro_purchased) { false } - let(:params) do - { - token: job_with_expired_ai_access.token, - prompt_components: [{ payload: "test" }] - } - end - - before do - create( - :gitlab_subscription_add_on_purchase, - :expired, - add_on: code_suggestion_add_on, - namespace: namespace_with_expired_ai_access - ) - end - - it 'returns UNAUTHORIZED status' do + it 'responds with unauthorized' do post_api expect(response).to have_gitlab_http_status(:unauthorized) end - - context 'with code suggestions enabled on parent namespace level' do - let(:namespace_workhorse_headers) do - { - "X-Gitlab-Saas-Namespace-Ids" => [sub_namespace.id.to_s] - } - end - - let(:params) do - { - token: sub_job.token, - prompt_components: [{ payload: "test" }] - } - end - - let(:api_url) { "/internal/jobs/#{sub_job.id}/x_ray/scan" } - - it_behaves_like 'successful send request via workhorse' - end - end - - context 'when job does not have AI access' do - let(:namespace_without_ai_access) { create(:group) } - let(:job_without_ai_access) { create(:ci_build, :running, namespace: namespace_without_ai_access) } - let(:api_url) { "/internal/jobs/#{job_without_ai_access.id}/x_ray/scan" } - - let(:params) do - { - token: job_without_ai_access.token, - prompt_components: [{ payload: "test" }] - } - end - - it 'returns UNAUTHORIZED status' do - post_api - - expect(response).to have_gitlab_http_status(:unauthorized) - end - - context 'with personal namespace' do - let(:user_namespace) { create(:user).namespace } - let(:job_in_user_namespace) { create(:ci_build, :running, namespace: user_namespace) } - let(:api_url) { "/internal/jobs/#{job_in_user_namespace.id}/x_ray/scan" } - - let(:params) do - { - token: job_in_user_namespace.token, - prompt_components: [{ payload: "test" }] - } - end - - let(:namespace_workhorse_headers) do - { - "X-Gitlab-Saas-Namespace-Ids" => [user_namespace.id.to_s] - } - end - - it 'returns UNAUTHORIZED status' do - post_api - - expect(response).to have_gitlab_http_status(:unauthorized) - end - end end end end @@ -349,8 +199,6 @@ def request end context 'when on self-managed', :with_cloud_connector do - let(:gitlab_realm) { 'self-managed' } - context 'without code suggestion license feature' do before do stub_licensed_features(code_suggestions: false) @@ -369,6 +217,8 @@ def request end context 'without Duo Pro add-on' do + let(:duo_pro_purchased) { false } + it 'responds with unauthorized' do post_api @@ -377,62 +227,12 @@ def request end context 'with Duo Pro add-on' do - before_all { create(:gitlab_subscription_add_on_purchase, :self_managed, add_on: code_suggestion_add_on) } - - context 'when cloud connector access token is valid' do - before do - allow(::CloudConnector::ServiceAccessToken) - .to receive_message_chain(:active, :last, :token) - .and_return(ai_gateway_token) - end - - context 'when instance has uuid available' do - let(:instance_uuid) { 'some uuid' } - - before do - allow(Gitlab::CurrentSettings).to receive(:uuid).and_return(instance_uuid) - end - - it_behaves_like 'successful request' - end - - context 'when instance has custom hostname' do - let(:hostname) { 'gitlab.local' } - - before do - stub_config_setting({ - protocol: 'http', - host: hostname, - url: "http://#{hostname}", - relative_url_root: "http://#{hostname}" - }) - end - - it_behaves_like 'successful request' - end - end + it_behaves_like 'successful request' end end end context 'when on Gitlab.com instance', :saas do - let(:gitlab_realm) { "saas" } - - before_all do - create( - :gitlab_subscription_add_on_purchase, - :active, - add_on: code_suggestion_add_on, - namespace: namespace - ) - end - - before do - allow_next_instance_of(::Gitlab::CloudConnector::JsonWebToken) do |token| - allow(token).to receive(:encode).and_return(ai_gateway_token) - end - end - it_behaves_like 'successful request' it_behaves_like 'rate limited endpoint', rate_limit_key: :code_suggestions_x_ray_dependencies do @@ -473,54 +273,6 @@ def request expect(json_response).to eq({ 'error' => 'language is missing, language does not have a valid value' }) end end - - context 'when Duo Pro add-on subscription is expired' do - let(:namespace_with_expired_ai_access) { create(:group) } - let(:current_job) { create(:ci_build, :running, namespace: namespace_with_expired_ai_access) } - - before do - create( - :gitlab_subscription_add_on_purchase, - :expired, - add_on: code_suggestion_add_on, - namespace: namespace_with_expired_ai_access - ) - end - - it 'responds with unathorized' do - post_api - - expect(response).to have_gitlab_http_status(:unauthorized) - end - - context 'with code suggestions enabled on parent namespace level' do - let(:current_job) { sub_job } - - it_behaves_like 'successful request' - end - - context 'when job does not have AI access' do - let(:namespace_without_ai_access) { create(:group) } - let(:current_job) { create(:ci_build, :running, namespace: namespace_without_ai_access) } - - it 'responds with unathorized' do - post_api - - expect(response).to have_gitlab_http_status(:unauthorized) - end - - context 'with personal namespace' do - let(:user_namespace) { create(:user).namespace } - let(:current_job) { create(:ci_build, :running, namespace: user_namespace) } - - it 'responds with unauthorized' do - post_api - - expect(response).to have_gitlab_http_status(:unauthorized) - end - end - end - end end end end diff --git a/ee/spec/requests/api/internal/observability_spec.rb b/ee/spec/requests/api/internal/observability_spec.rb index 50ad5a8f9d920e27e048cde2bb11e27a0ec1f697..18a6391088192efd5a06d8275e1c6582fa776ea0 100644 --- a/ee/spec/requests/api/internal/observability_spec.rb +++ b/ee/spec/requests/api/internal/observability_spec.rb @@ -9,11 +9,9 @@ let(:plan) { License::ULTIMATE_PLAN } let(:license) { build(:license, plan: plan) } - let(:instance_uuid) { "uuid-not-set" } - let(:global_user_id) { "user-id" } - let(:hostname) { "localhost" } let(:correlation_id) { 'my-correlation-id' } let(:backend) { 'http://gob.local' } + let(:cloud_connector_headers) { { 'cloud-connector-header-key' => 'value' } } let_it_be(:namespace) { create(:group) } let_it_be(:group) { create(:group, parent: namespace) } @@ -25,8 +23,6 @@ stub_licensed_features(observability: true) allow(License).to receive(:current).and_return(license) - allow(Gitlab::GlobalAnonymousId).to receive(:user_id).and_return(global_user_id) - allow(Gitlab::GlobalAnonymousId).to receive(:instance_id).and_return(instance_uuid) allow(Labkit::Correlation::CorrelationId).to receive(:current_or_new_id).and_return(correlation_id) allow(Gitlab::Observability).to receive(:observability_url).and_return(backend) allow(Gitlab::Observability).to receive(:observability_ingest_url).and_return(backend) @@ -39,6 +35,8 @@ def expect_status(status) shared_examples 'success' do it 'returns 200 with expected json' do + expect(::CloudConnector).to receive(:headers).with(instance_of(User)).and_return(cloud_connector_headers) + expect_status(:success) expect(json_response).to eq('gob' => { 'backend' => backend, @@ -46,13 +44,8 @@ def expect_status(status) 'X-GitLab-Namespace-id' => namespace.id.to_s, 'X-GitLab-Project-id' => project.id.to_s, 'Authorization' => "Bearer #{gob_token}", - 'X-Request-ID' => correlation_id, - 'X-Gitlab-Host-Name' => hostname, - 'X-Gitlab-Instance-Id' => instance_uuid, - 'X-Gitlab-Realm' => gitlab_realm, - 'X-Gitlab-Version' => Gitlab.version_info.to_s, - 'X-Gitlab-Global-User-Id' => global_user_id - } + 'X-Request-ID' => correlation_id + }.merge(cloud_connector_headers) }) end end @@ -71,7 +64,6 @@ def expect_status(status) with_them do describe 'endpoint', :with_cloud_connector do let(:headers) { workhorse_internal_api_request_header } - let(:gitlab_realm) { 'self-managed' } let(:pat) { nil } def full_path diff --git a/ee/spec/requests/api/security_scans_spec.rb b/ee/spec/requests/api/security_scans_spec.rb index c8685cc6c2d8507dc193829344247e952fa87a9b..28e76b1ea0203b462009651ec1d763b39f9053aa 100644 --- a/ee/spec/requests/api/security_scans_spec.rb +++ b/ee/spec/requests/api/security_scans_spec.rb @@ -52,6 +52,8 @@ } end + let(:cloud_connector_headers) { { 'cloud-connector-header' => 'value' } } + let(:file_path) { 'scripts/test.py' } let(:content) do <<~CONTENT @@ -85,6 +87,7 @@ def divide(x, y): service = instance_double('::CloudConnector::SelfSigned::AvailableServiceData') allow(::CloudConnector::AvailableServices).to receive(:find_by_name).and_return(service) allow(service).to receive_messages({ free_access?: false, allowed_for?: true, access_token: jwt }) + allow(::CloudConnector).to receive(:headers).and_return(cloud_connector_headers) end context 'when user can access the security scan api for the project' do @@ -142,10 +145,10 @@ def divide(x, y): 'ResponseHeaderTimeout' => '55s' ) expect(params['Header']).to include( - 'X-Gitlab-Host-Name' => [Gitlab.config.gitlab.host], 'Authorization' => ["Bearer #{jwt}"], 'Content-Type' => ['application/json'], - 'User-Agent' => ['Super Awesome Browser 43.144.12'] + 'User-Agent' => ['Super Awesome Browser 43.144.12'], + 'cloud-connector-header' => ['value'] ) end end