Commit f49acdf4 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '333416-add-missing-pat-audit-events-attributes' into 'master'

Add missing attributes on token audit events

See merge request gitlab-org/gitlab!63958
parents b2ba9483 a7713e44
...@@ -88,7 +88,12 @@ class AuditEvent < ApplicationRecord ...@@ -88,7 +88,12 @@ class AuditEvent < ApplicationRecord
end end
def parallel_persist def parallel_persist
PARALLEL_PERSISTENCE_COLUMNS.each { |col| self[col] = details[col] } PARALLEL_PERSISTENCE_COLUMNS.each do |name|
original = self[name] || self.details[name]
next unless original
self[name] = self.details[name] = original
end
end end
end end
......
...@@ -21,6 +21,8 @@ module EE ...@@ -21,6 +21,8 @@ module EE
::AuditEventService.new( ::AuditEventService.new(
current_user, current_user,
resource, resource,
target_id: token&.id,
target_type: token&.class&.name,
target_details: token&.user&.name, target_details: token&.user&.name,
action: :custom, action: :custom,
custom_message: message, custom_message: message,
......
...@@ -13,15 +13,18 @@ module EE ...@@ -13,15 +13,18 @@ module EE
def audit_event_service(token, response) def audit_event_service(token, response)
message = if response.success? message = if response.success?
"Revoked #{resource.class.name.downcase} access token with token_id: #{access_token.id}" "Revoked #{resource.class.name.downcase} access token with token_id: #{token.id}"
else else
"Attempted to revoke #{resource.class.name.downcase} access token with token_id: #{access_token.id}, but failed with message: #{response.message}" "Attempted to revoke #{resource.class.name.downcase} access token with token_id: #{token.id}, " \
"but failed with message: #{response.message}"
end end
::AuditEventService.new( ::AuditEventService.new(
current_user, current_user,
resource, resource,
target_details: access_token.user.name, target_id: token.id,
target_type: token.class.name,
target_details: token.user.name,
action: :custom, action: :custom,
custom_message: message, custom_message: message,
ip_address: current_user.current_sign_in_ip ip_address: current_user.current_sign_in_ip
......
...@@ -14,26 +14,6 @@ RSpec.describe AuditEvent, type: :model do ...@@ -14,26 +14,6 @@ RSpec.describe AuditEvent, type: :model do
end end
describe 'callbacks' do describe 'callbacks' do
context 'parallel_persist' do
let_it_be(:details) do
{ author_name: 'Kungfu Panda', entity_path: 'gitlab-org/gitlab', target_details: 'Project X', target_type: 'User' }
end
let_it_be(:event) { create(:project_audit_event, details: details, entity_path: nil, target_details: nil) }
it 'sets author_name' do
expect(event[:author_name]).to eq('Kungfu Panda')
end
it 'sets entity_path' do
expect(event[:entity_path]).to eq('gitlab-org/gitlab')
end
it 'sets target_details' do
expect(event[:target_details]).to eq('Project X')
end
end
context 'truncate_fields' do context 'truncate_fields' do
shared_examples 'a truncated field' do shared_examples 'a truncated field' do
context 'when values are provided' do context 'when values are provided' do
......
...@@ -7,17 +7,21 @@ RSpec.describe AuditEventPresenter do ...@@ -7,17 +7,21 @@ RSpec.describe AuditEventPresenter do
let(:details) do let(:details) do
{ {
author_name: 'author', change: 'name',
ip_address: '127.0.0.1',
target_details: 'target name',
entity_path: 'path',
from: 'a', from: 'a',
to: 'b' to: 'b',
entity_path: 'path'
} }
end end
let(:audit_event) do let(:audit_event) do
create(:audit_event, ip_address: '10.2.1.1', details: details) create(
:audit_event,
author_name: 'author',
target_details: 'target name',
ip_address: '10.2.1.1',
details: details
)
end end
subject(:presenter) do subject(:presenter) do
...@@ -36,13 +40,7 @@ RSpec.describe AuditEventPresenter do ...@@ -36,13 +40,7 @@ RSpec.describe AuditEventPresenter do
end end
context 'event authored by a user that no longer exists' do context 'event authored by a user that no longer exists' do
let(:user) { create(:user) } let(:audit_event) { build(:audit_event, user: build(:user), details: details) }
let(:audit_event) { create(:audit_event, user: user, details: details) }
before do
user.destroy!
audit_event.reload
end
context 'when `author_name` is not included in the details' do context 'when `author_name` is not included in the details' do
let(:details) do let(:details) do
...@@ -62,52 +60,13 @@ RSpec.describe AuditEventPresenter do ...@@ -62,52 +60,13 @@ RSpec.describe AuditEventPresenter do
end end
context 'when `author_name` is included in the details and not in the author_name column' do context 'when `author_name` is included in the details and not in the author_name column' do
before do let(:audit_event) { build(:audit_event, author_name: nil, details: details) }
audit_event.update!(author_name: nil)
end
it 'shows the author name as provided in the details' do it 'shows the author name as provided in the details' do
expect(presenter.author_name).to eq(details[:author_name]) expect(presenter.author_name).to eq(details[:author_name])
end end
end end
end end
context 'event authored by an unauthenticated user' do
before do
audit_event.author_id = -1
end
context 'when `author_name` is not included in details and not in the author_name column' do
before do
audit_event.update!(author_name: nil)
end
let(:details) do
{
author_name: nil,
ip_address: '127.0.0.1',
target_details: 'target name',
entity_path: 'path',
from: 'a',
to: 'b'
}
end
it 'shows `An unauthenticated user` as the author name' do
expect(presenter.author_name).to eq('An unauthenticated user')
end
end
context 'when `author_name` is included in details and not in the author_name column' do
before do
audit_event.update!(author_name: nil)
end
it 'shows the author name as provided in the details' do
expect(presenter.author_name).to eq('author')
end
end
end
end end
describe '#target' do describe '#target' do
...@@ -153,6 +112,6 @@ RSpec.describe AuditEventPresenter do ...@@ -153,6 +112,6 @@ RSpec.describe AuditEventPresenter do
end end
it 'exposes the action' do it 'exposes the action' do
expect(presenter.action).to eq('Changed author from a to b') expect(presenter.action).to eq('Changed name from a to b')
end end
end end
...@@ -27,9 +27,12 @@ RSpec.describe AuditEvents::ImpersonationAuditEventService do ...@@ -27,9 +27,12 @@ RSpec.describe AuditEvents::ImpersonationAuditEventService do
expect { service.security_event }.to change(AuditEvent, :count).by(1) expect { service.security_event }.to change(AuditEvent, :count).by(1)
security_event = AuditEvent.last security_event = AuditEvent.last
expect(security_event.details).to eq(custom_message: message, expect(security_event.details).to eq(
ip_address: ip_address, author_name: impersonator.name,
action: :custom) custom_message: message,
ip_address: ip_address,
action: :custom
)
expect(security_event.author_id).to eq(impersonator.id) expect(security_event.author_id).to eq(impersonator.id)
expect(security_event.entity_id).to eq(impersonator.id) expect(security_event.entity_id).to eq(impersonator.id)
expect(security_event.entity_type).to eq('User') expect(security_event.entity_type).to eq('User')
......
...@@ -84,9 +84,17 @@ RSpec.describe ResourceAccessTokens::CreateService do ...@@ -84,9 +84,17 @@ RSpec.describe ResourceAccessTokens::CreateService do
response = subject response = subject
audit_event = AuditEvent.where(author_id: user.id).last audit_event = AuditEvent.where(author_id: user.id).last
access_token = response.payload[:access_token]
expect(audit_event.details[:custom_message]).to eq("Created project access token with token_id: #{response.payload[:access_token].id} with scopes: #{response.payload[:access_token].scopes}") custom_message = <<~MESSAGE.squish
expect(audit_event.details[:target_details]).to match(response.payload[:access_token].user.name) Created project access token with token_id: #{access_token.id} with scopes: #{access_token.scopes}
MESSAGE
expect(audit_event.details).to include(
custom_message: custom_message,
target_id: access_token.id,
target_type: access_token.class.name,
target_details: access_token.user.name
)
end end
end end
...@@ -101,12 +109,24 @@ RSpec.describe ResourceAccessTokens::CreateService do ...@@ -101,12 +109,24 @@ RSpec.describe ResourceAccessTokens::CreateService do
it 'logs the permission error message' do it 'logs the permission error message' do
subject subject
expect(AuditEvent.where(author_id: user.id).last.details[:custom_message]).to eq('Attempted to create project access token but failed with message: User does not have permission to create project access token') audit_event = AuditEvent.where(author_id: user.id).last
custom_message = <<~MESSAGE.squish
Attempted to create project access token but failed with message:
User does not have permission to create project access token
MESSAGE
expect(audit_event.details).to include(
custom_message: custom_message,
target_id: nil,
target_type: nil,
target_details: nil
)
end end
end end
context "when access provisioning fails" do context "when access provisioning fails" do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:unpersisted_member) { build(:project_member, source: resource, user: user) } let(:unpersisted_member) { build(:project_member, source: resource, user: user) }
before do before do
...@@ -123,7 +143,18 @@ RSpec.describe ResourceAccessTokens::CreateService do ...@@ -123,7 +143,18 @@ RSpec.describe ResourceAccessTokens::CreateService do
it 'logs the provisioning error message' do it 'logs the provisioning error message' do
subject subject
expect(AuditEvent.where(author_id: user.id).last.details[:custom_message]).to eq('Attempted to create project access token but failed with message: Could not provision maintainer access to project access token') audit_event = AuditEvent.where(author_id: user.id).last
custom_message = <<~MESSAGE.squish
Attempted to create project access token but failed with message:
Could not provision maintainer access to project access token
MESSAGE
expect(audit_event.details).to include(
custom_message: custom_message,
target_id: nil,
target_type: nil,
target_details: nil
)
end end
end end
end end
......
...@@ -7,6 +7,7 @@ RSpec.describe ResourceAccessTokens::RevokeService do ...@@ -7,6 +7,7 @@ RSpec.describe ResourceAccessTokens::RevokeService do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:resource_bot) { create(:user, :project_bot) } let_it_be(:resource_bot) { create(:user, :project_bot) }
let(:access_token) { create(:personal_access_token, user: resource_bot) } let(:access_token) { create(:personal_access_token, user: resource_bot) }
shared_examples 'audit event details' do shared_examples 'audit event details' do
...@@ -40,8 +41,12 @@ RSpec.describe ResourceAccessTokens::RevokeService do ...@@ -40,8 +41,12 @@ RSpec.describe ResourceAccessTokens::RevokeService do
audit_event = AuditEvent.where(author_id: user.id).last audit_event = AuditEvent.where(author_id: user.id).last
expect(audit_event.details[:custom_message]).to match(/Revoked project access token with token_id: \d+/) expect(audit_event.details).to include(
expect(audit_event.details[:target_details]).to eq(access_token.user.name) custom_message: match(/Revoked project access token with token_id: \d+/),
target_id: access_token.id,
target_type: access_token.class.name,
target_details: access_token.user.name
)
end end
end end
...@@ -56,7 +61,18 @@ RSpec.describe ResourceAccessTokens::RevokeService do ...@@ -56,7 +61,18 @@ RSpec.describe ResourceAccessTokens::RevokeService do
it 'logs the find error message' do it 'logs the find error message' do
subject subject
expect(AuditEvent.where(author_id: user.id).last.details[:custom_message]).to match(/Attempted to revoke project access token with token_id: \d+, but failed with message: Failed to find bot user/) audit_event = AuditEvent.where(author_id: user.id).last
custom_message = <<~MESSAGE.squish
Attempted to revoke project access token with token_id: \\d+, but failed with message:
Failed to find bot user
MESSAGE
expect(audit_event.details).to include(
custom_message: match(custom_message),
target_id: access_token.id,
target_type: access_token.class.name,
target_details: access_token.user.name
)
end end
end end
...@@ -71,7 +87,18 @@ RSpec.describe ResourceAccessTokens::RevokeService do ...@@ -71,7 +87,18 @@ RSpec.describe ResourceAccessTokens::RevokeService do
it 'logs the permission error message' do it 'logs the permission error message' do
subject subject
expect(AuditEvent.where(author_id: user.id).last.details[:custom_message]).to match(/Attempted to revoke project access token with token_id: \d+, but failed with message: #{user.name} cannot delete #{access_token.user.name}/) audit_event = AuditEvent.where(author_id: user.id).last
custom_message = <<~MESSAGE.squish
Attempted to revoke project access token with token_id: \\d+, but failed with message:
#{user.name} cannot delete #{access_token.user.name}
MESSAGE
expect(audit_event.details).to include(
custom_message: match(custom_message),
target_id: access_token.id,
target_type: access_token.class.name,
target_details: access_token.user.name
)
end end
end end
end end
......
...@@ -60,9 +60,12 @@ RSpec.shared_examples 'logs the custom audit event' do ...@@ -60,9 +60,12 @@ RSpec.shared_examples 'logs the custom audit event' do
expect { service.security_event }.to change(AuditEvent, :count).by(1) expect { service.security_event }.to change(AuditEvent, :count).by(1)
security_event = AuditEvent.last security_event = AuditEvent.last
expect(security_event.details).to eq(custom_message: custom_message, expect(security_event.details).to eq(
ip_address: ip_address, author_name: user.name,
action: :custom) custom_message: custom_message,
ip_address: ip_address,
action: :custom
)
expect(security_event.author_id).to eq(user.id) expect(security_event.author_id).to eq(user.id)
expect(security_event.entity_id).to eq(entity.id) expect(security_event.entity_id).to eq(entity.id)
expect(security_event.entity_type).to eq(entity_type) expect(security_event.entity_type).to eq(entity_type)
...@@ -101,11 +104,14 @@ RSpec.shared_examples 'logs the release audit event' do ...@@ -101,11 +104,14 @@ RSpec.shared_examples 'logs the release audit event' do
security_event = AuditEvent.last security_event = AuditEvent.last
expect(security_event.details).to eq(custom_message: custom_message, expect(security_event.details).to eq(
ip_address: ip_address, author_name: user.name,
target_details: target_details, custom_message: custom_message,
target_id: target_id, ip_address: ip_address,
target_type: target_type) target_details: target_details,
target_id: target_id,
target_type: target_type
)
expect(security_event.author_id).to eq(user.id) expect(security_event.author_id).to eq(user.id)
expect(security_event.entity_id).to eq(entity.id) expect(security_event.entity_id).to eq(entity.id)
......
...@@ -51,6 +51,7 @@ FactoryBot.define do ...@@ -51,6 +51,7 @@ FactoryBot.define do
trait :unauthenticated do trait :unauthenticated do
author_id { -1 } author_id { -1 }
author_name { 'An unauthenticated user' }
details do details do
{ {
custom_message: 'Custom action', custom_message: 'Custom action',
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe AuditEvent do RSpec.describe AuditEvent do
let_it_be(:audit_event) { create(:project_audit_event) }
subject { audit_event }
describe 'validations' do describe 'validations' do
include_examples 'validates IP address' do include_examples 'validates IP address' do
let(:attribute) { :ip_address } let(:attribute) { :ip_address }
...@@ -13,6 +10,71 @@ RSpec.describe AuditEvent do ...@@ -13,6 +10,71 @@ RSpec.describe AuditEvent do
end end
end end
describe 'callbacks' do
describe '#parallel_persist' do
shared_examples 'a parallel persisted field' do
using RSpec::Parameterized::TableSyntax
where(:column, :details, :expected_value) do
:value | nil | :value
nil | :value | :value
:value | :another_value | :value
nil | nil | nil
end
with_them do
let(:values) { { value: value, another_value: "#{value}88" } }
let(:audit_event) do
build(:audit_event, name => values[column], details: { name => values[details] })
end
it 'sets both values to be the same', :aggregate_failures do
audit_event.validate
expect(audit_event[name]).to eq(values[expected_value])
expect(audit_event.details[name]).to eq(values[expected_value])
end
end
end
context 'wih author_name' do
let(:name) { :author_name }
let(:value) { 'Mary Poppins' }
it_behaves_like 'a parallel persisted field'
end
context 'with entity_path' do
let(:name) { :entity_path }
let(:value) { 'gitlab-org' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_details' do
let(:name) { :target_details }
let(:value) { 'gitlab-org/gitlab' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_type' do
let(:name) { :target_type }
let(:value) { 'Project' }
it_behaves_like 'a parallel persisted field'
end
context 'with target_id' do
let(:name) { :target_id }
let(:value) { 8 }
it_behaves_like 'a parallel persisted field'
end
end
end
describe '#as_json' do describe '#as_json' do
context 'ip_address' do context 'ip_address' do
subject { build(:group_audit_event, ip_address: '192.168.1.1').as_json } subject { build(:group_audit_event, ip_address: '192.168.1.1').as_json }
......
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