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
module Gitlab
module Audit
module Type
class Definition
include ActiveModel::Validations
include ::Gitlab::Audit::Type::Shared
attr_reader :path
attr_reader :attributes
validate :validate_schema
validate :validate_file_name
def self.declarative_policy_class
'AuditEvents::DefinitionPolicy'
end
InvalidAuditEventTypeError = Class.new(StandardError)
AUDIT_EVENT_TYPE_SCHEMA_PATH = Rails.root.join('config', 'audit_events', 'types', 'type_schema.json')
AUDIT_EVENT_TYPE_SCHEMA = JSONSchemer.schema(AUDIT_EVENT_TYPE_SCHEMA_PATH)
PARAMS.each do |param|
define_method(param) do
attributes[param]
end
end
def initialize(path, opts = {})
@path = path
@attributes = {}
# assign nil, for all unknown opts
PARAMS.each do |param|
@attributes[param] = opts[param]
end
end
def key
name.to_sym
end
private
def validate_schema
schema_errors = AUDIT_EVENT_TYPE_SCHEMA
.validate(attributes.to_h.deep_stringify_keys)
.map { |error| JSONSchemer::Errors.pretty(error) }
errors.add(:base, schema_errors) if schema_errors.present?
end
def validate_file_name
# ignoring Style/GuardClause because if we move this into one line, we cause Layout/LineLength errors
# rubocop:disable Style/GuardClause
unless File.basename(path, ".yml") == name
errors.add(:base, "Audit event type '#{name}' has an invalid path: '#{path}'. " \
"'#{name}' must match the filename")
end
# rubocop:enable Style/GuardClause
end
class << self
include ::Gitlab::Utils::StrongMemoize
def paths
@paths ||= [Rails.root.join('config', 'audit_events', 'types', '*.yml')]
end
def definitions
load_all!
end
strong_memoize_attr :definitions
def get(key)
definitions[key.to_sym]
end
def event_names
definitions.keys.map(&:to_s)
end
def names_with_category
definitions.map do |event_name, value|
{ event_name: event_name, feature_category: value.attributes[:feature_category] }
end
end
def defined?(key)
get(key).present?
end
def stream_only?(key)
event_definition = get(key)
return false unless event_definition
event_definition.streamed && !event_definition.saved_to_database
end
private
def load_all!
paths.each_with_object({}) do |glob_path, definitions|
load_all_from_path!(definitions, glob_path)
end
end
def load_all_from_path!(definitions, glob_path)
Dir.glob(glob_path).each do |path|
definition = load_from_file(path)
if previous = definitions[definition.key]
raise InvalidAuditEventTypeError, "Audit event type '#{definition.key}' " \
"is already defined in '#{previous.path}'"
end
definitions[definition.key] = definition
end
end
def load_from_file(path)
definition = File.read(path)
definition = YAML.safe_load(definition)
definition.deep_symbolize_keys!
new(path, definition).tap(&:validate!)
rescue StandardError => e
raise InvalidAuditEventTypeError, "Invalid definition for `#{path}`: #{e.message}"
end
end
end
end
end
end
Gitlab::Audit::Type::Definition.prepend_mod
|