diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index d854b95cb9386f80e25f2a2b5b91e5386f5552b7..53e810035c547e58b27da51c93bcd920fb4c4ebf 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class SystemHooksService - BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES = [GroupMember, Group, ProjectMember].freeze + BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES = [GroupMember, Group, ProjectMember, User].freeze def execute_hooks_for(model, event) data = build_event_data(model, event) @@ -47,15 +47,6 @@ def build_event_data(model, event) if event == :rename || event == :transfer data[:old_path_with_namespace] = model.old_path_with_namespace end - when User - data.merge!(user_data(model)) - - case event - when :rename - data[:old_username] = model.username_before_last_save - when :failed_login - data[:state] = model.state - end end data @@ -79,15 +70,6 @@ def project_data(model) } end - def user_data(model) - { - name: model.name, - email: model.email, - user_id: model.id, - username: model.username - } - end - def builder_driven_event_data_available?(model) model.class.in?(BUILDER_DRIVEN_EVENT_DATA_AVAILABLE_FOR_CLASSES) end @@ -100,10 +82,10 @@ def builder_driven_event_data(model, event) Gitlab::HookData::GroupBuilder when ProjectMember Gitlab::HookData::ProjectMemberBuilder + when User + Gitlab::HookData::UserBuilder end builder_class.new(model).build(event) end end - -SystemHooksService.prepend_if_ee('EE::SystemHooksService') diff --git a/ee/app/services/ee/system_hooks_service.rb b/ee/app/services/ee/system_hooks_service.rb deleted file mode 100644 index 56654aeffe63d43bcffbc06a43fda05504a9f2ce..0000000000000000000000000000000000000000 --- a/ee/app/services/ee/system_hooks_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module EE - module SystemHooksService - extend ::Gitlab::Utils::Override - - private - - override :user_data - def user_data(model) - super.tap do |data| - data.merge!(email_opted_in_data(model)) if ::Gitlab.com? - end - end - - def email_opted_in_data(model) - { - email_opted_in: model.email_opted_in, - email_opted_in_ip: model.email_opted_in_ip, - email_opted_in_source: model.email_opted_in_source, - email_opted_in_at: model.email_opted_in_at - } - end - end -end diff --git a/ee/lib/ee/gitlab/hook_data/user_builder.rb b/ee/lib/ee/gitlab/hook_data/user_builder.rb new file mode 100644 index 0000000000000000000000000000000000000000..3432aa7f4576c12effda4fc148cdec256167fa2b --- /dev/null +++ b/ee/lib/ee/gitlab/hook_data/user_builder.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +module EE + module Gitlab + module HookData + module UserBuilder + extend ::Gitlab::Utils::Override + + # Sample data + # { + # :created_at=>"2021-04-02T09:56:49Z", + # :updated_at=>"2021-04-02T09:56:49Z", + # :event_name=>"user_create", + # :name=>"John Doe", + # :email=>"john@example.com", + # :user_id=>2, + # :username=>"johndoe", + # :email_opted_in=>"john@example.com", + # :email_opted_in_ip=>"192.168.1.1", + # :email_opted_in_source=>"Gitlab.com", + # :email_opted_in_at=>"2021-03-31T10:30:58Z" + # } + + private + + override :user_data + def user_data + super.tap do |data| + data.merge!(email_opted_in_data) if ::Gitlab.com? + end + end + + def email_opted_in_data + { + email_opted_in: user.email_opted_in, + email_opted_in_ip: user.email_opted_in_ip, + email_opted_in_source: user.email_opted_in_source, + email_opted_in_at: user.email_opted_in_at + } + end + end + end + end +end diff --git a/ee/spec/lib/ee/gitlab/hook_data/user_builder_spec.rb b/ee/spec/lib/ee/gitlab/hook_data/user_builder_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0c76502fb10013efdecf63fd346c2680fb340b8e --- /dev/null +++ b/ee/spec/lib/ee/gitlab/hook_data/user_builder_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Gitlab::HookData::UserBuilder do + let_it_be(:user) { create(:user) } + + describe '#build' do + let(:event) { :create } + let(:data) { described_class.new(user).build(event) } + + context 'data for Gitlab.com' do + context 'contains `email_opted_in` attributes' do + let(:user) { create(:user, name: 'John Doe', username: 'johndoe', email: 'john@example.com') } + + before do + expect(Gitlab).to receive(:com?).and_return(true) + end + + it 'returns correct email_opted_in data' do + allow(user).to receive(:email_opted_in).and_return(user.email) + allow(user).to receive(:email_opted_in_ip).and_return('192.168.1.1') + allow(user).to receive(:email_opted_in_source).and_return('Gitlab.com') + allow(user).to receive(:email_opted_in_at).and_return('2021-03-31T10:30:58Z') + + expect(data[:email_opted_in]).to eq('john@example.com') + expect(data[:email_opted_in_ip]).to eq('192.168.1.1') + expect(data[:email_opted_in_source]).to eq('Gitlab.com') + expect(data[:email_opted_in_at]).to eq('2021-03-31T10:30:58Z') + end + end + end + end +end diff --git a/ee/spec/services/ee/system_hooks_service_spec.rb b/ee/spec/services/ee/system_hooks_service_spec.rb deleted file mode 100644 index 7dc4bfa4330944591e702ab67ddc87c32ad4d703..0000000000000000000000000000000000000000 --- a/ee/spec/services/ee/system_hooks_service_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe EE::SystemHooksService do - context 'when group member' do - let(:group) { create(:group) } - let(:group_member) { create(:group_member, group: group) } - - context 'event data' do - it { expect(event_data(group_member, :create)).to include(:event_name, :created_at, :updated_at, :group_name, :group_path, :group_plan, :group_id, :user_name, :user_username, :user_email, :user_id, :group_access) } - it { expect(event_data(group_member, :destroy)).to include(:event_name, :created_at, :updated_at, :group_name, :group_path, :group_plan, :group_id, :user_name, :user_username, :user_email, :user_id, :group_access) } - end - - context 'with a Ultimate plan' do - let(:group) { create(:group_with_plan, plan: :ultimate_plan) } - - it 'returns correct group_plan' do - expect(event_data(group_member, :create)[:group_plan]).to eq('ultimate') - end - end - end - - context 'when user' do - let_it_be(:user) { create(:user) } - - context 'event data' do - context 'for GitLab.com' do - before do - expect(Gitlab).to receive(:com?).and_return(true) - end - - it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :email_opted_in, :email_opted_in_ip, :email_opted_in_source, :email_opted_in_at) } - it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :email_opted_in, :email_opted_in_ip, :email_opted_in_source, :email_opted_in_at) } - end - - context 'for non-GitLab.com' do - before do - expect(Gitlab).to receive(:com?).and_return(false) - end - - it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } - it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username) } - end - end - end - - def event_data(*args) - SystemHooksService.new.send :build_event_data, *args - end -end diff --git a/lib/gitlab/hook_data/user_builder.rb b/lib/gitlab/hook_data/user_builder.rb new file mode 100644 index 0000000000000000000000000000000000000000..537245e948f6a0eb3b6abbe83298086bc5b489c8 --- /dev/null +++ b/lib/gitlab/hook_data/user_builder.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Gitlab + module HookData + class UserBuilder < BaseBuilder + alias_method :user, :object + + # Sample data + # { + # :created_at=>"2021-04-02T10:00:26Z", + # :updated_at=>"2021-04-02T10:00:26Z", + # :event_name=>"user_create", + # :name=>"John Doe", + # :email=>"john@example.com", + # :user_id=>1, + # :username=>"johndoe" + # } + + def build(event) + [ + timestamps_data, + event_data(event), + user_data, + event_specific_user_data(event) + ].reduce(:merge) + end + + private + + def user_data + { + name: user.name, + email: user.email, + user_id: user.id, + username: user.username + } + end + + def event_specific_user_data(event) + case event + when :rename + { old_username: user.username_before_last_save } + when :failed_login + { state: user.state } + else + {} + end + end + end + end +end + +Gitlab::HookData::UserBuilder.prepend_if_ee('EE::Gitlab::HookData::UserBuilder') diff --git a/spec/lib/gitlab/hook_data/user_builder_spec.rb b/spec/lib/gitlab/hook_data/user_builder_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f971089850b4836f4d1a2fcfeab0e3567ac66e0a --- /dev/null +++ b/spec/lib/gitlab/hook_data/user_builder_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::HookData::UserBuilder do + let_it_be(:user) { create(:user, name: 'John Doe', username: 'johndoe', email: 'john@example.com') } + + describe '#build' do + let(:data) { described_class.new(user).build(event) } + let(:event_name) { data[:event_name] } + let(:attributes) do + [ + :event_name, :created_at, :updated_at, :name, :email, :user_id, :username + ] + end + + context 'data' do + shared_examples_for 'includes the required attributes' do + it 'includes the required attributes' do + expect(data).to include(*attributes) + + expect(data[:name]).to eq('John Doe') + expect(data[:email]).to eq('john@example.com') + expect(data[:user_id]).to eq(user.id) + expect(data[:username]).to eq('johndoe') + expect(data[:created_at]).to eq(user.created_at.xmlschema) + expect(data[:updated_at]).to eq(user.updated_at.xmlschema) + end + end + + shared_examples_for 'does not include old username attributes' do + it 'does not include old username attributes' do + expect(data).not_to include(:old_username) + end + end + + shared_examples_for 'does not include state attributes' do + it 'does not include state attributes' do + expect(data).not_to include(:state) + end + end + + context 'on create' do + let(:event) { :create } + + it { expect(event_name).to eq('user_create') } + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include old username attributes' + it_behaves_like 'does not include state attributes' + end + + context 'on destroy' do + let(:event) { :destroy } + + it { expect(event_name).to eq('user_destroy') } + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include old username attributes' + it_behaves_like 'does not include state attributes' + end + + context 'on rename' do + let(:event) { :rename } + + it { expect(event_name).to eq('user_rename') } + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include state attributes' + + it 'includes old username details' do + allow(user).to receive(:username_before_last_save).and_return('old-username') + + expect(data[:old_username]).to eq(user.username_before_last_save) + end + end + + context 'on failed_login' do + let(:event) { :failed_login } + + it { expect(event_name).to eq('user_failed_login') } + it_behaves_like 'includes the required attributes' + it_behaves_like 'does not include old username attributes' + + it 'includes state details' do + user.ldap_block! + + expect(data[:state]).to eq('ldap_blocked') + end + end + end + end +end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 446325e5f71395dccc44082c8256e5b425a74672..d8435c72896a642ae80a1a849969942dce95fdfb 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -113,37 +113,9 @@ expect(data[:old_path]).to eq('old-path') end end - - context 'user_rename' do - it 'contains old and new username' do - allow(user).to receive(:username_before_last_save).and_return('old-username') - - data = event_data(user, :rename) - - expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username) - expect(data[:username]).to eq(user.username) - expect(data[:old_username]).to eq(user.username_before_last_save) - end - end - - context 'user_failed_login' do - it 'contains state of user' do - user.ldap_block! - - data = event_data(user, :failed_login) - - expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :state) - expect(data[:username]).to eq(user.username) - expect(data[:state]).to eq('ldap_blocked') - end - end end context 'event names' do - it { expect(event_name(user, :create)).to eq "user_create" } - it { expect(event_name(user, :destroy)).to eq "user_destroy" } - it { expect(event_name(user, :rename)).to eq 'user_rename' } - it { expect(event_name(user, :failed_login)).to eq 'user_failed_login' } it { expect(event_name(project, :create)).to eq "project_create" } it { expect(event_name(project, :destroy)).to eq "project_destroy" } it { expect(event_name(project, :rename)).to eq "project_rename" }