audited 4.8.0 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of audited might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +25 -0
- data/.travis.yml +32 -27
- data/Appraisals +28 -11
- data/CHANGELOG.md +50 -3
- data/README.md +62 -17
- data/gemfiles/rails42.gemfile +3 -0
- data/gemfiles/rails50.gemfile +3 -0
- data/gemfiles/rails51.gemfile +3 -0
- data/gemfiles/rails52.gemfile +3 -1
- data/gemfiles/rails60.gemfile +10 -0
- data/gemfiles/rails61.gemfile +10 -0
- data/lib/audited/audit.rb +22 -15
- data/lib/audited/auditor.rb +89 -33
- data/lib/audited/version.rb +1 -1
- data/spec/audited/audit_spec.rb +69 -21
- data/spec/audited/auditor_spec.rb +160 -32
- data/spec/audited/sweeper_spec.rb +15 -6
- data/spec/audited_spec_helpers.rb +3 -1
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- data/spec/rails_app/app/controllers/application_controller.rb +2 -0
- data/spec/rails_app/config/database.yml +1 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/active_record/models.rb +21 -0
- data/spec/support/active_record/schema.rb +4 -2
- metadata +58 -22
- data/gemfiles/rails40.gemfile +0 -9
- data/gemfiles/rails41.gemfile +0 -8
data/lib/audited/auditor.rb
CHANGED
@@ -34,6 +34,16 @@ module Audited
|
|
34
34
|
# * +require_comment+ - Ensures that audit_comment is supplied before
|
35
35
|
# any create, update or destroy operation.
|
36
36
|
# * +max_audits+ - Limits the number of stored audits.
|
37
|
+
|
38
|
+
# * +redacted+ - Changes to these fields will be logged, but the values
|
39
|
+
# will not. This is useful, for example, if you wish to audit when a
|
40
|
+
# password is changed, without saving the actual password in the log.
|
41
|
+
# To store values as something other than '[REDACTED]', pass an argument
|
42
|
+
# to the redaction_value option.
|
43
|
+
#
|
44
|
+
# class User < ActiveRecord::Base
|
45
|
+
# audited redacted: :password, redaction_value: SecureRandom.uuid
|
46
|
+
# end
|
37
47
|
#
|
38
48
|
# * +if+ - Only audit the model when the given function returns true
|
39
49
|
# * +unless+ - Only audit the model when the given function returns false
|
@@ -90,16 +100,8 @@ module Audited
|
|
90
100
|
end
|
91
101
|
|
92
102
|
module AuditedInstanceMethods
|
93
|
-
|
94
|
-
|
95
|
-
if method_name == :version
|
96
|
-
ActiveSupport::Deprecation.warn("`version` attribute has been changed to `audit_version`. This attribute will be removed.")
|
97
|
-
audit_version
|
98
|
-
else
|
99
|
-
super
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
+
REDACTED = '[REDACTED]'
|
104
|
+
|
103
105
|
# Temporarily turns off auditing while saving.
|
104
106
|
def save_without_auditing
|
105
107
|
without_auditing { save }
|
@@ -115,6 +117,21 @@ module Audited
|
|
115
117
|
self.class.without_auditing(&block)
|
116
118
|
end
|
117
119
|
|
120
|
+
# Temporarily turns on auditing while saving.
|
121
|
+
def save_with_auditing
|
122
|
+
with_auditing { save }
|
123
|
+
end
|
124
|
+
|
125
|
+
# Executes the block with the auditing callbacks enabled.
|
126
|
+
#
|
127
|
+
# @foo.with_auditing do
|
128
|
+
# @foo.save
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
def with_auditing(&block)
|
132
|
+
self.class.with_auditing(&block)
|
133
|
+
end
|
134
|
+
|
118
135
|
# Gets an array of the revisions available
|
119
136
|
#
|
120
137
|
# user.revisions.each do |revision|
|
@@ -152,7 +169,8 @@ module Audited
|
|
152
169
|
|
153
170
|
# List of attributes that are audited.
|
154
171
|
def audited_attributes
|
155
|
-
attributes.except(*non_audited_columns)
|
172
|
+
audited_attributes = attributes.except(*self.class.non_audited_columns)
|
173
|
+
normalize_enum_changes(audited_attributes)
|
156
174
|
end
|
157
175
|
|
158
176
|
# Returns a list combined of record audits and associated audits.
|
@@ -177,18 +195,9 @@ module Audited
|
|
177
195
|
|
178
196
|
protected
|
179
197
|
|
180
|
-
def non_audited_columns
|
181
|
-
self.class.non_audited_columns
|
182
|
-
end
|
183
|
-
|
184
|
-
def audited_columns
|
185
|
-
self.class.audited_columns
|
186
|
-
end
|
187
|
-
|
188
198
|
def revision_with(attributes)
|
189
199
|
dup.tap do |revision|
|
190
200
|
revision.id = id
|
191
|
-
revision.send :instance_variable_set, '@attributes', self.attributes if rails_below?('4.2.0')
|
192
201
|
revision.send :instance_variable_set, '@new_record', destroyed?
|
193
202
|
revision.send :instance_variable_set, '@persisted', !destroyed?
|
194
203
|
revision.send :instance_variable_set, '@readonly', false
|
@@ -211,19 +220,56 @@ module Audited
|
|
211
220
|
end
|
212
221
|
end
|
213
222
|
|
214
|
-
def rails_below?(rails_version)
|
215
|
-
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
216
|
-
end
|
217
|
-
|
218
223
|
private
|
219
224
|
|
220
225
|
def audited_changes
|
221
226
|
all_changes = respond_to?(:changes_to_save) ? changes_to_save : changes
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
227
|
+
filtered_changes = \
|
228
|
+
if audited_options[:only].present?
|
229
|
+
all_changes.slice(*self.class.audited_columns)
|
230
|
+
else
|
231
|
+
all_changes.except(*self.class.non_audited_columns)
|
232
|
+
end
|
233
|
+
|
234
|
+
filtered_changes = redact_values(filtered_changes)
|
235
|
+
filtered_changes = normalize_enum_changes(filtered_changes)
|
236
|
+
filtered_changes.to_hash
|
237
|
+
end
|
238
|
+
|
239
|
+
def normalize_enum_changes(changes)
|
240
|
+
self.class.defined_enums.each do |name, values|
|
241
|
+
if changes.has_key?(name)
|
242
|
+
changes[name] = \
|
243
|
+
if changes[name].is_a?(Array)
|
244
|
+
changes[name].map { |v| values[v] }
|
245
|
+
elsif rails_below?('5.0')
|
246
|
+
changes[name]
|
247
|
+
else
|
248
|
+
values[changes[name]]
|
249
|
+
end
|
250
|
+
end
|
226
251
|
end
|
252
|
+
changes
|
253
|
+
end
|
254
|
+
|
255
|
+
def redact_values(filtered_changes)
|
256
|
+
[audited_options[:redacted]].flatten.compact.each do |option|
|
257
|
+
changes = filtered_changes[option.to_s]
|
258
|
+
new_value = audited_options[:redaction_value] || REDACTED
|
259
|
+
if changes.is_a? Array
|
260
|
+
values = changes.map { new_value }
|
261
|
+
else
|
262
|
+
values = new_value
|
263
|
+
end
|
264
|
+
hash = Hash[option.to_s, values]
|
265
|
+
filtered_changes.merge!(hash)
|
266
|
+
end
|
267
|
+
|
268
|
+
filtered_changes
|
269
|
+
end
|
270
|
+
|
271
|
+
def rails_below?(rails_version)
|
272
|
+
Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new(rails_version)
|
227
273
|
end
|
228
274
|
|
229
275
|
def audits_to(version = nil)
|
@@ -244,7 +290,7 @@ module Audited
|
|
244
290
|
end
|
245
291
|
|
246
292
|
def audit_update
|
247
|
-
unless (changes = audited_changes).empty? && audit_comment.blank?
|
293
|
+
unless (changes = audited_changes).empty? && (audit_comment.blank? || audited_options[:update_with_comment_only] == false)
|
248
294
|
write_audit(action: 'update', audited_changes: changes,
|
249
295
|
comment: audit_comment)
|
250
296
|
end
|
@@ -314,10 +360,6 @@ module Audited
|
|
314
360
|
true
|
315
361
|
end
|
316
362
|
|
317
|
-
def auditing_enabled=(val)
|
318
|
-
self.class.auditing_enabled = val
|
319
|
-
end
|
320
|
-
|
321
363
|
def reconstruct_attributes(audits)
|
322
364
|
attributes = {}
|
323
365
|
audits.each { |audit| attributes.merge!(audit.new_attributes) }
|
@@ -355,6 +397,20 @@ module Audited
|
|
355
397
|
enable_auditing if auditing_was_enabled
|
356
398
|
end
|
357
399
|
|
400
|
+
# Executes the block with auditing enabled.
|
401
|
+
#
|
402
|
+
# Foo.with_auditing do
|
403
|
+
# @foo.save
|
404
|
+
# end
|
405
|
+
#
|
406
|
+
def with_auditing
|
407
|
+
auditing_was_enabled = auditing_enabled
|
408
|
+
enable_auditing
|
409
|
+
yield
|
410
|
+
ensure
|
411
|
+
disable_auditing unless auditing_was_enabled
|
412
|
+
end
|
413
|
+
|
358
414
|
def disable_auditing
|
359
415
|
self.auditing_enabled = false
|
360
416
|
end
|
data/lib/audited/version.rb
CHANGED
data/spec/audited/audit_spec.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
+
SingleCov.covered! uncovered: 1 # Rails version check
|
4
|
+
|
3
5
|
describe Audited::Audit do
|
4
6
|
let(:user) { Models::ActiveRecord::User.new name: "Testing" }
|
5
7
|
|
@@ -38,43 +40,64 @@ describe Audited::Audit do
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
+
context "when a custom audit class is not configured" do
|
44
|
+
it "should default to #{described_class}" do
|
45
|
+
TempModel.audited
|
46
|
+
|
47
|
+
record = TempModel.create
|
48
|
+
|
49
|
+
audit = record.audits.first
|
50
|
+
expect(audit).to be_a Audited::Audit
|
51
|
+
expect(audit.respond_to?(:custom_method)).to be false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#audited_changes" do
|
57
|
+
let(:audit) { Audited.audit_class.new }
|
58
|
+
|
59
|
+
it "can unserialize yaml from text columns" do
|
60
|
+
audit.audited_changes = {foo: "bar"}
|
61
|
+
expect(audit.audited_changes).to eq foo: "bar"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not unserialize from binary columns" do
|
65
|
+
allow(Audited::YAMLIfTextColumnType).to receive(:text_column?).and_return(false)
|
66
|
+
audit.audited_changes = {foo: "bar"}
|
67
|
+
expect(audit.audited_changes).to eq "{:foo=>\"bar\"}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#undo" do
|
72
|
+
let(:user) { Models::ActiveRecord::User.create(name: "John") }
|
73
|
+
|
74
|
+
it "undos changes" do
|
43
75
|
user.update_attribute(:name, 'Joe')
|
44
76
|
user.audits.last.undo
|
45
77
|
user.reload
|
46
|
-
|
47
78
|
expect(user.name).to eq("John")
|
48
79
|
end
|
49
80
|
|
50
|
-
it "
|
51
|
-
user = Models::ActiveRecord::User.create(name: "John")
|
81
|
+
it "undos destroy" do
|
52
82
|
user.destroy
|
53
83
|
user.audits.last.undo
|
54
84
|
user = Models::ActiveRecord::User.find_by(name: "John")
|
55
85
|
expect(user.name).to eq("John")
|
56
86
|
end
|
57
87
|
|
58
|
-
it "
|
59
|
-
user
|
88
|
+
it "undos creation" do
|
89
|
+
user # trigger create
|
60
90
|
expect {user.audits.last.undo}.to change(Models::ActiveRecord::User, :count).by(-1)
|
61
91
|
end
|
62
92
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
record = TempModel.create
|
68
|
-
|
69
|
-
audit = record.audits.first
|
70
|
-
expect(audit).to be_a Audited::Audit
|
71
|
-
expect(audit.respond_to?(:custom_method)).to be false
|
72
|
-
end
|
93
|
+
it "fails when trying to undo unknown" do
|
94
|
+
audit = user.audits.last
|
95
|
+
audit.action = 'oops'
|
96
|
+
expect { audit.undo }.to raise_error("invalid action given oops")
|
73
97
|
end
|
74
98
|
end
|
75
99
|
|
76
100
|
describe "user=" do
|
77
|
-
|
78
101
|
it "should be able to set the user to a model object" do
|
79
102
|
subject.user = user
|
80
103
|
expect(subject.user).to eq(user)
|
@@ -110,11 +133,9 @@ describe Audited::Audit do
|
|
110
133
|
subject.user = user
|
111
134
|
expect(subject.username).to be_nil
|
112
135
|
end
|
113
|
-
|
114
136
|
end
|
115
137
|
|
116
138
|
describe "revision" do
|
117
|
-
|
118
139
|
it "should recreate attributes" do
|
119
140
|
user = Models::ActiveRecord::User.create name: "1"
|
120
141
|
5.times {|i| user.update_attribute :name, (i + 2).to_s }
|
@@ -148,6 +169,34 @@ describe Audited::Audit do
|
|
148
169
|
end
|
149
170
|
end
|
150
171
|
|
172
|
+
describe ".collection_cache_key" do
|
173
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
174
|
+
it "uses created at" do
|
175
|
+
Audited::Audit.delete_all
|
176
|
+
audit = Models::ActiveRecord::User.create(name: "John").audits.last
|
177
|
+
audit.update_columns(created_at: Time.zone.parse('2018-01-01'))
|
178
|
+
expect(Audited::Audit.collection_cache_key).to match(/-20180101\d+$/)
|
179
|
+
end
|
180
|
+
else
|
181
|
+
it "is not defined" do
|
182
|
+
expect { Audited::Audit.collection_cache_key }.to raise_error(NoMethodError)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe ".assign_revision_attributes" do
|
188
|
+
it "dups when frozen" do
|
189
|
+
user.freeze
|
190
|
+
assigned = Audited::Audit.assign_revision_attributes(user, name: "Bar")
|
191
|
+
expect(assigned.name).to eq "Bar"
|
192
|
+
end
|
193
|
+
|
194
|
+
it "ignores unassignable attributes" do
|
195
|
+
assigned = Audited::Audit.assign_revision_attributes(user, oops: "Bar")
|
196
|
+
expect(assigned.name).to eq "Testing"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
151
200
|
it "should set the version number on create" do
|
152
201
|
user = Models::ActiveRecord::User.create! name: "Set Version Number"
|
153
202
|
expect(user.audits.first.version).to eq(1)
|
@@ -282,6 +331,5 @@ describe Audited::Audit do
|
|
282
331
|
}.to raise_exception('expected')
|
283
332
|
expect(Audited.store[:audited_user]).to be_nil
|
284
333
|
end
|
285
|
-
|
286
334
|
end
|
287
335
|
end
|