Commit 5eccf5b3 authored by Etienne Baqué's avatar Etienne Baqué

Merge branch '335016-add-filter-audit-events-by-username' into 'master'

[Backend] Update audit events to allow filtering by username

See merge request gitlab-org/gitlab!73741
parents cfe21ca7 7d35fa3e
......@@ -30,6 +30,8 @@ class AuditEvent < ApplicationRecord
scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) }
scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) }
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
scope :by_entity_username, -> (username) { where(entity_id: find_user_id(username)) }
scope :by_author_username, -> (username) { where(author_id: find_user_id(username)) }
after_initialize :initialize_details
......@@ -106,6 +108,10 @@ class AuditEvent < ApplicationRecord
self[name] = self.details[name] = original
end
end
def self.find_user_id(username)
User.find_by_username(username)&.id
end
end
AuditEvent.prepend_mod_with('AuditEvent')
......@@ -23,7 +23,7 @@ class Admin::AuditLogsController < Admin::ApplicationController
@entity = case audit_logs_params[:entity_type]
when 'User'
User.find_by_id(audit_logs_params[:entity_id])
user_entity
when 'Project'
Project.find_by_id(audit_logs_params[:entity_id])
when 'Group'
......@@ -54,4 +54,12 @@ class Admin::AuditLogsController < Admin::ApplicationController
def check_license_admin_audit_log_available!
render_404 unless License.feature_available?(:admin_audit_log)
end
def user_entity
if audit_logs_params[:entity_username].present?
return User.find_by_username(audit_logs_params[:entity_username])
end
User.find_by_id(audit_logs_params[:entity_id])
end
end
......@@ -3,7 +3,7 @@
module AuditEvents
module AuditLogsParams
def audit_logs_params
params.permit(:entity_type, :entity_id, :created_before, :created_after, :sort, :author_id)
params.permit(:entity_type, :entity_id, :created_before, :created_after, :sort, :author_id, :entity_username, :author_username)
end
def audit_params
......
......@@ -55,7 +55,9 @@ class AuditLogFinder
audit_events = audit_events.by_entity_type(params[:entity_type])
if valid_entity_id?
if valid_entity_username?
audit_events = audit_events.by_entity_username(params[:entity_username])
elsif valid_entity_id?
audit_events = audit_events.by_entity_id(params[:entity_id])
end
......@@ -63,9 +65,13 @@ class AuditLogFinder
end
def by_author(audit_events)
return audit_events unless valid_author_id?
if valid_author_username?
audit_events = audit_events.by_author_username(params[:author_username])
elsif valid_author_id?
audit_events = audit_events.by_author_id(params[:author_id])
end
audit_events.by_author_id(params[:author_id])
audit_events
end
def sort(audit_events)
......@@ -83,4 +89,16 @@ class AuditLogFinder
def valid_author_id?
params[:author_id].to_i.nonzero?
end
def valid_username?(username)
username.present? && username.length >= User::MIN_USERNAME_LENGTH && username.length <= User::MAX_USERNAME_LENGTH
end
def valid_entity_username?
valid_username?(params[:entity_username])
end
def valid_author_username?
valid_username?(params[:author_username])
end
end
......@@ -43,5 +43,28 @@ RSpec.describe Admin::AuditLogsController do
)
end
end
context 'by user' do
before do
stub_licensed_features(admin_audit_log: true)
end
it 'finds the user by id when provided with a entity_id' do
allow(User).to receive(:find_by_id).and_return(admin)
get :index, params: { 'entity_type': 'User', 'entity_id': '1' }
expect(User).to have_received(:find_by_id).with('1')
end
it 'finds the user by username when provided with a entity_username' do
allow(User).to receive(:find_by_username).and_return(admin)
get :index, params: { 'entity_type': 'User', 'entity_username': 'abc' }
# find_by_username gets called in thee controller and in the AuditEvent model
expect(User).to have_received(:find_by_username).twice.with('abc')
end
end
end
end
......@@ -9,7 +9,7 @@ RSpec.describe AuditLogFinder do
let_it_be(:project) { create(:project, namespace: group) }
let_it_be(:subproject) { create(:project, namespace: subgroup) }
let_it_be(:user_audit_event) { create(:user_audit_event, created_at: 3.days.ago) }
let_it_be(:user_audit_event) { create(:user_audit_event, created_at: 3.days.ago, entity_id: user.id) }
let_it_be(:project_audit_event) { create(:project_audit_event, entity_id: project.id, author_id: user.id, created_at: 2.days.ago) }
let_it_be(:subproject_audit_event) { create(:project_audit_event, entity_id: subproject.id, created_at: 2.days.ago) }
let_it_be(:group_audit_event) { create(:group_audit_event, entity_id: group.id, author_id: user.id, created_at: 1.day.ago) }
......@@ -259,6 +259,92 @@ RSpec.describe AuditLogFinder do
end
end
end
context 'filtering by entity_username' do
context 'User Event' do
let(:params) { { entity_type: 'User', entity_username: user.username } }
let(:entity_type) { 'User' }
let(:audit_event) { user_audit_event }
it 'finds the right event' do
expect(subject.count).to eq(1)
entity = subject.first
expect(entity.entity_type).to eq(entity_type)
expect(entity.id).to eq(audit_event.id)
expect(entity.entity_id).to eq(user.id)
end
end
end
context 'filtering by author_username' do
context 'username is too short' do
let(:params) { { author_username: 'a' * (User::MIN_USERNAME_LENGTH - 1) } }
it 'ignores author_username and returns all events irrespective of entity_type' do
expect(subject.count).to eq(4)
end
end
context 'username is too long' do
let(:params) { { author_username: 'a' * (User::MAX_USERNAME_LENGTH + 1) } }
it 'ignores author_username and returns all events irrespective of entity_type' do
expect(subject.count).to eq(4)
end
end
shared_examples 'finds the right event' do
it 'finds the right event' do
expect(subject.count).to eq(1)
entity = subject.first
expect(entity.entity_type).to eq(entity_type)
expect(entity.id).to eq(audit_event.id)
expect(entity.author_id).to eq(audit_event.author_id)
end
end
context 'Instance Event' do
let(:level) { Gitlab::Audit::Levels::Instance.new }
let(:params) { { author_username: user.username } }
it 'finds all the events the user authored', :aggregate_failures do
expect(subject.count).to eq(2)
subject.each do |entity|
expect(entity.author_id).to eq(user.id)
end
end
end
context 'Group Event' do
let(:level) { Gitlab::Audit::Levels::Group.new(group: group) }
let(:params) { { author_username: user.username } }
before do
# Only looking for group event, with this on it tests Group and Project events
stub_feature_flags(audit_log_group_level: false)
end
it_behaves_like 'finds the right event' do
let(:entity_type) { 'Group' }
let(:audit_event) { group_audit_event }
end
end
context 'Project Event' do
let(:level) { Gitlab::Audit::Levels::Project.new(project: project) }
let(:params) { { author_username: user.username } }
it_behaves_like 'finds the right event' do
let(:entity_type) { 'Project' }
let(:audit_event) { project_audit_event }
end
end
end
end
describe '#find_by!' do
......
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