Commit e3199ba2 authored by Thong Kuah's avatar Thong Kuah

Merge branch '12702-no-audit-event-when-access-is-removed-due-to-expiration' into 'master'

Resolve "No Audit Event When Access is Removed Due To Expiration"

See merge request gitlab-org/gitlab!20529
parents 26cbc463 362d2ead
...@@ -10,6 +10,8 @@ module AuditEventsHelper ...@@ -10,6 +10,8 @@ module AuditEventsHelper
def select_keys(key, value) def select_keys(key, value)
if key =~ /^(author|target)_.*/ if key =~ /^(author|target)_.*/
"" ""
elsif key.to_s == 'ip_address' && value.blank?
""
else else
"#{key} <strong>#{value}</strong>" "#{key} <strong>#{value}</strong>"
end end
......
...@@ -7,7 +7,6 @@ module EE ...@@ -7,7 +7,6 @@ module EE
def for_member(member) def for_member(member)
action = @details[:action] action = @details[:action]
old_access_level = @details[:old_access_level] old_access_level = @details[:old_access_level]
author_name = @author.name
user_id = member.id user_id = member.id
user_name = member.user ? member.user.name : 'Deleted User' user_name = member.user ? member.user.name : 'Deleted User'
...@@ -16,16 +15,26 @@ module EE ...@@ -16,16 +15,26 @@ module EE
when :destroy when :destroy
{ {
remove: "user_access", remove: "user_access",
author_name: author_name, author_name: @author.name,
target_id: user_id, target_id: user_id,
target_type: "User", target_type: "User",
target_details: user_name target_details: user_name
} }
when :expired
{
remove: "user_access",
author_name: member.created_by ? member.created_by.name : 'Deleted User',
target_id: user_id,
target_type: "User",
target_details: user_name,
system_event: true,
reason: "access expired on #{member.expires_at}"
}
when :create when :create
{ {
add: "user_access", add: "user_access",
as: ::Gitlab::Access.options_with_owner.key(member.access_level.to_i), as: ::Gitlab::Access.options_with_owner.key(member.access_level.to_i),
author_name: author_name, author_name: @author.name,
target_id: user_id, target_id: user_id,
target_type: "User", target_type: "User",
target_details: user_name target_details: user_name
...@@ -35,7 +44,7 @@ module EE ...@@ -35,7 +44,7 @@ module EE
change: "access_level", change: "access_level",
from: old_access_level, from: old_access_level,
to: member.human_access, to: member.human_access,
author_name: author_name, author_name: @author.name,
target_id: user_id, target_id: user_id,
target_type: "User", target_type: "User",
target_details: user_name target_details: user_name
......
...@@ -6,18 +6,30 @@ module EE ...@@ -6,18 +6,30 @@ module EE
def after_execute(member:) def after_execute(member:)
super super
log_audit_event(member: member) if system_event? && removed_due_to_expiry?(member)
log_audit_event(member: member, author: nil, action: :expired)
else
log_audit_event(member: member, author: current_user, action: :destroy)
end
cleanup_group_identity(member) cleanup_group_identity(member)
end end
private private
def log_audit_event(member:) def removed_due_to_expiry?(member)
member.expired?
end
def system_event?
current_user.blank?
end
def log_audit_event(member:, author:, action:)
::AuditEventService.new( ::AuditEventService.new(
current_user, author,
member.source, member.source,
action: :destroy action: action
).for_member(member).security_event ).for_member(member).security_event
end end
......
---
title: Add audit event when member access is removed due to expiration
merge_request: 20529
author:
type: added
...@@ -15,6 +15,8 @@ module Audit ...@@ -15,6 +15,8 @@ module Audit
def humanize def humanize
if @details[:with] if @details[:with]
"Signed in with #{@details[:with].upcase} authentication" "Signed in with #{@details[:with].upcase} authentication"
elsif event_created_by_system?
"#{action_text} via system job. Reason: #{@details[:reason]}"
else else
action_text action_text
end end
...@@ -22,6 +24,10 @@ module Audit ...@@ -22,6 +24,10 @@ module Audit
private private
def event_created_by_system?
@details[:system_event]
end
def action_text def action_text
action = @details.slice(*ACTIONS) action = @details.slice(*ACTIONS)
......
...@@ -44,6 +44,14 @@ describe AuditEventsHelper do ...@@ -44,6 +44,14 @@ describe AuditEventsHelper do
expect(select_keys('target_name', 'John Doe')).to eq '' expect(select_keys('target_name', 'John Doe')).to eq ''
end end
it 'returns empty string if key is ip_address and the value is blank' do
expect(select_keys('ip_address', nil)).to eq ''
end
it 'returns formatted text if key is ip_address and the value is not blank' do
expect(select_keys('ip_address', '127.0.0.1')).to eq 'ip_address <strong>127.0.0.1</strong>'
end
it 'returns formatted text if key does not start with author_, or target_' do it 'returns formatted text if key does not start with author_, or target_' do
expect(select_keys('remove', 'user_access')).to eq 'remove <strong>user_access</strong>' expect(select_keys('remove', 'user_access')).to eq 'remove <strong>user_access</strong>'
end end
......
...@@ -144,5 +144,28 @@ describe Audit::Details do ...@@ -144,5 +144,28 @@ describe Audit::Details do
expect(string).to eq('Updated ref master from b6bce79c to a7bce79c') expect(string).to eq('Updated ref master from b6bce79c to a7bce79c')
end end
end end
context 'system event' do
let(:user_member) { create(:user) }
let(:project) { create(:project) }
let(:member) { create(:project_member, :developer, user: user_member, project: project, expires_at: 1.day.from_now) }
let(:system_event_action) do
{
remove: 'user_access',
author_name: 'Admin User',
target_id: member.id,
target_type: 'User',
target_details: member.user.name,
system_event: true,
reason: "access expired on #{member.expires_at}"
}
end
it 'humanizes system event' do
string = described_class.humanize(system_event_action)
expect(string).to eq("Removed user access via system job. Reason: access expired on #{member.expires_at}")
end
end
end end
end end
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
describe AuditEventService do describe AuditEventService do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project_member) { create(:project_member, user: user) } let(:project_member) { create(:project_member, user: user, expires_at: 1.day.from_now) }
let(:service) { described_class.new(user, project, { action: :destroy }) } let(:service) { described_class.new(user, project, { action: :destroy }) }
describe '#for_member' do describe '#for_member' do
...@@ -26,6 +26,18 @@ describe AuditEventService do ...@@ -26,6 +26,18 @@ describe AuditEventService do
expect(event[:details][:ip_address]).to eq(user.current_sign_in_ip) expect(event[:details][:ip_address]).to eq(user.current_sign_in_ip)
end end
context 'user access expiry' do
let(:service) { described_class.new(nil, project, { action: :expired }) }
it 'generates a system event' do
event = service.for_member(project_member).security_event
expect(event[:details][:remove]).to eq('user_access')
expect(event[:details][:system_event]).to be_truthy
expect(event[:details][:reason]).to include('access expired on')
end
end
context 'admin audit log licensed' do context 'admin audit log licensed' do
before do before do
stub_licensed_features(admin_audit_log: true) stub_licensed_features(admin_audit_log: true)
......
...@@ -8,13 +8,20 @@ describe Members::DestroyService do ...@@ -8,13 +8,20 @@ describe Members::DestroyService do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:member) { group.members.find_by(user_id: member_user.id) } let(:member) { group.members.find_by(user_id: member_user.id) }
subject { described_class.new(current_user) }
before do before do
group.add_owner(current_user) group.add_owner(current_user)
group.add_developer(member_user) group.add_developer(member_user)
end end
shared_examples_for 'logs an audit event' do
it do
expect { event }.to change { SecurityEvent.count }.by(1)
end
end
context 'when current_user is present' do
subject { described_class.new(current_user) }
context 'with group membership via Group SAML' do context 'with group membership via Group SAML' do
let!(:saml_provider) { create(:saml_provider, group: group) } let!(:saml_provider) { create(:saml_provider, group: group) }
...@@ -36,4 +43,41 @@ describe Members::DestroyService do ...@@ -36,4 +43,41 @@ describe Members::DestroyService do
end end
end end
end end
context 'audit events' do
it_behaves_like 'logs an audit event' do
let(:event) { subject.execute(member, {}) }
end
it 'does not log the audit event as a system event' do
subject.execute(member, skip_authorization: true)
details = AuditEvent.last.details
expect(details[:system_event]).to be_nil
expect(details[:reason]).to be_nil
end
end
end
context 'when current user is not present' do # ie, when the system initiates the destroy
subject { described_class.new(nil) }
context 'for members with expired access' do
let(:member) { create(:project_member, user: member_user, expires_at: 1.day.ago) }
context 'audit events' do
it_behaves_like 'logs an audit event' do
let(:event) { subject.execute(member, skip_authorization: true) }
end
it 'logs the audit event as a system event' do
subject.execute(member, skip_authorization: true)
details = AuditEvent.last.details
expect(details[:system_event]).to be_truthy
expect(details[:reason]).to include('access expired on')
end
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment