From f56a10e7bca24fb7fac27ff56015345a48519eee Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Wed, 4 Jun 2025 16:37:18 +0100 Subject: [PATCH 01/16] Support minimum language server client restriction Support restricting users from accessing the API from out of date Editor Extensions clients through a minimum language server version application setting. https://gitlab.com/gitlab-org/gitlab/-/issues/541744 Changelog: added --- app/controllers/graphql_controller.rb | 16 +++ app/helpers/application_settings_helper.rb | 2 + app/models/application_setting.rb | 7 + ...application_setting_editor_extensions.json | 18 +++ .../_editor_extensions.html.haml | 21 +++ .../application_settings/general.html.haml | 1 + .../editor_extensions.yml | 12 ++ .../beta/enforce_language_server_version.yml | 10 ++ ...itor_extensions_to_application_settings.rb | 9 ++ db/schema_migrations/20250604171923 | 1 + db/structure.sql | 1 + .../settings/editor_extensions.md | 53 +++++++ lib/api/api_guard.rb | 18 +++ lib/api/settings.rb | 2 + lib/gitlab/auth/auth_finders.rb | 6 + .../language_server_client.rb | 32 +++++ .../language_server_client_verifier.rb | 47 ++++++ locale/gitlab.pot | 18 +++ .../language_server_client_spec.rb | 60 ++++++++ .../language_server_client_verifier_spec.rb | 136 ++++++++++++++++++ spec/models/application_setting_spec.rb | 50 +++++++ spec/requests/api/api_spec.rb | 106 ++++++++++++++ .../api/graphql/editor_extensions_spec.rb | 82 +++++++++++ 23 files changed, 708 insertions(+) create mode 100644 app/validators/json_schemas/application_setting_editor_extensions.json create mode 100644 app/views/admin/application_settings/_editor_extensions.html.haml create mode 100644 config/application_setting_columns/editor_extensions.yml create mode 100644 config/feature_flags/beta/enforce_language_server_version.yml create mode 100644 db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb create mode 100644 db/schema_migrations/20250604171923 create mode 100644 doc/administration/settings/editor_extensions.md create mode 100644 lib/gitlab/auth/editor_extensions/language_server_client.rb create mode 100644 lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb create mode 100644 spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb create mode 100644 spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb create mode 100644 spec/requests/api/graphql/editor_extensions_spec.rb diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index f73a609a7c2d6e..01a5dfa7a6a4ae 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -40,6 +40,7 @@ class GraphqlController < ApplicationController before_action :track_neovim_plugin_usage before_action :disable_query_limiting before_action :limit_query_size + before_action :enforce_language_server_restrictions before_action :disallow_mutations_for_get @@ -80,6 +81,12 @@ def execute end end + rescue_from Gitlab::Auth::RestrictedLanguageServerClientError do |exception| + log_exception(exception) + + render_error(exception.message, status: :unauthorized) + end + rescue_from Gitlab::Auth::DpopValidationError do |exception| log_exception(exception) @@ -147,6 +154,15 @@ def check_dpop! request: current_request).execute end + def enforce_language_server_restrictions + response = Gitlab::Auth::EditorExtensions::LanguageServerClientVerifier.new( + current_user: current_user, + request: current_request + ).execute + + raise Gitlab::Auth::RestrictedLanguageServerClientError, response.message if response.error? + end + def permitted_params @permitted_params ||= multiplex? ? permitted_multiplex_params : permitted_standalone_query_params end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 0a2e2a1e77dd4a..9828ffb0e7deac 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -616,6 +616,8 @@ def visible_attributes :global_search_issues_enabled, :global_search_merge_requests_enabled, :global_search_block_anonymous_searches_enabled, + :enable_language_server_restrictions, + :minimum_language_server_version, :vscode_extension_marketplace, :vscode_extension_marketplace_enabled, :reindexing_minimum_index_size, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 0c82f5b07de3e5..385f58b6f83039 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -980,6 +980,13 @@ def self.kroki_formats_attributes jsonb_accessor :vscode_extension_marketplace, vscode_extension_marketplace_enabled: [:boolean, { default: false, store_key: :enabled }] + jsonb_accessor :editor_extensions, + enable_language_server_restrictions: [:boolean, { default: false }], + minimum_language_server_version: [:string, { default: '0.0.0' }] + + validates :editor_extensions, + json_schema: { filename: 'application_setting_editor_extensions', detail_errors: true } + before_validation :ensure_uuid! before_validation :coerce_repository_storages_weighted, if: :repository_storages_weighted_changed? before_validation :normalize_default_branch_name diff --git a/app/validators/json_schemas/application_setting_editor_extensions.json b/app/validators/json_schemas/application_setting_editor_extensions.json new file mode 100644 index 00000000000000..e4fef275b12937 --- /dev/null +++ b/app/validators/json_schemas/application_setting_editor_extensions.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Instance-wide Editor Extension settings", + "type": "object", + "additionalProperties": false, + "properties": { + "enable_language_server_restrictions": { + "type": "boolean", + "default": false, + "description": "Enables enforcing language server restrictions" + }, + "minimum_language_server_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "The minimum language server version to accept requests from" + } + } +} diff --git a/app/views/admin/application_settings/_editor_extensions.html.haml b/app/views/admin/application_settings/_editor_extensions.html.haml new file mode 100644 index 00000000000000..2d0ddeabfc05a3 --- /dev/null +++ b/app/views/admin/application_settings/_editor_extensions.html.haml @@ -0,0 +1,21 @@ += render ::Layouts::SettingsBlockComponent.new(_('Editor Extensions'), + id: 'js-editor-extensions-settings', + testid: 'admin-editor-extensions-settings', + expanded: expanded_by_default?) do |c| + - c.with_description do + = _('Configure instance-wide Editor Extensions settings') + - c.with_body do + = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-editor-extensions-settings'), html: { class: 'fieldset-form', id: 'editor-extensions-settings' } do |f| + = form_errors(@application_setting) + + %fieldset + .form-group + = f.label :minimum_language_server_version, _('Minimum GitLab Language Server client version'), class: 'label-bold' + = f.text_field :minimum_language_server_version, placeholder: '0.0.0', class: 'form-control gl-form-input' + .form-text.gl-text-subtle + = _('Minimum client version to enforce for editor extensions using the GitLab Language Server.') + .form-group + = f.gitlab_ui_checkbox_component :enable_language_server_restrictions, _('Language Server restrictions enabled') + .form-text.gl-text-subtle + = _('Whether to enforce minimum language server version.') + = f.submit _('Save changes'), pajamas_button: true diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index e480b9879b046e..d6e0c9425d9d62 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -112,3 +112,4 @@ = render 'admin/application_settings/analytics' = render_if_exists 'admin/application_settings/amazon_q' = render 'admin/application_settings/extension_marketplace' += render 'admin/application_settings/editor_extensions' diff --git a/config/application_setting_columns/editor_extensions.yml b/config/application_setting_columns/editor_extensions.yml new file mode 100644 index 00000000000000..73412599d364be --- /dev/null +++ b/config/application_setting_columns/editor_extensions.yml @@ -0,0 +1,12 @@ +--- +api_type: +attr: editor_extensions +clusterwide: true +column: editor_extensions +db_type: jsonb +default: "'{}'::jsonb" +description: Editor Extensions restrictions and settings +encrypted: false +gitlab_com_different_than_default: false +jihu: false +not_null: true diff --git a/config/feature_flags/beta/enforce_language_server_version.yml b/config/feature_flags/beta/enforce_language_server_version.yml new file mode 100644 index 00000000000000..9b9aa70e332adf --- /dev/null +++ b/config/feature_flags/beta/enforce_language_server_version.yml @@ -0,0 +1,10 @@ +--- +name: enforce_language_server_version +description: Enforce a minimum version for GitLab Language Server clients +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/541744 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193642 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/541743 +milestone: '18.1' +group: group::editor extensions +type: beta +default_enabled: false diff --git a/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb b/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb new file mode 100644 index 00000000000000..aba616071e69f7 --- /dev/null +++ b/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddEditorExtensionsToApplicationSettings < Gitlab::Database::Migration[2.3] + milestone '18.1' + + def change + add_column :application_settings, :editor_extensions, :jsonb, default: {}, null: false + end +end diff --git a/db/schema_migrations/20250604171923 b/db/schema_migrations/20250604171923 new file mode 100644 index 00000000000000..b261ef27562693 --- /dev/null +++ b/db/schema_migrations/20250604171923 @@ -0,0 +1 @@ +68558ac5f9b87ee06fd8129b626c64b0814b617c41b81dc214bb537519e7acf9 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 113773e8cb8ae0..9fd8e68ccbe7fc 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9232,6 +9232,7 @@ CREATE TABLE application_settings ( web_based_commit_signing_enabled boolean DEFAULT false NOT NULL, lock_web_based_commit_signing_enabled boolean DEFAULT false NOT NULL, tmp_asset_proxy_secret_key jsonb, + editor_extensions jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), diff --git a/doc/administration/settings/editor_extensions.md b/doc/administration/settings/editor_extensions.md new file mode 100644 index 00000000000000..5df31ddd152541 --- /dev/null +++ b/doc/administration/settings/editor_extensions.md @@ -0,0 +1,53 @@ +--- +stage: Create +group: Editor Extensions +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: Administer GitLab Editor Extensions including Visual Studio Code, JetBrains IDEs, Visual Studio, Eclipse and Neovim. +title: Administer Editor Extensions +--- + +{{< details >}} + +- Tier: Free, Premium, Ultimate +- Offering: GitLab Self-Managed + +{{< /details >}} + +You can configure the following restrictions for Editor Extension usage in GitLab: + +- Enforce a minimum language server version. + +## Enforce a minimum language server version + +{{< history >}} + +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/541744) in GitLab 18.1 [with a flag](../feature_flags.md) named `enforce_language_server_version`. Disabled by default. + +{{< /history >}} + +Prerequisites: + +- You must be an administrator. +- You must enable the `enforce_language_server_version` feature flag for one or more users. + + ```ruby + # For a specific user + Feature.enable(:enforce_language_server_version, User.find(1)) + + # For all users + Feature.enable(:enforce_language_server_version) + ``` + +To enforce a minimum GitLab Language Server version: + +1. On the left sidebar, select **Settings > General**. +1. Expand **Editor Extensions**. +1. Check **Language Server restrictions enabled**. +1. Under **Minimum GitLab Language Server client version**, enter a valid GitLab Language Server version. + +To allow any GitLab Language Server clients: + +1. On the left sidebar, select **Settings > General**. +1. Expand **Editor Extensions**. +1. Uncheck **Language Server restrictions enabled**. +1. Under **Minimum GitLab Language Server client version**, enter a valid GitLab Language Server version. diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 832ade5ff25e00..a05e4bef1e7512 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -64,6 +64,7 @@ def find_current_user! forbidden!(api_access_denied_message(user)) end + check_language_server_client!(user) check_dpop!(user) user @@ -137,6 +138,17 @@ def check_dpop!(user) group_id: params[:id]) end + def check_language_server_client!(user) + return unless api_request? && user.is_a?(User) + + response = Gitlab::Auth::EditorExtensions::LanguageServerClientVerifier.new( + current_user: user, + request: current_request + ).execute + + raise Gitlab::Auth::RestrictedLanguageServerClientError, response.message if response.error? + end + def user_allowed_or_deploy_token?(user) Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken) end @@ -171,6 +183,7 @@ def install_error_responders(base) Gitlab::Auth::RevokedError, Gitlab::Auth::ImpersonationDisabled, Gitlab::Auth::InsufficientScopeError, + Gitlab::Auth::RestrictedLanguageServerClientError, Gitlab::Auth::DpopValidationError] base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend @@ -215,6 +228,11 @@ def oauth2_bearer_token_error_handler Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( :dpop_error, e) + + when Gitlab::Auth::RestrictedLanguageServerClientError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :restricted_language_server_client_error, + e) end status, headers, body = response.finish diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 631cd56277c469..674c55b627a923 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -252,6 +252,8 @@ def filter_attributes_using_license(attrs) optional :preset, type: String, desc: "The preset configuration of URL's for the VS Code Extension Marketplace" optional :custom_values, type: Hash, desc: "VS Code Extension Marketplace URL's when preset is 'custom'" end + optional :enable_language_server_restrictions, type: Boolean, desc: 'Enables enforcing language server restrictions' + optional :minimum_language_server_version, type: String, desc: 'The minimum language server version to accept requests from' Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb index 376c7bf7f573a4..5098f3b5f26210 100644 --- a/lib/gitlab/auth/auth_finders.rb +++ b/lib/gitlab/auth/auth_finders.rb @@ -16,6 +16,12 @@ def initialize(msg) end end + class RestrictedLanguageServerClientError < AuthenticationError + def initialize(msg) + super("Language server client error: #{msg}") + end + end + class InsufficientScopeError < AuthenticationError attr_reader :scopes diff --git a/lib/gitlab/auth/editor_extensions/language_server_client.rb b/lib/gitlab/auth/editor_extensions/language_server_client.rb new file mode 100644 index 00000000000000..00510cfc9087a4 --- /dev/null +++ b/lib/gitlab/auth/editor_extensions/language_server_client.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + module EditorExtensions + class LanguageServerClient + USER_AGENT_PATTERN = /gitlab-language-server|code-completions-language-server-experiment/ + VERSION_PATTERN = /^\d+\.\d+\.\d+/ + + def initialize(client_version:, user_agent:) + @client_version = client_version + @user_agent = user_agent + end + + def lsp_client? + client_version&.match(VERSION_PATTERN) || user_agent&.match(USER_AGENT_PATTERN) + end + + def version + return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) + + # For older clients it is likely LSP version information is absent. + Gem::Version.new('0.0.0') + end + + private + + attr_reader :client_version, :user_agent + end + end + end +end diff --git a/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb b/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb new file mode 100644 index 00000000000000..64f27c629b0c1e --- /dev/null +++ b/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Auth + module EditorExtensions + class LanguageServerClientVerifier + def initialize(current_user:, request:) + @current_user = current_user + @request = request + end + + def execute + return ServiceResponse.success unless client.lsp_client? && enforce_language_server_version? + + return ServiceResponse.success if client.version >= minimum_version + + ServiceResponse.error( + message: 'Requests from Editor Extension clients are restricted', + payload: { client_version: client.version }, + reason: :instance_requires_newer_client + ) + end + + private + + attr_reader :current_user, :request + + def client + Gitlab::Auth::EditorExtensions::LanguageServerClient.new( + client_version: request.headers['HTTP_X_GITLAB_LANGUAGE_SERVER_VERSION'], + user_agent: request.headers['HTTP_USER_AGENT'] + ) + end + + def enforce_language_server_version? + return false unless Feature.enabled?(:enforce_language_server_version, current_user) + + Gitlab::CurrentSettings.enable_language_server_restrictions + end + + def minimum_version + Gem::Version.new(Gitlab::CurrentSettings.minimum_language_server_version) + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 718f277e8c8dc7..2b36d3970af164 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16947,6 +16947,9 @@ msgstr "" msgid "Configure import sources and settings related to import and export features." msgstr "" +msgid "Configure instance-wide Editor Extensions settings" +msgstr "" + msgid "Configure it later" msgstr "" @@ -23913,6 +23916,9 @@ msgstr "" msgid "Editing this file is not supported" msgstr "" +msgid "Editor Extensions" +msgstr "" + msgid "Editor toolbar" msgstr "" @@ -35868,6 +35874,9 @@ msgstr "" msgid "Language" msgstr "" +msgid "Language Server restrictions enabled" +msgstr "" + msgid "Language type" msgstr "" @@ -39041,9 +39050,15 @@ msgstr "" msgid "Minimal Access" msgstr "" +msgid "Minimum GitLab Language Server client version" +msgstr "" + msgid "Minimum capacity to be available before we schedule more mirrors preemptively." msgstr "" +msgid "Minimum client version to enforce for editor extensions using the GitLab Language Server." +msgstr "" + msgid "Minimum required approvals" msgstr "" @@ -69205,6 +69220,9 @@ msgstr[1] "" msgid "When:" msgstr "" +msgid "Whether to enforce minimum language server version." +msgstr "" + msgid "Which API requests are affected?" msgstr "" diff --git a/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb new file mode 100644 index 00000000000000..e14bc6da355161 --- /dev/null +++ b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::EditorExtensions::LanguageServerClient, feature_category: :editor_extensions do + describe '#lsp_client?' do + subject { described_class.new(client_version: client_version, user_agent: user_agent) } + + let(:client_version) { nil } + let(:expected_version) do + Gem::Version.new(client_version) + rescue StandardError + Gem::Version.new('0.0.0') + end + + let(:user_agent) { nil } + + context 'with no client version' do + context 'with no user agent' do + it { is_expected.not_to be_lsp_client } + it { is_expected.to have_attributes(version: eq(expected_version)) } + end + + context 'with an outdated user agent' do + let(:user_agent) do + 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + end + + it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + end + + context 'with an unrecognized user agent' do + let(:user_agent) { 'unknown-agent 1.0.0' } + + it { is_expected.not_to be_lsp_client } + it { is_expected.to have_attributes(version: eq(expected_version)) } + end + end + + context 'with invalid client version and an outdated user agent' do + let(:client_version) { 'a.b.c' } + let(:user_agent) { 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' } + + it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + end + + context 'with valid client version and a recognized user agent' do + let(:client_version) { '1.0.0' } + let(:user_agent) { 'gitlab-language-server 1.0.0' } + + it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + end + + context 'with valid client version and no user agent' do + let(:client_version) { '1.0.0' } + + it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + end + end +end diff --git a/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb b/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb new file mode 100644 index 00000000000000..90003a1fcd8519 --- /dev/null +++ b/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Auth::EditorExtensions::LanguageServerClientVerifier, feature_category: :editor_extensions do + let_it_be(:user, freeze: true) { create(:user) } + + let(:request) do + instance_double(ActionDispatch::Request, { + headers: ActionDispatch::Http::Headers.from_hash(headers) + }) + end + + describe '#execute' do + subject { described_class.new(current_user: user, request: request).execute } + + shared_examples 'client verification was successful' do |params| + let(:headers) { params.fetch(:headers) } + + it { expect(subject).to be_success } + end + + shared_examples 'client verification was unsuccessful' do |params| + let(:headers) { params.fetch(:headers) } + + it { expect(subject).to be_error.and have_attributes(reason: params[:reason]) } + end + + shared_examples 'allowed clients were successful' do + context 'with a different client' do + it_behaves_like 'client verification was successful', headers: { + 'HTTP_USER_AGENT' => 'my-cool-app 1.2.3' + } + end + + context 'with a matching language server client' do + it_behaves_like 'client verification was successful', headers: { + 'HTTP_USER_AGENT' => 'gitlab-language-server 2.0.0', + 'HTTP_X_GITLAB_LANGUAGE_SERVER_VERSION' => '2.0.0' + } + end + + context 'with an updated language server client' do + it_behaves_like 'client verification was successful', headers: { + 'HTTP_USER_AGENT' => 'gitlab-language-server 999.99.9', + 'HTTP_X_GITLAB_LANGUAGE_SERVER_VERSION' => '999.99.9' + } + end + end + + shared_examples 'restricted clients were unsuccessful' do + context 'with an obsolete language server client' do + it_behaves_like 'client verification was unsuccessful', + reason: :instance_requires_newer_client, + headers: { + 'HTTP_USER_AGENT' => 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' + } + end + + context 'with an outdated language server client' do + it_behaves_like 'client verification was unsuccessful', + reason: :instance_requires_newer_client, + headers: { + 'HTTP_USER_AGENT' => 'gitlab-language-server 1.2.3', + 'HTTP_X_GITLAB_LANGUAGE_SERVER_VERSION' => '1.2.3' + } + end + end + + shared_examples 'the enable_language_server_restrictions application setting is disabled' do + include_examples 'allowed clients were successful' + + context 'with an obsolete language server client' do + it_behaves_like 'client verification was successful', headers: { + 'HTTP_USER_AGENT' => 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' + } + end + + context 'with an outdated language server client' do + it_behaves_like 'client verification was successful', headers: { + 'HTTP_USER_AGENT' => 'gitlab-language-server 1.2.3', + 'HTTP_X_GITLAB_LANGUAGE_SERVER_VERSION' => '1.2.3' + } + end + end + + shared_context 'with language server restrictions disabled' do + before do + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: false, + minimum_language_server_version: '2.0.0') + end + end + + shared_context 'with language server restrictions enabled' do + before do + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, + minimum_language_server_version: '2.0.0') + end + end + + context 'with the enforce_language_server_version feature flag disabled' do + before do + stub_feature_flags(enforce_language_server_version: false) + end + + context 'with the enable_language_server_restrictions application setting disabled' do + include_context 'with language server restrictions disabled' + it_behaves_like 'the enable_language_server_restrictions application setting is disabled' + end + + context 'with the enable_language_server_restrictions application setting enabled' do + include_context 'with language server restrictions enabled' + it_behaves_like 'the enable_language_server_restrictions application setting is disabled' + end + end + + context 'with the enforce_language_server_version feature flag enabled' do + before do + stub_feature_flags(enforce_language_server_version: true) + end + + context 'with the enable_language_server_restrictions application setting disabled' do + include_context 'with language server restrictions disabled' + it_behaves_like 'the enable_language_server_restrictions application setting is disabled' + end + + context 'with the enable_language_server_restrictions application setting enabled' do + include_context 'with language server restrictions enabled' + it_behaves_like 'allowed clients were successful' + it_behaves_like 'restricted clients were unsuccessful' + end + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 7481999a153b2d..7ec9c01eb2f0e3 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -103,6 +103,7 @@ ecdsa_sk_key_restriction: 0, ed25519_key_restriction: 0, ed25519_sk_key_restriction: 0, + enable_language_server_restrictions: false, eks_integration_enabled: false, email_confirmation_setting: 'off', email_restrictions_enabled: false, @@ -175,6 +176,7 @@ max_yaml_depth: 100, max_yaml_size_bytes: 2.megabytes, members_delete_limit: 60, + minimum_language_server_version: '0.0.0', minimum_password_length: ApplicationSettingImplementation::DEFAULT_MINIMUM_PASSWORD_LENGTH, mirror_available: true, notes_create_limit: 300, @@ -2150,6 +2152,54 @@ def expect_invalid end end + describe '#editor_extensions' do + it 'sets the correct default values' do + expect(setting.enable_language_server_restrictions).to be(false) + expect(setting.minimum_language_server_version).to eq('0.0.0') + end + + context 'when provided different invalid values' do + using RSpec::Parameterized::TableSyntax + + where(:enable_language_server_restrictions, :minimum_language_server_version) do + false | nil + true | 'invalid semantic version' + true | '' + end + + with_them do + let(:value) do + { + enable_language_server_restrictions: enable_language_server_restrictions, + minimum_language_server_version: minimum_language_server_version + } + end + + it { is_expected.not_to allow_value(value).for(:editor_extensions) } + end + end + + context 'when provided different valid values' do + using RSpec::Parameterized::TableSyntax + + where(:enable_language_server_restrictions, :minimum_language_server_version) do + false | '0.0.0' + true | '8.0.0' + end + + with_them do + let(:value) do + { + enable_language_server_restrictions: enable_language_server_restrictions, + minimum_language_server_version: minimum_language_server_version + } + end + + it { is_expected.to allow_value(value).for(:editor_extensions) } + end + end + end + describe '#vscode_extension_marketplace' do let(:invalid_custom) { { enabled: false, preset: "custom", custom_values: {} } } let(:invalid_custom_urls) do diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index cd891f20a9fde8..09987e85af94a5 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -657,4 +657,110 @@ it_behaves_like 'not audited request' end end + + describe 'Language Server client restrictions', feature_category: :editor_extensions do + let_it_be(:user) { create(:user) } + let_it_be(:oauth_access_token) { create(:oauth_access_token, user: user, scopes: [:api]) } + let_it_be(:personal_access_token) { create(:personal_access_token, user: user, scopes: [:api]) } + # It does not matter which endpoint is used because language server restrictions + # should apply to every request. `/user` is used as an example + # to represent any API endpoint. + let(:request_path) { '/version' } + + shared_examples 'an allowed client' do + it 'returns 200 when using an OAuth token' do + get(api(request_path, oauth_access_token: oauth_access_token), headers: headers) + + expect(response).to have_gitlab_http_status(:ok) + end + + it 'returns 200 when using a Personal Access Token' do + get(api(request_path, personal_access_token: personal_access_token), headers: headers) + + expect(response).to have_gitlab_http_status(:ok) + end + end + + shared_examples 'an unallowed client' do + it 'returns 401 when using an OAuth token' do + get(api(request_path, oauth_access_token: oauth_access_token), headers: headers) + + expect(json_response["error_description"]).to a_string_including( + "Requests from Editor Extension clients are restricted") + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'returns 401 when using a Personal Access Token' do + get(api(request_path, personal_access_token: personal_access_token), headers: headers) + + expect(json_response["error_description"]).to a_string_including( + "Requests from Editor Extension clients are restricted") + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'with allowed clients' do + using RSpec::Parameterized::TableSyntax + + where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do + false | false | '0.0.1' | 'gitlab-language-server 0.0.1' + false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' + false | false | nil | 'gitlab-language-server 1.0.0' + false | false | nil | 'unknown-app 1.0.0' + false | false | nil | nil + false | true | '0.0.1' | 'gitlab-language-server 0.0.1' + true | true | '1.0.0' | 'gitlab-language-server 1.0.0' + true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + end + + with_them do + before do + stub_feature_flags(enforce_language_server_version: enforce_language_server_version) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: enable_language_server_restrictions, + minimum_language_server_version: '1.0.0') + end + + let(:headers) do + { + 'User-Agent' => user_agent, + 'X-GitLab-Language-Server-Version' => client_version + } + end + + it_behaves_like 'an allowed client' + end + end + + context 'with unallowed clients' do + using RSpec::Parameterized::TableSyntax + + where(:client_version, :user_agent) do + '0.0.1' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + '0.0.1' | 'gitlab-language-server 1.0.0' + '0.0.1' | nil + nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + nil | 'gitlab-language-server 0.0.1' + end + + with_them do + before do + stub_feature_flags(enforce_language_server_version: true) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, minimum_language_server_version: '1.0.0') + end + + let(:headers) do + { + 'User-Agent' => user_agent, + 'X-GitLab-Language-Server-Version' => client_version + } + end + + it_behaves_like 'an unallowed client' + end + end + end end diff --git a/spec/requests/api/graphql/editor_extensions_spec.rb b/spec/requests/api/graphql/editor_extensions_spec.rb new file mode 100644 index 00000000000000..7d753bb85ace99 --- /dev/null +++ b/spec/requests/api/graphql/editor_extensions_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Editor Extensions GraphQL integration', :clean_gitlab_redis_cache, feature_category: :editor_extensions do + include GraphqlHelpers + + let_it_be(:organization) { create(:organization) } + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user, organizations: [organization], developer_of: project) } + let(:query) { graphql_query_for('project', { fullPath: project.full_path }, 'id') } + + describe '#enforce_language_server_version' do + context 'with allowed clients' do + using RSpec::Parameterized::TableSyntax + + where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do + false | false | '0.0.1' | 'gitlab-language-server 0.0.1' + false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' + false | false | nil | 'gitlab-language-server 1.0.0' + false | false | nil | 'unknown-app 1.0.0' + false | false | nil | nil + false | true | '0.0.1' | 'gitlab-language-server 0.0.1' + true | true | '1.0.0' | 'gitlab-language-server 1.0.0' + true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + end + + with_them do + before do + stub_feature_flags(enforce_language_server_version: enforce_language_server_version) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: enable_language_server_restrictions, + minimum_language_server_version: '1.0.0') + end + + it 'is unsuppported' do + post_graphql(query, current_user: user, headers: { + 'User-Agent' => user_agent, + 'X-GitLab-Language-Server-Version' => client_version + }) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_data['project']['id']).to eq(project.to_gid.to_s) + end + end + end + + context 'with unallowed clients' do + using RSpec::Parameterized::TableSyntax + + where(:client_version, :user_agent) do + '0.0.1' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + '0.0.1' | 'gitlab-language-server 1.0.0' + '0.0.1' | nil + nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + nil | 'gitlab-language-server 0.0.1' + end + + with_them do + before do + stub_feature_flags(enforce_language_server_version: true) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, minimum_language_server_version: '1.0.0') + end + + it 'is supported' do + post_graphql(query, current_user: user, headers: { + 'User-Agent' => user_agent, + 'X-GitLab-Language-Server-Version' => client_version + }) + + expect(response).to have_gitlab_http_status(:unauthorized) + expect(graphql_errors).to contain_exactly( + hash_including('message' => a_string_including('Requests from Editor Extension clients are restricted')) + ) + end + end + end + end +end -- GitLab From f4a64255b6afa2e1683bee86b17895aca9b163b9 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 22:35:08 +0100 Subject: [PATCH 02/16] Apply 1 suggestion(s) to 1 file(s) --- doc/administration/settings/editor_extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/administration/settings/editor_extensions.md b/doc/administration/settings/editor_extensions.md index 5df31ddd152541..29ce092b01f914 100644 --- a/doc/administration/settings/editor_extensions.md +++ b/doc/administration/settings/editor_extensions.md @@ -34,7 +34,7 @@ Prerequisites: # For a specific user Feature.enable(:enforce_language_server_version, User.find(1)) - # For all users + # For this GitLab instance Feature.enable(:enforce_language_server_version) ``` -- GitLab From 89491f2a4a92e128e67eaba62d80d6d4f1a5a92d Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 22:56:16 +0100 Subject: [PATCH 03/16] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: GitLab Duo --- .../json_schemas/application_setting_editor_extensions.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/validators/json_schemas/application_setting_editor_extensions.json b/app/validators/json_schemas/application_setting_editor_extensions.json index e4fef275b12937..47d09ac0e52c46 100644 --- a/app/validators/json_schemas/application_setting_editor_extensions.json +++ b/app/validators/json_schemas/application_setting_editor_extensions.json @@ -9,10 +9,11 @@ "default": false, "description": "Enables enforcing language server restrictions" }, + "minimum_language_server_version": { + "type": "string", "minimum_language_server_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", - "description": "The minimum language server version to accept requests from" - } + "maxLength": 64, } } -- GitLab From ddce729fc6e828d72e2cf0e140fcab8fb3a345e0 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 22:57:01 +0100 Subject: [PATCH 04/16] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: GitLab Duo --- .../auth/editor_extensions/language_server_client.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/auth/editor_extensions/language_server_client.rb b/lib/gitlab/auth/editor_extensions/language_server_client.rb index 00510cfc9087a4..17348f41609df9 100644 --- a/lib/gitlab/auth/editor_extensions/language_server_client.rb +++ b/lib/gitlab/auth/editor_extensions/language_server_client.rb @@ -7,15 +7,12 @@ class LanguageServerClient USER_AGENT_PATTERN = /gitlab-language-server|code-completions-language-server-experiment/ VERSION_PATTERN = /^\d+\.\d+\.\d+/ - def initialize(client_version:, user_agent:) - @client_version = client_version - @user_agent = user_agent - end + def version + return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) - def lsp_client? - client_version&.match(VERSION_PATTERN) || user_agent&.match(USER_AGENT_PATTERN) + # For older clients it is likely LSP version information is absent. + Gem::Version.new('0.0.0') end - def version return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) -- GitLab From 150ad5cb41f53fd95a727dc68af8f2404f6ae604 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 23:10:08 +0100 Subject: [PATCH 05/16] Apply 1 suggestion(s) to 1 file(s) --- .../auth/editor_extensions/language_server_client.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/auth/editor_extensions/language_server_client.rb b/lib/gitlab/auth/editor_extensions/language_server_client.rb index 17348f41609df9..2f791fb9719071 100644 --- a/lib/gitlab/auth/editor_extensions/language_server_client.rb +++ b/lib/gitlab/auth/editor_extensions/language_server_client.rb @@ -7,12 +7,13 @@ class LanguageServerClient USER_AGENT_PATTERN = /gitlab-language-server|code-completions-language-server-experiment/ VERSION_PATTERN = /^\d+\.\d+\.\d+/ - def version - return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) - - # For older clients it is likely LSP version information is absent. - Gem::Version.new('0.0.0') + def lsp_client? + # Either condition is sufficient to identify an LSP client: + # - client_version matching semantic versioning pattern, or + # - user_agent containing known LSP client identifiers + client_version&.match(VERSION_PATTERN) || user_agent&.match(USER_AGENT_PATTERN) end + def version return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) -- GitLab From 9545b23108e8288fefc8e0af47b22e151fe9fe67 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 23:42:47 +0100 Subject: [PATCH 06/16] Apply 1 suggestion(s) to 1 file(s) --- .../json_schemas/application_setting_editor_extensions.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/validators/json_schemas/application_setting_editor_extensions.json b/app/validators/json_schemas/application_setting_editor_extensions.json index 47d09ac0e52c46..697f224d058f6e 100644 --- a/app/validators/json_schemas/application_setting_editor_extensions.json +++ b/app/validators/json_schemas/application_setting_editor_extensions.json @@ -9,8 +9,6 @@ "default": false, "description": "Enables enforcing language server restrictions" }, - "minimum_language_server_version": { - "type": "string", "minimum_language_server_version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", -- GitLab From 8cb4c42ac71a5449e32ee220f5bedcb93de566b8 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 23:49:07 +0100 Subject: [PATCH 07/16] Apply 1 suggestion(s) to 1 file(s) --- .../json_schemas/application_setting_editor_extensions.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/validators/json_schemas/application_setting_editor_extensions.json b/app/validators/json_schemas/application_setting_editor_extensions.json index 697f224d058f6e..a017f11614d15d 100644 --- a/app/validators/json_schemas/application_setting_editor_extensions.json +++ b/app/validators/json_schemas/application_setting_editor_extensions.json @@ -13,5 +13,7 @@ "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "maxLength": 64, + "description": "Minimum language server version to accept requests from" + } } } -- GitLab From c6a4711ef4fd96fd68c21046ea23c3a074ce3de0 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Thu, 5 Jun 2025 23:50:37 +0100 Subject: [PATCH 08/16] Apply 1 suggestion(s) to 1 file(s) --- lib/gitlab/auth/editor_extensions/language_server_client.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/gitlab/auth/editor_extensions/language_server_client.rb b/lib/gitlab/auth/editor_extensions/language_server_client.rb index 2f791fb9719071..0aff66d1519a68 100644 --- a/lib/gitlab/auth/editor_extensions/language_server_client.rb +++ b/lib/gitlab/auth/editor_extensions/language_server_client.rb @@ -7,6 +7,11 @@ class LanguageServerClient USER_AGENT_PATTERN = /gitlab-language-server|code-completions-language-server-experiment/ VERSION_PATTERN = /^\d+\.\d+\.\d+/ + def initialize(client_version:, user_agent:) + @client_version = client_version + @user_agent = user_agent + end + def lsp_client? # Either condition is sufficient to identify an LSP client: # - client_version matching semantic versioning pattern, or -- GitLab From 30708a5723a0b54d98e02f8b8cb2e939f2ca75ed Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 00:26:03 +0100 Subject: [PATCH 09/16] Apply 12 suggestion(s) to 6 file(s) --- .../_editor_extensions.html.haml | 2 +- .../auth/editor_extensions/language_server_client.rb | 4 ++-- .../editor_extensions/language_server_client_spec.rb | 6 +----- spec/models/application_setting_spec.rb | 6 +++--- spec/requests/api/api_spec.rb | 12 ++++++------ spec/requests/api/graphql/editor_extensions_spec.rb | 12 ++++++------ 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/app/views/admin/application_settings/_editor_extensions.html.haml b/app/views/admin/application_settings/_editor_extensions.html.haml index 2d0ddeabfc05a3..9026d4876142d5 100644 --- a/app/views/admin/application_settings/_editor_extensions.html.haml +++ b/app/views/admin/application_settings/_editor_extensions.html.haml @@ -11,7 +11,7 @@ %fieldset .form-group = f.label :minimum_language_server_version, _('Minimum GitLab Language Server client version'), class: 'label-bold' - = f.text_field :minimum_language_server_version, placeholder: '0.0.0', class: 'form-control gl-form-input' + = f.text_field :minimum_language_server_version, placeholder: '0.1.0', class: 'form-control gl-form-input' .form-text.gl-text-subtle = _('Minimum client version to enforce for editor extensions using the GitLab Language Server.') .form-group diff --git a/lib/gitlab/auth/editor_extensions/language_server_client.rb b/lib/gitlab/auth/editor_extensions/language_server_client.rb index 0aff66d1519a68..fd7158d5088a57 100644 --- a/lib/gitlab/auth/editor_extensions/language_server_client.rb +++ b/lib/gitlab/auth/editor_extensions/language_server_client.rb @@ -5,7 +5,7 @@ module Auth module EditorExtensions class LanguageServerClient USER_AGENT_PATTERN = /gitlab-language-server|code-completions-language-server-experiment/ - VERSION_PATTERN = /^\d+\.\d+\.\d+/ + VERSION_PATTERN = ::Gitlab::Regex.semver_regex def initialize(client_version:, user_agent:) @client_version = client_version @@ -23,7 +23,7 @@ def version return Gem::Version.new(client_version) if client_version&.match(VERSION_PATTERN) # For older clients it is likely LSP version information is absent. - Gem::Version.new('0.0.0') + Gem::Version.new('0.1.0') end private diff --git a/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb index e14bc6da355161..272af3f4fc3a4b 100644 --- a/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb +++ b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb @@ -7,11 +7,7 @@ subject { described_class.new(client_version: client_version, user_agent: user_agent) } let(:client_version) { nil } - let(:expected_version) do - Gem::Version.new(client_version) - rescue StandardError - Gem::Version.new('0.0.0') - end + let(:expected_version) { Gem::Version.new(client_version) } let(:user_agent) { nil } diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 7ec9c01eb2f0e3..e1311431c4a7b0 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -176,7 +176,7 @@ max_yaml_depth: 100, max_yaml_size_bytes: 2.megabytes, members_delete_limit: 60, - minimum_language_server_version: '0.0.0', + minimum_language_server_version: '0.1.0', minimum_password_length: ApplicationSettingImplementation::DEFAULT_MINIMUM_PASSWORD_LENGTH, mirror_available: true, notes_create_limit: 300, @@ -2155,7 +2155,7 @@ def expect_invalid describe '#editor_extensions' do it 'sets the correct default values' do expect(setting.enable_language_server_restrictions).to be(false) - expect(setting.minimum_language_server_version).to eq('0.0.0') + expect(setting.minimum_language_server_version).to eq('0.1.0') end context 'when provided different invalid values' do @@ -2183,7 +2183,7 @@ def expect_invalid using RSpec::Parameterized::TableSyntax where(:enable_language_server_restrictions, :minimum_language_server_version) do - false | '0.0.0' + false | '0.1.0' true | '8.0.0' end diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index 09987e85af94a5..971a5f758efa27 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -703,12 +703,12 @@ using RSpec::Parameterized::TableSyntax where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do - false | false | '0.0.1' | 'gitlab-language-server 0.0.1' + false | false | '0.1.0' | 'gitlab-language-server 0.1.0' false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' false | false | nil | 'gitlab-language-server 1.0.0' false | false | nil | 'unknown-app 1.0.0' false | false | nil | nil - false | true | '0.0.1' | 'gitlab-language-server 0.0.1' + false | true | '0.1.0' | 'gitlab-language-server 0.1.0' true | true | '1.0.0' | 'gitlab-language-server 1.0.0' true | true | '2.0.0' | 'gitlab-language-server 2.0.0' end @@ -737,11 +737,11 @@ using RSpec::Parameterized::TableSyntax where(:client_version, :user_agent) do - '0.0.1' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' - '0.0.1' | 'gitlab-language-server 1.0.0' - '0.0.1' | nil + '0.1.0' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + '0.1.0' | 'gitlab-language-server 1.0.0' + '0.1.0' | nil nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' - nil | 'gitlab-language-server 0.0.1' + nil | 'gitlab-language-server 0.1.0' end with_them do diff --git a/spec/requests/api/graphql/editor_extensions_spec.rb b/spec/requests/api/graphql/editor_extensions_spec.rb index 7d753bb85ace99..d3b2240ff7fd31 100644 --- a/spec/requests/api/graphql/editor_extensions_spec.rb +++ b/spec/requests/api/graphql/editor_extensions_spec.rb @@ -15,12 +15,12 @@ using RSpec::Parameterized::TableSyntax where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do - false | false | '0.0.1' | 'gitlab-language-server 0.0.1' + false | false | '0.1.0' | 'gitlab-language-server 0.1.0' false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' false | false | nil | 'gitlab-language-server 1.0.0' false | false | nil | 'unknown-app 1.0.0' false | false | nil | nil - false | true | '0.0.1' | 'gitlab-language-server 0.0.1' + false | true | '0.1.0' | 'gitlab-language-server 0.1.0' true | true | '1.0.0' | 'gitlab-language-server 1.0.0' true | true | '2.0.0' | 'gitlab-language-server 2.0.0' end @@ -50,11 +50,11 @@ using RSpec::Parameterized::TableSyntax where(:client_version, :user_agent) do - '0.0.1' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' - '0.0.1' | 'gitlab-language-server 1.0.0' - '0.0.1' | nil + '0.1.0' | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' + '0.1.0' | 'gitlab-language-server 1.0.0' + '0.1.0' | nil nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' - nil | 'gitlab-language-server 0.0.1' + nil | 'gitlab-language-server 0.1.0' end with_them do -- GitLab From 6e7219734c4814321599958c4b8a8f920d992089 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 00:27:22 +0100 Subject: [PATCH 10/16] Apply 1 suggestion(s) to 1 file(s) --- app/models/application_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 385f58b6f83039..10346a2c1e8163 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -982,7 +982,7 @@ def self.kroki_formats_attributes jsonb_accessor :editor_extensions, enable_language_server_restrictions: [:boolean, { default: false }], - minimum_language_server_version: [:string, { default: '0.0.0' }] + minimum_language_server_version: [:string, { default: '0.1.0' }] validates :editor_extensions, json_schema: { filename: 'application_setting_editor_extensions', detail_errors: true } -- GitLab From 459e0949b4ee99060213116957895bfd5e20bc39 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 02:44:15 +0100 Subject: [PATCH 11/16] Fix test expectations --- .../language_server_client_spec.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb index 272af3f4fc3a4b..23b2e24cdc2287 100644 --- a/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb +++ b/spec/lib/gitlab/auth/editor_extensions/language_server_client_spec.rb @@ -6,15 +6,14 @@ describe '#lsp_client?' do subject { described_class.new(client_version: client_version, user_agent: user_agent) } - let(:client_version) { nil } - let(:expected_version) { Gem::Version.new(client_version) } - - let(:user_agent) { nil } - context 'with no client version' do + let(:client_version) { nil } + context 'with no user agent' do + let(:user_agent) { nil } + it { is_expected.not_to be_lsp_client } - it { is_expected.to have_attributes(version: eq(expected_version)) } + it { is_expected.to have_attributes(version: eq(Gem::Version.new('0.1.0'))) } end context 'with an outdated user agent' do @@ -22,14 +21,14 @@ 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' end - it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + it { is_expected.to be_lsp_client.and have_attributes(version: eq(Gem::Version.new('0.1.0'))) } end context 'with an unrecognized user agent' do let(:user_agent) { 'unknown-agent 1.0.0' } it { is_expected.not_to be_lsp_client } - it { is_expected.to have_attributes(version: eq(expected_version)) } + it { is_expected.to have_attributes(version: eq(Gem::Version.new('0.1.0'))) } end end @@ -37,20 +36,21 @@ let(:client_version) { 'a.b.c' } let(:user_agent) { 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0; arch:X64;)' } - it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + it { is_expected.to be_lsp_client.and have_attributes(version: eq(Gem::Version.new('0.1.0'))) } end context 'with valid client version and a recognized user agent' do let(:client_version) { '1.0.0' } let(:user_agent) { 'gitlab-language-server 1.0.0' } - it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + it { is_expected.to be_lsp_client.and have_attributes(version: eq(Gem::Version.new('1.0.0'))) } end context 'with valid client version and no user agent' do let(:client_version) { '1.0.0' } + let(:user_agent) { nil } - it { is_expected.to be_lsp_client.and have_attributes(version: eq(expected_version)) } + it { is_expected.to be_lsp_client.and have_attributes(version: eq(Gem::Version.new('1.0.0'))) } end end end -- GitLab From bb369472a87c8c0e67d3ddf7b89bd0735addf0b5 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 04:03:14 +0100 Subject: [PATCH 12/16] Support language server restrictions on Dedicated --- .../language_server_client_verifier.rb | 3 +- .../language_server_client_verifier_spec.rb | 23 +++++++ spec/requests/api/api_spec.rb | 57 ++++++++++++----- .../api/graphql/editor_extensions_spec.rb | 64 +++++++++++++------ 4 files changed, 110 insertions(+), 37 deletions(-) diff --git a/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb b/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb index 64f27c629b0c1e..bc44c1db112525 100644 --- a/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb +++ b/lib/gitlab/auth/editor_extensions/language_server_client_verifier.rb @@ -33,7 +33,8 @@ def client end def enforce_language_server_version? - return false unless Feature.enabled?(:enforce_language_server_version, current_user) + return false unless Gitlab::CurrentSettings.gitlab_dedicated_instance? || + Feature.enabled?(:enforce_language_server_version, current_user) Gitlab::CurrentSettings.enable_language_server_restrictions end diff --git a/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb b/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb index 90003a1fcd8519..b9d26ae3dacf3e 100644 --- a/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb +++ b/spec/lib/gitlab/auth/editor_extensions/language_server_client_verifier_spec.rb @@ -103,6 +103,8 @@ context 'with the enforce_language_server_version feature flag disabled' do before do stub_feature_flags(enforce_language_server_version: false) + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:gitlab_dedicated_instance?) + .and_return(false) end context 'with the enable_language_server_restrictions application setting disabled' do @@ -119,6 +121,27 @@ context 'with the enforce_language_server_version feature flag enabled' do before do stub_feature_flags(enforce_language_server_version: true) + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:gitlab_dedicated_instance?) + .and_return(false) + end + + context 'with the enable_language_server_restrictions application setting disabled' do + include_context 'with language server restrictions disabled' + it_behaves_like 'the enable_language_server_restrictions application setting is disabled' + end + + context 'with the enable_language_server_restrictions application setting enabled' do + include_context 'with language server restrictions enabled' + it_behaves_like 'allowed clients were successful' + it_behaves_like 'restricted clients were unsuccessful' + end + end + + context 'on a dedicated instance' do + before do + stub_feature_flags(enforce_language_server_version: false) + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:gitlab_dedicated_instance?) + .and_return(true) end context 'with the enable_language_server_restrictions application setting disabled' do diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index 971a5f758efa27..9d829da176403d 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -702,15 +702,18 @@ context 'with allowed clients' do using RSpec::Parameterized::TableSyntax - where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do - false | false | '0.1.0' | 'gitlab-language-server 0.1.0' - false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' - false | false | nil | 'gitlab-language-server 1.0.0' - false | false | nil | 'unknown-app 1.0.0' - false | false | nil | nil - false | true | '0.1.0' | 'gitlab-language-server 0.1.0' - true | true | '1.0.0' | 'gitlab-language-server 1.0.0' - true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + where(:is_dedicated, :enforce_language_server_version, :enable_language_server_restrictions, :client_version, + :user_agent) do + false | false | false | '0.1.0' | 'gitlab-language-server 0.1.0' + false | false | true | '0.1.0' | 'gitlab-language-server 0.1.0' + false | false | false | nil | 'code-completions-language-server-experiment (gitlab.vim: 1.0.0)' + false | false | false | nil | 'gitlab-language-server 1.0.0' + false | false | false | nil | 'unknown-app 1.0.0' + false | false | false | nil | nil + false | true | true | '1.0.0' | 'gitlab-language-server 1.0.0' + false | true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + true | false | true | '1.0.0' | 'gitlab-language-server 1.0.0' + true | false | true | '2.0.0' | 'gitlab-language-server 2.0.0' end with_them do @@ -719,6 +722,7 @@ allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( enable_language_server_restrictions: enable_language_server_restrictions, + gitlab_dedicated_instance?: is_dedicated, minimum_language_server_version: '1.0.0') end @@ -733,7 +737,7 @@ end end - context 'with unallowed clients' do + shared_examples 'unallowed clients' do using RSpec::Parameterized::TableSyntax where(:client_version, :user_agent) do @@ -745,13 +749,6 @@ end with_them do - before do - stub_feature_flags(enforce_language_server_version: true) - - allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( - enable_language_server_restrictions: true, minimum_language_server_version: '1.0.0') - end - let(:headers) do { 'User-Agent' => user_agent, @@ -762,5 +759,31 @@ it_behaves_like 'an unallowed client' end end + + context 'with unallowed clients on dedicated' do + before do + stub_feature_flags(enforce_language_server_version: false) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, + gitlab_dedicated_instance?: true, + minimum_language_server_version: '1.0.0') + end + + it_behaves_like 'unallowed clients' + end + + context 'with unallowed clients outside of dedicated' do + before do + stub_feature_flags(enforce_language_server_version: true) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, + gitlab_dedicated_instance?: false, + minimum_language_server_version: '1.0.0') + end + + it_behaves_like 'unallowed clients' + end end end diff --git a/spec/requests/api/graphql/editor_extensions_spec.rb b/spec/requests/api/graphql/editor_extensions_spec.rb index d3b2240ff7fd31..faee63e307e3e2 100644 --- a/spec/requests/api/graphql/editor_extensions_spec.rb +++ b/spec/requests/api/graphql/editor_extensions_spec.rb @@ -10,19 +10,25 @@ let_it_be(:user) { create(:user, organizations: [organization], developer_of: project) } let(:query) { graphql_query_for('project', { fullPath: project.full_path }, 'id') } - describe '#enforce_language_server_version' do + describe 'language server client restrictions' do context 'with allowed clients' do using RSpec::Parameterized::TableSyntax - where(:enforce_language_server_version, :enable_language_server_restrictions, :client_version, :user_agent) do - false | false | '0.1.0' | 'gitlab-language-server 0.1.0' - false | false | nil | 'code-completions-language-server-experiment (gl-visual-studio-extension:1.0.0.0)' - false | false | nil | 'gitlab-language-server 1.0.0' - false | false | nil | 'unknown-app 1.0.0' - false | false | nil | nil - false | true | '0.1.0' | 'gitlab-language-server 0.1.0' - true | true | '1.0.0' | 'gitlab-language-server 1.0.0' - true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + where(:is_dedicated, :enforce_language_server_version, :enable_language_server_restrictions, :client_version, + :user_agent) do + false | false | false | '0.1.0' | 'gitlab-language-server 0.1.0' + false | false | false | nil | 'code-completions-language-server-experiment (gitlab.vim: 1.0.0)' + false | false | false | nil | 'gitlab-language-server 1.0.0' + false | false | false | nil | 'unknown-app 1.0.0' + false | false | false | nil | nil + false | false | true | '0.1.0' | 'gitlab-language-server 0.1.0' + false | true | true | '1.0.0' | 'gitlab-language-server 1.0.0' + false | true | true | '2.0.0' | 'gitlab-language-server 2.0.0' + true | false | false | '0.1.0' | 'gitlab-language-server 0.1.0' + true | false | false | nil | 'code-completions-language-server-experiment (gitlab.vim: 1.0.0)' + true | false | false | nil | 'gitlab-language-server 1.0.0' + true | false | false | nil | 'unknown-app 1.0.0' + true | false | false | nil | nil end with_them do @@ -31,10 +37,11 @@ allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( enable_language_server_restrictions: enable_language_server_restrictions, + gitlab_dedicated_instance?: is_dedicated, minimum_language_server_version: '1.0.0') end - it 'is unsuppported' do + it 'is suppported' do post_graphql(query, current_user: user, headers: { 'User-Agent' => user_agent, 'X-GitLab-Language-Server-Version' => client_version @@ -46,7 +53,7 @@ end end - context 'with unallowed clients' do + shared_examples 'unallowed clients' do using RSpec::Parameterized::TableSyntax where(:client_version, :user_agent) do @@ -58,13 +65,6 @@ end with_them do - before do - stub_feature_flags(enforce_language_server_version: true) - - allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( - enable_language_server_restrictions: true, minimum_language_server_version: '1.0.0') - end - it 'is supported' do post_graphql(query, current_user: user, headers: { 'User-Agent' => user_agent, @@ -78,5 +78,31 @@ end end end + + context 'with unallowed clients on dedicated' do + before do + stub_feature_flags(enforce_language_server_version: false) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, + gitlab_dedicated_instance?: true, + minimum_language_server_version: '1.0.0') + end + + it_behaves_like 'unallowed clients' + end + + context 'with unallowed clients outside of dedicated' do + before do + stub_feature_flags(enforce_language_server_version: true) + + allow(Gitlab::CurrentSettings.current_application_settings).to receive_messages( + enable_language_server_restrictions: true, + gitlab_dedicated_instance?: false, + minimum_language_server_version: '1.0.0') + end + + it_behaves_like 'unallowed clients' + end end end -- GitLab From b4c6fce31e9eaeb1d6ba11c8fb96cd77d8296f62 Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 04:06:35 +0100 Subject: [PATCH 13/16] Make enforce_language_server_version a gitlab_com_derisk feature flag --- .../enforce_language_server_version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename config/feature_flags/{beta => gitlab_com_derisk}/enforce_language_server_version.yml (94%) diff --git a/config/feature_flags/beta/enforce_language_server_version.yml b/config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml similarity index 94% rename from config/feature_flags/beta/enforce_language_server_version.yml rename to config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml index 9b9aa70e332adf..a98caffc18d52d 100644 --- a/config/feature_flags/beta/enforce_language_server_version.yml +++ b/config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml @@ -6,5 +6,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193642 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/541743 milestone: '18.1' group: group::editor extensions -type: beta +type: gitlab_com_derisk default_enabled: false -- GitLab From 3c37d9e96b231bc62236b9e05fd48109bf2f1dcd Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 04:09:21 +0100 Subject: [PATCH 14/16] Revert "Make enforce_language_server_version a gitlab_com_derisk feature flag" This reverts commit b4c6fce31e9eaeb1d6ba11c8fb96cd77d8296f62. --- .../enforce_language_server_version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename config/feature_flags/{gitlab_com_derisk => beta}/enforce_language_server_version.yml (94%) diff --git a/config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml b/config/feature_flags/beta/enforce_language_server_version.yml similarity index 94% rename from config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml rename to config/feature_flags/beta/enforce_language_server_version.yml index a98caffc18d52d..9b9aa70e332adf 100644 --- a/config/feature_flags/gitlab_com_derisk/enforce_language_server_version.yml +++ b/config/feature_flags/beta/enforce_language_server_version.yml @@ -6,5 +6,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193642 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/541743 milestone: '18.1' group: group::editor extensions -type: gitlab_com_derisk +type: beta default_enabled: false -- GitLab From a8bca0364440116e431bca303fbfa639ef8d96fb Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 05:23:26 +0100 Subject: [PATCH 15/16] Add feature flag information to editor extension settings --- .../settings/editor_extensions.md | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/doc/administration/settings/editor_extensions.md b/doc/administration/settings/editor_extensions.md index 29ce092b01f914..bf747af458a266 100644 --- a/doc/administration/settings/editor_extensions.md +++ b/doc/administration/settings/editor_extensions.md @@ -2,18 +2,20 @@ stage: Create group: Editor Extensions info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments -description: Administer GitLab Editor Extensions including Visual Studio Code, JetBrains IDEs, Visual Studio, Eclipse and Neovim. -title: Administer Editor Extensions +description: Configure GitLab Editor Extensions including Visual Studio Code, JetBrains IDEs, Visual Studio, Eclipse and Neovim. +title: Configure Editor Extensions --- {{< details >}} - Tier: Free, Premium, Ultimate -- Offering: GitLab Self-Managed +- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated {{< /details >}} -You can configure the following restrictions for Editor Extension usage in GitLab: +Configure Editor Extensions settings for your GitLab instance in the Admin area. + +You can enforce the following restrictions on Editor Extensions: - Enforce a minimum language server version. @@ -25,10 +27,21 @@ You can configure the following restrictions for Editor Extension usage in GitLa {{< /history >}} +{{< alert type="flag" >}} + +On GitLab Self-Managed, by default this feature is not available. To make it available, an administrator can [enable the feature flag](../feature_flags.md) named `enforce_language_server_version`. +On GitLab.com, this feature is available but can be configured by GitLab.com administrators only. +On GitLab Dedicated, this feature is available. + +{{< /alert >}} + +By default, any GitLab Language Server version can connect to your GitLab instance when +Personal Access Tokens are enabled. You can configure a minimum language server version and +block requests from clients on older versions. Existing clients will receive an API error + Prerequisites: - You must be an administrator. -- You must enable the `enforce_language_server_version` feature flag for one or more users. ```ruby # For a specific user @@ -40,6 +53,7 @@ Prerequisites: To enforce a minimum GitLab Language Server version: +1. On the left sidebar, at the bottom, select **Admin**. 1. On the left sidebar, select **Settings > General**. 1. Expand **Editor Extensions**. 1. Check **Language Server restrictions enabled**. @@ -47,7 +61,15 @@ To enforce a minimum GitLab Language Server version: To allow any GitLab Language Server clients: +1. On the left sidebar, at the bottom, select **Admin**. 1. On the left sidebar, select **Settings > General**. 1. Expand **Editor Extensions**. 1. Uncheck **Language Server restrictions enabled**. 1. Under **Minimum GitLab Language Server client version**, enter a valid GitLab Language Server version. + +{{< alert type="note" >}} + +Allowing all requests is **not recommended** because it can cause incompatibility if your GitLab version is ahead of your Editor Extensions. +Updating your Editor Extensions is **recommended** to receive the latest feature improvements, bug fixes, and security fixes. + +{{< /alert >}} -- GitLab From cc50f0e898e422785a3c7928b392f21a86b1221c Mon Sep 17 00:00:00 2001 From: Erran Carey Date: Fri, 6 Jun 2025 15:52:09 +0100 Subject: [PATCH 16/16] Add constraint for editor extensions settings hash --- ...itor_extensions_to_application_settings.rb | 24 +++++++++++++++++-- db/structure.sql | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb b/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb index aba616071e69f7..f4506c3458f293 100644 --- a/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb +++ b/db/migrate/20250604171923_add_editor_extensions_to_application_settings.rb @@ -1,9 +1,29 @@ # frozen_string_literal: true class AddEditorExtensionsToApplicationSettings < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.1' - def change - add_column :application_settings, :editor_extensions, :jsonb, default: {}, null: false + CONSTRAINT_NAME = 'check_application_settings_editor_extensions_is_hash' + + def up + with_lock_retries do + add_column :application_settings, :editor_extensions, :jsonb, default: {}, null: false, if_not_exists: true + end + + add_check_constraint( + :application_settings, + "(jsonb_typeof(editor_extensions) = 'object')", + CONSTRAINT_NAME + ) + end + + def down + remove_check_constraint :application_settings, CONSTRAINT_NAME + + with_lock_retries do + remove_column :application_settings, :editor_extensions, if_exists: true + end end end diff --git a/db/structure.sql b/db/structure.sql index 9fd8e68ccbe7fc..8686133f74db3a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9288,6 +9288,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_application_settings_database_reindexing_is_hash CHECK ((jsonb_typeof(database_reindexing) = 'object'::text)), CONSTRAINT check_application_settings_duo_chat_is_hash CHECK ((jsonb_typeof(duo_chat) = 'object'::text)), CONSTRAINT check_application_settings_duo_workflow_is_hash CHECK ((jsonb_typeof(duo_workflow) = 'object'::text)), + CONSTRAINT check_application_settings_editor_extensions_is_hash CHECK ((jsonb_typeof(editor_extensions) = 'object'::text)), CONSTRAINT check_application_settings_elasticsearch_is_hash CHECK ((jsonb_typeof(elasticsearch) = 'object'::text)), CONSTRAINT check_application_settings_group_settings_is_hash CHECK ((jsonb_typeof(group_settings) = 'object'::text)), CONSTRAINT check_application_settings_importers_is_hash CHECK ((jsonb_typeof(importers) = 'object'::text)), -- GitLab