pg_sql_triggers 1.2.0 → 1.3.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 +144 -0
- data/COVERAGE.md +26 -19
- data/Goal.md +276 -155
- data/README.md +27 -1
- data/app/assets/javascripts/pg_sql_triggers/trigger_actions.js +50 -0
- data/app/controllers/concerns/pg_sql_triggers/error_handling.rb +56 -0
- data/app/controllers/concerns/pg_sql_triggers/kill_switch_protection.rb +66 -0
- data/app/controllers/concerns/pg_sql_triggers/permission_checking.rb +117 -0
- data/app/controllers/pg_sql_triggers/application_controller.rb +10 -62
- data/app/controllers/pg_sql_triggers/audit_logs_controller.rb +102 -0
- data/app/controllers/pg_sql_triggers/dashboard_controller.rb +4 -9
- data/app/controllers/pg_sql_triggers/tables_controller.rb +30 -4
- data/app/controllers/pg_sql_triggers/triggers_controller.rb +3 -21
- data/app/helpers/pg_sql_triggers/permissions_helper.rb +43 -0
- data/app/models/pg_sql_triggers/audit_log.rb +106 -0
- data/app/models/pg_sql_triggers/trigger_registry.rb +178 -9
- data/app/views/layouts/pg_sql_triggers/application.html.erb +26 -6
- data/app/views/pg_sql_triggers/audit_logs/index.html.erb +177 -0
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +33 -8
- data/app/views/pg_sql_triggers/tables/index.html.erb +76 -3
- data/app/views/pg_sql_triggers/tables/show.html.erb +17 -4
- data/app/views/pg_sql_triggers/triggers/_drop_modal.html.erb +16 -7
- data/app/views/pg_sql_triggers/triggers/_re_execute_modal.html.erb +16 -7
- data/app/views/pg_sql_triggers/triggers/show.html.erb +26 -6
- data/config/routes.rb +2 -0
- data/db/migrate/20260103000001_create_pg_sql_triggers_audit_log.rb +28 -0
- data/docs/README.md +15 -5
- data/docs/api-reference.md +191 -0
- data/docs/audit-trail.md +413 -0
- data/docs/configuration.md +6 -6
- data/docs/permissions.md +369 -0
- data/docs/troubleshooting.md +486 -0
- data/docs/ui-guide.md +211 -0
- data/docs/web-ui.md +257 -34
- data/lib/pg_sql_triggers/errors.rb +245 -0
- data/lib/pg_sql_triggers/generator/service.rb +32 -0
- data/lib/pg_sql_triggers/permissions/checker.rb +9 -2
- data/lib/pg_sql_triggers/registry.rb +141 -8
- data/lib/pg_sql_triggers/sql/kill_switch.rb +33 -5
- data/lib/pg_sql_triggers/testing/function_tester.rb +2 -0
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +3 -6
- metadata +29 -6
- data/docs/screenshots/.gitkeep +0 -1
- data/docs/screenshots/Generate Trigger.png +0 -0
- data/docs/screenshots/Triggers Page.png +0 -0
- data/docs/screenshots/kill error.png +0 -0
- data/docs/screenshots/kill modal for migration down.png +0 -0
data/docs/permissions.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Permissions Guide
|
|
2
|
+
|
|
3
|
+
PgSqlTriggers includes a comprehensive three-tier permission system to control access to trigger operations. This guide explains how to configure and use permissions.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Permission Levels](#permission-levels)
|
|
9
|
+
- [Actions and Required Roles](#actions-and-required-roles)
|
|
10
|
+
- [Configuration](#configuration)
|
|
11
|
+
- [Integration Examples](#integration-examples)
|
|
12
|
+
- [UI Behavior](#ui-behavior)
|
|
13
|
+
- [Console API](#console-api)
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
The permission system provides three levels of access:
|
|
18
|
+
|
|
19
|
+
- **Viewer**: Read-only access to view triggers and their status
|
|
20
|
+
- **Operator**: Can enable/disable triggers, apply migrations, generate triggers
|
|
21
|
+
- **Admin**: Full access including drop, re-execute, and SQL capsule execution
|
|
22
|
+
|
|
23
|
+
By default, all permissions are allowed (permissive mode). **You must configure permissions in production** to enforce access controls.
|
|
24
|
+
|
|
25
|
+
## Permission Levels
|
|
26
|
+
|
|
27
|
+
### Viewer
|
|
28
|
+
|
|
29
|
+
Viewers have read-only access and can:
|
|
30
|
+
- View the dashboard
|
|
31
|
+
- View trigger details
|
|
32
|
+
- View table listings
|
|
33
|
+
- View audit logs
|
|
34
|
+
- View drift information
|
|
35
|
+
|
|
36
|
+
### Operator
|
|
37
|
+
|
|
38
|
+
Operators can perform routine operations:
|
|
39
|
+
- All Viewer permissions
|
|
40
|
+
- Enable/disable triggers
|
|
41
|
+
- Apply trigger migrations
|
|
42
|
+
- Generate new triggers (via UI)
|
|
43
|
+
- Perform dry-run operations
|
|
44
|
+
- Test triggers
|
|
45
|
+
|
|
46
|
+
### Admin
|
|
47
|
+
|
|
48
|
+
Admins have full access:
|
|
49
|
+
- All Operator permissions
|
|
50
|
+
- Drop triggers
|
|
51
|
+
- Re-execute triggers
|
|
52
|
+
- Execute SQL capsules
|
|
53
|
+
- Override drift detection
|
|
54
|
+
|
|
55
|
+
## Actions and Required Roles
|
|
56
|
+
|
|
57
|
+
The following actions are mapped to permission levels:
|
|
58
|
+
|
|
59
|
+
| Action | Required Role | Description |
|
|
60
|
+
|--------|--------------|-------------|
|
|
61
|
+
| `view_triggers` | Viewer | View trigger list and details |
|
|
62
|
+
| `view_diffs` | Viewer | View SQL differences and drift |
|
|
63
|
+
| `enable_trigger` | Operator | Enable a trigger |
|
|
64
|
+
| `disable_trigger` | Operator | Disable a trigger |
|
|
65
|
+
| `apply_trigger` | Operator | Apply a trigger migration |
|
|
66
|
+
| `dry_run_sql` | Operator | Perform dry-run validation |
|
|
67
|
+
| `generate_trigger` | Operator | Generate triggers via UI |
|
|
68
|
+
| `test_trigger` | Operator | Test trigger functions |
|
|
69
|
+
| `drop_trigger` | Admin | Drop a trigger from database |
|
|
70
|
+
| `execute_sql` | Admin | Execute SQL capsules |
|
|
71
|
+
| `override_drift` | Admin | Override drift detection warnings |
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
### Basic Configuration
|
|
76
|
+
|
|
77
|
+
Configure permissions in your initializer:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# config/initializers/pg_sql_triggers.rb
|
|
81
|
+
PgSqlTriggers.configure do |config|
|
|
82
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
83
|
+
# Your permission logic here
|
|
84
|
+
# Return true if allowed, false if denied
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Simple Role-Based Example
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
PgSqlTriggers.configure do |config|
|
|
93
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
94
|
+
# Assume actor has a :role method or attribute
|
|
95
|
+
user_role = actor.role.to_s.downcase
|
|
96
|
+
|
|
97
|
+
case action
|
|
98
|
+
when :view_triggers, :view_diffs
|
|
99
|
+
true # Everyone can view
|
|
100
|
+
when :enable_trigger, :disable_trigger, :apply_trigger,
|
|
101
|
+
:dry_run_sql, :generate_trigger, :test_trigger
|
|
102
|
+
user_role.in?(%w[operator admin])
|
|
103
|
+
when :drop_trigger, :execute_sql, :override_drift
|
|
104
|
+
user_role == 'admin'
|
|
105
|
+
else
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Pundit Integration
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
PgSqlTriggers.configure do |config|
|
|
116
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
117
|
+
resource = PgSqlTriggers::TriggerPolicy.new(actor, nil)
|
|
118
|
+
|
|
119
|
+
case action
|
|
120
|
+
when :view_triggers, :view_diffs
|
|
121
|
+
resource.view?
|
|
122
|
+
when :enable_trigger, :disable_trigger
|
|
123
|
+
resource.enable? || resource.disable?
|
|
124
|
+
when :drop_trigger, :execute_sql
|
|
125
|
+
resource.admin?
|
|
126
|
+
else
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### CanCanCan Integration
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
PgSqlTriggers.configure do |config|
|
|
137
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
138
|
+
ability = Ability.new(actor)
|
|
139
|
+
|
|
140
|
+
case action
|
|
141
|
+
when :view_triggers, :view_diffs
|
|
142
|
+
ability.can?(:read, :trigger)
|
|
143
|
+
when :enable_trigger, :disable_trigger
|
|
144
|
+
ability.can?(:manage, :trigger)
|
|
145
|
+
when :drop_trigger, :execute_sql
|
|
146
|
+
ability.can?(:destroy, :trigger)
|
|
147
|
+
else
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Environment-Based Permissions
|
|
155
|
+
|
|
156
|
+
You can restrict permissions by environment:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
PgSqlTriggers.configure do |config|
|
|
160
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
161
|
+
# Stricter permissions in production
|
|
162
|
+
if environment == 'production'
|
|
163
|
+
case action
|
|
164
|
+
when :drop_trigger, :execute_sql
|
|
165
|
+
actor.role == 'admin' && actor.super_admin?
|
|
166
|
+
else
|
|
167
|
+
actor.role.in?(%w[operator admin])
|
|
168
|
+
end
|
|
169
|
+
else
|
|
170
|
+
# More permissive in development
|
|
171
|
+
actor.role.in?(%w[viewer operator admin])
|
|
172
|
+
end
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Integration Examples
|
|
178
|
+
|
|
179
|
+
### Controller Integration
|
|
180
|
+
|
|
181
|
+
The permission system integrates with controllers automatically. Override `current_actor` in your application controller:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# app/controllers/application_controller.rb
|
|
185
|
+
class ApplicationController < ActionController::Base
|
|
186
|
+
private
|
|
187
|
+
|
|
188
|
+
def current_actor
|
|
189
|
+
{
|
|
190
|
+
type: current_user.class.name,
|
|
191
|
+
id: current_user.id.to_s,
|
|
192
|
+
role: current_user.role # Add role if needed
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
For PgSqlTriggers controllers, override the ApplicationController:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# app/controllers/pg_sql_triggers/application_controller.rb
|
|
202
|
+
module PgSqlTriggers
|
|
203
|
+
class ApplicationController < ::PgSqlTriggers::ApplicationController
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def current_actor
|
|
207
|
+
{
|
|
208
|
+
type: current_user.class.name,
|
|
209
|
+
id: current_user.id.to_s,
|
|
210
|
+
role: current_user.role
|
|
211
|
+
}
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Custom Actor Format
|
|
218
|
+
|
|
219
|
+
Your actor can be any format as long as your permission checker understands it:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# Actor as User object
|
|
223
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
224
|
+
return false unless actor.is_a?(User)
|
|
225
|
+
actor.role >= required_role_for(action)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Actor as Hash
|
|
229
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
230
|
+
return false unless actor.is_a?(Hash)
|
|
231
|
+
actor[:role] >= required_role_for(action)
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## UI Behavior
|
|
236
|
+
|
|
237
|
+
The UI automatically adjusts based on permissions:
|
|
238
|
+
|
|
239
|
+
### Button Visibility
|
|
240
|
+
|
|
241
|
+
- **Enable/Disable buttons**: Only visible to Operator+ roles
|
|
242
|
+
- **Drop button**: Only visible to Admin roles
|
|
243
|
+
- **Re-execute button**: Only visible to Admin roles
|
|
244
|
+
- **Execute SQL button**: Only visible to Admin roles
|
|
245
|
+
- **Generate Trigger button**: Only visible to Operator+ roles
|
|
246
|
+
|
|
247
|
+
### Permission Errors
|
|
248
|
+
|
|
249
|
+
When a user attempts an unauthorized action:
|
|
250
|
+
|
|
251
|
+
1. **UI Actions**: User is redirected with an alert message
|
|
252
|
+
2. **API Calls**: `PermissionError` is raised with recovery suggestions
|
|
253
|
+
3. **Error Messages**: Include the required role and recovery steps
|
|
254
|
+
|
|
255
|
+
## Console API
|
|
256
|
+
|
|
257
|
+
All console API methods check permissions:
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
# Enable trigger (requires Operator+)
|
|
261
|
+
PgSqlTriggers::Registry.enable(
|
|
262
|
+
"trigger_name",
|
|
263
|
+
actor: current_user,
|
|
264
|
+
confirmation: "EXECUTE TRIGGER_ENABLE"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Drop trigger (requires Admin)
|
|
268
|
+
PgSqlTriggers::Registry.drop(
|
|
269
|
+
"trigger_name",
|
|
270
|
+
actor: current_user,
|
|
271
|
+
reason: "No longer needed",
|
|
272
|
+
confirmation: "EXECUTE TRIGGER_DROP"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Execute SQL capsule (requires Admin)
|
|
276
|
+
PgSqlTriggers::SQL::Executor.execute(
|
|
277
|
+
capsule,
|
|
278
|
+
actor: current_user,
|
|
279
|
+
confirmation: "EXECUTE SQL"
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Permission Errors in Console
|
|
284
|
+
|
|
285
|
+
When permission is denied, a `PermissionError` is raised:
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
begin
|
|
289
|
+
PgSqlTriggers::Registry.drop("trigger_name", actor: user)
|
|
290
|
+
rescue PgSqlTriggers::PermissionError => e
|
|
291
|
+
puts "Permission denied: #{e.message}"
|
|
292
|
+
puts "Recovery: #{e.recovery_suggestion}"
|
|
293
|
+
# Error code: e.error_code => "PERMISSION_DENIED"
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Testing Permissions
|
|
298
|
+
|
|
299
|
+
### In Tests
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
# RSpec example
|
|
303
|
+
RSpec.describe "Trigger permissions" do
|
|
304
|
+
let(:operator_user) { create(:user, role: 'operator') }
|
|
305
|
+
let(:admin_user) { create(:user, role: 'admin') }
|
|
306
|
+
let(:viewer_user) { create(:user, role: 'viewer') }
|
|
307
|
+
|
|
308
|
+
it "allows operators to enable triggers" do
|
|
309
|
+
actor = { type: "User", id: operator_user.id.to_s, role: 'operator' }
|
|
310
|
+
expect {
|
|
311
|
+
PgSqlTriggers::Registry.enable("trigger_name", actor: actor)
|
|
312
|
+
}.not_to raise_error
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it "denies viewers from dropping triggers" do
|
|
316
|
+
actor = { type: "User", id: viewer_user.id.to_s, role: 'viewer' }
|
|
317
|
+
expect {
|
|
318
|
+
PgSqlTriggers::Registry.drop("trigger_name", actor: actor)
|
|
319
|
+
}.to raise_error(PgSqlTriggers::PermissionError)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Best Practices
|
|
325
|
+
|
|
326
|
+
1. **Always configure permissions in production** - Default permissive mode is unsafe
|
|
327
|
+
2. **Use environment-based permissions** - Stricter in production, permissive in development
|
|
328
|
+
3. **Test permission scenarios** - Ensure your permission checker works correctly
|
|
329
|
+
4. **Log permission denials** - Monitor unauthorized access attempts
|
|
330
|
+
5. **Document your permission model** - Help team members understand access levels
|
|
331
|
+
|
|
332
|
+
## Troubleshooting
|
|
333
|
+
|
|
334
|
+
### All users have full access
|
|
335
|
+
|
|
336
|
+
**Problem**: Permissions are not being enforced.
|
|
337
|
+
|
|
338
|
+
**Solution**: Check that `permission_checker` is configured. The default is permissive (allows all).
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
# Verify configuration
|
|
342
|
+
PgSqlTriggers.permission_checker # Should not be nil in production
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Permission errors in development
|
|
346
|
+
|
|
347
|
+
**Problem**: Permission checks are blocking development work.
|
|
348
|
+
|
|
349
|
+
**Solution**: Use environment-based permissions or disable checks in development:
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
config.permission_checker = ->(actor, action, environment) {
|
|
353
|
+
return true if Rails.env.development?
|
|
354
|
+
# Production permission logic
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Actor format errors
|
|
359
|
+
|
|
360
|
+
**Problem**: Permission checker receives unexpected actor format.
|
|
361
|
+
|
|
362
|
+
**Solution**: Ensure your permission checker handles the actor format you're using. Check controller `current_actor` method.
|
|
363
|
+
|
|
364
|
+
## Related Documentation
|
|
365
|
+
|
|
366
|
+
- [Configuration Reference](configuration.md#permission-system) - Complete configuration options
|
|
367
|
+
- [API Reference](api-reference.md) - Console API methods
|
|
368
|
+
- [Web UI Guide](web-ui.md) - UI features and behavior
|
|
369
|
+
|