standard_audit 0.3.0 → 0.5.0
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/CHANGELOG.md +29 -0
- data/README.md +32 -3
- data/lib/generators/standard_audit/install/install_generator.rb +67 -1
- data/lib/generators/standard_audit/install/templates/initializer.rb.erb +7 -4
- data/lib/standard_audit/configuration.rb +0 -18
- data/lib/standard_audit/engine.rb +6 -0
- data/lib/standard_audit/event_subscriber.rb +101 -0
- data/lib/standard_audit/rspec.rb +29 -0
- data/lib/standard_audit/subscriber.rb +1 -1
- data/lib/standard_audit/version.rb +1 -1
- data/lib/standard_audit.rb +12 -2
- metadata +28 -12
- data/lib/standard_audit/presets/standard_id.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dff9827071db99dfcb9dc184f3cc20d0bcfd208de8f5d41c86efff0f7e4c2be
|
|
4
|
+
data.tar.gz: 979f93e8ed8d293695337e453ade9926b553be7904617d7c61d653ef5997a3ea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98bf60f4d4d40d46ff28d9203c8686a5c57765a86c4c3ef1db97c59d32b27e355ac7b585730cd69865e54cf8540109c87546259ea19e4afdfa9a3510b5b52e1f
|
|
7
|
+
data.tar.gz: dda3b2376d9afcd2a61808fc79025e246365302f7b097c17a801bf47cc6a8f3e26a3e11b411dc19b31f6ebac32dc44107e92582e02813a22476be2fa1eaca32a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-04-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- CI and release workflows migrated to the shared `rarebit-one/.github` reusable workflows (`reusable-gem-ci.yml@v1`, `reusable-gem-release.yml@v1`); `.github/workflows/ci.yml` and `release.yml` are now thin shims.
|
|
15
|
+
- The `standard_audit:install` generator is now idempotent. Re-running it skips the migration when a `*_create_audit_logs.rb` file already exists in `db/migrate/`, and skips the initializer when `config/initializers/standard_audit.rb` already exists. New flags: `--skip-migration`, `--skip-initializer`, and `--force` (overwrite the existing initializer; defaults to skip without an interactive prompt).
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- **BREAKING:** Removed `Configuration#use_preset` and the `lib/standard_audit/presets/` directory. The preset pattern (`config.use_preset(:standard_id)`) created a direct dependency from `standard_audit` on a specific publisher gem, which inverted the intended dependency direction — `standard_audit` should be a generic event consumer with no knowledge of any particular publisher. Host apps should subscribe to event patterns directly:
|
|
20
|
+
```ruby
|
|
21
|
+
StandardAudit.configure do |c|
|
|
22
|
+
c.subscribe_to "standard_id.authentication.*"
|
|
23
|
+
c.subscribe_to "standard_id.session.created"
|
|
24
|
+
c.subscribe_to "standard_id.session.revoked"
|
|
25
|
+
c.subscribe_to "standard_id.session.expired"
|
|
26
|
+
c.subscribe_to "standard_id.account.*"
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
Each publisher gem documents its event namespace.
|
|
30
|
+
- **BREAKING:** Dropped support for Ruby < 4.0. `required_ruby_version` is now `>= 4.0`. Hosts must upgrade to Ruby 4.0+ before bundling this version. CI tests all four published 4.0.x patches.
|
|
31
|
+
- **BREAKING:** Dropped support for Rails < 8.0. `activerecord`, `activejob`, and `activesupport` constraints are now `>= 8.0` (was `>= 7.1`). Hosts on Rails 7.x must upgrade to Rails 8.0+ before bundling this version. Aligns with the org-wide policy of supporting Rails 8 and up.
|
|
32
|
+
|
|
33
|
+
## [0.4.0] - 2026-04-19
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Rails 8.1+ structured event reporter (`Rails.event`) integration. A new `StandardAudit::EventSubscriber` is registered automatically when `Rails.event` is available, so `Rails.event.notify("myapp.orders.created", actor: user, target: order)` persists an `AuditLog` the same way an `ActiveSupport::Notifications.instrument` call does. Event name is matched against the existing `subscribe_to` patterns (supports `*`, `**`, and `Regexp`). `Rails.event.set_context(...)` values take precedence over the `Current.*` resolvers for `request_id`, `ip_address`, `user_agent`, and `session_id`. `Rails.event.tagged(...)` and `source_location` are captured under the reserved metadata keys `_tags` and `_source`.
|
|
38
|
+
|
|
10
39
|
## [0.3.0] - 2026-03-31
|
|
11
40
|
|
|
12
41
|
### Added
|
data/README.md
CHANGED
|
@@ -23,6 +23,8 @@ This creates:
|
|
|
23
23
|
- A migration for the `audit_logs` table (UUID primary keys, JSON metadata)
|
|
24
24
|
- An initializer at `config/initializers/standard_audit.rb`
|
|
25
25
|
|
|
26
|
+
The generator is idempotent — re-running it skips the migration when a `*_create_audit_logs.rb` already exists in `db/migrate/`, and skips the initializer when `config/initializers/standard_audit.rb` already exists. Pass `--skip-migration` or `--skip-initializer` to opt out of individual steps, or `--force` to overwrite the existing initializer.
|
|
27
|
+
|
|
26
28
|
## Quick Start
|
|
27
29
|
|
|
28
30
|
### 1. Subscribe to events
|
|
@@ -52,11 +54,38 @@ StandardAudit::AuditLog.for_actor(current_user).this_week
|
|
|
52
54
|
|
|
53
55
|
## Recording Events
|
|
54
56
|
|
|
55
|
-
StandardAudit provides
|
|
57
|
+
StandardAudit provides four ways to record audit events. On Rails 8.1+, prefer `Rails.event` — it is the standard Rails interface for structured events.
|
|
58
|
+
|
|
59
|
+
### Rails.event (Rails 8.1+)
|
|
60
|
+
|
|
61
|
+
StandardAudit registers a `Rails.event` subscriber at boot, so any `notify` call whose name matches a configured `subscribe_to` pattern is persisted automatically:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class ApplicationController < ActionController::Base
|
|
65
|
+
before_action do
|
|
66
|
+
Rails.event.set_context(
|
|
67
|
+
request_id: request.request_id,
|
|
68
|
+
ip_address: request.remote_ip,
|
|
69
|
+
user_agent: request.user_agent
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Rails.event.tagged("checkout") do
|
|
75
|
+
Rails.event.notify("myapp.orders.created",
|
|
76
|
+
actor: current_user,
|
|
77
|
+
target: @order,
|
|
78
|
+
scope: current_organisation,
|
|
79
|
+
total: @order.total
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`Rails.event.set_context` values override the `Current.*` resolvers for `request_id`, `ip_address`, `user_agent`, and `session_id`. Tags and `source_location` are captured as metadata under the reserved keys `_tags` and `_source`.
|
|
56
85
|
|
|
57
86
|
### Convenience API
|
|
58
87
|
|
|
59
|
-
|
|
88
|
+
Call `StandardAudit.record` directly:
|
|
60
89
|
|
|
61
90
|
```ruby
|
|
62
91
|
StandardAudit.record("orders.created",
|
|
@@ -71,7 +100,7 @@ When `actor` is omitted, it falls back to the configured `current_actor_resolver
|
|
|
71
100
|
|
|
72
101
|
### ActiveSupport::Notifications
|
|
73
102
|
|
|
74
|
-
|
|
103
|
+
For Rails < 8.1, or when `Rails.event` is unavailable, instrument events via `ActiveSupport::Notifications`:
|
|
75
104
|
|
|
76
105
|
```ruby
|
|
77
106
|
ActiveSupport::Notifications.instrument("myapp.orders.created", {
|
|
@@ -1,19 +1,85 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
1
3
|
module StandardAudit
|
|
2
4
|
module Generators
|
|
5
|
+
# Installs StandardAudit in a host Rails application.
|
|
6
|
+
#
|
|
7
|
+
# Creates the migration for the `audit_logs` table and writes the
|
|
8
|
+
# initializer at `config/initializers/standard_audit.rb`.
|
|
9
|
+
#
|
|
10
|
+
# Idempotent: re-running the generator will skip pieces it has already
|
|
11
|
+
# installed. Pass `--skip-*` flags to opt out of individual steps and
|
|
12
|
+
# `--force` to overwrite an existing initializer.
|
|
3
13
|
class InstallGenerator < Rails::Generators::Base
|
|
4
14
|
include Rails::Generators::Migration
|
|
5
15
|
source_root File.expand_path("templates", __dir__)
|
|
6
16
|
|
|
17
|
+
desc <<~DESC
|
|
18
|
+
Installs StandardAudit. By default this:
|
|
19
|
+
* copies a CreateAuditLogs migration into db/migrate/
|
|
20
|
+
* writes config/initializers/standard_audit.rb
|
|
21
|
+
|
|
22
|
+
Use --skip-* flags to opt out of individual steps when re-running on an
|
|
23
|
+
existing install. The generator is idempotent — already-installed
|
|
24
|
+
pieces are skipped with a clear message. Pass --force to overwrite an
|
|
25
|
+
existing initializer.
|
|
26
|
+
DESC
|
|
27
|
+
|
|
28
|
+
class_option :skip_migration, type: :boolean, default: false,
|
|
29
|
+
desc: "Do not copy the CreateAuditLogs migration into db/migrate"
|
|
30
|
+
class_option :skip_initializer, type: :boolean, default: false,
|
|
31
|
+
desc: "Do not write config/initializers/standard_audit.rb"
|
|
32
|
+
class_option :force, type: :boolean, default: false,
|
|
33
|
+
desc: "Overwrite config/initializers/standard_audit.rb if it already exists"
|
|
34
|
+
|
|
7
35
|
def self.next_migration_number(dirname)
|
|
8
36
|
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
9
37
|
end
|
|
10
38
|
|
|
11
39
|
def copy_migration
|
|
40
|
+
if options[:skip_migration]
|
|
41
|
+
say_status("skip", "db/migrate/*_create_audit_logs.rb (--skip-migration)", :yellow)
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if existing_migration
|
|
46
|
+
say_status(
|
|
47
|
+
"identical",
|
|
48
|
+
"AuditLog migration already present (#{relative_migration_path(existing_migration)}), skipping",
|
|
49
|
+
:blue
|
|
50
|
+
)
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
12
54
|
migration_template "create_audit_logs.rb.erb", "db/migrate/create_audit_logs.rb"
|
|
13
55
|
end
|
|
14
56
|
|
|
15
57
|
def copy_initializer
|
|
16
|
-
|
|
58
|
+
initializer_path = "config/initializers/standard_audit.rb"
|
|
59
|
+
|
|
60
|
+
if options[:skip_initializer]
|
|
61
|
+
say_status("skip", "#{initializer_path} (--skip-initializer)", :yellow)
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if File.exist?(File.join(destination_root, initializer_path)) && !options[:force]
|
|
66
|
+
say_status("identical", "#{initializer_path} (already exists; pass --force to overwrite)", :blue)
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
template "initializer.rb.erb", initializer_path, force: options[:force]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
no_commands do
|
|
74
|
+
def existing_migration
|
|
75
|
+
Dir.glob(File.join(destination_root, "db/migrate/*_create_audit_logs.rb")).first
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def relative_migration_path(absolute_path)
|
|
79
|
+
Pathname.new(absolute_path).relative_path_from(Pathname.new(destination_root)).to_s
|
|
80
|
+
rescue ArgumentError
|
|
81
|
+
absolute_path
|
|
82
|
+
end
|
|
17
83
|
end
|
|
18
84
|
end
|
|
19
85
|
end
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
StandardAudit.configure do |config|
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
#
|
|
2
|
+
# Subscribe to ActiveSupport::Notifications / Rails.event patterns.
|
|
3
|
+
# Each gem documents its own event namespace; subscribe to whichever
|
|
4
|
+
# patterns you want audited:
|
|
5
|
+
# config.subscribe_to "standard_id.authentication.*"
|
|
6
|
+
# config.subscribe_to "standard_id.session.created"
|
|
7
|
+
# config.subscribe_to "standard_id.session.revoked"
|
|
8
|
+
# config.subscribe_to "standard_circuit.circuit.*"
|
|
6
9
|
# config.subscribe_to "audit.**"
|
|
7
10
|
|
|
8
11
|
# Actor extractor from notification payload
|
|
@@ -10,7 +10,6 @@ module StandardAudit
|
|
|
10
10
|
|
|
11
11
|
def initialize
|
|
12
12
|
@subscriptions = []
|
|
13
|
-
@applied_presets = []
|
|
14
13
|
@async = false
|
|
15
14
|
@queue_name = :default
|
|
16
15
|
@enabled = true
|
|
@@ -56,22 +55,5 @@ module StandardAudit
|
|
|
56
55
|
def subscriptions
|
|
57
56
|
@subscriptions.dup.freeze
|
|
58
57
|
end
|
|
59
|
-
|
|
60
|
-
def use_preset(name)
|
|
61
|
-
key = name.to_sym
|
|
62
|
-
return self if @applied_presets.include?(key)
|
|
63
|
-
|
|
64
|
-
preset = case key
|
|
65
|
-
when :standard_id
|
|
66
|
-
require "standard_audit/presets/standard_id"
|
|
67
|
-
StandardAudit::Presets::StandardId
|
|
68
|
-
else
|
|
69
|
-
raise ArgumentError, "Unknown preset: #{name}. Available presets: :standard_id"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
preset.apply(self)
|
|
73
|
-
@applied_presets << key
|
|
74
|
-
self
|
|
75
|
-
end
|
|
76
58
|
end
|
|
77
59
|
end
|
|
@@ -5,6 +5,12 @@ module StandardAudit
|
|
|
5
5
|
initializer "standard_audit.subscriber" do
|
|
6
6
|
ActiveSupport.on_load(:active_record) do
|
|
7
7
|
StandardAudit.subscriber.setup!
|
|
8
|
+
|
|
9
|
+
# Rails 8.1+ structured event reporter. Feature-detected so the gem
|
|
10
|
+
# still works on older Rails versions that only have AS::Notifications.
|
|
11
|
+
if Rails.respond_to?(:event) && Rails.event.respond_to?(:subscribe)
|
|
12
|
+
Rails.event.subscribe(StandardAudit.event_subscriber)
|
|
13
|
+
end
|
|
8
14
|
end
|
|
9
15
|
end
|
|
10
16
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module StandardAudit
|
|
2
|
+
# Subscriber for Rails 8.1+ structured event reporting (`Rails.event`).
|
|
3
|
+
#
|
|
4
|
+
# Registered with `Rails.event.subscribe(...)` so that every `Rails.event.notify`
|
|
5
|
+
# call flows through StandardAudit for persistence. Events whose name does not
|
|
6
|
+
# match any configured `subscribe_to` pattern are ignored.
|
|
7
|
+
#
|
|
8
|
+
# Payload is extracted with the same extractors used by the
|
|
9
|
+
# ActiveSupport::Notifications subscriber. Rails.event `context` supplies
|
|
10
|
+
# request_id/ip_address/user_agent/session_id and takes precedence over the
|
|
11
|
+
# Current.* resolvers. Tags and source_location are captured as metadata
|
|
12
|
+
# under the reserved keys `_tags` and `_source`.
|
|
13
|
+
class EventSubscriber
|
|
14
|
+
RESERVED_PAYLOAD_KEYS = %i[actor target scope request_id ip_address user_agent session_id].freeze
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@pattern_cache = {}
|
|
18
|
+
@pattern_cache_mutex = Mutex.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def emit(event)
|
|
22
|
+
return unless StandardAudit.config.enabled
|
|
23
|
+
|
|
24
|
+
name = event[:name]
|
|
25
|
+
return if name.nil?
|
|
26
|
+
return unless matches_subscription?(name)
|
|
27
|
+
|
|
28
|
+
config = StandardAudit.config
|
|
29
|
+
payload = event[:payload] || {}
|
|
30
|
+
context = event[:context] || {}
|
|
31
|
+
|
|
32
|
+
actor = config.actor_extractor.call(payload)
|
|
33
|
+
target = config.target_extractor.call(payload)
|
|
34
|
+
scope = config.scope_extractor.call(payload)
|
|
35
|
+
|
|
36
|
+
metadata = build_metadata(payload, event[:tags], event[:source_location], config)
|
|
37
|
+
|
|
38
|
+
StandardAudit.record(
|
|
39
|
+
name,
|
|
40
|
+
actor: actor,
|
|
41
|
+
target: target,
|
|
42
|
+
scope: scope,
|
|
43
|
+
metadata: metadata,
|
|
44
|
+
request_id: context[:request_id] || payload[:request_id],
|
|
45
|
+
ip_address: context[:ip_address] || payload[:ip_address],
|
|
46
|
+
user_agent: context[:user_agent] || payload[:user_agent],
|
|
47
|
+
session_id: context[:session_id] || payload[:session_id]
|
|
48
|
+
)
|
|
49
|
+
rescue => e
|
|
50
|
+
Rails.logger.error("[StandardAudit] Error handling Rails.event: #{e.class}: #{e.message}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def matches_subscription?(name)
|
|
56
|
+
StandardAudit.config.subscriptions.any? { |pattern| pattern_match?(pattern, name) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Supports the same pattern shapes as ActiveSupport::Notifications.subscribe:
|
|
60
|
+
# a Regexp, or a String with `*` matching a single segment and `**` matching
|
|
61
|
+
# the remainder.
|
|
62
|
+
def pattern_match?(pattern, name)
|
|
63
|
+
case pattern
|
|
64
|
+
when Regexp
|
|
65
|
+
pattern.match?(name)
|
|
66
|
+
when String
|
|
67
|
+
compiled_pattern_for(pattern).match?(name)
|
|
68
|
+
else
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def compiled_pattern_for(pattern)
|
|
74
|
+
cached = @pattern_cache[pattern]
|
|
75
|
+
return cached if cached
|
|
76
|
+
|
|
77
|
+
@pattern_cache_mutex.synchronize do
|
|
78
|
+
@pattern_cache[pattern] ||= Regexp.new(
|
|
79
|
+
"\\A" + Regexp.escape(pattern).gsub('\\*\\*', ".*").gsub('\\*', "[^.]*") + "\\z"
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# `_tags` and `_source` are reserved metadata keys owned by this
|
|
85
|
+
# subscriber. Sensitive-key filtering is handled downstream by
|
|
86
|
+
# `StandardAudit.record`, so we don't re-run it here.
|
|
87
|
+
def build_metadata(payload, tags, source_location, config)
|
|
88
|
+
reserved = RESERVED_PAYLOAD_KEYS.map(&:to_s)
|
|
89
|
+
raw = payload.reject { |k, _| reserved.include?(k.to_s) }
|
|
90
|
+
raw = config.metadata_builder.call(raw) if config.metadata_builder
|
|
91
|
+
|
|
92
|
+
if tags.is_a?(Hash) && tags.any?
|
|
93
|
+
raw[:_tags] = tags
|
|
94
|
+
elsif tags && !tags.is_a?(Hash)
|
|
95
|
+
Rails.logger.warn("[StandardAudit] Dropping Rails.event tags of unexpected type: #{tags.class}")
|
|
96
|
+
end
|
|
97
|
+
raw[:_source] = source_location if source_location
|
|
98
|
+
raw
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "standard_audit"
|
|
2
|
+
|
|
3
|
+
# StandardAudit state reset between examples.
|
|
4
|
+
#
|
|
5
|
+
# - Clears the thread-local batch buffer so a spec that exits inside a
|
|
6
|
+
# `StandardAudit.batch { ... }` block (e.g. via an unhandled error or
|
|
7
|
+
# abort) cannot leak buffered records into the next example.
|
|
8
|
+
# - Resets the Configuration via `StandardAudit.reset_configuration!` so
|
|
9
|
+
# that mutations to `StandardAudit.config` (subscriptions, sensitive
|
|
10
|
+
# keys, async flag, custom resolvers, etc.) do not bleed across specs.
|
|
11
|
+
# Consumers that customise configuration must re-call
|
|
12
|
+
# `StandardAudit.configure { |c| ... }` from a `before` hook in their
|
|
13
|
+
# own suite if they need a non-default baseline.
|
|
14
|
+
#
|
|
15
|
+
# The memoized `Subscriber` and `EventSubscriber` instances are *not*
|
|
16
|
+
# torn down here — they are wired up at engine boot via initializers and
|
|
17
|
+
# rebuilding them per-example would unsubscribe from
|
|
18
|
+
# `ActiveSupport::Notifications` / `Rails.event` for the rest of the run.
|
|
19
|
+
# Specs that need to assert on subscriber behaviour should manage that
|
|
20
|
+
# locally.
|
|
21
|
+
#
|
|
22
|
+
# Intentionally `before(:example)` rather than `after(:example)` so the
|
|
23
|
+
# reset always runs even when a previous example aborted in an after hook.
|
|
24
|
+
RSpec.configure do |config|
|
|
25
|
+
config.before(:example) do
|
|
26
|
+
Thread.current[:standard_audit_batch] = nil
|
|
27
|
+
StandardAudit.reset_configuration!
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -67,7 +67,7 @@ module StandardAudit
|
|
|
67
67
|
log.save!
|
|
68
68
|
end
|
|
69
69
|
rescue => e
|
|
70
|
-
Rails.logger.error("[StandardAudit] Error creating audit log: #{e.message}")
|
|
70
|
+
Rails.logger.error("[StandardAudit] Error creating audit log: #{e.class}: #{e.message}")
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def extract_metadata(payload, config)
|
data/lib/standard_audit.rb
CHANGED
|
@@ -2,10 +2,15 @@ require "standard_audit/version"
|
|
|
2
2
|
require "standard_audit/engine"
|
|
3
3
|
require "standard_audit/configuration"
|
|
4
4
|
require "standard_audit/subscriber"
|
|
5
|
+
require "standard_audit/event_subscriber"
|
|
5
6
|
require "standard_audit/auditable"
|
|
6
7
|
require "standard_audit/audit_scope"
|
|
7
8
|
|
|
8
9
|
module StandardAudit
|
|
10
|
+
# Metadata keys owned internally by StandardAudit. Never filtered by
|
|
11
|
+
# `sensitive_keys` even if a user adds them there.
|
|
12
|
+
RESERVED_METADATA_KEYS = %w[_tags _source].freeze
|
|
13
|
+
|
|
9
14
|
class << self
|
|
10
15
|
def configure
|
|
11
16
|
yield(config) if block_given?
|
|
@@ -20,8 +25,9 @@ module StandardAudit
|
|
|
20
25
|
|
|
21
26
|
actor ||= config.current_actor_resolver.call
|
|
22
27
|
|
|
23
|
-
# Filter sensitive keys
|
|
24
|
-
|
|
28
|
+
# Filter sensitive keys. `_tags` and `_source` are reserved internal
|
|
29
|
+
# metadata keys owned by EventSubscriber and are never stripped.
|
|
30
|
+
sensitive = config.sensitive_keys.map(&:to_s) - RESERVED_METADATA_KEYS
|
|
25
31
|
filtered_metadata = metadata.reject { |k, _| sensitive.include?(k.to_s) }
|
|
26
32
|
|
|
27
33
|
attrs = {
|
|
@@ -90,6 +96,10 @@ module StandardAudit
|
|
|
90
96
|
@subscriber ||= Subscriber.new
|
|
91
97
|
end
|
|
92
98
|
|
|
99
|
+
def event_subscriber
|
|
100
|
+
@event_subscriber ||= EventSubscriber.new
|
|
101
|
+
end
|
|
102
|
+
|
|
93
103
|
def reset_configuration!
|
|
94
104
|
@configuration = nil
|
|
95
105
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: standard_audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jaryl Sim
|
|
@@ -15,42 +15,42 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '8.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '8.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activejob
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '8.0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '8.0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: activesupport
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '8.0'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '8.0'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: globalid
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,8 +65,23 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '1.0'
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: simplecov
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.22'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.22'
|
|
82
|
+
description: StandardAudit is a standalone Rails gem for database-backed audit logging.
|
|
83
|
+
On Rails 8.1+ it subscribes to Rails.event; on Rails 8.0 it falls back to ActiveSupport::Notifications.
|
|
84
|
+
Generic, flexible, and works with any Rails application.
|
|
70
85
|
email:
|
|
71
86
|
- code@jaryl.dev
|
|
72
87
|
executables: []
|
|
@@ -92,7 +107,8 @@ files:
|
|
|
92
107
|
- lib/standard_audit/auditable.rb
|
|
93
108
|
- lib/standard_audit/configuration.rb
|
|
94
109
|
- lib/standard_audit/engine.rb
|
|
95
|
-
- lib/standard_audit/
|
|
110
|
+
- lib/standard_audit/event_subscriber.rb
|
|
111
|
+
- lib/standard_audit/rspec.rb
|
|
96
112
|
- lib/standard_audit/subscriber.rb
|
|
97
113
|
- lib/standard_audit/version.rb
|
|
98
114
|
- lib/tasks/standard_audit_tasks.rake
|
|
@@ -111,7 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
111
127
|
requirements:
|
|
112
128
|
- - ">="
|
|
113
129
|
- !ruby/object:Gem::Version
|
|
114
|
-
version: '
|
|
130
|
+
version: '4.0'
|
|
115
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
132
|
requirements:
|
|
117
133
|
- - ">="
|
|
@@ -120,5 +136,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
120
136
|
requirements: []
|
|
121
137
|
rubygems_version: 4.0.3
|
|
122
138
|
specification_version: 4
|
|
123
|
-
summary: Database-backed audit logging for Rails via ActiveSupport::Notifications.
|
|
139
|
+
summary: Database-backed audit logging for Rails via Rails.event and ActiveSupport::Notifications.
|
|
124
140
|
test_files: []
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
module StandardAudit
|
|
2
|
-
module Presets
|
|
3
|
-
module StandardId
|
|
4
|
-
# Regex wildcards capture all events in a namespace. Session uses
|
|
5
|
-
# explicit strings to exclude noisy events like session.validated
|
|
6
|
-
# that fire on every authenticated request.
|
|
7
|
-
SUBSCRIPTIONS = [
|
|
8
|
-
/\Astandard_id\.authentication\./,
|
|
9
|
-
"standard_id.session.created",
|
|
10
|
-
"standard_id.session.revoked",
|
|
11
|
-
"standard_id.session.expired",
|
|
12
|
-
/\Astandard_id\.account\./,
|
|
13
|
-
/\Astandard_id\.social\./,
|
|
14
|
-
/\Astandard_id\.passwordless\./
|
|
15
|
-
].freeze
|
|
16
|
-
|
|
17
|
-
def self.apply(config)
|
|
18
|
-
SUBSCRIPTIONS.each { |pattern| config.subscribe_to(pattern) }
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|