From 4df645e6c0ace3d132a8891a80b2d5dbd26c263c Mon Sep 17 00:00:00 2001 From: Eduardo Bonet Date: Mon, 9 Sep 2024 14:26:12 +0200 Subject: [PATCH] Logs events when changing feature config Changing the configuration of a Duo feature model usage now records an audit log Changelog: added --- .../self_hosted_model_feature_changed.yml | 9 ++++ doc/user/compliance/audit_event_types.md | 6 +++ .../admin/ai/feature_settings_controller.rb | 8 ++-- .../ai/feature_settings/update_service.rb | 39 ++++++++++++++++ .../feature_settings/update_service_spec.rb | 44 +++++++++++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 config/audit_events/types/self_hosted_model_feature_changed.yml create mode 100644 ee/app/services/ai/feature_settings/update_service.rb create mode 100644 ee/spec/services/ai/feature_settings/update_service_spec.rb diff --git a/config/audit_events/types/self_hosted_model_feature_changed.yml b/config/audit_events/types/self_hosted_model_feature_changed.yml new file mode 100644 index 00000000000000..e6d76a0f564c59 --- /dev/null +++ b/config/audit_events/types/self_hosted_model_feature_changed.yml @@ -0,0 +1,9 @@ +name: self_hosted_model_feature_changed +description: A self-hosted model feature had its configuration changed +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/463215 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489 +feature_category: self-hosted_models +milestone: '17.4' +saved_to_database: true +scope: [Project] +streamed: true diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index f38d27ef7a4091..893e736ba48ea4 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -449,6 +449,12 @@ Audit event types belong to the following product categories. |:------------|:------------|:------------------|:---------|:--------------|:--------------| | [`policy_project_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102154) | This event is triggered whenever the security policy project is updated for a project. | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/377877) | Group, Project | +### Self-hosted models + +| Name | Description | Saved to database | Streamed | Introduced in | Scope | +|:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`self_hosted_model_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489) | A self-hosted model feature had its configuration changed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/463215) | Project | + ### Source code management | Name | Description | Saved to database | Streamed | Introduced in | Scope | diff --git a/ee/app/controllers/admin/ai/feature_settings_controller.rb b/ee/app/controllers/admin/ai/feature_settings_controller.rb index 4dd63f132a75e3..cff1a9d96fe4e7 100644 --- a/ee/app/controllers/admin/ai/feature_settings_controller.rb +++ b/ee/app/controllers/admin/ai/feature_settings_controller.rb @@ -41,15 +41,17 @@ def create def update @feature_setting = ::Ai::FeatureSetting.find(params[:id]) - if @feature_setting.update(feature_settings_params) + result = ::Ai::FeatureSettings::UpdateService.new( + @feature_setting, current_user, feature_settings_params + ).execute + + if result.success? redirect_to admin_ai_feature_settings_url, notice: _("Feature settings updated successfully") else render :edit end end - private - def feature_settings_params params.require(:feature_setting).permit( :feature, :provider, :ai_self_hosted_model_id diff --git a/ee/app/services/ai/feature_settings/update_service.rb b/ee/app/services/ai/feature_settings/update_service.rb new file mode 100644 index 00000000000000..5d31b2916f181a --- /dev/null +++ b/ee/app/services/ai/feature_settings/update_service.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Ai + module FeatureSettings + class UpdateService + def initialize(feature_setting, user, params) + @feature_setting = feature_setting + @user = user + @params = params + end + + def execute + if feature_setting.update(@params) + audit_event + + ServiceResponse.success(payload: feature_setting) + else + ServiceResponse.error(payload: feature_setting, message: feature_setting.errors.full_messages.join(", ")) + end + end + + private + + attr_accessor :feature_setting, :user + + def audit_event + audit_context = { + name: 'self_hosted_model_feature_changed', + author: user, + scope: user, + target: feature_setting, + message: "Feature #{feature_setting.feature} changed to #{feature_setting.provider_title}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/spec/services/ai/feature_settings/update_service_spec.rb b/ee/spec/services/ai/feature_settings/update_service_spec.rb new file mode 100644 index 00000000000000..8af16f81c7a4f5 --- /dev/null +++ b/ee/spec/services/ai/feature_settings/update_service_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ai::FeatureSettings::UpdateService, feature_category: :"self-hosted_models" do + let_it_be(:user) { create(:user) } + let_it_be(:self_hosted_model) { create(:ai_self_hosted_model) } + let(:feature_setting) { create(:ai_feature_setting, provider: :vendored, self_hosted_model: nil) } + + let(:params) { { provider: :self_hosted, self_hosted_model: self_hosted_model } } + + subject(:service_result) { described_class.new(feature_setting, user, params).execute } + + describe '#execute' do + let(:audit_event) do + { + name: 'self_hosted_model_feature_changed', + author: user, + scope: user, + target: feature_setting, + message: "Feature code_generations changed to Self-hosted model (mistral-7b-ollama-api)" + } + end + + it 'returns a success response' do + expect(Gitlab::Audit::Auditor).to receive(:audit).with(audit_event) + expect { service_result }.to change { feature_setting.reload.provider }.to("self_hosted") + + expect(service_result).to be_success + expect(service_result.payload).to eq(feature_setting) + end + + context 'when update fails' do + let(:params) { { provider: '' } } + + it 'returns an error response' do + expect(Gitlab::Audit::Auditor).not_to receive(:audit) + + expect(service_result).to be_error + expect(service_result.message).to include("Provider can't be blank") + end + end + end +end -- GitLab