diff --git a/ee/app/policies/ee/global_policy.rb b/ee/app/policies/ee/global_policy.rb index 3e9396cdcfe91bbc1acfae01975067dce4632403..064ddb202176f98ce4e0b617ac5c95acd7a6a439 100644 --- a/ee/app/policies/ee/global_policy.rb +++ b/ee/app/policies/ee/global_policy.rb @@ -198,6 +198,28 @@ module GlobalPolicy rule { security_policy_bot }.policy do enable :access_git end + + condition(:generate_commit_message_licensed) do + next false unless ::Feature.enabled?(:generate_commit_message_flag, @user) + + ::License.feature_available?(:generate_commit_message) + end + + condition(:user_allowed_to_use_generate_commit_message) do + if generate_commit_message_data.free_access? + user.any_group_with_ai_available? + else + generate_commit_message_data.allowed_for?(@user) + end + end + + rule { generate_commit_message_licensed & user_allowed_to_use_generate_commit_message }.policy do + enable :access_generate_commit_message + end + end + + def generate_commit_message_data + CloudConnector::AvailableServices.find_by_name(:generate_commit_message) end def duo_chat_free_access_was_cut_off? diff --git a/ee/app/services/llm/generate_commit_message_service.rb b/ee/app/services/llm/generate_commit_message_service.rb index 38c479028f64269e6afd835a2d78cce85df1c7a5..828a351e44adcfa9a3a78d7e02076e12d1e28036 100644 --- a/ee/app/services/llm/generate_commit_message_service.rb +++ b/ee/app/services/llm/generate_commit_message_service.rb @@ -4,9 +4,8 @@ module Llm class GenerateCommitMessageService < BaseService def valid? super && - Feature.enabled?(:generate_commit_message_flag, user) && - resource.resource_parent.root_ancestor.licensed_feature_available?(:generate_commit_message) && - Gitlab::Llm::StageCheck.available?(resource.resource_parent, :generate_commit_message) + Gitlab::Llm::StageCheck.available?(resource.resource_parent, :generate_commit_message) && + user.can?(:access_generate_commit_message) end private diff --git a/ee/config/cloud_connector/access_data.yml b/ee/config/cloud_connector/access_data.yml index 3f04657c8d6d3407b26b63fbd2c50ff6919ea909..c18b6443dd6b57caca2767288e2ede132afc9d5a 100644 --- a/ee/config/cloud_connector/access_data.yml +++ b/ee/config/cloud_connector/access_data.yml @@ -72,3 +72,9 @@ services: # Cloud connector features (i.e. code_suggestions, duo_chat...) unit_primitives: - code_suggestions - duo_chat + generate_commit_message: + backend: 'gitlab-ai-gateway' + bundled_with: + duo_enterprise: + unit_primitives: + - generate_commit_message diff --git a/ee/spec/lib/cloud_connector/self_signed/access_data_reader_spec.rb b/ee/spec/lib/cloud_connector/self_signed/access_data_reader_spec.rb index d8c94c6fe2ab693665bf305cb373652d95c4a329..6ede0ae383b6a7fcc67ce9bd5bff27e7e3eec854 100644 --- a/ee/spec/lib/cloud_connector/self_signed/access_data_reader_spec.rb +++ b/ee/spec/lib/cloud_connector/self_signed/access_data_reader_spec.rb @@ -56,6 +56,14 @@ } end + let_it_be(:generate_commit_message_bundled_with) do + { + "duo_enterprise" => %i[ + generate_commit_message + ] + } + end + include_examples 'access data reader' do let_it_be(:available_service_data_class) { CloudConnector::SelfSigned::AvailableServiceData } let_it_be(:arguments_map) do @@ -65,7 +73,8 @@ anthropic_proxy: [nil, anthropic_proxy_bundled_with, backend], vertex_ai_proxy: [nil, vertex_ai_proxy_bundled_with, backend], resolve_vulnerability: [nil, resolve_vulnerability_bundled_with, backend], - self_hosted_models: [self_hosted_models_cut_off_date, self_hosted_models_bundled_with, backend] + self_hosted_models: [self_hosted_models_cut_off_date, self_hosted_models_bundled_with, backend], + generate_commit_message: [nil, generate_commit_message_bundled_with, backend] } end end diff --git a/ee/spec/policies/global_policy_spec.rb b/ee/spec/policies/global_policy_spec.rb index b6b82283277e942d208d27b814d1c3ddeb68af2e..43dcff22f7c5704729ae0343d9ba2c6468b7e7a2 100644 --- a/ee/spec/policies/global_policy_spec.rb +++ b/ee/spec/policies/global_policy_spec.rb @@ -833,4 +833,36 @@ it { is_expected.to be_disallowed(:manage_ai_settings) } end end + + describe 'access_generate_commit_message' do + let(:policy) { :access_generate_commit_message } + + where(:flag_enabled, :licensed, :free_access, :any_group_with_ai_available, :allowed_for, + :enabled_for_user) do + false | false | false | false | false | be_disallowed(:access_generate_commit_message) + true | false | false | false | false | be_disallowed(:access_generate_commit_message) + true | true | true | false | false | be_disallowed(:access_generate_commit_message) + true | true | false | false | false | be_disallowed(:access_generate_commit_message) + true | true | false | false | true | be_allowed(:access_generate_commit_message) + true | true | true | true | false | be_allowed(:access_generate_commit_message) + end + + with_them do + before do + stub_licensed_features(generate_commit_message: licensed) + stub_feature_flags(generate_commit_message_flag: flag_enabled) + + service_data = CloudConnector::SelfManaged::AvailableServiceData.new(:generate_commit_message, nil, nil) + allow(CloudConnector::AvailableServices).to receive(:find_by_name) + .with(:generate_commit_message) + .and_return(service_data) + allow(service_data).to receive(:allowed_for?).with(current_user).and_return(allowed_for) + allow(service_data).to receive(:free_access?).and_return(free_access) + allow(current_user).to receive(:any_group_with_ai_available?) + .and_return(any_group_with_ai_available) + end + + it { is_expected.to enabled_for_user } + end + end end diff --git a/ee/spec/requests/api/graphql/mutations/projects/generate_commit_message_spec.rb b/ee/spec/requests/api/graphql/mutations/projects/generate_commit_message_spec.rb index ecb051f493a8405ddd55f92bcbc57d1b79cc353c..18e00e2a5ea595c120b60b61c5484b71a738ce91 100644 --- a/ee/spec/requests/api/graphql/mutations/projects/generate_commit_message_spec.rb +++ b/ee/spec/requests/api/graphql/mutations/projects/generate_commit_message_spec.rb @@ -25,6 +25,12 @@ stub_ee_application_setting(should_check_namespace_plan: true) stub_licensed_features(generate_commit_message: true, ai_features: true, experimental_features: true) group.namespace_settings.update!(experiment_features_enabled: true) + + service_data = CloudConnector::SelfManaged::AvailableServiceData.new(:generate_commit_message, nil, nil) + allow(CloudConnector::AvailableServices).to receive(:find_by_name) + .with(:generate_commit_message) + .and_return(service_data) + allow(service_data).to receive(:allowed_for?).with(current_user).and_return(true) end it 'successfully performs an generate commit message request' do diff --git a/ee/spec/services/llm/generate_commit_message_service_spec.rb b/ee/spec/services/llm/generate_commit_message_service_spec.rb index f2bca2f4146d602338f5498f37f665be3924e299..421cfdcaed542bf558678509d803ffe163eeb27d 100644 --- a/ee/spec/services/llm/generate_commit_message_service_spec.rb +++ b/ee/spec/services/llm/generate_commit_message_service_spec.rb @@ -14,19 +14,24 @@ before do stub_ee_application_setting(should_check_namespace_plan: true) stub_licensed_features(generate_commit_message: true, ai_features: true, experimental_features: true) + + allow(user).to receive(:can?).with("read_merge_request", resource).and_call_original + allow(user).to receive(:can?).with(:access_duo_features, resource.project).and_call_original + allow(user).to receive(:can?).with(:admin_all_resources).and_call_original + + group.namespace_settings.update!(experiment_features_enabled: true) end describe '#execute' do before do - project.root_ancestor.namespace_settings.update!( - experiment_features_enabled: true - ) allow(Llm::CompletionWorker).to receive(:perform_for) end context 'when the user is permitted to view the merge request' do before do group.add_developer(user) + + allow(user).to receive(:can?).with(:access_generate_commit_message).and_return(true) end it_behaves_like 'schedules completion worker' do @@ -62,7 +67,7 @@ describe '#valid?' do using RSpec::Parameterized::TableSyntax - where(:experiment_features_enabled, :result) do + where(:access_generate_commit_message, :result) do true | true false | false end @@ -70,9 +75,8 @@ with_them do before do group.add_maintainer(user) - project.root_ancestor.namespace_settings.update!( - experiment_features_enabled: experiment_features_enabled - ) + + allow(user).to receive(:can?).with(:access_generate_commit_message).and_return(access_generate_commit_message) end subject { described_class.new(user, resource, options) }