login_attack_report 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.md +4 -4
- data/lib/login_attack_report/frameworks/active_record/models/login_attack_report_version.rb +7 -0
- data/lib/login_attack_report/frameworks/active_record.rb +4 -0
- data/lib/login_attack_report/frameworks/rails/engine.rb +7 -0
- data/lib/login_attack_report/frameworks/rails.rb +6 -0
- data/lib/login_attack_report/has_login_attack_report.rb +504 -0
- data/lib/login_attack_report/login_attack_report_version_concern.rb +85 -0
- data/lib/login_attack_report/version.rb +1 -1
- data/lib/login_attack_report.rb +15 -1
- data/login_attack_report.gemspec +6 -3
- metadata +79 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83b80275b8c644e56b383ad22135a52e94e3188d
|
4
|
+
data.tar.gz: ae64da9fc95fb5131185f4add79e6ba46e16e860
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ade69377b319c33560880822e528a63aab873213a7d74e5ed12564cdc4a1b823989f6a91283404190889903ab7da66c90ba1c10209f50e3e37c8aff57212739
|
7
|
+
data.tar.gz: fa8c39bff1431eb75406f3bf9ad1aace546270f6d08dbfecc0c908f4898f10246c8706d8217cb231c55d3b337bc89496d795dbc21824dff4eeeaf96b72066f5c
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# LoginAttackReport
|
2
2
|
|
3
3
|
TODO: Write a gem description
|
4
4
|
|
@@ -7,7 +7,7 @@ TODO: Write a gem description
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
gem '
|
10
|
+
gem 'login_attack_report'
|
11
11
|
```
|
12
12
|
|
13
13
|
And then execute:
|
@@ -16,7 +16,7 @@ And then execute:
|
|
16
16
|
|
17
17
|
Or install it yourself as:
|
18
18
|
|
19
|
-
$ gem install
|
19
|
+
$ gem install login_attack_report
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
@@ -24,7 +24,7 @@ TODO: Write usage instructions here
|
|
24
24
|
|
25
25
|
## Contributing
|
26
26
|
|
27
|
-
1. Fork it ( https://github.com/[my-github-username]/
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/login_attack_report/fork )
|
28
28
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
29
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
30
|
4. Push to the branch (`git push origin my-new-feature`)
|
@@ -0,0 +1,4 @@
|
|
1
|
+
#Dir[File.join(File.dirname(__FILE__), 'active_record', 'models', 'login_attack_report', '*.rb')].each do |file|
|
2
|
+
# require "login_attack_report/frameworks/active_record/models/#{File.basename(file, '.rb')}"
|
3
|
+
#end
|
4
|
+
require "login_attack_report/frameworks/active_record/models/login_attack_report_version.rb"
|
@@ -0,0 +1,504 @@
|
|
1
|
+
require 'active_support/core_ext/object' # provides the `try` method
|
2
|
+
|
3
|
+
module LoginAttackReport
|
4
|
+
module Model
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Declare this in your model to track every create, update, and destroy. Each version of
|
12
|
+
# the model is available in the `versions` association.
|
13
|
+
#
|
14
|
+
# Options:
|
15
|
+
# :on the events to track (optional; defaults to all of them). Set to an array of
|
16
|
+
# `:create`, `:update`, `:destroy` as desired.
|
17
|
+
# :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
|
18
|
+
# :ignore an array of attributes for which a new `Version` will not be created if only they change.
|
19
|
+
# it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
|
20
|
+
# which will only be ignored if the value is a `Proc` which returns truthily.
|
21
|
+
# :if, :unless Procs that allow to specify conditions when to save versions for an object
|
22
|
+
# :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
|
23
|
+
# it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
|
24
|
+
# which will only be counted if the value is a `Proc` which returns truthily.
|
25
|
+
# :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
|
26
|
+
# a new `Version`. In addition, these fields will not be included in the serialized versions
|
27
|
+
# of the object whenever a new `Version` is created.
|
28
|
+
# :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
|
29
|
+
# Values are objects or procs (which are called with `self`, i.e. the model with the paper
|
30
|
+
# trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
|
31
|
+
# the controller.
|
32
|
+
# :versions the name to use for the versions association. Default is `:versions`.
|
33
|
+
# :version the name to use for the method which returns the version the instance was reified from.
|
34
|
+
# Default is `:version`.
|
35
|
+
# :save_changes whether or not to save changes to the object_changes column if it exists. Default is true
|
36
|
+
#
|
37
|
+
def has_paper_trail(options = {})
|
38
|
+
# Lazily include the instance methods so we don't clutter up
|
39
|
+
# any more ActiveRecord models than we have to.
|
40
|
+
send :include, InstanceMethods
|
41
|
+
|
42
|
+
class_attribute :version_association_name
|
43
|
+
self.version_association_name = options[:version] || :version
|
44
|
+
|
45
|
+
# The version this instance was reified from.
|
46
|
+
attr_accessor self.version_association_name
|
47
|
+
|
48
|
+
class_attribute :version_class_name
|
49
|
+
self.version_class_name = options[:class_name] || 'PaperTrail::Version'
|
50
|
+
|
51
|
+
class_attribute :paper_trail_options
|
52
|
+
self.paper_trail_options = options.dup
|
53
|
+
|
54
|
+
[:ignore, :skip, :only].each do |k|
|
55
|
+
paper_trail_options[k] =
|
56
|
+
[paper_trail_options[k]].flatten.compact.map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
57
|
+
end
|
58
|
+
|
59
|
+
paper_trail_options[:meta] ||= {}
|
60
|
+
paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
|
61
|
+
|
62
|
+
class_attribute :versions_association_name
|
63
|
+
self.versions_association_name = options[:versions] || :versions
|
64
|
+
|
65
|
+
attr_accessor :paper_trail_event
|
66
|
+
|
67
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4 # `has_many` syntax for specifying order uses a lambda in Rails 4
|
68
|
+
has_many self.versions_association_name,
|
69
|
+
lambda { order(model.timestamp_sort_order) },
|
70
|
+
:class_name => self.version_class_name, :as => :item
|
71
|
+
else
|
72
|
+
has_many self.versions_association_name,
|
73
|
+
:class_name => self.version_class_name,
|
74
|
+
:as => :item,
|
75
|
+
:order => self.paper_trail_version_class.timestamp_sort_order
|
76
|
+
end
|
77
|
+
|
78
|
+
options[:on] ||= [:create, :update, :destroy]
|
79
|
+
options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
|
80
|
+
after_create :record_create, :if => :save_version? if options_on.include?(:create)
|
81
|
+
if options_on.include?(:update)
|
82
|
+
before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
|
83
|
+
after_update :record_update, :if => :save_version?
|
84
|
+
after_update :clear_version_instance!
|
85
|
+
end
|
86
|
+
after_destroy :record_destroy, :if => :save_version? if options_on.include?(:destroy)
|
87
|
+
|
88
|
+
# Reset the transaction id when the transaction is closed
|
89
|
+
after_commit :reset_transaction_id
|
90
|
+
after_rollback :reset_transaction_id
|
91
|
+
after_rollback :clear_rolled_back_versions
|
92
|
+
end
|
93
|
+
|
94
|
+
# Switches PaperTrail off for this class.
|
95
|
+
def paper_trail_off!
|
96
|
+
PaperTrail.enabled_for_model(self, false)
|
97
|
+
end
|
98
|
+
|
99
|
+
def paper_trail_off
|
100
|
+
warn "DEPRECATED: use `paper_trail_off!` instead of `paper_trail_off`. Support for `paper_trail_off` will be removed in PaperTrail 4.0"
|
101
|
+
self.paper_trail_off!
|
102
|
+
end
|
103
|
+
|
104
|
+
# Switches PaperTrail on for this class.
|
105
|
+
def paper_trail_on!
|
106
|
+
PaperTrail.enabled_for_model(self, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def paper_trail_on
|
110
|
+
warn "DEPRECATED: use `paper_trail_on!` instead of `paper_trail_on`. Support for `paper_trail_on` will be removed in PaperTrail 4.0"
|
111
|
+
self.paper_trail_on!
|
112
|
+
end
|
113
|
+
|
114
|
+
def paper_trail_enabled_for_model?
|
115
|
+
return false unless self.include?(PaperTrail::Model::InstanceMethods)
|
116
|
+
PaperTrail.enabled_for_model?(self)
|
117
|
+
end
|
118
|
+
|
119
|
+
def paper_trail_version_class
|
120
|
+
@paper_trail_version_class ||= version_class_name.constantize
|
121
|
+
end
|
122
|
+
|
123
|
+
# Used for Version#object attribute
|
124
|
+
def serialize_attributes_for_paper_trail!(attributes)
|
125
|
+
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
126
|
+
return attributes if self.paper_trail_version_class.object_col_is_json?
|
127
|
+
|
128
|
+
serialized_attributes.each do |key, coder|
|
129
|
+
if attributes.key?(key)
|
130
|
+
# Fall back to current serializer if `coder` has no `dump` method
|
131
|
+
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
132
|
+
attributes[key] = coder.dump(attributes[key])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def unserialize_attributes_for_paper_trail!(attributes)
|
138
|
+
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
139
|
+
return attributes if self.paper_trail_version_class.object_col_is_json?
|
140
|
+
|
141
|
+
serialized_attributes.each do |key, coder|
|
142
|
+
if attributes.key?(key)
|
143
|
+
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
144
|
+
attributes[key] = coder.load(attributes[key])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Used for Version#object_changes attribute
|
150
|
+
def serialize_attribute_changes_for_paper_trail!(changes)
|
151
|
+
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
152
|
+
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
153
|
+
|
154
|
+
serialized_attributes.each do |key, coder|
|
155
|
+
if changes.key?(key)
|
156
|
+
# Fall back to current serializer if `coder` has no `dump` method
|
157
|
+
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
158
|
+
old_value, new_value = changes[key]
|
159
|
+
changes[key] = [coder.dump(old_value),
|
160
|
+
coder.dump(new_value)]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def unserialize_attribute_changes_for_paper_trail!(changes)
|
166
|
+
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
167
|
+
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
168
|
+
|
169
|
+
serialized_attributes.each do |key, coder|
|
170
|
+
if changes.key?(key)
|
171
|
+
coder = PaperTrail.serializer unless coder.respond_to?(:dump)
|
172
|
+
old_value, new_value = changes[key]
|
173
|
+
changes[key] = [coder.load(old_value),
|
174
|
+
coder.load(new_value)]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Wrap the following methods in a module so we can include them only in the
|
181
|
+
# ActiveRecord models that declare `has_paper_trail`.
|
182
|
+
module InstanceMethods
|
183
|
+
# Returns true if this instance is the current, live one;
|
184
|
+
# returns false if this instance came from a previous version.
|
185
|
+
def live?
|
186
|
+
source_version.nil?
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns who put the object into its current state.
|
190
|
+
def paper_trail_originator
|
191
|
+
(source_version || send(self.class.versions_association_name).last).try(:whodunnit)
|
192
|
+
end
|
193
|
+
|
194
|
+
def originator
|
195
|
+
warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
|
196
|
+
self.paper_trail_originator
|
197
|
+
end
|
198
|
+
|
199
|
+
# Invoked after rollbacks to ensure versions records are not created
|
200
|
+
# for changes that never actually took place
|
201
|
+
def clear_rolled_back_versions
|
202
|
+
send(self.class.versions_association_name).reload
|
203
|
+
end
|
204
|
+
|
205
|
+
# Returns the object (not a Version) as it was at the given timestamp.
|
206
|
+
def version_at(timestamp, reify_options={})
|
207
|
+
# Because a version stores how its object looked *before* the change,
|
208
|
+
# we need to look for the first version created *after* the timestamp.
|
209
|
+
v = send(self.class.versions_association_name).subsequent(timestamp, true).first
|
210
|
+
return v.reify(reify_options) if v
|
211
|
+
self unless self.destroyed?
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns the objects (not Versions) as they were between the given times.
|
215
|
+
def versions_between(start_time, end_time, reify_options={})
|
216
|
+
versions = send(self.class.versions_association_name).between(start_time, end_time)
|
217
|
+
versions.collect { |version| version_at(version.send PaperTrail.timestamp_field) }
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns the object (not a Version) as it was most recently.
|
221
|
+
def previous_version
|
222
|
+
preceding_version = source_version ? source_version.previous : send(self.class.versions_association_name).last
|
223
|
+
preceding_version.reify if preceding_version
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns the object (not a Version) as it became next.
|
227
|
+
# NOTE: if self (the item) was not reified from a version, i.e. it is the
|
228
|
+
# "live" item, we return nil. Perhaps we should return self instead?
|
229
|
+
def next_version
|
230
|
+
subsequent_version = source_version.next
|
231
|
+
subsequent_version ? subsequent_version.reify : self.class.find(self.id)
|
232
|
+
rescue
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
236
|
+
def paper_trail_enabled_for_model?
|
237
|
+
self.class.paper_trail_enabled_for_model?
|
238
|
+
end
|
239
|
+
|
240
|
+
# Executes the given method or block without creating a new version.
|
241
|
+
def without_versioning(method = nil)
|
242
|
+
paper_trail_was_enabled = self.paper_trail_enabled_for_model?
|
243
|
+
self.class.paper_trail_off!
|
244
|
+
method ? method.to_proc.call(self) : yield(self)
|
245
|
+
ensure
|
246
|
+
self.class.paper_trail_on! if paper_trail_was_enabled
|
247
|
+
end
|
248
|
+
|
249
|
+
# Utility method for reifying. Anything executed inside the block will appear like a new record
|
250
|
+
def appear_as_new_record
|
251
|
+
instance_eval {
|
252
|
+
alias :old_new_record? :new_record?
|
253
|
+
alias :new_record? :present?
|
254
|
+
}
|
255
|
+
yield
|
256
|
+
instance_eval { alias :new_record? :old_new_record? }
|
257
|
+
end
|
258
|
+
|
259
|
+
# Temporarily overwrites the value of whodunnit and then executes the provided block.
|
260
|
+
def whodunnit(value)
|
261
|
+
raise ArgumentError, 'expected to receive a block' unless block_given?
|
262
|
+
current_whodunnit = PaperTrail.whodunnit
|
263
|
+
PaperTrail.whodunnit = value
|
264
|
+
yield self
|
265
|
+
ensure
|
266
|
+
PaperTrail.whodunnit = current_whodunnit
|
267
|
+
end
|
268
|
+
|
269
|
+
# Mimicks behavior of `touch` method from `ActiveRecord::Persistence`, but generates a version
|
270
|
+
#
|
271
|
+
# TODO: lookinto leveraging the `after_touch` callback from `ActiveRecord` to allow the
|
272
|
+
# regular `touch` method go generate a version as normal. May make sense to switch the `record_update`
|
273
|
+
# method to leverage an `after_update` callback anyways (likely for v4.0.0)
|
274
|
+
def touch_with_version(name = nil)
|
275
|
+
raise ActiveRecordError, "can not touch on a new record object" unless persisted?
|
276
|
+
|
277
|
+
attributes = timestamp_attributes_for_update_in_model
|
278
|
+
attributes << name if name
|
279
|
+
current_time = current_time_from_proper_timezone
|
280
|
+
|
281
|
+
attributes.each { |column| write_attribute(column, current_time) }
|
282
|
+
# ensure a version is written even if the `:on` collection is empty
|
283
|
+
record_update(true) if paper_trail_options[:on] == []
|
284
|
+
save!(:validate => false)
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
def source_version
|
290
|
+
send self.class.version_association_name
|
291
|
+
end
|
292
|
+
|
293
|
+
def record_create
|
294
|
+
if paper_trail_switched_on?
|
295
|
+
data = {
|
296
|
+
:event => paper_trail_event || 'create',
|
297
|
+
:whodunnit => PaperTrail.whodunnit
|
298
|
+
}
|
299
|
+
if respond_to?(:created_at)
|
300
|
+
data[PaperTrail.timestamp_field] = created_at
|
301
|
+
end
|
302
|
+
if paper_trail_options[:save_changes] && changed_notably? && self.class.paper_trail_version_class.column_names.include?('object_changes')
|
303
|
+
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
|
304
|
+
PaperTrail.serializer.dump(changes_for_paper_trail)
|
305
|
+
end
|
306
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
307
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
308
|
+
end
|
309
|
+
version = send(self.class.versions_association_name).create! merge_metadata(data)
|
310
|
+
set_transaction_id(version)
|
311
|
+
save_associations(version)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def record_update(force = nil)
|
316
|
+
if paper_trail_switched_on? && (force || changed_notably?)
|
317
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
318
|
+
data = {
|
319
|
+
:event => paper_trail_event || 'update',
|
320
|
+
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
|
321
|
+
:whodunnit => PaperTrail.whodunnit
|
322
|
+
}
|
323
|
+
if respond_to?(:updated_at)
|
324
|
+
data[PaperTrail.timestamp_field] = updated_at
|
325
|
+
end
|
326
|
+
if paper_trail_options[:save_changes] && self.class.paper_trail_version_class.column_names.include?('object_changes')
|
327
|
+
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
|
328
|
+
PaperTrail.serializer.dump(changes_for_paper_trail)
|
329
|
+
end
|
330
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
331
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
332
|
+
end
|
333
|
+
version = send(self.class.versions_association_name).create merge_metadata(data)
|
334
|
+
set_transaction_id(version)
|
335
|
+
save_associations(version)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def changes_for_paper_trail
|
340
|
+
_changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
|
341
|
+
if PaperTrail.serialized_attributes?
|
342
|
+
self.class.serialize_attribute_changes_for_paper_trail!(_changes)
|
343
|
+
end
|
344
|
+
_changes.to_hash
|
345
|
+
end
|
346
|
+
|
347
|
+
# Invoked via`after_update` callback for when a previous version is reified and then saved
|
348
|
+
def clear_version_instance!
|
349
|
+
send("#{self.class.version_association_name}=", nil)
|
350
|
+
end
|
351
|
+
|
352
|
+
def reset_timestamp_attrs_for_update_if_needed!
|
353
|
+
return if self.live? # invoked via callback when a user attempts to persist a reified `Version`
|
354
|
+
timestamp_attributes_for_update_in_model.each do |column|
|
355
|
+
# ActiveRecord 4.2 deprecated `reset_column!` in favor of `restore_column!`
|
356
|
+
if respond_to?("restore_#{column}!")
|
357
|
+
send("restore_#{column}!")
|
358
|
+
else
|
359
|
+
send("reset_#{column}!")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def record_destroy
|
365
|
+
if paper_trail_switched_on? and not new_record?
|
366
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
367
|
+
data = {
|
368
|
+
:item_id => self.id,
|
369
|
+
:item_type => self.class.base_class.name,
|
370
|
+
:event => paper_trail_event || 'destroy',
|
371
|
+
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
|
372
|
+
:whodunnit => PaperTrail.whodunnit
|
373
|
+
}
|
374
|
+
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
375
|
+
data[:transaction_id] = PaperTrail.transaction_id
|
376
|
+
end
|
377
|
+
version = self.class.paper_trail_version_class.create(merge_metadata(data))
|
378
|
+
send("#{self.class.version_association_name}=", version)
|
379
|
+
send(self.class.versions_association_name).send :load_target
|
380
|
+
set_transaction_id(version)
|
381
|
+
save_associations(version)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# saves associations if the join table for `VersionAssociation` exists
|
386
|
+
def save_associations(version)
|
387
|
+
return unless PaperTrail.config.track_associations?
|
388
|
+
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
389
|
+
assoc_version_args = {
|
390
|
+
:version_id => version.id,
|
391
|
+
:foreign_key_name => assoc.foreign_key
|
392
|
+
}
|
393
|
+
|
394
|
+
if assoc.options[:polymorphic]
|
395
|
+
associated_record = send(assoc.name) if send(assoc.foreign_type)
|
396
|
+
if associated_record && associated_record.class.paper_trail_enabled_for_model?
|
397
|
+
assoc_version_args.merge!(:foreign_key_id => associated_record.id)
|
398
|
+
end
|
399
|
+
elsif assoc.klass.paper_trail_enabled_for_model?
|
400
|
+
assoc_version_args.merge!(:foreign_key_id => send(assoc.foreign_key))
|
401
|
+
end
|
402
|
+
|
403
|
+
PaperTrail::VersionAssociation.create(assoc_version_args) if assoc_version_args.has_key?(:foreign_key_id)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def set_transaction_id(version)
|
408
|
+
return unless self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
409
|
+
if PaperTrail.transaction? && PaperTrail.transaction_id.nil?
|
410
|
+
PaperTrail.transaction_id = version.id
|
411
|
+
version.transaction_id = version.id
|
412
|
+
version.save
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def reset_transaction_id
|
417
|
+
PaperTrail.transaction_id = nil
|
418
|
+
end
|
419
|
+
|
420
|
+
def merge_metadata(data)
|
421
|
+
# First we merge the model-level metadata in `meta`.
|
422
|
+
paper_trail_options[:meta].each do |k,v|
|
423
|
+
data[k] =
|
424
|
+
if v.respond_to?(:call)
|
425
|
+
v.call(self)
|
426
|
+
elsif v.is_a?(Symbol) && respond_to?(v)
|
427
|
+
# if it is an attribute that is changing in an existing object,
|
428
|
+
# be sure to grab the current version
|
429
|
+
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != 'create'
|
430
|
+
send("#{v}_was".to_sym)
|
431
|
+
else
|
432
|
+
send(v)
|
433
|
+
end
|
434
|
+
else
|
435
|
+
v
|
436
|
+
end
|
437
|
+
end
|
438
|
+
# Second we merge any extra data from the controller (if available).
|
439
|
+
data.merge(PaperTrail.controller_info || {})
|
440
|
+
end
|
441
|
+
|
442
|
+
def attributes_before_change
|
443
|
+
attributes.tap do |prev|
|
444
|
+
enums = self.respond_to?(:defined_enums) ? self.defined_enums : {}
|
445
|
+
changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
|
446
|
+
before = enums[attr][before] if enums[attr]
|
447
|
+
prev[attr] = before
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# returns hash of attributes (with appropriate attributes serialized),
|
453
|
+
# ommitting attributes to be skipped
|
454
|
+
def object_attrs_for_paper_trail(attributes_hash)
|
455
|
+
attrs = attributes_hash.except(*self.paper_trail_options[:skip])
|
456
|
+
if PaperTrail.serialized_attributes?
|
457
|
+
self.class.serialize_attributes_for_paper_trail!(attrs)
|
458
|
+
end
|
459
|
+
attrs
|
460
|
+
end
|
461
|
+
|
462
|
+
# This method is invoked in order to determine whether it is appropriate to generate a new version instance.
|
463
|
+
# Because we are now using `after_(create/update/etc)` callbacks, we need to go out of our way to
|
464
|
+
# ensure that during updates timestamp attributes are not acknowledged as a notable changes
|
465
|
+
# to raise false positives when attributes are ignored.
|
466
|
+
def changed_notably?
|
467
|
+
if self.paper_trail_options[:ignore].any? && (changed & self.paper_trail_options[:ignore]).any?
|
468
|
+
(notably_changed - timestamp_attributes_for_update_in_model.map(&:to_s)).any?
|
469
|
+
else
|
470
|
+
notably_changed.any?
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def notably_changed
|
475
|
+
only = self.paper_trail_options[:only].dup
|
476
|
+
# remove Hash arguments and then evaluate whether the attributes (the keys of the hash) should also get pushed into the collection
|
477
|
+
only.delete_if do |obj|
|
478
|
+
obj.is_a?(Hash) && obj.each { |attr, condition| only << attr if condition.respond_to?(:call) && condition.call(self) }
|
479
|
+
end
|
480
|
+
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
481
|
+
end
|
482
|
+
|
483
|
+
def changed_and_not_ignored
|
484
|
+
ignore = self.paper_trail_options[:ignore].dup
|
485
|
+
# remove Hash arguments and then evaluate whether the attributes (the keys of the hash) should also get pushed into the collection
|
486
|
+
ignore.delete_if do |obj|
|
487
|
+
obj.is_a?(Hash) && obj.each { |attr, condition| ignore << attr if condition.respond_to?(:call) && condition.call(self) }
|
488
|
+
end
|
489
|
+
skip = self.paper_trail_options[:skip]
|
490
|
+
changed - ignore - skip
|
491
|
+
end
|
492
|
+
|
493
|
+
def paper_trail_switched_on?
|
494
|
+
PaperTrail.enabled? && PaperTrail.enabled_for_controller? && self.paper_trail_enabled_for_model?
|
495
|
+
end
|
496
|
+
|
497
|
+
def save_version?
|
498
|
+
if_condition = self.paper_trail_options[:if]
|
499
|
+
unless_condition = self.paper_trail_options[:unless]
|
500
|
+
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module LoginAttackReport
|
4
|
+
module LoginAttackReportVersionConcern
|
5
|
+
extend ::ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def login_ok_limit_over
|
9
|
+
# TODO config
|
10
|
+
model = :User
|
11
|
+
login_limit = 200
|
12
|
+
login_err_limit = 50
|
13
|
+
|
14
|
+
PaperTrail::Version
|
15
|
+
.where(item_type: model)
|
16
|
+
.where(
|
17
|
+
'created_at >= ? and created_at <= ? and ' \
|
18
|
+
'object_changes like \'%sign_in_count:%\'',
|
19
|
+
Time.now.prev_month.beginning_of_month,
|
20
|
+
Time.now.prev_month.end_of_month
|
21
|
+
).group(:item_id).having("count(item_id) > #{login_limit}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def alert_login_ng_limit_over
|
25
|
+
# TODO config
|
26
|
+
model = :User
|
27
|
+
login_limit = 200
|
28
|
+
login_err_limit = 50
|
29
|
+
|
30
|
+
PaperTrail::Version
|
31
|
+
.where(item_type: model)
|
32
|
+
.where(
|
33
|
+
'created_at >= ? and created_at <= ? and ' \
|
34
|
+
'object_changes like \'%sign_in_count:%\'',
|
35
|
+
Time.now.prev_month.beginning_of_month,
|
36
|
+
Time.now.prev_month.end_of_month
|
37
|
+
).group(:item_id).having("count(item_id) > #{login_limit}")
|
38
|
+
end
|
39
|
+
def alert_ip_limit_over
|
40
|
+
# TODO config
|
41
|
+
model = :User
|
42
|
+
login_limit = 200
|
43
|
+
login_err_limit = 50
|
44
|
+
alert_ip_limit_over = PaperTrail::Version
|
45
|
+
.where(item_type: model)
|
46
|
+
.where(
|
47
|
+
'created_at >= ? and created_at <= ? and ' \
|
48
|
+
'(object_changes like \'%sign_in_count:%\' or ' \
|
49
|
+
'object_changes like \'--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nfailed_attempts:%\'' \
|
50
|
+
')',
|
51
|
+
Time.now.prev_month.beginning_of_month,
|
52
|
+
Time.now.prev_month.end_of_month
|
53
|
+
)
|
54
|
+
|
55
|
+
if alert_ip_limit_over.present?
|
56
|
+
ok_hash = Hash.new({})
|
57
|
+
ng_hash = Hash.new({})
|
58
|
+
alert_ip_limit_over.find_each do |version|
|
59
|
+
# アクセス元ipアドレス取得
|
60
|
+
if /current_sign_in_ip/ =~ version.object_changes
|
61
|
+
current_sign_in_ip = YAML.load(version.object_changes)['current_sign_in_ip'][1]
|
62
|
+
else
|
63
|
+
current_sign_in_ip = YAML.load(version.object)['current_sign_in_ip']
|
64
|
+
end
|
65
|
+
# ログイン成功回数取得
|
66
|
+
if /sign_in_count/ =~ version.object_changes
|
67
|
+
if ok_hash[current_sign_in_ip].present?
|
68
|
+
ok_hash[current_sign_in_ip] += 1
|
69
|
+
else
|
70
|
+
ok_hash[current_sign_in_ip] = 1
|
71
|
+
end
|
72
|
+
# ログイン失敗回数取得
|
73
|
+
else
|
74
|
+
if ng_hash[current_sign_in_ip].present?
|
75
|
+
ng_hash[current_sign_in_ip] += 1
|
76
|
+
else
|
77
|
+
ng_hash[current_sign_in_ip] = 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/login_attack_report.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
require
|
1
|
+
require 'login_attack_report/version'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_record'
|
4
|
+
require 'paper_trail'
|
5
|
+
require 'rails'
|
6
|
+
|
7
|
+
Dir[File.join(File.dirname(__FILE__), 'login_attack_report', '*.rb')].each do |file|
|
8
|
+
require File.join('login_attack_report', File.basename(file, '.rb'))
|
9
|
+
end
|
10
|
+
require 'login_attack_report/frameworks/active_record'
|
11
|
+
require 'login_attack_report/frameworks/rails'
|
2
12
|
|
3
13
|
module LoginAttackReport
|
4
14
|
# Your code goes here...
|
@@ -6,3 +16,7 @@ module LoginAttackReport
|
|
6
16
|
"call new_method"
|
7
17
|
end
|
8
18
|
end
|
19
|
+
|
20
|
+
ActiveSupport.on_load(:active_record) do
|
21
|
+
include LoginAttackReport::Model
|
22
|
+
end
|
data/login_attack_report.gemspec
CHANGED
@@ -19,13 +19,16 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
-
spec.required_rubygems_version = '>= 1.
|
22
|
+
spec.required_rubygems_version = '>= 1.9.0'
|
23
23
|
|
24
|
-
spec.add_dependency 'rails', '>=
|
25
|
-
spec.add_dependency '
|
24
|
+
spec.add_dependency 'rails', ['>= 3.0', '< 6.0']
|
25
|
+
spec.add_dependency 'activerecord', ['>= 3.0', '< 6.0']
|
26
|
+
spec.add_dependency 'activesupport', ['>= 3.0', '< 6.0']
|
27
|
+
spec.add_dependency 'paper_trail', ['>= 3.0', '< 6.0']
|
26
28
|
spec.add_dependency 'devise', '>= 3.2.2'
|
27
29
|
|
28
30
|
spec.add_development_dependency 'bundler', '~> 1.7'
|
29
31
|
spec.add_development_dependency 'rake', '~> 10.0'
|
30
32
|
spec.add_development_dependency 'rspec'
|
33
|
+
spec.add_development_dependency 'pry'
|
31
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: login_attack_report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- taru m
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,28 +16,80 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '3.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activerecord
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.0'
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '6.0'
|
20
43
|
type: :runtime
|
21
44
|
prerelease: false
|
22
45
|
version_requirements: !ruby/object:Gem::Requirement
|
23
46
|
requirements:
|
24
47
|
- - ">="
|
25
48
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
49
|
+
version: '3.0'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '6.0'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: activesupport
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '3.0'
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '6.0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '6.0'
|
27
73
|
- !ruby/object:Gem::Dependency
|
28
74
|
name: paper_trail
|
29
75
|
requirement: !ruby/object:Gem::Requirement
|
30
76
|
requirements:
|
31
77
|
- - ">="
|
32
78
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.0
|
79
|
+
version: '3.0'
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '6.0'
|
34
83
|
type: :runtime
|
35
84
|
prerelease: false
|
36
85
|
version_requirements: !ruby/object:Gem::Requirement
|
37
86
|
requirements:
|
38
87
|
- - ">="
|
39
88
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.0
|
89
|
+
version: '3.0'
|
90
|
+
- - "<"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '6.0'
|
41
93
|
- !ruby/object:Gem::Dependency
|
42
94
|
name: devise
|
43
95
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +146,20 @@ dependencies:
|
|
94
146
|
- - ">="
|
95
147
|
- !ruby/object:Gem::Version
|
96
148
|
version: '0'
|
149
|
+
- !ruby/object:Gem::Dependency
|
150
|
+
name: pry
|
151
|
+
requirement: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
type: :development
|
157
|
+
prerelease: false
|
158
|
+
version_requirements: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
97
163
|
description: login attack report in Rails.
|
98
164
|
email:
|
99
165
|
- Write your email address
|
@@ -109,6 +175,12 @@ files:
|
|
109
175
|
- README.md
|
110
176
|
- Rakefile
|
111
177
|
- lib/login_attack_report.rb
|
178
|
+
- lib/login_attack_report/frameworks/active_record.rb
|
179
|
+
- lib/login_attack_report/frameworks/active_record/models/login_attack_report_version.rb
|
180
|
+
- lib/login_attack_report/frameworks/rails.rb
|
181
|
+
- lib/login_attack_report/frameworks/rails/engine.rb
|
182
|
+
- lib/login_attack_report/has_login_attack_report.rb
|
183
|
+
- lib/login_attack_report/login_attack_report_version_concern.rb
|
112
184
|
- lib/login_attack_report/version.rb
|
113
185
|
- login_attack_report.gemspec
|
114
186
|
- spec/login_attack_report_spec.rb
|
@@ -130,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
202
|
requirements:
|
131
203
|
- - ">="
|
132
204
|
- !ruby/object:Gem::Version
|
133
|
-
version: 1.
|
205
|
+
version: 1.9.0
|
134
206
|
requirements: []
|
135
207
|
rubyforge_project:
|
136
208
|
rubygems_version: 2.2.0
|