action-audit 1.0.1
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/CHANGELOG.md +29 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/LICENSE +21 -0
- data/README.md +232 -0
- data/Rakefile +12 -0
- data/docs/README.md +25 -0
- data/docs/api-reference.md +434 -0
- data/docs/configuration.md +230 -0
- data/docs/examples.md +609 -0
- data/docs/installation.md +114 -0
- data/docs/migration.md +571 -0
- data/docs/multi-engine.md +413 -0
- data/docs/troubleshooting.md +577 -0
- data/docs/usage.md +347 -0
- data/lib/action-audit.rb +3 -0
- data/lib/action_audit/audit_messages.rb +73 -0
- data/lib/action_audit/engine.rb +19 -0
- data/lib/action_audit/version.rb +5 -0
- data/lib/action_audit.rb +75 -0
- data/lib/generators/action_audit/install_generator.rb +25 -0
- data/lib/generators/action_audit/templates/README +25 -0
- data/lib/generators/action_audit/templates/action_audit.rb +17 -0
- data/lib/generators/action_audit/templates/audit.yml +30 -0
- data/sig/action_audit.rbs +4 -0
- metadata +146 -0
data/docs/examples.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Real-world examples of using ActionAudit in different scenarios and applications.
|
|
4
|
+
|
|
5
|
+
## Basic Examples
|
|
6
|
+
|
|
7
|
+
### Simple User Management
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# app/controllers/users_controller.rb
|
|
11
|
+
class UsersController < ApplicationController
|
|
12
|
+
include ActionAudit
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
@user = User.create!(user_params)
|
|
16
|
+
redirect_to @user, notice: 'User created successfully'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update
|
|
20
|
+
@user = User.find(params[:id])
|
|
21
|
+
@user.update!(user_params)
|
|
22
|
+
redirect_to @user, notice: 'User updated successfully'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def destroy
|
|
26
|
+
@user = User.find(params[:id])
|
|
27
|
+
@user.destroy!
|
|
28
|
+
redirect_to users_path, notice: 'User deleted successfully'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def user_params
|
|
34
|
+
params.require(:user).permit(:name, :email, :role)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# config/audit.yml
|
|
41
|
+
users:
|
|
42
|
+
create: "Created user %{email} with role %{role}"
|
|
43
|
+
update: "Updated user %{id} - %{name}"
|
|
44
|
+
destroy: "Deleted user %{id}"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Log Output:**
|
|
48
|
+
```
|
|
49
|
+
users/create - Created user john@example.com with role admin
|
|
50
|
+
users/update - Updated user 123 - John Smith
|
|
51
|
+
users/destroy - Deleted user 123
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Advanced Examples
|
|
55
|
+
|
|
56
|
+
### Multi-Level Admin Interface
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# app/controllers/admin/users_controller.rb
|
|
60
|
+
class Admin::UsersController < ApplicationController
|
|
61
|
+
include ActionAudit
|
|
62
|
+
before_action :require_admin
|
|
63
|
+
|
|
64
|
+
def create
|
|
65
|
+
@user = User.create!(user_params)
|
|
66
|
+
@user.send_welcome_email if params[:send_welcome]
|
|
67
|
+
redirect_to admin_user_path(@user)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def activate
|
|
71
|
+
@user = User.find(params[:id])
|
|
72
|
+
@user.update!(active: true, activated_at: Time.current)
|
|
73
|
+
redirect_to admin_user_path(@user)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def impersonate
|
|
77
|
+
@user = User.find(params[:id])
|
|
78
|
+
session[:impersonated_user_id] = @user.id
|
|
79
|
+
redirect_to root_path
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def user_params
|
|
85
|
+
params.require(:user).permit(:name, :email, :role, :department)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
# config/audit.yml
|
|
92
|
+
admin:
|
|
93
|
+
users:
|
|
94
|
+
create: "Admin created user %{email} in %{department} department"
|
|
95
|
+
update: "Admin updated user %{id}"
|
|
96
|
+
destroy: "Admin deleted user %{id}"
|
|
97
|
+
activate: "Admin activated user %{id}"
|
|
98
|
+
deactivate: "Admin deactivated user %{id}"
|
|
99
|
+
impersonate: "Admin impersonated user %{id}"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### API Endpoints with Detailed Logging
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# app/controllers/api/v1/webhooks_controller.rb
|
|
106
|
+
class API::V1::WebhooksController < ApplicationController
|
|
107
|
+
include ActionAudit
|
|
108
|
+
skip_before_action :verify_authenticity_token
|
|
109
|
+
before_action :set_audit_context
|
|
110
|
+
|
|
111
|
+
def create
|
|
112
|
+
@webhook = WebhookEvent.create!(webhook_params)
|
|
113
|
+
WebhookProcessor.perform_async(@webhook.id)
|
|
114
|
+
render json: { status: 'received', id: @webhook.id }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def webhook_params
|
|
120
|
+
params.permit(:source, :event_type, :payload).tap do |wp|
|
|
121
|
+
wp[:payload_size] = params[:payload].to_s.bytesize
|
|
122
|
+
wp[:client_ip] = request.remote_ip
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def set_audit_context
|
|
127
|
+
# Add context for auditing
|
|
128
|
+
params[:source] = request.headers['X-Webhook-Source'] || 'unknown'
|
|
129
|
+
params[:user_agent] = request.user_agent
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# config/audit.yml
|
|
136
|
+
api:
|
|
137
|
+
v1:
|
|
138
|
+
webhooks:
|
|
139
|
+
create: "Webhook received from %{source} - %{event_type} (%{payload_size} bytes) from %{client_ip}"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## E-commerce Examples
|
|
143
|
+
|
|
144
|
+
### Order Management
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# app/controllers/orders_controller.rb
|
|
148
|
+
class OrdersController < ApplicationController
|
|
149
|
+
include ActionAudit
|
|
150
|
+
before_action :authenticate_user!
|
|
151
|
+
before_action :set_audit_user_context
|
|
152
|
+
|
|
153
|
+
def create
|
|
154
|
+
@order = current_user.orders.build(order_params)
|
|
155
|
+
@order.calculate_totals!
|
|
156
|
+
@order.save!
|
|
157
|
+
|
|
158
|
+
redirect_to @order, notice: 'Order placed successfully'
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def cancel
|
|
162
|
+
@order = current_user.orders.find(params[:id])
|
|
163
|
+
@order.cancel!
|
|
164
|
+
|
|
165
|
+
redirect_to @order, notice: 'Order cancelled'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def refund
|
|
169
|
+
@order = Order.find(params[:id])
|
|
170
|
+
@refund = @order.create_refund!(refund_amount: params[:amount])
|
|
171
|
+
|
|
172
|
+
redirect_to @order, notice: 'Refund processed'
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def order_params
|
|
178
|
+
params.require(:order).permit(:shipping_address, line_items: [:product_id, :quantity])
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def set_audit_user_context
|
|
182
|
+
params[:user_id] = current_user.id
|
|
183
|
+
params[:user_email] = current_user.email
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
# config/audit.yml
|
|
190
|
+
orders:
|
|
191
|
+
create: "User %{user_email} placed order %{id} for $%{total}"
|
|
192
|
+
cancel: "User %{user_email} cancelled order %{id}"
|
|
193
|
+
refund: "Refunded $%{amount} for order %{id} to user %{user_email}"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Inventory Management
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
# app/controllers/admin/inventory_controller.rb
|
|
200
|
+
class Admin::InventoryController < ApplicationController
|
|
201
|
+
include ActionAudit
|
|
202
|
+
before_action :require_inventory_manager
|
|
203
|
+
|
|
204
|
+
def adjust
|
|
205
|
+
@product = Product.find(params[:id])
|
|
206
|
+
old_quantity = @product.inventory_quantity
|
|
207
|
+
@product.update!(inventory_quantity: params[:new_quantity])
|
|
208
|
+
|
|
209
|
+
params[:old_quantity] = old_quantity
|
|
210
|
+
params[:adjustment] = params[:new_quantity].to_i - old_quantity
|
|
211
|
+
params[:product_name] = @product.name
|
|
212
|
+
|
|
213
|
+
redirect_to admin_product_path(@product)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def restock
|
|
217
|
+
@product = Product.find(params[:id])
|
|
218
|
+
@product.increment!(:inventory_quantity, params[:quantity].to_i)
|
|
219
|
+
|
|
220
|
+
params[:product_name] = @product.name
|
|
221
|
+
|
|
222
|
+
redirect_to admin_product_path(@product)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
# config/audit.yml
|
|
229
|
+
admin:
|
|
230
|
+
inventory:
|
|
231
|
+
adjust: "Inventory adjusted for %{product_name}: %{old_quantity} → %{new_quantity} (Δ%{adjustment})"
|
|
232
|
+
restock: "Restocked %{product_name} with %{quantity} units"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Authentication & Security Examples
|
|
236
|
+
|
|
237
|
+
### Session Management
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
# app/controllers/sessions_controller.rb
|
|
241
|
+
class SessionsController < ApplicationController
|
|
242
|
+
include ActionAudit
|
|
243
|
+
before_action :set_audit_context, except: [:new]
|
|
244
|
+
|
|
245
|
+
def create
|
|
246
|
+
@user = User.find_by(email: params[:email])
|
|
247
|
+
|
|
248
|
+
if @user&.authenticate(params[:password])
|
|
249
|
+
if @user.active?
|
|
250
|
+
session[:user_id] = @user.id
|
|
251
|
+
params[:login_method] = 'password'
|
|
252
|
+
redirect_to dashboard_path
|
|
253
|
+
else
|
|
254
|
+
params[:failure_reason] = 'account_inactive'
|
|
255
|
+
redirect_to login_path, alert: 'Account is inactive'
|
|
256
|
+
end
|
|
257
|
+
else
|
|
258
|
+
params[:failure_reason] = 'invalid_credentials'
|
|
259
|
+
redirect_to login_path, alert: 'Invalid credentials'
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def destroy
|
|
264
|
+
params[:session_duration] = time_since_login
|
|
265
|
+
session.clear
|
|
266
|
+
redirect_to root_path
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
private
|
|
270
|
+
|
|
271
|
+
def set_audit_context
|
|
272
|
+
params[:ip_address] = request.remote_ip
|
|
273
|
+
params[:user_agent] = request.user_agent&.truncate(100)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def time_since_login
|
|
277
|
+
return 'unknown' unless session[:login_time]
|
|
278
|
+
Time.current - Time.parse(session[:login_time])
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
# config/audit.yml
|
|
285
|
+
sessions:
|
|
286
|
+
create: "Login %{email} via %{login_method} from %{ip_address} - %{user_agent}"
|
|
287
|
+
destroy: "Logout %{email} after %{session_duration} seconds"
|
|
288
|
+
|
|
289
|
+
# For failed attempts, you might have:
|
|
290
|
+
login_failures:
|
|
291
|
+
create: "Failed login attempt for %{email}: %{failure_reason} from %{ip_address}"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Password Management
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
# app/controllers/passwords_controller.rb
|
|
298
|
+
class PasswordsController < ApplicationController
|
|
299
|
+
include ActionAudit
|
|
300
|
+
before_action :authenticate_user!, except: [:forgot, :reset]
|
|
301
|
+
|
|
302
|
+
def forgot
|
|
303
|
+
@user = User.find_by(email: params[:email])
|
|
304
|
+
if @user
|
|
305
|
+
@user.generate_reset_token!
|
|
306
|
+
PasswordMailer.reset_instructions(@user).deliver_now
|
|
307
|
+
params[:user_id] = @user.id
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
redirect_to login_path, notice: 'Reset instructions sent if email exists'
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def reset
|
|
314
|
+
@user = User.find_by(reset_token: params[:token])
|
|
315
|
+
if @user&.reset_token_valid?
|
|
316
|
+
@user.update!(password: params[:password], reset_token: nil)
|
|
317
|
+
params[:user_id] = @user.id
|
|
318
|
+
redirect_to login_path, notice: 'Password reset successfully'
|
|
319
|
+
else
|
|
320
|
+
redirect_to forgot_password_path, alert: 'Invalid or expired token'
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def change
|
|
325
|
+
if current_user.authenticate(params[:current_password])
|
|
326
|
+
current_user.update!(password: params[:new_password])
|
|
327
|
+
params[:user_id] = current_user.id
|
|
328
|
+
redirect_to profile_path, notice: 'Password changed successfully'
|
|
329
|
+
else
|
|
330
|
+
redirect_to change_password_path, alert: 'Current password is incorrect'
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
# config/audit.yml
|
|
338
|
+
passwords:
|
|
339
|
+
forgot: "Password reset requested for user %{user_id}"
|
|
340
|
+
reset: "Password reset completed for user %{user_id}"
|
|
341
|
+
change: "Password changed for user %{user_id}"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Content Management Examples
|
|
345
|
+
|
|
346
|
+
### Blog Post Management
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
# app/controllers/admin/posts_controller.rb
|
|
350
|
+
class Admin::PostsController < ApplicationController
|
|
351
|
+
include ActionAudit
|
|
352
|
+
before_action :authenticate_admin!
|
|
353
|
+
|
|
354
|
+
def create
|
|
355
|
+
@post = current_user.posts.build(post_params)
|
|
356
|
+
@post.save!
|
|
357
|
+
|
|
358
|
+
params[:author_name] = current_user.name
|
|
359
|
+
|
|
360
|
+
redirect_to admin_post_path(@post)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def publish
|
|
364
|
+
@post = Post.find(params[:id])
|
|
365
|
+
@post.update!(published: true, published_at: Time.current)
|
|
366
|
+
|
|
367
|
+
params[:title] = @post.title
|
|
368
|
+
params[:author_name] = @post.author.name
|
|
369
|
+
|
|
370
|
+
redirect_to admin_post_path(@post)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def feature
|
|
374
|
+
@post = Post.find(params[:id])
|
|
375
|
+
@post.update!(featured: true, featured_at: Time.current)
|
|
376
|
+
|
|
377
|
+
params[:title] = @post.title
|
|
378
|
+
|
|
379
|
+
redirect_to admin_post_path(@post)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
private
|
|
383
|
+
|
|
384
|
+
def post_params
|
|
385
|
+
params.require(:post).permit(:title, :content, :category_id, :tags)
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
```yaml
|
|
391
|
+
# config/audit.yml
|
|
392
|
+
admin:
|
|
393
|
+
posts:
|
|
394
|
+
create: "Created post '%{title}' by %{author_name}"
|
|
395
|
+
update: "Updated post '%{title}'"
|
|
396
|
+
destroy: "Deleted post '%{title}'"
|
|
397
|
+
publish: "Published post '%{title}' by %{author_name}"
|
|
398
|
+
unpublish: "Unpublished post '%{title}'"
|
|
399
|
+
feature: "Featured post '%{title}'"
|
|
400
|
+
unfeature: "Unfeatured post '%{title}'"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Integration Examples
|
|
404
|
+
|
|
405
|
+
### Third-Party Service Integration
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
# app/controllers/integrations/stripe_controller.rb
|
|
409
|
+
class Integrations::StripeController < ApplicationController
|
|
410
|
+
include ActionAudit
|
|
411
|
+
skip_before_action :verify_authenticity_token
|
|
412
|
+
before_action :verify_stripe_signature
|
|
413
|
+
before_action :set_audit_context
|
|
414
|
+
|
|
415
|
+
def webhook
|
|
416
|
+
case params[:type]
|
|
417
|
+
when 'payment_intent.succeeded'
|
|
418
|
+
handle_successful_payment
|
|
419
|
+
when 'subscription.cancelled'
|
|
420
|
+
handle_subscription_cancellation
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
render json: { received: true }
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
private
|
|
427
|
+
|
|
428
|
+
def handle_successful_payment
|
|
429
|
+
payment_intent = params[:data][:object]
|
|
430
|
+
order = Order.find_by(stripe_payment_intent: payment_intent[:id])
|
|
431
|
+
order&.mark_as_paid!
|
|
432
|
+
|
|
433
|
+
params[:order_id] = order&.id
|
|
434
|
+
params[:amount] = payment_intent[:amount]
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def handle_subscription_cancellation
|
|
438
|
+
subscription = params[:data][:object]
|
|
439
|
+
user = User.find_by(stripe_subscription_id: subscription[:id])
|
|
440
|
+
user&.cancel_subscription!
|
|
441
|
+
|
|
442
|
+
params[:user_id] = user&.id
|
|
443
|
+
params[:subscription_id] = subscription[:id]
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def set_audit_context
|
|
447
|
+
params[:webhook_id] = params[:id]
|
|
448
|
+
params[:event_type] = params[:type]
|
|
449
|
+
params[:stripe_account] = params[:account] if params[:account]
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```yaml
|
|
455
|
+
# config/audit.yml
|
|
456
|
+
integrations:
|
|
457
|
+
stripe:
|
|
458
|
+
webhook: "Stripe webhook %{webhook_id}: %{event_type} processed"
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Custom Formatting Examples
|
|
462
|
+
|
|
463
|
+
### JSON Structured Logging
|
|
464
|
+
|
|
465
|
+
```ruby
|
|
466
|
+
# config/initializers/action_audit.rb
|
|
467
|
+
ActionAudit.log_formatter = lambda do |controller, action, message|
|
|
468
|
+
{
|
|
469
|
+
event_type: 'audit',
|
|
470
|
+
timestamp: Time.current.iso8601,
|
|
471
|
+
controller: controller,
|
|
472
|
+
action: action,
|
|
473
|
+
message: message,
|
|
474
|
+
user_id: defined?(current_user) && current_user&.id,
|
|
475
|
+
request_id: defined?(request) && request&.request_id,
|
|
476
|
+
ip_address: defined?(request) && request&.remote_ip
|
|
477
|
+
}.to_json
|
|
478
|
+
end
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Syslog Integration
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
# config/initializers/action_audit.rb
|
|
485
|
+
require 'syslog/logger'
|
|
486
|
+
|
|
487
|
+
ActionAudit.log_formatter = lambda do |controller, action, message|
|
|
488
|
+
# Format for syslog
|
|
489
|
+
"AUDIT user_id=#{current_user&.id} controller=#{controller} action=#{action} message=\"#{message}\""
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Custom logger for audit messages
|
|
493
|
+
class AuditLogger
|
|
494
|
+
def self.info(message)
|
|
495
|
+
syslog = Syslog::Logger.new('rails_audit')
|
|
496
|
+
syslog.info(message)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Override default Rails logger for audit messages
|
|
501
|
+
module ActionAudit
|
|
502
|
+
private
|
|
503
|
+
|
|
504
|
+
def audit_request
|
|
505
|
+
# ... existing logic ...
|
|
506
|
+
|
|
507
|
+
# Use custom logger instead of Rails.logger
|
|
508
|
+
AuditLogger.info(formatted_message)
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Slack Integration
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
# config/initializers/action_audit.rb
|
|
517
|
+
ActionAudit.log_formatter = lambda do |controller, action, message|
|
|
518
|
+
# Log to Rails logger
|
|
519
|
+
Rails.logger.info("[AUDIT] #{controller}/#{action} - #{message}")
|
|
520
|
+
|
|
521
|
+
# Also send critical actions to Slack
|
|
522
|
+
if critical_action?(controller, action)
|
|
523
|
+
SlackNotifier.ping(
|
|
524
|
+
text: "🔒 Critical Action: #{message}",
|
|
525
|
+
channel: '#security-alerts',
|
|
526
|
+
username: 'AuditBot'
|
|
527
|
+
)
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
"[AUDIT] #{controller}/#{action} - #{message}"
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def critical_action?(controller, action)
|
|
534
|
+
critical_patterns = [
|
|
535
|
+
'admin/users/destroy',
|
|
536
|
+
'admin/settings/update',
|
|
537
|
+
'integrations/*/webhook'
|
|
538
|
+
]
|
|
539
|
+
|
|
540
|
+
path = "#{controller}/#{action}"
|
|
541
|
+
critical_patterns.any? { |pattern| File.fnmatch(pattern, path) }
|
|
542
|
+
end
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Testing Examples
|
|
546
|
+
|
|
547
|
+
### RSpec Controller Tests
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
# spec/controllers/users_controller_spec.rb
|
|
551
|
+
RSpec.describe UsersController, type: :controller do
|
|
552
|
+
describe '#create' do
|
|
553
|
+
let(:user_params) { { name: 'John Doe', email: 'john@example.com' } }
|
|
554
|
+
|
|
555
|
+
it 'creates a user and logs the action' do
|
|
556
|
+
expect(Rails.logger).to receive(:info).with(/Created user john@example\.com/)
|
|
557
|
+
|
|
558
|
+
post :create, params: { user: user_params }
|
|
559
|
+
|
|
560
|
+
expect(response).to redirect_to(User.last)
|
|
561
|
+
expect(User.last.email).to eq('john@example.com')
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
context 'with custom log formatter' do
|
|
565
|
+
before do
|
|
566
|
+
ActionAudit.log_formatter = ->(c, a, m) { "CUSTOM: #{c}/#{a} - #{m}" }
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
it 'uses custom formatting' do
|
|
570
|
+
expect(Rails.logger).to receive(:info).with(/CUSTOM: users\/create - Created user/)
|
|
571
|
+
|
|
572
|
+
post :create, params: { user: user_params }
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Feature Tests
|
|
580
|
+
|
|
581
|
+
```ruby
|
|
582
|
+
# spec/features/admin_user_management_spec.rb
|
|
583
|
+
RSpec.feature 'Admin User Management', type: :feature do
|
|
584
|
+
let(:admin) { create(:admin_user) }
|
|
585
|
+
|
|
586
|
+
before { sign_in admin }
|
|
587
|
+
|
|
588
|
+
scenario 'Admin creates a new user' do
|
|
589
|
+
visit new_admin_user_path
|
|
590
|
+
|
|
591
|
+
fill_in 'Email', with: 'newuser@example.com'
|
|
592
|
+
fill_in 'Name', with: 'New User'
|
|
593
|
+
select 'Editor', from: 'Role'
|
|
594
|
+
|
|
595
|
+
expect {
|
|
596
|
+
click_button 'Create User'
|
|
597
|
+
}.to change(User, :count).by(1)
|
|
598
|
+
|
|
599
|
+
# Check that audit log was created
|
|
600
|
+
expect(Rails.logger).to have_received(:info).with(/Admin created user newuser@example\.com/)
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## Next Steps
|
|
606
|
+
|
|
607
|
+
- [Learn about troubleshooting](troubleshooting.md)
|
|
608
|
+
- [Check the API reference](api-reference.md)
|
|
609
|
+
- [See migration guide](migration.md)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Installation Guide
|
|
2
|
+
|
|
3
|
+
This guide will walk you through installing and setting up ActionAudit in your Rails application.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Rails 6.0 or higher
|
|
8
|
+
- Ruby 3.1.0 or higher
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
### 1. Add to Gemfile
|
|
13
|
+
|
|
14
|
+
Add ActionAudit to your application's Gemfile:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'action_audit'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then execute:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Run the Generator
|
|
27
|
+
|
|
28
|
+
ActionAudit provides a Rails generator to set up the initial configuration:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
rails generate action_audit:install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This generator will create:
|
|
35
|
+
- `config/audit.yml` - Your audit message configuration file
|
|
36
|
+
- `config/initializers/action_audit.rb` - Configuration for custom formatting and tagging
|
|
37
|
+
|
|
38
|
+
### 3. Include in Controllers
|
|
39
|
+
|
|
40
|
+
Add ActionAudit to your controllers. You can include it in your `ApplicationController` to enable auditing across all controllers:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# app/controllers/application_controller.rb
|
|
44
|
+
class ApplicationController < ActionController::Base
|
|
45
|
+
include ActionAudit
|
|
46
|
+
|
|
47
|
+
# Your existing code...
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or include it in specific controllers:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# app/controllers/admin/users_controller.rb
|
|
55
|
+
class Admin::UsersController < ApplicationController
|
|
56
|
+
include ActionAudit
|
|
57
|
+
|
|
58
|
+
# Your controller actions...
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Configure Audit Messages
|
|
63
|
+
|
|
64
|
+
Edit the generated `config/audit.yml` file to define your audit messages:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# config/audit.yml
|
|
68
|
+
admin:
|
|
69
|
+
users:
|
|
70
|
+
create: "Created user %{email}"
|
|
71
|
+
update: "Updated user %{id}"
|
|
72
|
+
destroy: "Deleted user %{id}"
|
|
73
|
+
|
|
74
|
+
sessions:
|
|
75
|
+
create: "User logged in with %{email}"
|
|
76
|
+
destroy: "User logged out"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 5. Test Your Setup
|
|
80
|
+
|
|
81
|
+
Start your Rails server and trigger a controller action that should be audited. Check your Rails logs for audit entries.
|
|
82
|
+
|
|
83
|
+
## Verification
|
|
84
|
+
|
|
85
|
+
To verify that ActionAudit is working correctly:
|
|
86
|
+
|
|
87
|
+
1. Start your Rails console:
|
|
88
|
+
```bash
|
|
89
|
+
rails console
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. Check that audit messages are loaded:
|
|
93
|
+
```ruby
|
|
94
|
+
ActionAudit::AuditMessages.lookup("admin/users", "create")
|
|
95
|
+
# Should return: "Created user %{email}"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
3. Test in your application by triggering an audited action and checking the logs.
|
|
99
|
+
|
|
100
|
+
## Next Steps
|
|
101
|
+
|
|
102
|
+
- [Configure custom logging](configuration.md)
|
|
103
|
+
- [Learn about usage patterns](usage.md)
|
|
104
|
+
- [Set up multi-engine auditing](multi-engine.md)
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
If you encounter issues during installation:
|
|
109
|
+
|
|
110
|
+
1. **Missing audit messages**: Ensure your `config/audit.yml` file exists and has the correct YAML structure
|
|
111
|
+
2. **No log output**: Check that you've included `ActionAudit` in your controllers
|
|
112
|
+
3. **Rails engine issues**: See the [Multi-Engine Setup Guide](multi-engine.md)
|
|
113
|
+
|
|
114
|
+
For more detailed troubleshooting, see the [Troubleshooting Guide](troubleshooting.md).
|