loggable_activity 0.1.56 → 0.1.58
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/.rubocop.yml +10 -1
- data/CHANGELOG.md +9 -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 +53 -102
- 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/generators/loggable_activity/install_templates_generator.rb +0 -105
- data/lib/generators/loggable_activity/templates/helpers/activity_helper.rb +0 -37
- data/lib/generators/loggable_activity/templates/helpers/router.rb +0 -52
- data/lib/generators/loggable_activity/templates/helpers/routes_helper.rb +0 -20
- 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
|
-
route: payload.payload_route
|
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 primary_route
|
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
|
|
@@ -180,57 +151,37 @@ module LoggableActivity
|
|
180
151
|
#
|
181
152
|
# Example usage:
|
182
153
|
# payload = @activity.primary_payload
|
183
|
-
#
|
154
|
+
# payload.record_type # => 'SOMD_MODEL_NAME'
|
184
155
|
#
|
185
156
|
def primary_payload
|
186
|
-
|
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) }
|
187
159
|
end
|
188
160
|
|
189
|
-
# Returns
|
190
|
-
def update_attrs
|
191
|
-
update_payload_attrs = attrs.find { |p| p[:payload_type] == 'update_payload' }
|
192
|
-
return nil unless update_payload_attrs
|
193
|
-
|
194
|
-
update_payload_attrs.delete(:payload_type)
|
195
|
-
update_payload_attrs
|
196
|
-
end
|
197
|
-
|
198
|
-
|
199
|
-
# Returns the attributes for the updated relations.
|
200
|
-
def updated_relations_attrs
|
201
|
-
grouped_associations = attrs.group_by { |p| p[:record_type] }
|
202
|
-
|
203
|
-
grouped_associations.map do |record_type, payloads|
|
204
|
-
previous_attrs = payloads.find { |p| p[:payload_type] == 'previous_association' }
|
205
|
-
current_attrs = payloads.find { |p| p[:payload_type] == 'current_association' }
|
206
|
-
next if previous_attrs.nil? && current_attrs.nil?
|
207
|
-
|
208
|
-
{ record_type:, previous_attrs:, current_attrs: }
|
209
|
-
end.compact
|
210
|
-
end
|
211
|
-
|
212
|
-
# Returns the previous association attributes.
|
213
|
-
def previous_associations_attrs
|
214
|
-
attrs.select { |p| p[:payload_type] == 'previous_association' }
|
215
|
-
end
|
216
|
-
|
217
|
-
# Returns payloads sorted by :payload_type.
|
161
|
+
# # Returns payloads sorted by :related_to_activity_as.
|
218
162
|
def ordered_payloads
|
219
|
-
payloads.order(:
|
163
|
+
# payloads.order(related_to_activity_as: :desc)
|
164
|
+
payloads
|
220
165
|
end
|
221
166
|
|
222
167
|
# Returns the key for the logged record.
|
223
168
|
def record_key
|
224
169
|
return nil if record.nil?
|
225
170
|
|
226
|
-
LoggableActivity::EncryptionKey.for_record(record)&.
|
171
|
+
::LoggableActivity::EncryptionKey.for_record(record)&.secret_key
|
227
172
|
end
|
228
173
|
|
229
|
-
#
|
230
|
-
|
231
|
-
|
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
|
232
180
|
|
233
|
-
|
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
|
234
185
|
end
|
235
186
|
|
236
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
|