1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# frozen_string_literal: true
class AuditEventService
# Instantiates a new service
#
# @param [User] author the user who authors the change
# @param [User, Project, Group] entity the scope which audit event belongs to
# This param is also used to determine the visibility of the audit event.
# - Project: events are visible at Project and Instance level
# - Group: events are visible at Group and Instance level
# - User: events are visible at Instance level
# @param [Hash] details extra data of audit event
#
# @return [AuditEventService]
def initialize(author, entity, details = {})
@author = build_author(author)
@entity = entity
@details = details
@ip_address = resolve_ip_address(@author)
end
# Builds the @details attribute for authentication
#
# This uses the @author as the target object being audited
#
# @return [AuditEventService]
def for_authentication
mark_as_authentication_event!
@details = {
with: @details[:with],
target_id: @author.id,
target_type: 'User',
target_details: @author.name
}
self
end
# Writes event to a file and creates an event record in DB
#
# @return [AuditEvent] persited if saves and non-persisted if fails
def security_event
log_security_event_to_file
log_authentication_event_to_database
log_security_event_to_database
end
# Writes event to a file
def log_security_event_to_file
file_logger.info(base_payload.merge(formatted_details))
end
private
attr_reader :ip_address
def build_author(author)
case author
when User
author.impersonated? ? Gitlab::Audit::ImpersonatedAuthor.new(author) : author
else
Gitlab::Audit::UnauthenticatedAuthor.new(name: author)
end
end
def resolve_ip_address(author)
Gitlab::RequestContext.instance.client_ip ||
author.current_sign_in_ip
end
def base_payload
{
author_id: @author.id,
author_name: @author.name,
entity_id: @entity.id,
entity_type: @entity.class.name
}
end
def authentication_event_payload
{
# @author can be a User or various Gitlab::Audit authors.
# Only capture real users for successful authentication events.
user: author_if_user,
user_name: @author.name,
ip_address: ip_address,
result: AuthenticationEvent.results[:success],
provider: @details[:with]
}
end
def author_if_user
@author if @author.is_a?(User)
end
def file_logger
@file_logger ||= Gitlab::AuditJsonLogger.build
end
def formatted_details
@details.merge(@details.slice(:from, :to).transform_values(&:to_s))
end
def mark_as_authentication_event!
@authentication_event = true
end
def authentication_event?
@authentication_event
end
def log_security_event_to_database
return if Gitlab::Database.read_only?
event = AuditEvent.new(base_payload.merge(details: @details))
save_or_track event
event
end
def log_authentication_event_to_database
return unless Gitlab::Database.read_write? && authentication_event?
event = AuthenticationEvent.new(authentication_event_payload)
save_or_track event
event
end
def save_or_track(event)
event.save!
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, audit_event_type: event.class.to_s)
end
end
AuditEventService.prepend_mod_with('AuditEventService')
|