loggable_activity 0.1.55 → 0.1.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -1
- data/CHANGELOG.md +14 -0
- data/GETTING-STARTED.md +8 -47
- data/PAYLOAD_EXAMPLE.md +63 -0
- data/docs/LoggableActivity/Activity.html +92 -241
- data/docs/LoggableActivity/Configuration.html +162 -15
- data/docs/LoggableActivity/ConfigurationError.html +148 -0
- data/docs/LoggableActivity/DataOwner.html +138 -0
- data/docs/LoggableActivity/Encryption.html +18 -14
- data/docs/LoggableActivity/EncryptionError.html +47 -1
- data/docs/LoggableActivity/EncryptionKey.html +54 -35
- data/docs/LoggableActivity/Error.html +48 -0
- data/docs/LoggableActivity/Hooks.html +221 -89
- data/docs/LoggableActivity/Payload.html +103 -27
- data/docs/LoggableActivity/Services/BasePayloadsBuilder.html +442 -0
- data/docs/LoggableActivity/Services/DestroyPayloadsBuilder.html +395 -0
- data/docs/LoggableActivity/Services/PayloadsBuilder.html +342 -0
- data/docs/LoggableActivity/Services/UpdatePayloadsBuilder.html +490 -0
- data/docs/LoggableActivity/Services.html +93 -0
- data/docs/created.rid +14 -10
- data/docs/index.html +10 -2
- data/docs/js/navigation.js.gz +0 -0
- data/docs/js/search_index.js +1 -1
- data/docs/js/search_index.js.gz +0 -0
- data/docs/js/searcher.js.gz +0 -0
- data/docs/table_of_contents.html +253 -85
- data/lib/generators/loggable_activity/install_generator.rb +2 -18
- data/lib/generators/loggable_activity/templates/binary_ids/create_loggable_activities.rb +9 -10
- data/lib/generators/loggable_activity/templates/create_loggable_activities.rb +16 -13
- data/lib/loggable_activity/activity.rb +58 -101
- data/lib/loggable_activity/configuration.rb +60 -3
- data/lib/loggable_activity/data_owner.rb +18 -0
- data/lib/loggable_activity/encryption.rb +14 -14
- data/lib/loggable_activity/encryption_key.rb +26 -25
- data/lib/loggable_activity/error.rb +34 -0
- data/lib/loggable_activity/hooks.rb +78 -43
- data/lib/loggable_activity/payload.rb +70 -20
- data/lib/loggable_activity/services/base_payloads_builder.rb +127 -0
- data/lib/loggable_activity/services/destroy_payloads_builder.rb +127 -0
- data/lib/loggable_activity/services/payloads_builder.rb +98 -0
- data/lib/loggable_activity/services/update_payloads_builder.rb +168 -0
- data/lib/loggable_activity/version.rb +1 -1
- data/lib/loggable_activity.rb +6 -6
- data/lib/schemas/config_schema.json +90 -0
- metadata +37 -26
- data/docs/LoggableActivity/PayloadsBuilder.html +0 -441
- data/docs/LoggableActivity/UpdatePayloadsBuilder.html +0 -424
- data/lib/.DS_Store +0 -0
- data/lib/generators/loggable_activity/install_templates_generator.rb +0 -103
- data/lib/generators/loggable_activity/templates/helpers/loggable_activity_helper.rb +0 -49
- data/lib/generators/loggable_activity/templates/loggable_activity_helper.rb +0 -58
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_create.html.erb +0 -23
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_create.html.slim +0 -18
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_destroy.html.erb +0 -18
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_destroy.html.slim +0 -17
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_show.html.erb +0 -18
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_show.html.slim +0 -17
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_update.html.erb +0 -18
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/default/_update.html.slim +0 -12
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_activity_info.html.erb +0 -12
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_activity_info.html.slim +0 -11
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_list_attrs.html.erb +0 -8
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_list_attrs.html.slim +0 -6
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_update_attrs.html.erb +0 -17
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_update_attrs.html.slim +0 -14
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_updated_relations.html.erb +0 -23
- data/lib/generators/loggable_activity/templates/views/loggable_activity/templates/shared/_updated_relations.html.slim +0 -21
- data/lib/loggable_activity/payloads_builder.rb +0 -153
- data/lib/loggable_activity/update_payloads_builder.rb +0 -127
@@ -1,32 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class CreateLoggableActivities < ActiveRecord::Migration[
|
3
|
+
class CreateLoggableActivities < ActiveRecord::Migration[7.1]
|
4
4
|
def change
|
5
|
+
create_table :loggable_data_owners do |t|
|
6
|
+
t.references :record, polymorphic: true, null: true, index: true
|
7
|
+
t.references :encryption_key, null: false, foreign_key: { to_table: 'loggable_encryption_keys' }
|
8
|
+
end
|
9
|
+
|
5
10
|
create_table :loggable_activities do |t|
|
6
11
|
t.string :action
|
7
12
|
t.references :actor, polymorphic: true, null: true
|
8
|
-
t.string :
|
9
|
-
t.string :encrypted_record_display_name
|
13
|
+
t.string :encrypted_actor_name
|
10
14
|
t.references :record, polymorphic: true, null: true
|
11
|
-
|
12
15
|
t.timestamps
|
13
16
|
end
|
14
17
|
|
15
18
|
create_table :loggable_payloads do |t|
|
16
|
-
t.references :
|
19
|
+
t.references :activity, null: false, foreign_key: { to_table: 'loggable_activities' }
|
20
|
+
t.references :encryption_key, null: false, foreign_key: { to_table: 'loggable_encryption_keys' }
|
21
|
+
t.references :record, polymorphic: true, null: true, index: true
|
22
|
+
t.string :encrypted_record_name
|
17
23
|
t.json :encrypted_attrs
|
18
|
-
t.integer :
|
24
|
+
t.integer :related_to_activity_as, default: 0
|
19
25
|
t.boolean :data_owner, default: false
|
20
|
-
t.string :route
|
21
|
-
t.
|
22
|
-
|
23
|
-
t.timestamps
|
26
|
+
t.string :route
|
27
|
+
t.boolean :current_payload, default: true
|
24
28
|
end
|
25
29
|
|
26
30
|
create_table :loggable_encryption_keys do |t|
|
27
|
-
t.references :
|
28
|
-
t.string :
|
29
|
-
t.references :record, polymorphic: true, on_delete: :nullify
|
31
|
+
t.references :record, polymorphic: true, null: true, index: true
|
32
|
+
t.string :secret_key
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
@@ -7,9 +7,9 @@ module LoggableActivity
|
|
7
7
|
class Activity < ActiveRecord::Base
|
8
8
|
self.table_name = 'loggable_activities'
|
9
9
|
# Associations
|
10
|
-
has_many :payloads, class_name: 'LoggableActivity::Payload', dependent: :destroy
|
11
|
-
belongs_to :record, polymorphic: true, optional: true
|
10
|
+
has_many :payloads, class_name: '::LoggableActivity::Payload', dependent: :destroy
|
12
11
|
belongs_to :actor, polymorphic: true, optional: true
|
12
|
+
belongs_to :record, polymorphic: true, optional: true
|
13
13
|
|
14
14
|
accepts_nested_attributes_for :payloads
|
15
15
|
|
@@ -22,7 +22,7 @@ module LoggableActivity
|
|
22
22
|
#
|
23
23
|
# Each hash in the array contains:
|
24
24
|
# - :record_type: The class name of the record.
|
25
|
-
# - :
|
25
|
+
# - :related_to_activity_as: A descriptor of the payload's role (e.g., 'primary_payload' or 'current_association').
|
26
26
|
# - :attrs: A hash of the record's attributes.
|
27
27
|
#
|
28
28
|
# Example usage:
|
@@ -30,65 +30,36 @@ module LoggableActivity
|
|
30
30
|
#
|
31
31
|
# Sample return value:
|
32
32
|
# [
|
33
|
-
# { record_type: MODEL_NAME,
|
34
|
-
# { record_type: MODEL_NAME,
|
33
|
+
# { record_type: MODEL_NAME, related_to_activity_as: "primary_payload", attrs: { "KEY" => "VALUE", ... } },
|
34
|
+
# { record_type: MODEL_NAME, related_to_activity_as: "current_association", attrs: { "KEY" => "VALUE", ... } },
|
35
35
|
# ...
|
36
36
|
# ]
|
37
37
|
def attrs
|
38
|
+
{
|
39
|
+
actor_type:,
|
40
|
+
actor_id:,
|
41
|
+
action:,
|
42
|
+
actor_display_name:,
|
43
|
+
record_display_name:,
|
44
|
+
payloads: payloads_attrs
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def payloads_attrs
|
38
49
|
ordered_payloads.map do |payload|
|
39
50
|
{
|
51
|
+
related_to_activity_as: payload.related_to_activity_as,
|
40
52
|
record_type: payload.record_type,
|
41
|
-
record_id: payload.record_id,
|
42
|
-
payload_type: payload.payload_type,
|
53
|
+
record_id: payload.deleted? ? nil : payload.record_id,
|
43
54
|
attrs: payload.attrs,
|
44
|
-
|
55
|
+
route: payload.payload_route,
|
56
|
+
record_display_name: payload.record_display_name,
|
57
|
+
current_payload: payload.current_payload,
|
58
|
+
data_owner: payload.data_owner
|
45
59
|
}
|
46
60
|
end
|
47
61
|
end
|
48
62
|
|
49
|
-
# Returns a hash describing the attributes of an update activity, including updated attributes for a record and any updated related attributes.
|
50
|
-
#
|
51
|
-
# Example:
|
52
|
-
# @activity.update_activity_attrs
|
53
|
-
#
|
54
|
-
# The return value is structured as follows:
|
55
|
-
# - :update_attrs contains the attributes of the record being updated, detailing changes from previous to new values.
|
56
|
-
# - :updated_relations_attrs is an array of hashes, each representing an updated related record. Each hash details the previous and current attributes of the relation.
|
57
|
-
#
|
58
|
-
# Example Return Structure:
|
59
|
-
# {
|
60
|
-
# update_attrs: {
|
61
|
-
# record_class: "CLASS.NAME",
|
62
|
-
# attrs: [{ "KEY" => { from: "OLD_VALUE", to: "NEW_VALUE" } }]
|
63
|
-
# },
|
64
|
-
# updated_relations_attrs: [
|
65
|
-
# {
|
66
|
-
# record_class: "CLASS.NAME",
|
67
|
-
# previous_attrs: { attrs: { "KEY" => "VALUE", ... } },
|
68
|
-
# current_attrs: { attrs: { "KEY" => "VALUE", ... } }
|
69
|
-
# }
|
70
|
-
# ]
|
71
|
-
# }
|
72
|
-
def update_activity_attrs
|
73
|
-
{
|
74
|
-
update_attrs:,
|
75
|
-
updated_relations_attrs:
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
# Returns the attributes for the primary payload, without the relations.
|
80
|
-
#
|
81
|
-
# Example:
|
82
|
-
#
|
83
|
-
# @activity.primary_payload_attrs
|
84
|
-
#
|
85
|
-
# Returns:
|
86
|
-
# { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
|
87
|
-
#
|
88
|
-
def primary_payload_attrs
|
89
|
-
primary_payload ? primary_payload.attrs : {}
|
90
|
-
end
|
91
|
-
|
92
63
|
# Returns the attributes for the relations.
|
93
64
|
#
|
94
65
|
# Example:
|
@@ -100,13 +71,13 @@ module LoggableActivity
|
|
100
71
|
# {
|
101
72
|
# record_type: CLASS.NAME,
|
102
73
|
# record_id: INTEGER,
|
103
|
-
#
|
74
|
+
# related_to_activity_as: ENUM
|
104
75
|
# attrs: { "KEY_A" => "VALUE_A", "KEY_B" => "VALUE_B", ... }
|
105
76
|
# }
|
106
77
|
# ]
|
107
|
-
def relations_attrs
|
108
|
-
|
109
|
-
end
|
78
|
+
# def relations_attrs
|
79
|
+
# attrs.filter { |p| p[:related_to_activity_as] == 'current_association' }
|
80
|
+
# end
|
110
81
|
|
111
82
|
# Returns the display name for a record. what method to use if defined in '/config/loggable_activity.yaml'
|
112
83
|
#
|
@@ -118,9 +89,7 @@ module LoggableActivity
|
|
118
89
|
# "David Bowie"
|
119
90
|
#
|
120
91
|
def record_display_name
|
121
|
-
|
122
|
-
|
123
|
-
LoggableActivity::Encryption.decrypt(encrypted_record_display_name, record_key)
|
92
|
+
primary_payload.record_display_name
|
124
93
|
end
|
125
94
|
|
126
95
|
# Returns the path for the activity.
|
@@ -132,8 +101,8 @@ module LoggableActivity
|
|
132
101
|
# Returns:
|
133
102
|
# "/path/to/activity"
|
134
103
|
#
|
135
|
-
def
|
136
|
-
primary_payload
|
104
|
+
def primary_route
|
105
|
+
primary_payload.payload_route
|
137
106
|
end
|
138
107
|
|
139
108
|
# Returns the display name for a actor. what method to use if defined in '/config/loggable_activity.yaml'
|
@@ -146,28 +115,30 @@ module LoggableActivity
|
|
146
115
|
# "Elvis Presley"
|
147
116
|
#
|
148
117
|
def actor_display_name
|
149
|
-
return I18n.t('loggable.activity.deleted') if
|
118
|
+
return I18n.t('loggable.activity.deleted') if actor_deleted?
|
150
119
|
|
151
|
-
LoggableActivity::Encryption.decrypt(
|
120
|
+
::LoggableActivity::Encryption.decrypt(encrypted_actor_name, actor_secret_key)
|
152
121
|
end
|
153
122
|
|
154
123
|
# Returns a list of activities for a given actor.
|
155
124
|
def self.activities_for_actor(actor, limit = 20, params = { offset: 0 })
|
156
|
-
LoggableActivity::Activity.latest(limit, params).where(actor:)
|
125
|
+
::LoggableActivity::Activity.latest(limit, params).where(actor:)
|
157
126
|
end
|
158
127
|
|
159
128
|
# Returns a list of activities ordered by creation date.
|
129
|
+
# This is done to support UUID primary keys.
|
160
130
|
def self.latest(limit = 20, params = { offset: 0 })
|
161
131
|
offset = params[:offset] || 0
|
162
|
-
LoggableActivity::Activity
|
132
|
+
::LoggableActivity::Activity
|
163
133
|
.all
|
164
134
|
.order(created_at: :desc)
|
165
|
-
.includes(:
|
135
|
+
.includes(payloads: :encryption_key)
|
166
136
|
.offset(offset)
|
167
137
|
.limit(limit)
|
168
138
|
end
|
169
139
|
|
170
140
|
# Returns the last activity.
|
141
|
+
# This is done to support of UUID primary keys.
|
171
142
|
def self.last(limit = 1)
|
172
143
|
return latest(1).first if limit == 1
|
173
144
|
|
@@ -176,55 +147,41 @@ module LoggableActivity
|
|
176
147
|
|
177
148
|
private
|
178
149
|
|
179
|
-
# Returns the
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
update_payload_attrs
|
186
|
-
end
|
187
|
-
|
188
|
-
# Returns the primary payload.
|
150
|
+
# Returns the primary payload associated with the activity.
|
151
|
+
#
|
152
|
+
# Example usage:
|
153
|
+
# payload = @activity.primary_payload
|
154
|
+
# payload.record_type # => 'SOMD_MODEL_NAME'
|
155
|
+
#
|
189
156
|
def primary_payload
|
190
|
-
|
157
|
+
related_to_activity_as = %w[primary_payload primary_update_payload primary_destroy_payload]
|
158
|
+
payloads.detect { |p| related_to_activity_as.include?(p.related_to_activity_as) }
|
191
159
|
end
|
192
160
|
|
193
|
-
# Returns
|
194
|
-
def updated_relations_attrs
|
195
|
-
grouped_associations = attrs.group_by { |p| p[:record_type] }
|
196
|
-
|
197
|
-
grouped_associations.map do |record_type, payloads|
|
198
|
-
previous_attrs = payloads.find { |p| p[:payload_type] == 'previous_association' }
|
199
|
-
current_attrs = payloads.find { |p| p[:payload_type] == 'current_association' }
|
200
|
-
next if previous_attrs.nil? && current_attrs.nil?
|
201
|
-
|
202
|
-
{ record_type:, previous_attrs:, current_attrs: }
|
203
|
-
end.compact
|
204
|
-
end
|
205
|
-
|
206
|
-
# Returns the previous association attributes.
|
207
|
-
def previous_associations_attrs
|
208
|
-
attrs.select { |p| p[:payload_type] == 'previous_association' }
|
209
|
-
end
|
210
|
-
|
211
|
-
# Returns payloads sorted by :payload_type.
|
161
|
+
# # Returns payloads sorted by :related_to_activity_as.
|
212
162
|
def ordered_payloads
|
213
|
-
payloads.order(:
|
163
|
+
# payloads.order(related_to_activity_as: :desc)
|
164
|
+
payloads
|
214
165
|
end
|
215
166
|
|
216
167
|
# Returns the key for the logged record.
|
217
168
|
def record_key
|
218
169
|
return nil if record.nil?
|
219
170
|
|
220
|
-
LoggableActivity::EncryptionKey.for_record(record)&.
|
171
|
+
::LoggableActivity::EncryptionKey.for_record(record)&.secret_key
|
221
172
|
end
|
222
173
|
|
223
|
-
#
|
224
|
-
|
225
|
-
|
174
|
+
# Check if the actor is deleted.
|
175
|
+
# If the actor is deleted, it will return true.
|
176
|
+
# This way we don't rely on access to the main DB to check if the actor is deleted.
|
177
|
+
def actor_deleted?
|
178
|
+
actor_secret_key.nil?
|
179
|
+
end
|
226
180
|
|
227
|
-
|
181
|
+
# Returns the key for the actor.
|
182
|
+
def actor_secret_key
|
183
|
+
# @actor_key_secret_key ||=
|
184
|
+
::LoggableActivity::EncryptionKey.for_record_by_type_and_id(actor_type, actor_id)&.secret_key
|
228
185
|
end
|
229
186
|
|
230
187
|
# Validates that the activity has at least one payload.
|
@@ -1,20 +1,66 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json-schema'
|
4
|
+
require 'json'
|
5
|
+
|
3
6
|
module LoggableActivity
|
4
7
|
# This class is used to load the configuration file located at config/loggable_activity.yml
|
8
|
+
class ConfigurationError < StandardError
|
9
|
+
def initialize(msg = '')
|
10
|
+
# https://api.loggable_activity.com/msg
|
11
|
+
puts '---------------- LOGGABLE ACTIVITY -----------------'
|
12
|
+
puts msg
|
13
|
+
puts '----------------------------------------------------'
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# This class is used to load the configuration file located at config/loggable_activity.yml
|
19
|
+
# When the LoggableActivity::Hook is included in a model
|
20
|
+
# it takes the model's name and find the configuration for that model in the configuration file.
|
5
21
|
class Configuration
|
6
22
|
# Loads the configuration file
|
7
23
|
def self.load_config_file(config_file_path)
|
8
24
|
@config_data = YAML.load_file(config_file_path)
|
25
|
+
validate_config_file
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
raise ConfigurationError, 'config/loggable_activity.yaml not found'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Loads the schema file for the configuration file
|
31
|
+
def self.load_schema
|
32
|
+
schema_path = File.join(__dir__, '..', 'schemas', 'config_schema.json')
|
33
|
+
JSON.parse(File.read(schema_path))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Validates the configuration file againss the schema
|
37
|
+
def self.validate_config_file
|
38
|
+
schema = load_schema
|
39
|
+
errors = JSON::Validator.fully_validate(schema, @config_data)
|
40
|
+
return unless errors.any?
|
41
|
+
|
42
|
+
raise ConfigurationError,
|
43
|
+
"config/loggable_activity.yaml is invalid: #{errors.join(', ')}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns true if the configuration file has been loaded
|
47
|
+
def self.loaded?
|
48
|
+
!@config_data.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the configuration data
|
52
|
+
class << self
|
53
|
+
# @return [Hash]
|
54
|
+
attr_reader :config_data
|
9
55
|
end
|
10
56
|
|
11
57
|
# Returns the configuration data for the given class
|
12
58
|
#
|
13
59
|
# Example:
|
14
|
-
# LoggableActivity::Configuration.for_class('User')
|
60
|
+
# ::LoggableActivity::Configuration.for_class('User')
|
15
61
|
# Returns:
|
16
62
|
# {
|
17
|
-
# "
|
63
|
+
# "fetch_record_name_from": "full_name",
|
18
64
|
# "loggable_attrs": [
|
19
65
|
# "first_name",
|
20
66
|
# "last_name",
|
@@ -23,10 +69,21 @@ module LoggableActivity
|
|
23
69
|
# "create",
|
24
70
|
# "update",
|
25
71
|
# "destroy"
|
26
|
-
# ]
|
72
|
+
# ],
|
73
|
+
# ....
|
27
74
|
# }
|
28
75
|
def self.for_class(class_name)
|
29
76
|
@config_data[class_name]
|
30
77
|
end
|
78
|
+
|
79
|
+
# Returns the name of the field or method to use for the actor's display name.
|
80
|
+
def self.fetch_current_user_name_from
|
81
|
+
@config_data['fetch_current_user_name_from']
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the name of the model to use for the current user.
|
85
|
+
def self.current_user_model_name
|
86
|
+
@config_data['current_user_model_name']
|
87
|
+
end
|
31
88
|
end
|
32
89
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module LoggableActivity
|
6
|
+
# This class represends an additional data owner for a record.
|
7
|
+
# For it to kick in, the data_owner configuration has to be set to true in the loggable_activity.yaml file.
|
8
|
+
class DataOwner < ActiveRecord::Base
|
9
|
+
self.table_name = 'loggable_data_owners'
|
10
|
+
belongs_to :record, polymorphic: true, optional: true
|
11
|
+
belongs_to :encryption_key, class_name: '::LoggableActivity::EncryptionKey'
|
12
|
+
|
13
|
+
# When a record is deleted, all data owner added to the record is also deleted.
|
14
|
+
def mark_as_deleted!
|
15
|
+
encryption_key.mark_as_deleted!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -5,24 +5,21 @@ require 'openssl'
|
|
5
5
|
require 'base64'
|
6
6
|
|
7
7
|
module LoggableActivity
|
8
|
-
# This error is raised when encryption or decryption fails
|
9
|
-
class EncryptionError < StandardError
|
10
|
-
end
|
11
|
-
|
12
8
|
# This module is used to encrypt and decrypt attributes
|
13
9
|
module Encryption
|
14
10
|
# Encrypts the given data using the given encryption key
|
15
11
|
#
|
16
12
|
# Example:
|
17
|
-
# LoggableActivity::Encryption.encrypt('my secret data', 'my secret key')
|
13
|
+
# ::LoggableActivity::Encryption.encrypt('my secret data', 'my secret key')
|
18
14
|
#
|
19
15
|
# Returns:
|
20
16
|
# "SOME_ENCRYPTED_STRING"
|
21
17
|
#
|
22
|
-
def self.encrypt(data,
|
23
|
-
return nil if
|
18
|
+
def self.encrypt(data, secret_key)
|
19
|
+
return nil if secret_key.nil?
|
20
|
+
return nil if data.nil?
|
24
21
|
|
25
|
-
encryption_key = Base64.decode64(
|
22
|
+
encryption_key = Base64.decode64(secret_key)
|
26
23
|
raise EncryptionError, "Encryption failed: Invalid encoded_key length #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
|
27
24
|
|
28
25
|
cipher = OpenSSL::Cipher.new('AES-256-CBC').encrypt
|
@@ -39,19 +36,21 @@ module LoggableActivity
|
|
39
36
|
# Decrypts the given data using the given encryption key
|
40
37
|
#
|
41
38
|
# Example:
|
42
|
-
# LoggableActivity::Encryption.decrypt('SOME_ENCRYPTED_STRING', 'SECRET_KEY')
|
39
|
+
# ::LoggableActivity::Encryption.decrypt('SOME_ENCRYPTED_STRING', 'SECRET_KEY')
|
43
40
|
#
|
44
41
|
# Returns:
|
45
42
|
# "my secret data"
|
46
43
|
#
|
47
|
-
def self.decrypt(data,
|
48
|
-
return '' if
|
44
|
+
def self.decrypt(data, secret_key)
|
45
|
+
return I18n.t('loggable.activity.deleted') if secret_key.nil?
|
46
|
+
return '' if data.blank?
|
49
47
|
|
50
|
-
encryption_key = Base64.decode64(
|
48
|
+
encryption_key = Base64.decode64(secret_key)
|
51
49
|
raise EncryptionError, "Decryption failed: Invalid encoded_key length: #{encryption_key.bytesize}" unless encryption_key.bytesize == 32
|
52
50
|
|
53
51
|
cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
|
54
52
|
cipher.key = encryption_key
|
53
|
+
raise EncryptionError, 'Decryption failed: Invalid data length' unless data.bytesize > cipher.iv_len
|
55
54
|
|
56
55
|
raw_data = Base64.decode64(data)
|
57
56
|
cipher.iv = raw_data[0...cipher.iv_len] # Extract IV from the beginning of raw_data
|
@@ -59,9 +58,10 @@ module LoggableActivity
|
|
59
58
|
|
60
59
|
decrypted_data.force_encoding('UTF-8')
|
61
60
|
rescue OpenSSL::Cipher::CipherError => e
|
62
|
-
|
61
|
+
puts "CipherError Decryption failed: #{e.message}"
|
62
|
+
'*** DECRYPTION FAILED ***'
|
63
63
|
rescue EncryptionError => e
|
64
|
-
puts e.message
|
64
|
+
puts "EncryptionError failed: #{e.message}"
|
65
65
|
'*** DECRYPTION FAILED ***'
|
66
66
|
end
|
67
67
|
|
@@ -10,24 +10,29 @@ module LoggableActivity
|
|
10
10
|
|
11
11
|
# Associations
|
12
12
|
belongs_to :record, polymorphic: true, optional: true
|
13
|
-
belongs_to :
|
14
|
-
|
13
|
+
# belongs_to :payload, class_name: '::LoggableActivity::Payload', optional: true
|
14
|
+
# belongs_to :parent_key, class_name: '::LoggableActivity::EncryptionKey', optional: true,
|
15
|
+
# foreign_key: 'parent_key_id'
|
15
16
|
|
16
17
|
# Marks the encryption key as deleted by updating the key to nil.
|
17
|
-
def mark_as_deleted
|
18
|
-
update(
|
19
|
-
|
18
|
+
def mark_as_deleted!
|
19
|
+
update(secret_key: nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
# check if the encryption key is deleted
|
23
|
+
def deleted?
|
24
|
+
secret_key.nil?
|
20
25
|
end
|
21
26
|
|
22
27
|
# Returns an encryption key for a record by its type and ID, optionally using a parent key.
|
23
28
|
#
|
24
29
|
# @param record_type [String] The type of the record.
|
25
30
|
# @param record_id [Integer] The ID of the record.
|
26
|
-
# @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
27
|
-
# @return [LoggableActivity::EncryptionKey] The encryption key for the record.
|
31
|
+
# @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
32
|
+
# @return [::LoggableActivity::EncryptionKey] The encryption key for the record.
|
28
33
|
#
|
29
34
|
# Example:
|
30
|
-
# LoggableActivity::EncryptionKey.for_record_by_type_and_id('User', 1)
|
35
|
+
# ::LoggableActivity::EncryptionKey.for_record_by_type_and_id('User', 1)
|
31
36
|
#
|
32
37
|
# Returns:
|
33
38
|
# {
|
@@ -38,22 +43,22 @@ module LoggableActivity
|
|
38
43
|
# :record_id => 1
|
39
44
|
# }
|
40
45
|
#
|
41
|
-
def self.for_record_by_type_and_id(record_type, record_id
|
46
|
+
def self.for_record_by_type_and_id(record_type, record_id)
|
42
47
|
encryption_key = find_by(record_type:, record_id:)
|
43
48
|
return encryption_key if encryption_key
|
44
49
|
|
45
|
-
create_encryption_key(record_type, record_id
|
50
|
+
create_encryption_key(record_type, record_id)
|
46
51
|
end
|
47
52
|
|
48
53
|
# Returns an encryption key for a record, optionally using a parent key.
|
49
54
|
#
|
50
55
|
# @param record [ActiveRecord::Base] The record for which to get the encryption key.
|
51
|
-
# @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
52
|
-
# @return [LoggableActivity::EncryptionKey] The encryption key for the record.
|
56
|
+
# @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
57
|
+
# @return [::LoggableActivity::EncryptionKey] The encryption key for the record.
|
53
58
|
#
|
54
59
|
# Example:
|
55
60
|
# user = User.find(1)
|
56
|
-
# LoggableActivity::EncryptionKey.for_record(user)
|
61
|
+
# ::LoggableActivity::EncryptionKey.for_record(user)
|
57
62
|
#
|
58
63
|
# Returns:
|
59
64
|
# {
|
@@ -64,24 +69,24 @@ module LoggableActivity
|
|
64
69
|
# :record_id => 1
|
65
70
|
# }
|
66
71
|
#
|
67
|
-
def self.for_record(record
|
72
|
+
def self.for_record(record)
|
68
73
|
return nil if record.nil?
|
69
74
|
|
70
75
|
encryption_key = find_by(record:)
|
71
76
|
return encryption_key if encryption_key
|
72
77
|
|
73
|
-
create_encryption_key(record.class.name, record.id
|
78
|
+
create_encryption_key(record.class.name, record.id)
|
74
79
|
end
|
75
80
|
|
76
81
|
# Creates an encryption key for a record, optionally using a parent key.
|
77
82
|
#
|
78
83
|
# @param record_type [String] The type of the record.
|
79
84
|
# @param record_id [Integer] The ID of the record.
|
80
|
-
# @param parent_key [LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
81
|
-
# @return [LoggableActivity::EncryptionKey] The created encryption key.
|
85
|
+
# @param parent_key [::LoggableActivity::EncryptionKey, nil] The parent encryption key, if any.
|
86
|
+
# @return [::LoggableActivity::EncryptionKey] The created encryption key.
|
82
87
|
#
|
83
88
|
# Example:
|
84
|
-
# LoggableActivity::EncryptionKey.create_encryption_key('User', 1)
|
89
|
+
# ::LoggableActivity::EncryptionKey.create_encryption_key('User', 1)
|
85
90
|
#
|
86
91
|
# Returns:
|
87
92
|
# {
|
@@ -92,12 +97,8 @@ module LoggableActivity
|
|
92
97
|
# :record_id => 1
|
93
98
|
# }
|
94
99
|
#
|
95
|
-
def self.create_encryption_key(record_type, record_id
|
96
|
-
|
97
|
-
create(record_type:, record_id:, key: random_key, parent_key:)
|
98
|
-
else
|
99
|
-
create(record_type:, record_id:, key: random_key)
|
100
|
-
end
|
100
|
+
def self.create_encryption_key(record_type, record_id)
|
101
|
+
create(record_type:, record_id:, secret_key: random_key)
|
101
102
|
end
|
102
103
|
|
103
104
|
# Generates a random encryption key.
|
@@ -105,7 +106,7 @@ module LoggableActivity
|
|
105
106
|
# @return [String] The generated encryption key.
|
106
107
|
#
|
107
108
|
# Example:
|
108
|
-
# LoggableActivity::EncryptionKey.random_key
|
109
|
+
# ::LoggableActivity::EncryptionKey.random_key
|
109
110
|
#
|
110
111
|
# Returns:
|
111
112
|
# "a8f4774e7f42eb253045a4db7de7b79e"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LoggableActivity
|
4
|
+
# Error class for loggable activity.
|
5
|
+
class Error < StandardError
|
6
|
+
def initialize(msg = '')
|
7
|
+
puts '---------------- LOGGABLE ACTIVITY -----------------'
|
8
|
+
puts msg
|
9
|
+
puts '----------------------------------------------------'
|
10
|
+
super(msg)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Error class for encryption.
|
15
|
+
class EncryptionError < StandardError
|
16
|
+
def initialize(msg = '')
|
17
|
+
puts '---------------- LOGGABLE ACTIVITY -----------------'
|
18
|
+
puts msg
|
19
|
+
puts '----------------------------------------------------'
|
20
|
+
super(msg)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# This class is used to load the configuration file located at config/loggable_activity.yml
|
25
|
+
class ConfigurationError < StandardError
|
26
|
+
def initialize(msg = '')
|
27
|
+
# https://api.loggable_activity.com/msg
|
28
|
+
puts '---------------- LOGGABLE ACTIVITY -----------------'
|
29
|
+
puts msg
|
30
|
+
puts '----------------------------------------------------'
|
31
|
+
super(msg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|