Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
0ecca2b3
Commit
0ecca2b3
authored
Apr 03, 2020
by
Tan Le
Committed by
Sean McGivern
Apr 03, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Revert "Merge branch 'tle-view-audit-events-query-opt' into 'master'""
This reverts commit
c6935b7b
.
parent
8d02faa1
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
494 additions
and
102 deletions
+494
-102
app/models/audit_event.rb
app/models/audit_event.rb
+15
-1
app/services/audit_event_service.rb
app/services/audit_event_service.rb
+9
-1
ee/app/controllers/admin/audit_logs_controller.rb
ee/app/controllers/admin/audit_logs_controller.rb
+5
-2
ee/app/controllers/groups/audit_events_controller.rb
ee/app/controllers/groups/audit_events_controller.rb
+3
-1
ee/app/controllers/projects/audit_events_controller.rb
ee/app/controllers/projects/audit_events_controller.rb
+3
-1
ee/app/models/ee/audit_event.rb
ee/app/models/ee/audit_event.rb
+9
-23
ee/app/presenters/audit_event_presenter.rb
ee/app/presenters/audit_event_presenter.rb
+6
-8
ee/app/services/ee/audit_event_service.rb
ee/app/services/ee/audit_event_service.rb
+4
-4
ee/changelogs/unreleased/fix-audit-events-controllers-n-plus-1.yml
...logs/unreleased/fix-audit-events-controllers-n-plus-1.yml
+5
-0
ee/lib/gitlab/audit/deleted_author.rb
ee/lib/gitlab/audit/deleted_author.rb
+8
-0
ee/lib/gitlab/audit/events/preloader.rb
ee/lib/gitlab/audit/events/preloader.rb
+18
-0
ee/lib/gitlab/audit/null_author.rb
ee/lib/gitlab/audit/null_author.rb
+36
-0
ee/lib/gitlab/audit/null_entity.rb
ee/lib/gitlab/audit/null_entity.rb
+8
-0
ee/lib/gitlab/audit/unauthenticated_author.rb
ee/lib/gitlab/audit/unauthenticated_author.rb
+17
-0
ee/spec/factories/audit_events.rb
ee/spec/factories/audit_events.rb
+21
-14
ee/spec/lib/gitlab/audit/events/preloader_spec.rb
ee/spec/lib/gitlab/audit/events/preloader_spec.rb
+36
-0
ee/spec/lib/gitlab/audit/null_author_spec.rb
ee/spec/lib/gitlab/audit/null_author_spec.rb
+22
-0
ee/spec/lib/gitlab/audit/unauthenticated_author_spec.rb
ee/spec/lib/gitlab/audit/unauthenticated_author_spec.rb
+17
-0
ee/spec/models/audit_event_spec.rb
ee/spec/models/audit_event_spec.rb
+2
-2
ee/spec/requests/admin/audit_events_spec.rb
ee/spec/requests/admin/audit_events_spec.rb
+39
-0
ee/spec/requests/api/audit_events_spec.rb
ee/spec/requests/api/audit_events_spec.rb
+2
-2
ee/spec/requests/api/repositories_spec.rb
ee/spec/requests/api/repositories_spec.rb
+13
-3
ee/spec/requests/groups/audit_events_spec.rb
ee/spec/requests/groups/audit_events_spec.rb
+44
-0
ee/spec/requests/projects/audit_events_spec.rb
ee/spec/requests/projects/audit_events_spec.rb
+40
-0
ee/spec/services/audit_event_service_spec.rb
ee/spec/services/audit_event_service_spec.rb
+112
-40
No files found.
app/models/audit_event.rb
View file @
0ecca2b3
...
@@ -30,12 +30,26 @@ class AuditEvent < ApplicationRecord
...
@@ -30,12 +30,26 @@ class AuditEvent < ApplicationRecord
end
end
def
author_name
def
author_name
self
.
use
r
.
name
lazy_autho
r
.
name
end
end
def
formatted_details
def
formatted_details
details
.
merge
(
details
.
slice
(
:from
,
:to
).
transform_values
(
&
:to_s
))
details
.
merge
(
details
.
slice
(
:from
,
:to
).
transform_values
(
&
:to_s
))
end
end
def
lazy_author
BatchLoader
.
for
(
author_id
).
batch
(
default_value:
default_author_value
)
do
|
author_ids
,
loader
|
User
.
where
(
id:
author_ids
).
find_each
do
|
user
|
loader
.
call
(
user
.
id
,
user
)
end
end
end
private
def
default_author_value
::
Gitlab
::
Audit
::
NullAuthor
.
for
(
author_id
,
details
[
:author_name
])
end
end
end
AuditEvent
.
prepend_if_ee
(
'EE::AuditEvent'
)
AuditEvent
.
prepend_if_ee
(
'EE::AuditEvent'
)
app/services/audit_event_service.rb
View file @
0ecca2b3
...
@@ -13,7 +13,7 @@ class AuditEventService
...
@@ -13,7 +13,7 @@ class AuditEventService
#
#
# @return [AuditEventService]
# @return [AuditEventService]
def
initialize
(
author
,
entity
,
details
=
{})
def
initialize
(
author
,
entity
,
details
=
{})
@author
=
author
@author
=
build_author
(
author
)
@entity
=
entity
@entity
=
entity
@details
=
details
@details
=
details
end
end
...
@@ -49,6 +49,14 @@ class AuditEventService
...
@@ -49,6 +49,14 @@ class AuditEventService
private
private
def
build_author
(
author
)
if
author
.
is_a?
(
User
)
author
else
Gitlab
::
Audit
::
UnauthenticatedAuthor
.
new
(
name:
author
)
end
end
def
base_payload
def
base_payload
{
{
author_id:
@author
.
id
,
author_id:
@author
.
id
,
...
...
ee/app/controllers/admin/audit_logs_controller.rb
View file @
0ecca2b3
...
@@ -10,7 +10,7 @@ class Admin::AuditLogsController < Admin::ApplicationController
...
@@ -10,7 +10,7 @@ class Admin::AuditLogsController < Admin::ApplicationController
PER_PAGE
=
25
PER_PAGE
=
25
def
index
def
index
@events
=
audit_log_events
.
page
(
params
[
:page
]).
per
(
PER_PAGE
).
without_count
@events
=
audit_log_events
@entity
=
case
audit_logs_params
[
:entity_type
]
@entity
=
case
audit_logs_params
[
:entity_type
]
when
'User'
when
'User'
...
@@ -27,7 +27,10 @@ class Admin::AuditLogsController < Admin::ApplicationController
...
@@ -27,7 +27,10 @@ class Admin::AuditLogsController < Admin::ApplicationController
private
private
def
audit_log_events
def
audit_log_events
AuditLogFinder
.
new
(
audit_logs_params
).
execute
events
=
AuditLogFinder
.
new
(
audit_logs_params
).
execute
events
=
events
.
page
(
params
[
:page
]).
per
(
PER_PAGE
).
without_count
Gitlab
::
Audit
::
Events
::
Preloader
.
preload!
(
events
)
end
end
def
check_license_admin_audit_log_available!
def
check_license_admin_audit_log_available!
...
...
ee/app/controllers/groups/audit_events_controller.rb
View file @
0ecca2b3
...
@@ -11,7 +11,9 @@ class Groups::AuditEventsController < Groups::ApplicationController
...
@@ -11,7 +11,9 @@ class Groups::AuditEventsController < Groups::ApplicationController
layout
'group_settings'
layout
'group_settings'
def
index
def
index
@events
=
AuditLogFinder
.
new
(
audit_logs_params
).
execute
.
page
(
params
[
:page
])
events
=
AuditLogFinder
.
new
(
audit_logs_params
).
execute
.
page
(
params
[
:page
])
@events
=
Gitlab
::
Audit
::
Events
::
Preloader
.
preload!
(
events
)
end
end
private
private
...
...
ee/app/controllers/projects/audit_events_controller.rb
View file @
0ecca2b3
...
@@ -12,7 +12,9 @@ class Projects::AuditEventsController < Projects::ApplicationController
...
@@ -12,7 +12,9 @@ class Projects::AuditEventsController < Projects::ApplicationController
layout
'project_settings'
layout
'project_settings'
def
index
def
index
@events
=
AuditLogFinder
.
new
(
audit_logs_params
).
execute
.
page
(
params
[
:page
])
events
=
AuditLogFinder
.
new
(
audit_logs_params
).
execute
.
page
(
params
[
:page
])
@events
=
Gitlab
::
Audit
::
Events
::
Preloader
.
preload!
(
events
)
end
end
private
private
...
...
ee/app/models/ee/audit_event.rb
View file @
0ecca2b3
...
@@ -5,36 +5,22 @@ module EE
...
@@ -5,36 +5,22 @@ module EE
extend
ActiveSupport
::
Concern
extend
ActiveSupport
::
Concern
extend
::
Gitlab
::
Utils
::
Override
extend
::
Gitlab
::
Utils
::
Override
# While tracking events that could take place even when
# a user is not logged in, (eg: downloading repo of a public project),
# we set the author_id of such events as -1
UNAUTH_USER_AUTHOR_ID
=
-
1
# Events that are authored by unathenticated users, should be
# shown as authored by `An unauthenticated user` in the UI.
UNAUTH_USER_AUTHOR_NAME
=
'An unauthenticated user'
.
freeze
override
:author_name
def
author_name
if
(
author_name
=
details
[
:author_name
].
presence
||
user
&
.
name
)
author_name
elsif
authored_by_unauth_user?
UNAUTH_USER_AUTHOR_NAME
end
end
def
entity
def
entity
return
unless
entity_type
&&
entity_id
lazy_entity
# Avoiding exception if the record doesn't exist
@entity
||=
entity_type
.
constantize
.
find_by_id
(
entity_id
)
# rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
def
present
def
present
AuditEventPresenter
.
new
(
self
)
AuditEventPresenter
.
new
(
self
)
end
end
def
authored_by_unauth_user?
def
lazy_entity
author_id
==
UNAUTH_USER_AUTHOR_ID
BatchLoader
.
for
(
entity_id
)
.
batch
(
key:
entity_type
,
default_value:
::
Gitlab
::
Audit
::
NullEntity
.
new
)
do
|
ids
,
loader
,
args
|
model
=
Object
.
const_get
(
args
[
:key
],
false
)
model
.
where
(
id:
ids
).
find_each
{
|
record
|
loader
.
call
(
record
.
id
,
record
)
}
end
end
end
end
end
end
end
ee/app/presenters/audit_event_presenter.rb
View file @
0ecca2b3
...
@@ -4,13 +4,11 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple
...
@@ -4,13 +4,11 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple
presents
:audit_event
presents
:audit_event
def
author_name
def
author_name
user
=
audit_event
.
use
r
author
=
audit_event
.
lazy_autho
r
if
user
return
author
.
name
if
author
.
is_a?
(
Gitlab
::
Audit
::
NullAuthor
)
link_to
(
user
.
name
,
user_path
(
user
))
else
link_to
(
author
.
name
,
user_path
(
author
))
audit_event
.
author_name
end
end
end
def
target
def
target
...
@@ -26,9 +24,9 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple
...
@@ -26,9 +24,9 @@ class AuditEventPresenter < Gitlab::View::Presenter::Simple
end
end
def
object
def
object
entity
=
audit_event
.
entity
entity
=
audit_event
.
lazy_
entity
return
unless
entity
return
if
entity
.
is_a?
(
Gitlab
::
Audit
::
NullEntity
)
link_to
(
details
[
:entity_path
]
||
entity
.
name
,
entity
).
html_safe
link_to
(
details
[
:entity_path
]
||
entity
.
name
,
entity
).
html_safe
end
end
...
...
ee/app/services/ee/audit_event_service.rb
View file @
0ecca2b3
...
@@ -89,7 +89,7 @@ module EE
...
@@ -89,7 +89,7 @@ module EE
@details
=
{
@details
=
{
failed_login:
auth
.
upcase
,
failed_login:
auth
.
upcase
,
author_name:
@author
,
author_name:
@author
.
name
,
target_details:
@author
,
target_details:
@author
,
ip_address:
ip
ip_address:
ip
}
}
...
@@ -141,7 +141,7 @@ module EE
...
@@ -141,7 +141,7 @@ module EE
@details
[
:entity_path
]
=
@entity
&
.
full_path
if
admin_audit_log_enabled?
@details
[
:entity_path
]
=
@entity
&
.
full_path
if
admin_audit_log_enabled?
SecurityEvent
.
create
(
SecurityEvent
.
create
(
author_id:
@author
.
respond_to?
(
:id
)
?
@author
.
id
:
AuditEvent
::
UNAUTH_USER_AUTHOR_ID
,
author_id:
@author
.
id
,
entity_id:
@entity
.
respond_to?
(
:id
)
?
@entity
.
id
:
-
1
,
entity_id:
@entity
.
respond_to?
(
:id
)
?
@entity
.
id
:
-
1
,
entity_type:
'User'
,
entity_type:
'User'
,
details:
@details
details:
@details
...
@@ -213,7 +213,7 @@ module EE
...
@@ -213,7 +213,7 @@ module EE
override
:base_payload
override
:base_payload
def
base_payload
def
base_payload
{
{
author_id:
@author
.
respond_to?
(
:id
)
?
@author
.
id
:
AuditEvent
::
UNAUTH_USER_AUTHOR_ID
,
author_id:
@author
.
id
,
# `@author.respond_to?(:id)` is to support cases where we need to log events
# `@author.respond_to?(:id)` is to support cases where we need to log events
# that could take place even when a user is unathenticated, Eg: downloading a public repo.
# that could take place even when a user is unathenticated, Eg: downloading a public repo.
# For such events, it is not mandatory that an author is always present.
# For such events, it is not mandatory that an author is always present.
...
@@ -260,7 +260,7 @@ module EE
...
@@ -260,7 +260,7 @@ module EE
end
end
def
ip_address
def
ip_address
@author
&
.
current_sign_in_ip
||
@details
[
:ip_address
]
@author
.
current_sign_in_ip
||
@details
[
:ip_address
]
end
end
def
add_security_event_admin_details!
def
add_security_event_admin_details!
...
...
ee/changelogs/unreleased/fix-audit-events-controllers-n-plus-1.yml
0 → 100644
View file @
0ecca2b3
---
title
:
Fix N+1 queries in Audit Events controllers
merge_request
:
28399
author
:
type
:
performance
ee/lib/gitlab/audit/deleted_author.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
module
Gitlab
module
Audit
class
DeletedAuthor
<
Gitlab
::
Audit
::
NullAuthor
end
end
end
ee/lib/gitlab/audit/events/preloader.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
module
Gitlab
module
Audit
module
Events
class
Preloader
def
self
.
preload!
(
audit_events
)
audit_events
.
tap
do
|
audit_events
|
audit_events
.
each
do
|
audit_event
|
audit_event
.
lazy_author
audit_event
.
lazy_entity
end
end
end
end
end
end
end
ee/lib/gitlab/audit/null_author.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
module
Gitlab
module
Audit
class
NullAuthor
attr_reader
:id
,
:name
# Creates an Author
#
# While tracking events that could take place even when
# a user is not logged in, (eg: downloading repo of a public project),
# we set the author_id of such events as -1
#
# @param [Integer] id
# @param [String] name
#
# @return [Gitlab::Audit::UnauthenticatedAuthor, Gitlab::Audit::DeletedAuthor]
def
self
.
for
(
id
,
name
)
if
id
==
-
1
Gitlab
::
Audit
::
UnauthenticatedAuthor
.
new
(
name:
name
)
else
Gitlab
::
Audit
::
DeletedAuthor
.
new
(
id:
id
,
name:
name
)
end
end
def
initialize
(
id
:,
name
:)
@id
=
id
@name
=
name
end
def
current_sign_in_ip
nil
end
end
end
end
ee/lib/gitlab/audit/null_entity.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
module
Gitlab
module
Audit
class
NullEntity
end
end
end
ee/lib/gitlab/audit/unauthenticated_author.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
module
Gitlab
module
Audit
class
UnauthenticatedAuthor
<
Gitlab
::
Audit
::
NullAuthor
def
initialize
(
name:
nil
)
super
(
id:
-
1
,
name:
name
)
end
# Events that are authored by unathenticated users, should be
# shown as authored by `An unauthenticated user` in the UI.
def
name
@name
||
'An unauthenticated user'
end
end
end
end
ee/spec/factories/audit_events.rb
View file @
0ecca2b3
...
@@ -4,33 +4,38 @@ FactoryBot.define do
...
@@ -4,33 +4,38 @@ FactoryBot.define do
factory
:audit_event
,
class:
'SecurityEvent'
,
aliases:
[
:user_audit_event
]
do
factory
:audit_event
,
class:
'SecurityEvent'
,
aliases:
[
:user_audit_event
]
do
user
user
transient
{
target_user
{
create
(
:user
)
}
}
entity_type
{
'User'
}
entity_type
{
'User'
}
entity_id
{
user
.
id
}
entity_id
{
target_
user
.
id
}
details
do
details
do
{
{
change:
'email address'
,
change:
'email address'
,
from:
'admin@gitlab.com'
,
from:
'admin@gitlab.com'
,
to:
'maintainer@gitlab.com'
,
to:
'maintainer@gitlab.com'
,
author_name:
user
.
name
,
author_name:
user
.
name
,
target_id:
user
.
id
,
target_id:
target_
user
.
id
,
target_type:
'User'
,
target_type:
'User'
,
target_details:
user
.
name
,
target_details:
target_
user
.
name
,
ip_address:
'127.0.0.1'
,
ip_address:
'127.0.0.1'
,
entity_path:
user
.
username
entity_path:
target_
user
.
username
}
}
end
end
trait
:project_event
do
trait
:project_event
do
transient
{
target_project
{
create
(
:project
)
}
}
entity_type
{
'Project'
}
entity_type
{
'Project'
}
entity_id
{
create
(
:project
)
.
id
}
entity_id
{
target_project
.
id
}
details
do
details
do
{
{
add:
'user_access'
,
change:
'packges_enabled'
,
as:
'Developer'
,
from:
true
,
to:
false
,
author_name:
user
.
name
,
author_name:
user
.
name
,
target_id:
user
.
id
,
target_id:
target_project
.
id
,
target_type:
'
User
'
,
target_type:
'
Project
'
,
target_details:
user
.
name
,
target_details:
target_project
.
name
,
ip_address:
'127.0.0.1'
,
ip_address:
'127.0.0.1'
,
entity_path:
'gitlab.org/gitlab-ce'
entity_path:
'gitlab.org/gitlab-ce'
}
}
...
@@ -38,17 +43,19 @@ FactoryBot.define do
...
@@ -38,17 +43,19 @@ FactoryBot.define do
end
end
trait
:group_event
do
trait
:group_event
do
transient
{
target_group
{
create
(
:group
)
}
}
entity_type
{
'Group'
}
entity_type
{
'Group'
}
entity_id
{
create
(
:group
)
.
id
}
entity_id
{
target_group
.
id
}
details
do
details
do
{
{
change:
'project_creation_level'
,
change:
'project_creation_level'
,
from:
nil
,
from:
nil
,
to:
'Developers + Maintainers'
,
to:
'Developers + Maintainers'
,
author_name:
'Administrator'
,
author_name:
user
.
name
,
target_id:
1
,
target_id:
target_group
.
id
,
target_type:
'Group'
,
target_type:
'Group'
,
target_details:
"gitlab-org"
,
target_details:
target_group
.
name
,
ip_address:
'127.0.0.1'
,
ip_address:
'127.0.0.1'
,
entity_path:
"gitlab-org"
entity_path:
"gitlab-org"
}
}
...
...
ee/spec/lib/gitlab/audit/events/preloader_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
Gitlab
::
Audit
::
Events
::
Preloader
do
describe
'.preload!'
do
let_it_be
(
:audit_events
)
{
create_list
(
:audit_event
,
2
)
}
let
(
:audit_events_relation
)
{
AuditEvent
.
where
(
id:
audit_events
.
map
(
&
:id
))
}
subject
{
described_class
.
preload!
(
audit_events_relation
)
}
it
'returns an ActiveRecord::Relation'
do
expect
(
subject
).
to
be_an
(
ActiveRecord
::
Relation
)
end
it
'preloads associated records'
do
log
=
ActiveRecord
::
QueryRecorder
.
new
do
subject
.
map
do
|
event
|
[
event
.
author_name
,
event
.
lazy_entity
.
name
]
end
end
# Expected queries when requesting for AuditEvent with associated records
#
# 1. On the audit_events table
# SELECT "audit_events".* FROM "audit_events"
# 2. On the users table for author_name
# SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 3)
# 3. On the users table for entity name
# SELECT "users".* FROM "users" WHERE "users"."id" IN (2, 4)
#
expect
(
log
.
count
).
to
eq
(
3
)
end
end
end
ee/spec/lib/gitlab/audit/null_author_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
Gitlab
::
Audit
::
NullAuthor
do
subject
{
described_class
}
describe
'.for'
do
it
'returns an DeletedAuthor'
do
expect
(
subject
.
for
(
666
,
'Old Hat'
)).
to
be_a
(
Gitlab
::
Audit
::
DeletedAuthor
)
end
it
'returns an UnauthenticatedAuthor when id equals -1'
,
:aggregate_failures
do
expect
(
subject
.
for
(
-
1
,
'Frank'
)).
to
be_a
(
Gitlab
::
Audit
::
UnauthenticatedAuthor
)
expect
(
subject
.
for
(
-
1
,
'Frank'
)).
to
have_attributes
(
id:
-
1
,
name:
'Frank'
)
end
end
describe
'#current_sign_in_ip'
do
it
{
expect
(
subject
.
new
(
id:
888
,
name:
'Guest'
).
current_sign_in_ip
).
to
be_nil
}
end
end
ee/spec/lib/gitlab/audit/unauthenticated_author_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
Gitlab
::
Audit
::
UnauthenticatedAuthor
do
describe
'#initialize'
do
it
'sets correct attributes'
do
expect
(
described_class
.
new
(
name:
'Peppa Pig'
))
.
to
have_attributes
(
id:
-
1
,
name:
'Peppa Pig'
)
end
it
'sets default name when it is not provided'
do
expect
(
described_class
.
new
)
.
to
have_attributes
(
id:
-
1
,
name:
'An unauthenticated user'
)
end
end
end
ee/spec/models/audit_event_spec.rb
View file @
0ecca2b3
...
@@ -91,8 +91,8 @@ RSpec.describe AuditEvent, type: :model do
...
@@ -91,8 +91,8 @@ RSpec.describe AuditEvent, type: :model do
context
'when entity does not exist'
do
context
'when entity does not exist'
do
subject
(
:event
)
{
described_class
.
new
(
entity_id:
non_existing_record_id
,
entity_type:
'User'
)
}
subject
(
:event
)
{
described_class
.
new
(
entity_id:
non_existing_record_id
,
entity_type:
'User'
)
}
it
'returns
nil
'
do
it
'returns
a NullEntity
'
do
expect
(
event
.
entity
).
to
be_
blank
expect
(
event
.
entity
).
to
be_
a
(
Gitlab
::
Audit
::
NullEntity
)
end
end
end
end
end
end
...
...
ee/spec/requests/admin/audit_events_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
'view audit events'
do
describe
'GET /audit_events'
do
let_it_be
(
:admin
)
{
create
(
:admin
)
}
let_it_be
(
:audit_event
)
{
create
(
:user_audit_event
)
}
before
do
stub_licensed_features
(
admin_audit_log:
true
)
login_as
(
admin
)
end
it
'returns 200 response'
do
send_request
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
end
it
'avoids N+1 DB queries'
,
:request_store
do
# warm up cache so these initial queries would not leak in our QueryRecorder
send_request
control
=
ActiveRecord
::
QueryRecorder
.
new
(
skip_cached:
false
)
{
send_request
}
create_list
(
:user_audit_event
,
2
)
expect
do
send_request
end
.
not_to
exceed_all_query_limit
(
control
)
end
def
send_request
get
admin_audit_logs_path
end
end
end
ee/spec/requests/api/audit_events_spec.rb
View file @
0ecca2b3
...
@@ -107,7 +107,7 @@ describe API::AuditEvents do
...
@@ -107,7 +107,7 @@ describe API::AuditEvents do
expect
(
response
[
"id"
]).
to
eq
(
user_audit_event
.
id
)
expect
(
response
[
"id"
]).
to
eq
(
user_audit_event
.
id
)
expect
(
response
[
"author_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
response
[
"author_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
response
[
"entity_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
response
[
"entity_id"
]).
to
eq
(
user_audit_event
.
entity_
id
)
expect
(
response
[
"entity_type"
]).
to
eq
(
'User'
)
expect
(
response
[
"entity_type"
]).
to
eq
(
'User'
)
expect
(
Time
.
parse
(
response
[
"created_at"
])).
to
be_like_time
(
user_audit_event
.
created_at
)
expect
(
Time
.
parse
(
response
[
"created_at"
])).
to
be_like_time
(
user_audit_event
.
created_at
)
expect
(
details
).
to
eq
user_audit_event
.
formatted_details
.
with_indifferent_access
expect
(
details
).
to
eq
user_audit_event
.
formatted_details
.
with_indifferent_access
...
@@ -157,7 +157,7 @@ describe API::AuditEvents do
...
@@ -157,7 +157,7 @@ describe API::AuditEvents do
expect
(
json_response
[
"id"
]).
to
eq
(
user_audit_event
.
id
)
expect
(
json_response
[
"id"
]).
to
eq
(
user_audit_event
.
id
)
expect
(
json_response
[
"author_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
json_response
[
"author_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
json_response
[
"entity_id"
]).
to
eq
(
user_audit_event
.
user
.
id
)
expect
(
json_response
[
"entity_id"
]).
to
eq
(
user_audit_event
.
entity_
id
)
expect
(
json_response
[
"entity_type"
]).
to
eq
(
'User'
)
expect
(
json_response
[
"entity_type"
]).
to
eq
(
'User'
)
expect
(
Time
.
parse
(
json_response
[
"created_at"
])).
to
be_like_time
(
user_audit_event
.
created_at
)
expect
(
Time
.
parse
(
json_response
[
"created_at"
])).
to
be_like_time
(
user_audit_event
.
created_at
)
expect
(
details
).
to
eq
user_audit_event
.
formatted_details
.
with_indifferent_access
expect
(
details
).
to
eq
user_audit_event
.
formatted_details
.
with_indifferent_access
...
...
ee/spec/requests/api/repositories_spec.rb
View file @
0ecca2b3
...
@@ -6,18 +6,28 @@ describe API::Repositories do
...
@@ -6,18 +6,28 @@ describe API::Repositories do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
describe
"GET /projects/:id/repository/archive(.:format)?:sha"
do
describe
"GET /projects/:id/repository/archive(.:format)?:sha"
do
shared_examples
'
logs the audit even
t'
do
shared_examples
'
an auditable and successful reques
t'
do
let
(
:route
)
{
"/projects/
#{
project
.
id
}
/repository/archive"
}
let
(
:route
)
{
"/projects/
#{
project
.
id
}
/repository/archive"
}
before
do
stub_licensed_features
(
admin_audit_log:
true
)
end
it
'logs the audit event'
do
it
'logs the audit event'
do
expect
do
expect
do
get
api
(
route
,
current_user
)
get
api
(
route
,
current_user
)
end
.
to
change
{
SecurityEvent
.
count
}.
by
(
1
)
end
.
to
change
{
SecurityEvent
.
count
}.
by
(
1
)
end
end
it
'sends the archive'
do
get
api
(
route
,
current_user
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
end
end
end
context
'when unauthenticated'
,
'and project is public'
do
context
'when unauthenticated'
,
'and project is public'
do
it_behaves_like
'
logs the audit even
t'
do
it_behaves_like
'
an auditable and successful reques
t'
do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
let
(
:current_user
)
{
nil
}
let
(
:current_user
)
{
nil
}
end
end
...
@@ -28,7 +38,7 @@ describe API::Repositories do
...
@@ -28,7 +38,7 @@ describe API::Repositories do
project
.
add_developer
(
user
)
project
.
add_developer
(
user
)
end
end
it_behaves_like
'
logs the audit even
t'
do
it_behaves_like
'
an auditable and successful reques
t'
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:current_user
)
{
user
}
let
(
:current_user
)
{
user
}
end
end
...
...
ee/spec/requests/groups/audit_events_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
'view audit events'
do
describe
'GET /groups/:group/-/audit_events'
do
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:audit_event
)
{
create
(
:group_audit_event
,
entity_id:
group
.
id
)
}
before_all
do
group
.
add_owner
(
user
)
end
before
do
stub_licensed_features
(
audit_events:
true
)
login_as
(
user
)
end
it
'returns 200 response'
do
send_request
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
end
it
'avoids N+1 DB queries'
,
:request_store
do
# warm up cache so these initial queries would not leak in our QueryRecorder
send_request
control
=
ActiveRecord
::
QueryRecorder
.
new
(
skip_cached:
false
)
{
send_request
}
create_list
(
:group_audit_event
,
2
,
entity_id:
group
.
id
)
expect
do
send_request
end
.
not_to
exceed_all_query_limit
(
control
)
end
def
send_request
get
group_audit_events_path
(
group
)
end
end
end
ee/spec/requests/projects/audit_events_spec.rb
0 → 100644
View file @
0ecca2b3
# frozen_string_literal: true
require
'spec_helper'
describe
'view audit events'
do
describe
'GET /:namespace/:project/-/audit_events'
do
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let_it_be
(
:user
)
{
project
.
owner
}
let_it_be
(
:audit_event
)
{
create
(
:project_audit_event
,
entity_id:
project
.
id
)
}
before
do
stub_licensed_features
(
audit_events:
true
)
login_as
(
user
)
end
it
'returns 200 response'
do
send_request
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
end
it
'avoids N+1 DB queries'
,
:request_store
do
control
=
ActiveRecord
::
QueryRecorder
.
new
(
skip_cached:
false
)
{
send_request
}
create_list
(
:project_audit_event
,
2
,
entity_id:
project
.
id
)
expect
do
send_request
end
.
not_to
exceed_all_query_limit
(
control
)
end
def
send_request
get
namespace_project_audit_events_path
(
namespace_id:
project
.
namespace
,
project_id:
project
)
end
end
end
ee/spec/services/audit_event_service_spec.rb
View file @
0ecca2b3
...
@@ -4,9 +4,11 @@ require 'spec_helper'
...
@@ -4,9 +4,11 @@ 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
,
current_sign_in_ip:
'192.168.68.104'
)
}
let
(
:project_member
)
{
create
(
:project_member
,
user:
user
,
expires_at:
1
.
day
.
from_now
)
}
let
(
:project_member
)
{
create
(
:project_member
,
user:
user
,
expires_at:
1
.
day
.
from_now
)
}
let
(
:service
)
{
described_class
.
new
(
user
,
project
,
{
action: :destroy
})
}
let
(
:details
)
{
{
action: :destroy
}
}
let
(
:service
)
{
described_class
.
new
(
user
,
project
,
details
)
}
describe
'#for_member'
do
describe
'#for_member'
do
it
'generates event'
do
it
'generates event'
do
...
@@ -21,11 +23,6 @@ describe AuditEventService do
...
@@ -21,11 +23,6 @@ describe AuditEventService do
expect
(
event
[
:details
][
:target_details
]).
to
eq
(
'Deleted User'
)
expect
(
event
[
:details
][
:target_details
]).
to
eq
(
'Deleted User'
)
end
end
it
'has the IP address'
do
event
=
service
.
for_member
(
project_member
).
security_event
expect
(
event
[
:details
][
:ip_address
]).
to
eq
(
user
.
current_sign_in_ip
)
end
context
'user access expiry'
do
context
'user access expiry'
do
let
(
:service
)
{
described_class
.
new
(
nil
,
project
,
{
action: :expired
})
}
let
(
:service
)
{
described_class
.
new
(
nil
,
project
,
{
action: :expired
})
}
...
@@ -63,21 +60,46 @@ describe AuditEventService do
...
@@ -63,21 +60,46 @@ describe AuditEventService do
it
'has the entity full path'
do
it
'has the entity full path'
do
event
=
service
.
for_member
(
project_member
).
security_event
event
=
service
.
for_member
(
project_member
).
security_event
expect
(
event
[
:details
][
:entity_path
]).
to
eq
(
project
.
full_path
)
expect
(
event
[
:details
][
:entity_path
]).
to
eq
(
project
.
full_path
)
end
end
it
'has the IP address'
do
event
=
service
.
for_member
(
project_member
).
security_event
expect
(
event
[
:details
][
:ip_address
]).
to
eq
(
user
.
current_sign_in_ip
)
end
end
context
'admin audit log unlicensed'
do
before
do
stub_licensed_features
(
admin_audit_log:
false
)
end
it
'does not have the entity full path'
do
event
=
service
.
for_member
(
project_member
).
security_event
expect
(
event
[
:details
]).
not_to
have_key
(
:entity_path
)
end
it
'does not have the ip_address'
do
event
=
service
.
for_member
(
project_member
).
security_event
expect
(
event
[
:details
]).
not_to
have_key
(
:ip_address
)
end
end
end
end
end
describe
'#security_event'
do
describe
'#security_event'
do
context
'unlicensed'
do
context
'unlicensed'
do
before
do
before
do
stub_licensed_features
(
audit_events:
false
)
disable_license_audit_features
end
end
it
'does not create an event'
do
it
'does not create an event'
do
expect
(
SecurityEvent
).
not_to
receive
(
:create
)
expect
(
SecurityEvent
).
not_to
receive
(
:create
)
service
.
security_event
expect
{
service
.
security_event
}.
not_to
change
(
SecurityEvent
,
:count
)
end
end
end
end
...
@@ -96,6 +118,31 @@ describe AuditEventService do
...
@@ -96,6 +118,31 @@ describe AuditEventService do
end
end
end
end
end
end
context
'admin audit log licensed'
do
before
do
stub_licensed_features
(
admin_audit_log:
true
)
end
context
'for an unauthenticated user'
do
let
(
:details
)
{
{
ip_address:
'10.11.12.13'
}
}
let
(
:user
)
{
Gitlab
::
Audit
::
UnauthenticatedAuthor
.
new
}
it
'defaults to the IP address in the details hash'
do
event
=
service
.
security_event
expect
(
event
[
:details
][
:ip_address
]).
to
eq
(
'10.11.12.13'
)
end
end
context
'for an authenticated user'
do
it
'has the user IP address'
do
event
=
service
.
security_event
expect
(
event
[
:details
][
:ip_address
]).
to
eq
(
user
.
current_sign_in_ip
)
end
end
end
end
end
describe
'#enabled?'
do
describe
'#enabled?'
do
...
@@ -110,9 +157,11 @@ describe AuditEventService do
...
@@ -110,9 +157,11 @@ describe AuditEventService do
with_them
do
with_them
do
before
do
before
do
stub_licensed_features
(
admin_audit_log:
admin_audit_log
,
stub_licensed_features
(
audit_events:
audit_events
,
admin_audit_log:
admin_audit_log
,
extended_audit_events:
extended_audit_events
)
audit_events:
audit_events
,
extended_audit_events:
extended_audit_events
)
end
end
it
'returns the correct result when feature is available'
do
it
'returns the correct result when feature is available'
do
...
@@ -121,7 +170,7 @@ describe AuditEventService do
...
@@ -121,7 +170,7 @@ describe AuditEventService do
end
end
end
end
describe
'#entity_audit_events_enabled?
?
'
do
describe
'#entity_audit_events_enabled?'
do
context
'entity is a project'
do
context
'entity is a project'
do
let
(
:service
)
{
described_class
.
new
(
user
,
project
,
{
action: :destroy
})
}
let
(
:service
)
{
described_class
.
new
(
user
,
project
,
{
action: :destroy
})
}
...
@@ -145,7 +194,7 @@ describe AuditEventService do
...
@@ -145,7 +194,7 @@ describe AuditEventService do
it
'returns false when group is unlicensed'
do
it
'returns false when group is unlicensed'
do
stub_licensed_features
(
audit_events:
false
)
stub_licensed_features
(
audit_events:
false
)
expect
(
service
.
entity_audit_events_enabled?
).
to
be_falsy
expect
(
service
.
entity_audit_events_enabled?
).
to
be_fals
e
y
end
end
it
'returns true when group is licensed'
do
it
'returns true when group is licensed'
do
...
@@ -200,23 +249,37 @@ describe AuditEventService do
...
@@ -200,23 +249,37 @@ describe AuditEventService do
expect
(
event
.
details
[
:author_name
]).
to
eq
(
author_name
)
expect
(
event
.
details
[
:author_name
]).
to
eq
(
author_name
)
end
end
it
'has the right IP address'
do
allow
(
service
).
to
receive
(
:admin_audit_log_enabled?
).
and_return
(
true
)
expect
(
event
.
details
[
:ip_address
]).
to
eq
(
ip_address
)
end
it
'has the right auth method for OAUTH'
do
it
'has the right auth method for OAUTH'
do
oauth_service
=
described_class
.
new
(
author_name
,
nil
,
ip_address:
ip_address
,
with:
'ldap'
)
oauth_service
=
described_class
.
new
(
author_name
,
nil
,
ip_address:
ip_address
,
with:
'ldap'
)
event
=
oauth_service
.
for_failed_login
.
unauth_security_event
event
=
oauth_service
.
for_failed_login
.
unauth_security_event
expect
(
event
.
details
[
:failed_login
]).
to
eq
(
'LDAP'
)
expect
(
event
.
details
[
:failed_login
]).
to
eq
(
'LDAP'
)
end
end
context
'admin audit log licensed'
do
before
do
stub_licensed_features
(
extended_audit_events:
true
,
admin_audit_log:
true
)
end
it
'has the right IP address'
do
expect
(
event
.
details
[
:ip_address
]).
to
eq
(
ip_address
)
end
end
context
'admin audit log unlicensed'
do
before
do
stub_licensed_features
(
extended_audit_events:
true
,
admin_audit_log:
false
)
end
it
'does not have the ip_address'
do
expect
(
event
.
details
).
not_to
have_key
(
:ip_address
)
end
end
end
end
describe
'#for_user'
do
describe
'#for_user'
do
let
(
:author_name
)
{
'Administrator'
}
let
(
:author_name
)
{
'Administrator'
}
let
(
:current_user
)
{
instance_spy
(
User
,
name:
author_name
)
}
let
(
:current_user
)
{
User
.
new
(
name:
author_name
)
}
let
(
:target_user_full_path
)
{
'ejohn'
}
let
(
:target_user_full_path
)
{
'ejohn'
}
let
(
:user
)
{
instance_spy
(
User
,
full_path:
target_user_full_path
)
}
let
(
:user
)
{
instance_spy
(
User
,
full_path:
target_user_full_path
)
}
let
(
:custom_message
)
{
'Some strange event has occurred'
}
let
(
:custom_message
)
{
'Some strange event has occurred'
}
...
@@ -291,18 +354,15 @@ describe AuditEventService do
...
@@ -291,18 +354,15 @@ describe AuditEventService do
end
end
describe
'license'
do
describe
'license'
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:service
)
{
described_class
.
new
(
user
,
project
,
action: :create
)
}
let
(
:event
)
{
service
.
for_project
.
security_event
}
let
(
:event
)
{
service
.
for_project
.
security_event
}
before
do
before
do
disable_license_audit_features
(
service
)
disable_license_audit_features
end
end
describe
'has the audit_admin feature'
do
describe
'has the audit_admin feature'
do
before
do
before
do
allow
(
service
).
to
receive
(
:admin_audit_log_enabled?
).
and_return
(
true
)
stub_licensed_features
(
admin_audit_log:
true
)
end
end
it
'logs an audit event'
do
it
'logs an audit event'
do
...
@@ -312,48 +372,60 @@ describe AuditEventService do
...
@@ -312,48 +372,60 @@ describe AuditEventService do
it
'has the entity_path'
do
it
'has the entity_path'
do
expect
(
event
.
details
[
:entity_path
]).
to
eq
(
project
.
full_path
)
expect
(
event
.
details
[
:entity_path
]).
to
eq
(
project
.
full_path
)
end
end
it
'has the user IP address'
do
expect
(
event
.
details
[
:ip_address
]).
to
eq
(
user
.
current_sign_in_ip
)
end
end
end
describe
'has the extended_audit_events feature'
do
describe
'has the extended_audit_events feature'
do
before
do
before
do
allow
(
service
).
to
receive
(
:entity_audit_events_enabled?
).
and_return
(
true
)
stub_licensed_features
(
extended_audit_events:
true
)
end
end
it
'logs an audit event'
do
it
'logs an audit event'
do
expect
{
event
}.
to
change
(
AuditEvent
,
:count
).
by
(
1
)
expect
{
event
}.
to
change
(
AuditEvent
,
:count
).
by
(
1
)
end
end
it
'has not the entity_path'
do
it
'does not have the entity_path'
do
expect
(
event
.
details
[
:entity_path
]).
not_to
eq
(
project
.
full_path
)
expect
(
event
.
details
).
not_to
have_key
(
:entity_path
)
end
it
'does not have the ip_address'
do
expect
(
event
.
details
).
not_to
have_key
(
:ip_address
)
end
end
end
end
describe
'entity has the audit_events feature'
do
describe
'entity has the audit_events feature'
do
before
do
before
do
allow
(
service
).
to
receive
(
:audit_events_enabled?
).
and_return
(
true
)
stub_licensed_features
(
audit_events:
true
)
end
end
it
'logs an audit event'
do
it
'logs an audit event'
do
expect
{
event
}.
to
change
(
AuditEvent
,
:count
).
by
(
1
)
expect
{
event
}.
to
change
(
AuditEvent
,
:count
).
by
(
1
)
end
end
it
'has not the entity_path'
do
it
'does not have the entity_path'
do
expect
(
event
.
details
[
:entity_path
]).
not_to
eq
(
project
.
full_path
)
expect
(
event
.
details
).
not_to
have_key
(
:entity_path
)
end
it
'does not have the ip_address'
do
expect
(
event
.
details
).
not_to
have_key
(
:ip_address
)
end
end
end
end
describe
'
has not
any audit event feature'
do
describe
'
does not have
any audit event feature'
do
it
'does not log the audit event'
do
it
'does not log the audit event'
do
expect
{
event
}.
not_to
change
(
AuditEvent
,
:count
)
expect
{
event
}.
not_to
change
(
AuditEvent
,
:count
)
end
end
end
end
end
def
disable_license_audit_features
(
service
)
def
disable_license_audit_features
[
:entity_audit_events_enabled?
,
stub_licensed_features
(
:admin_audit_log_enabled?
,
admin_audit_log:
false
,
:audit_events_enabled?
].
each
do
|
f
|
audit_events:
false
,
allow
(
service
).
to
receive
(
f
).
and_return
(
false
)
extended_audit_events:
false
end
)
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment