From 5eb6f94b8282c9511533532ef3b236365ee2cb26 Mon Sep 17 00:00:00 2001 From: Alex Buijs <abuijs@gitlab.com> Date: Thu, 11 Jan 2024 16:30:16 +0100 Subject: [PATCH] Log audit events when updating and deleting member roles Whenever a member role is updated or deleted, log an audit event. Changelog: added EE: true --- .../audit_event_types.md | 14 ++++--- ee/app/services/member_roles/base_service.rb | 21 ++++++++++ .../services/member_roles/create_service.rb | 19 +-------- .../services/member_roles/delete_service.rb | 2 + .../services/member_roles/update_service.rb | 2 + .../types/member_role_created.yml | 2 +- .../types/member_role_deleted.yml | 9 +++++ .../types/member_role_updated.yml | 9 +++++ .../member_roles/create_service_spec.rb | 14 +++++-- .../member_roles/delete_service_spec.rb | 31 ++++++++++++++ .../member_roles/update_service_spec.rb | 40 ++++++++++++++++++- 11 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 ee/config/audit_events/types/member_role_deleted.yml create mode 100644 ee/config/audit_events/types/member_role_updated.yml diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md index ae6d042b89fa9..ff99fc1f6c64e 100644 --- a/doc/administration/audit_event_streaming/audit_event_types.md +++ b/doc/administration/audit_event_streaming/audit_event_types.md @@ -64,12 +64,6 @@ Audit event types belong to the following product categories. | [`update_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74632) | Event triggered when an external audit event destination is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/344664) | | [`update_instance_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125846) | Event triggered when an instance level external audit event destination is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.2](https://gitlab.com/gitlab-org/gitlab/-/issues/404730) | -### Authorization - -| Name | Description | Saved to database | Streamed | Introduced in | -|:-----|:------------|:------------------|:---------|:--------------| -| [`member_role_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137087) | Event triggered when a custom role is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/388934) | - ### Code review | Name | Description | Saved to database | Streamed | Introduced in | @@ -332,6 +326,14 @@ Audit event types belong to the following product categories. |:-----|:------------|:------------------|:---------|:--------------| | [`experiment_features_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) | Event triggered on toggling setting for enabling experiment AI features| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.0](https://gitlab.com/gitlab-org/gitlab/-/issues/404856/) | +### Permissions + +| Name | Description | Saved to database | Streamed | Introduced in | +|:-----|:------------|:------------------|:---------|:--------------| +| [`member_role_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137087) | Event triggered when a custom role is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/388934) | +| [`member_role_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630) | Event triggered when a custom role is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.9](https://gitlab.com/gitlab-org/gitlab/-/issues/437672) | +| [`member_role_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630) | Event triggered when a custom role is updated.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.9](https://gitlab.com/gitlab-org/gitlab/-/issues/437672) | + ### Portfolio management | Name | Description | Saved to database | Streamed | Introduced in | diff --git a/ee/app/services/member_roles/base_service.rb b/ee/app/services/member_roles/base_service.rb index 81635c0d7a1fc..b15633a70e910 100644 --- a/ee/app/services/member_roles/base_service.rb +++ b/ee/app/services/member_roles/base_service.rb @@ -24,5 +24,26 @@ def authorized_error def group params[:namespace] || member_role&.namespace end + + def log_audit_event(member_role, action:) + audit_context = { + name: "member_role_#{action}", + author: current_user, + scope: audit_event_scope, + target: member_role, + target_details: { + name: member_role.name, + description: member_role.description, + abilities: member_role.enabled_permissions.join(', ') + }, + message: "Member role was #{action}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + + def audit_event_scope + group || Gitlab::Audit::InstanceScope.new + end end end diff --git a/ee/app/services/member_roles/create_service.rb b/ee/app/services/member_roles/create_service.rb index 9bc37836e03cd..9ff93603bf4af 100644 --- a/ee/app/services/member_roles/create_service.rb +++ b/ee/app/services/member_roles/create_service.rb @@ -46,29 +46,12 @@ def create_member_role member_role = MemberRole.new(params.merge(namespace: group)) if member_role.save - log_audit_event(member_role) + log_audit_event(member_role, action: :created) ::ServiceResponse.success(payload: { member_role: member_role }) else ::ServiceResponse.error(message: member_role.errors.full_messages.join(', ')) end end - - def log_audit_event(member_role) - audit_context = { - name: 'member_role_created', - author: current_user, - scope: audit_event_scope, - target: member_role, - target_details: member_role.enabled_permissions.join(', '), - message: "Member role #{member_role.name} was created" - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - - def audit_event_scope - group || Gitlab::Audit::InstanceScope.new - end end end diff --git a/ee/app/services/member_roles/delete_service.rb b/ee/app/services/member_roles/delete_service.rb index eeb7be263d6d4..4a232252b64a2 100644 --- a/ee/app/services/member_roles/delete_service.rb +++ b/ee/app/services/member_roles/delete_service.rb @@ -8,6 +8,8 @@ def execute(member_role) return authorized_error unless allowed? if member_role.destroy + log_audit_event(member_role, action: :deleted) + ::ServiceResponse.success(payload: { member_role: member_role }) diff --git a/ee/app/services/member_roles/update_service.rb b/ee/app/services/member_roles/update_service.rb index c73ce392247bb..fd817d82eefd5 100644 --- a/ee/app/services/member_roles/update_service.rb +++ b/ee/app/services/member_roles/update_service.rb @@ -16,6 +16,8 @@ def update_member_role member_role.assign_attributes(params.slice(:name, :description, *MemberRole.all_customizable_permissions.keys)) if member_role.save + log_audit_event(member_role, action: :updated) + ::ServiceResponse.success(payload: { member_role: member_role }) else ::ServiceResponse.error(message: member_role.errors.full_messages, payload: { member_role: member_role.reset }) diff --git a/ee/config/audit_events/types/member_role_created.yml b/ee/config/audit_events/types/member_role_created.yml index 26305a0fbd742..84cc54216d984 100644 --- a/ee/config/audit_events/types/member_role_created.yml +++ b/ee/config/audit_events/types/member_role_created.yml @@ -3,7 +3,7 @@ name: member_role_created description: Event triggered when a custom role is created. introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/388934 introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137087 -feature_category: authorization +feature_category: permissions milestone: '16.7' saved_to_database: true streamed: true diff --git a/ee/config/audit_events/types/member_role_deleted.yml b/ee/config/audit_events/types/member_role_deleted.yml new file mode 100644 index 0000000000000..888e03fe8a0f6 --- /dev/null +++ b/ee/config/audit_events/types/member_role_deleted.yml @@ -0,0 +1,9 @@ +--- +name: member_role_deleted +description: Event triggered when a custom role is deleted. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/437672 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630 +feature_category: permissions +milestone: "16.9" +saved_to_database: true +streamed: true diff --git a/ee/config/audit_events/types/member_role_updated.yml b/ee/config/audit_events/types/member_role_updated.yml new file mode 100644 index 0000000000000..2cc23ca0711e4 --- /dev/null +++ b/ee/config/audit_events/types/member_role_updated.yml @@ -0,0 +1,9 @@ +--- +name: member_role_updated +description: Event triggered when a custom role is updated. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/437672 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141630 +feature_category: permissions +milestone: '16.9' +saved_to_database: true +streamed: true diff --git a/ee/spec/services/member_roles/create_service_spec.rb b/ee/spec/services/member_roles/create_service_spec.rb index e29cfc3430d08..96a7569aed6c6 100644 --- a/ee/spec/services/member_roles/create_service_spec.rb +++ b/ee/spec/services/member_roles/create_service_spec.rb @@ -33,6 +33,10 @@ it 'does not create the member role' do expect { create_member_role }.not_to change { MemberRole.count } end + + it 'does not log an audit event' do + expect { create_member_role }.not_to change { AuditEvent.count } + end end shared_examples 'member role creation' do @@ -66,9 +70,13 @@ details: { author_name: user.name, target_id: operation.id, - target_type: 'MemberRole', - target_details: 'admin_merge_request, read_vulnerability', - custom_message: "Member role #{operation.name} was created", + target_type: operation.class.name, + target_details: { + name: operation.name, + description: operation.description, + abilities: operation.enabled_permissions.join(', ') + }.to_s, + custom_message: 'Member role was created', author_class: user.class.name } } diff --git a/ee/spec/services/member_roles/delete_service_spec.rb b/ee/spec/services/member_roles/delete_service_spec.rb index 9ca0946ac96c1..f72c5224d70a7 100644 --- a/ee/spec/services/member_roles/delete_service_spec.rb +++ b/ee/spec/services/member_roles/delete_service_spec.rb @@ -37,6 +37,33 @@ expect(member_role).to be_destroyed end + + include_examples 'audit event logging' do + let(:licensed_features_to_stub) { { custom_roles: true } } + let(:event_type) { 'member_role_deleted' } + let(:operation) { result } + let(:fail_condition!) { allow(member_role).to receive(:destroy).and_return(false) } + + let(:attributes) do + { + author_id: user.id, + entity_id: group.id, + entity_type: group.class.name, + details: { + author_name: user.name, + target_id: member_role.id, + target_type: member_role.class.name, + target_details: { + name: member_role.name, + description: member_role.description, + abilities: member_role.enabled_permissions.join(', ') + }.to_s, + custom_message: 'Member role was deleted', + author_class: user.class.name + } + } + end + end end context 'when failing to destroy the member role' do @@ -50,6 +77,10 @@ expect(result).to be_error expect(result.message).to match_array(['error message']) end + + it 'does not log an audit event' do + expect { result }.not_to change { AuditEvent.count } + end end end end diff --git a/ee/spec/services/member_roles/update_service_spec.rb b/ee/spec/services/member_roles/update_service_spec.rb index 1ca6344e226f1..4c7cf5b684d45 100644 --- a/ee/spec/services/member_roles/update_service_spec.rb +++ b/ee/spec/services/member_roles/update_service_spec.rb @@ -8,7 +8,14 @@ let_it_be(:member_role) { create(:member_role, :guest, namespace: group, read_vulnerability: true) } describe '#execute' do - let(:params) { { name: 'new name', read_vulnerability: false, base_access_level: Gitlab::Access::DEVELOPER } } + let(:params) do + { + name: 'new name', + description: 'new description', + read_vulnerability: false, + base_access_level: Gitlab::Access::DEVELOPER + } + end subject(:result) { described_class.new(user, params).execute(member_role) } @@ -45,6 +52,33 @@ it 'does not update unpermitted attributes' do expect { result }.not_to change { member_role.reload.base_access_level } end + + include_examples 'audit event logging' do + let(:licensed_features_to_stub) { { custom_roles: true } } + let(:event_type) { 'member_role_updated' } + let(:operation) { result } + let(:fail_condition!) { allow(member_role).to receive(:save).and_return(false) } + + let(:attributes) do + { + author_id: user.id, + entity_id: group.id, + entity_type: group.class.name, + details: { + author_name: user.name, + target_id: member_role.id, + target_type: member_role.class.name, + target_details: { + name: 'new name', + description: 'new description', + abilities: '' + }.to_s, + custom_message: 'Member role was updated', + author_class: user.class.name + } + } + end + end end context 'when member role can not be updated' do @@ -63,6 +97,10 @@ it 'includes the object errors' do expect(result.message).to eq(['this is wrong']) end + + it 'does not log an audit event' do + expect { result }.not_to change { AuditEvent.count } + end end end end -- GitLab