diff --git a/app/services/namespaces/groups/archive_service.rb b/app/services/namespaces/groups/archive_service.rb index d1be2490ee9400009eb5cd9179f6dd4ffb693ea8..f0c153699025718590a29d4030efbfffbc3b5fce 100644 --- a/app/services/namespaces/groups/archive_service.rb +++ b/app/services/namespaces/groups/archive_service.rb @@ -25,14 +25,21 @@ def execute return AncestorAlreadyArchivedError if group.ancestors_archived? return ArchivingFailedError unless group.archive + after_archive ServiceResponse.success end private + def after_archive + system_hook_service.execute_hooks_for(group, :update) + end + def error_response(message) ServiceResponse.error(message: message) end end end end + +Namespaces::Groups::ArchiveService.prepend_mod diff --git a/app/services/namespaces/groups/unarchive_service.rb b/app/services/namespaces/groups/unarchive_service.rb index 267c214596e25cbe71360b34d8ee6395fbb6494b..05c67f2cd959bd3697beb4a7d411cbb5f0630cc3 100644 --- a/app/services/namespaces/groups/unarchive_service.rb +++ b/app/services/namespaces/groups/unarchive_service.rb @@ -25,14 +25,21 @@ def execute return AlreadyUnarchivedError unless group.archived return UnarchivingFailedError unless group.unarchive + after_unarchive ServiceResponse.success end private + def after_unarchive + system_hook_service.execute_hooks_for(group, :update) + end + def error_response(message) ServiceResponse.error(message: message) end end end end + +Namespaces::Groups::UnarchiveService.prepend_mod diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index 1818b441d3ad9b18d2b75b9183bcea8b278993c9..cc19f27c427ea79d57323409257e632d6e702d31 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -358,6 +358,7 @@ Audit event types belong to the following product categories. | [`emails_enabled_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973) | Setting Enable email notifications is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | | [`enabled_git_access_protocol_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973) | Setting Enabled Git access protocols is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | | [`enforce_ssh_certificates_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973) | Setting Enforce SSH Certificates is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | +| [`group_archived`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/199419) | A group is archived | {{< icon name="check-circle" >}} Yes | GitLab [18.3](https://gitlab.com/gitlab-org/gitlab/-/issues/520013) | Group | | [`group_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121005) | A group is created | {{< icon name="check-circle" >}} Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/411595) | Group | | [`group_deletion_marked`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116986) | A group is marked for deletion | {{< icon name="check-circle" >}} Yes | GitLab [15.11](https://gitlab.com/gitlab-org/gitlab/-/issues/374106) | Group | | [`group_description_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973/) | Group description is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | @@ -374,6 +375,7 @@ Audit event types belong to the following product categories. | [`group_share_with_group_link_removed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112719) | You remove a group from another group by using the group's membership page | {{< icon name="check-circle" >}} Yes | GitLab [15.10](https://gitlab.com/gitlab-org/gitlab/-/issues/327909) | Group | | [`group_share_with_group_link_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112719) | You update a group's access settings to another group by using the group's membership page | {{< icon name="check-circle" >}} Yes | GitLab [15.10](https://gitlab.com/gitlab-org/gitlab/-/issues/327909) | Group | | [`group_shared_with_group_lock_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973/) | Group can be shared with other group setting is updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | +| [`group_unarchived`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/199419) | A group is unarchived | {{< icon name="check-circle" >}} Yes | GitLab [18.3](https://gitlab.com/gitlab-org/gitlab/-/issues/520013) | Group | | [`group_visibility_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106079) | A group's visibility level is updated | {{< icon name="check-circle" >}} Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369322) | Group | | [`prevent_sharing_groups_outside_hierarchy_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164973) | A group's setting to Members cannot invite groups outside of group and its subgroup updated | {{< icon name="check-circle" >}} Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/486532) | Group | | [`project_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117543) | A project is created | {{< icon name="check-circle" >}} Yes | GitLab [16.0](https://gitlab.com/gitlab-org/gitlab/-/issues/374105) | Project | diff --git a/ee/app/services/ee/namespaces/groups/archive_service.rb b/ee/app/services/ee/namespaces/groups/archive_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..5a9c11b55104ab7fc0abfd772ea78df4fcde191b --- /dev/null +++ b/ee/app/services/ee/namespaces/groups/archive_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module EE + module Namespaces + module Groups + module ArchiveService + extend ::Gitlab::Utils::Override + + private + + override :after_archive + def after_archive + super + + log_audit_event + end + + def log_audit_event + audit_context = { + name: 'group_archived', + author: current_user, + target: group, + scope: group, + message: 'Group archived' + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end + end +end diff --git a/ee/app/services/ee/namespaces/groups/unarchive_service.rb b/ee/app/services/ee/namespaces/groups/unarchive_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..28fb2a4dfdcadf21b40694d8658cd8c046155831 --- /dev/null +++ b/ee/app/services/ee/namespaces/groups/unarchive_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module EE + module Namespaces + module Groups + module UnarchiveService + extend ::Gitlab::Utils::Override + + private + + override :after_unarchive + def after_unarchive + super + + log_audit_event + end + + def log_audit_event + audit_context = { + name: 'group_unarchived', + author: current_user, + target: group, + scope: group, + message: 'Group unarchived' + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end + end +end diff --git a/ee/config/audit_events/types/group_archived.yml b/ee/config/audit_events/types/group_archived.yml new file mode 100644 index 0000000000000000000000000000000000000000..c530c4c6339effb05fb521cfb386cef516c8d318 --- /dev/null +++ b/ee/config/audit_events/types/group_archived.yml @@ -0,0 +1,10 @@ +--- +name: group_archived +description: A group is archived +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/520013 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/199419 +feature_category: groups_and_projects +milestone: '18.3' +saved_to_database: true +streamed: true +scope: [Group] diff --git a/ee/config/audit_events/types/group_unarchived.yml b/ee/config/audit_events/types/group_unarchived.yml new file mode 100644 index 0000000000000000000000000000000000000000..b36778eb5168c63c04ce44019ff61499753df3f0 --- /dev/null +++ b/ee/config/audit_events/types/group_unarchived.yml @@ -0,0 +1,10 @@ +--- +name: group_unarchived +description: A group is unarchived +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/520013 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/199419 +feature_category: groups_and_projects +milestone: '18.3' +saved_to_database: true +streamed: true +scope: [Group] diff --git a/ee/spec/requests/api/groups_spec.rb b/ee/spec/requests/api/groups_spec.rb index ab61d50309a4840ed3b0421630eecb07daa8644d..ae10ec0eca3e41092170feb54c726e5a7a3d43e3 100644 --- a/ee/spec/requests/api/groups_spec.rb +++ b/ee/spec/requests/api/groups_spec.rb @@ -223,6 +223,42 @@ end end + describe 'POST /groups/:id/archive' do + let(:path) { "/groups/#{group.id}/archive" } + + context 'when group archiving fails' do + it 'does not log an audit event' do + expect { post api(path, another_user) }.not_to change { AuditEvent.count } + end + end + + context 'when project archiving succeeds' do + it 'logs an audit event' do + expect { post api(path, user) }.to change { AuditEvent.count }.by(1) + end + end + end + + describe 'POST /groups/:id/unarchive' do + let(:path) { "/groups/#{group.id}/unarchive" } + + context 'when group unarchiving fails' do + it 'does not log an audit event' do + expect { post api(path, another_user) }.not_to change { AuditEvent.count } + end + end + + context 'when group unarchiving succeeds' do + before do + group.namespace_settings.update!(archived: true) + end + + it 'logs an audit event' do + expect { post api(path, user) }.to change { AuditEvent.count }.by(1) + end + end + end + describe 'PUT /groups/:id' do let_it_be(:admin_mode) { false } diff --git a/ee/spec/services/ee/namespaces/groups/archive_service_spec.rb b/ee/spec/services/ee/namespaces/groups/archive_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..acc8cbcceb2b5506307562af68b1683a231f5456 --- /dev/null +++ b/ee/spec/services/ee/namespaces/groups/archive_service_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespaces::Groups::ArchiveService, feature_category: :groups_and_projects do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + let(:service) { described_class.new(group, user) } + + describe '#execute' do + context 'when group archiving fails' do + it 'does not log an audit event' do + expect { service.execute }.not_to change { AuditEvent.count } + end + end + + context 'when group archiving succeeds' do + before_all do + group.add_owner(user) + end + + it 'logs an audit event' do + expect { service.execute }.to change { AuditEvent.count }.by(1) + + audit_event = AuditEvent.last + expect(audit_event).to have_attributes( + author_id: user.id, + entity_type: 'Group', + target_type: "Group", + author_name: user.name + ) + expect(audit_event.details).to include( + event_name: "group_archived", + author_name: user.name, + author_class: "User", + target_type: "Group", + custom_message: "Group archived" + ) + end + end + end +end diff --git a/ee/spec/services/ee/namespaces/groups/unarchive_service_spec.rb b/ee/spec/services/ee/namespaces/groups/unarchive_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..77713c1cb4ea0bc45e1bcb3d26cd4fdd64e69326 --- /dev/null +++ b/ee/spec/services/ee/namespaces/groups/unarchive_service_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespaces::Groups::UnarchiveService, feature_category: :groups_and_projects do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + let(:service) { described_class.new(group, user) } + + describe '#execute' do + context 'when group unarchiving fails' do + it 'does not log an audit event' do + expect { service.execute }.not_to change { AuditEvent.count } + end + end + + context 'when group unarchiving succeeds' do + before_all do + group.add_owner(user) + group.namespace_settings.update!(archived: true) + end + + it 'logs an audit event' do + expect { service.execute }.to change { AuditEvent.count }.by(1) + + audit_event = AuditEvent.last + expect(audit_event).to have_attributes( + author_id: user.id, + entity_type: 'Group', + target_type: "Group", + author_name: user.name + ) + expect(audit_event.details).to include( + event_name: "group_unarchived", + author_name: user.name, + author_class: "User", + target_type: "Group", + custom_message: "Group unarchived" + ) + end + end + end +end