legion-rbac 0.3.3 → 0.3.5
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 +10 -0
- data/README.md +6 -0
- data/lib/legion/rbac/permission.rb +4 -4
- data/lib/legion/rbac/policy_engine.rb +1 -1
- data/lib/legion/rbac/routes.rb +16 -18
- data/lib/legion/rbac/version.rb +1 -1
- data/lib/legion/rbac.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c62ae3d8b8ed4b7285f13ecc1e5d90d79f343cf98a4d56a342af761aba03185
|
|
4
|
+
data.tar.gz: f705bad75d3d459e82463c50d3110df25e847d36f2e092e88e10ae545ba67cac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7d12ee4a0eaac6ea80132816a429dac7621253a4d720ef69dceaedcf1a00b63af70f0004b4c3e1bbec3f48ff9c8e8eb34eee8f0365e3abbd6a96b94c86727c4
|
|
7
|
+
data.tar.gz: 7f62612de461d2339ea9d70c9de87d5b8cc601622986a51ae3dfa02a22ac78d709b46dbc4ff5ad7cd78d8418f6ee2defa9912d9d2cc187ddb48a8f0541305fbf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.5] - 2026-10-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Deliver authorization decisions and policy-change audit events through the Core event bus keyword payload contract instead of losing events to a rescued argument error.
|
|
7
|
+
|
|
8
|
+
## [0.3.4] - 2026-05-09
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- Unnecessary `defined?(Legion::Logging)` guards from route handlers — legion-logging is a hard gemspec dependency and always available
|
|
12
|
+
|
|
3
13
|
## [0.3.3] - 2026-04-08
|
|
4
14
|
|
|
5
15
|
### Added
|
data/README.md
CHANGED
|
@@ -90,6 +90,12 @@ result = Legion::Rbac::PolicyEngine.evaluate(principal: principal, action: :read
|
|
|
90
90
|
# => { allowed: true, reason: "...", would_deny: false }
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
### Audit Events
|
|
94
|
+
|
|
95
|
+
When the optional Core `Legion::Events` bus is loaded and `rbac.emit_events` is enabled, subscribers receive `rbac.granted` and `rbac.denied` decisions with principal, resource/capability, enforcement and request-correlation context. Dry-run denials still emit `rbac.denied` with `would_deny: true`; authorization remains nonblocking.
|
|
96
|
+
|
|
97
|
+
Successful policy mutations emit `rbac.policy_changed` with the actor, target record and request correlation. RBAC 0.3.5 uses Core's keyword payload contract, so these notifications reach ordinary `Legion::Events.on` subscribers rather than being lost to an argument error. Event-delivery failures are logged without changing the authorization decision.
|
|
98
|
+
|
|
93
99
|
### CLI
|
|
94
100
|
|
|
95
101
|
```bash
|
|
@@ -34,8 +34,8 @@ module Legion
|
|
|
34
34
|
def self.pattern_to_regex(pattern)
|
|
35
35
|
parts = pattern.split('/').map do |segment|
|
|
36
36
|
case segment
|
|
37
|
-
when '*'
|
|
38
|
-
when '+'
|
|
37
|
+
when '*' then '.*'
|
|
38
|
+
when '+' then '[^/]+'
|
|
39
39
|
else Regexp.escape(segment)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -80,8 +80,8 @@ module Legion
|
|
|
80
80
|
def self.pattern_to_regex(pattern)
|
|
81
81
|
parts = pattern.split('/').map do |segment|
|
|
82
82
|
case segment
|
|
83
|
-
when '*'
|
|
84
|
-
when '+'
|
|
83
|
+
when '*' then '.*'
|
|
84
|
+
when '+' then '[^/]+'
|
|
85
85
|
else Regexp.escape(segment)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
@@ -393,7 +393,7 @@ module Legion
|
|
|
393
393
|
enforce: context[:enforce],
|
|
394
394
|
reason: result[:reason]
|
|
395
395
|
}.merge(context[:event_context]).compact
|
|
396
|
-
Legion::Events.emit(event_name, payload)
|
|
396
|
+
Legion::Events.emit(event_name, **payload)
|
|
397
397
|
rescue StandardError => e
|
|
398
398
|
handle_exception(e, level: :warn, operation: 'rbac.policy_engine.emit_decision_event', event: event_name)
|
|
399
399
|
end
|
data/lib/legion/rbac/routes.rb
CHANGED
|
@@ -146,7 +146,7 @@ module Legion
|
|
|
146
146
|
|
|
147
147
|
def self.register_check(app)
|
|
148
148
|
app.post '/api/rbac/check' do
|
|
149
|
-
Legion::Logging.debug "API: POST /api/rbac/check params=#{params.keys}"
|
|
149
|
+
Legion::Logging.debug "API: POST /api/rbac/check params=#{params.keys}"
|
|
150
150
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
151
151
|
|
|
152
152
|
body = parse_request_body
|
|
@@ -163,12 +163,12 @@ module Legion
|
|
|
163
163
|
)
|
|
164
164
|
json_response(result)
|
|
165
165
|
rescue StandardError => e
|
|
166
|
-
Legion::Logging.error "API POST /api/rbac/check: #{e.class} — #{e.message}"
|
|
166
|
+
Legion::Logging.error "API POST /api/rbac/check: #{e.class} — #{e.message}"
|
|
167
167
|
json_error('rbac_error', e.message, status_code: 500)
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
-
def self.register_assignments(app) # rubocop:disable Metrics/AbcSize
|
|
171
|
+
def self.register_assignments(app) # rubocop:disable Metrics/AbcSize
|
|
172
172
|
app.get '/api/rbac/assignments' do
|
|
173
173
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
174
174
|
return json_error('db_unavailable', 'legion-data not connected', status_code: 503) unless Legion::Rbac::Store.db_available?
|
|
@@ -181,7 +181,7 @@ module Legion
|
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
app.post '/api/rbac/assignments' do
|
|
184
|
-
Legion::Logging.debug "API: POST /api/rbac/assignments params=#{params.keys}"
|
|
184
|
+
Legion::Logging.debug "API: POST /api/rbac/assignments params=#{params.keys}"
|
|
185
185
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
186
186
|
return json_error('db_unavailable', 'legion-data not connected', status_code: 503) unless Legion::Rbac::Store.db_available?
|
|
187
187
|
|
|
@@ -194,13 +194,13 @@ module Legion
|
|
|
194
194
|
granted_by: current_owner_msid || 'api',
|
|
195
195
|
expires_at: parse_optional_time(body[:expires_at], field: 'expires_at')
|
|
196
196
|
)
|
|
197
|
-
Legion::Logging.info "API: created RBAC assignment #{record.id} role=#{body[:role]} principal=#{body[:principal_id]}"
|
|
197
|
+
Legion::Logging.info "API: created RBAC assignment #{record.id} role=#{body[:role]} principal=#{body[:principal_id]}"
|
|
198
198
|
emit_rbac_policy_changed('assignment.created', 'role_assignment', record.values)
|
|
199
199
|
json_response(record.values, status_code: 201)
|
|
200
200
|
rescue Legion::Rbac::Routes::InvalidTimestamp => e
|
|
201
201
|
json_error('validation_error', e.message, status_code: 422)
|
|
202
202
|
rescue Sequel::ValidationFailed => e
|
|
203
|
-
Legion::Logging.warn "API POST /api/rbac/assignments returned 422: #{e.message}"
|
|
203
|
+
Legion::Logging.warn "API POST /api/rbac/assignments returned 422: #{e.message}"
|
|
204
204
|
json_error('validation_error', e.message, status_code: 422)
|
|
205
205
|
end
|
|
206
206
|
|
|
@@ -213,7 +213,7 @@ module Legion
|
|
|
213
213
|
|
|
214
214
|
snapshot = record.values.dup
|
|
215
215
|
record.destroy
|
|
216
|
-
Legion::Logging.info "API: deleted RBAC assignment #{params[:id]}"
|
|
216
|
+
Legion::Logging.info "API: deleted RBAC assignment #{params[:id]}"
|
|
217
217
|
emit_rbac_policy_changed('assignment.deleted', 'role_assignment', snapshot)
|
|
218
218
|
json_response({ deleted: true })
|
|
219
219
|
end
|
|
@@ -230,7 +230,7 @@ module Legion
|
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
app.post '/api/rbac/grants' do
|
|
233
|
-
Legion::Logging.debug "API: POST /api/rbac/grants params=#{params.keys}"
|
|
233
|
+
Legion::Logging.debug "API: POST /api/rbac/grants params=#{params.keys}"
|
|
234
234
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
235
235
|
return json_error('db_unavailable', 'legion-data not connected', status_code: 503) unless Legion::Rbac::Store.db_available?
|
|
236
236
|
|
|
@@ -241,11 +241,11 @@ module Legion
|
|
|
241
241
|
actions: Array(body[:actions]).join(','),
|
|
242
242
|
granted_by: current_owner_msid || 'api'
|
|
243
243
|
)
|
|
244
|
-
Legion::Logging.info "API: created RBAC grant #{record.id} team=#{body[:team]} pattern=#{body[:runner_pattern]}"
|
|
244
|
+
Legion::Logging.info "API: created RBAC grant #{record.id} team=#{body[:team]} pattern=#{body[:runner_pattern]}"
|
|
245
245
|
emit_rbac_policy_changed('runner_grant.created', 'runner_grant', record.values)
|
|
246
246
|
json_response(record.values, status_code: 201)
|
|
247
247
|
rescue Sequel::ValidationFailed => e
|
|
248
|
-
Legion::Logging.warn "API POST /api/rbac/grants returned 422: #{e.message}"
|
|
248
|
+
Legion::Logging.warn "API POST /api/rbac/grants returned 422: #{e.message}"
|
|
249
249
|
json_error('validation_error', e.message, status_code: 422)
|
|
250
250
|
end
|
|
251
251
|
|
|
@@ -258,7 +258,7 @@ module Legion
|
|
|
258
258
|
|
|
259
259
|
snapshot = record.values.dup
|
|
260
260
|
record.destroy
|
|
261
|
-
Legion::Logging.info "API: deleted RBAC grant #{params[:id]}"
|
|
261
|
+
Legion::Logging.info "API: deleted RBAC grant #{params[:id]}"
|
|
262
262
|
emit_rbac_policy_changed('runner_grant.deleted', 'runner_grant', snapshot)
|
|
263
263
|
json_response({ deleted: true })
|
|
264
264
|
end
|
|
@@ -274,7 +274,7 @@ module Legion
|
|
|
274
274
|
end
|
|
275
275
|
|
|
276
276
|
app.post '/api/rbac/grants/cross-team' do
|
|
277
|
-
Legion::Logging.debug "API: POST /api/rbac/grants/cross-team params=#{params.keys}"
|
|
277
|
+
Legion::Logging.debug "API: POST /api/rbac/grants/cross-team params=#{params.keys}"
|
|
278
278
|
return json_error('rbac_unavailable', 'legion-rbac not installed', status_code: 501) unless defined?(Legion::Rbac)
|
|
279
279
|
return json_error('db_unavailable', 'legion-data not connected', status_code: 503) unless Legion::Rbac::Store.db_available?
|
|
280
280
|
|
|
@@ -287,13 +287,13 @@ module Legion
|
|
|
287
287
|
granted_by: current_owner_msid || 'api',
|
|
288
288
|
expires_at: parse_optional_time(body[:expires_at], field: 'expires_at')
|
|
289
289
|
)
|
|
290
|
-
Legion::Logging.info "API: created cross-team RBAC grant #{record.id} #{body[:source_team]}->#{body[:target_team]}"
|
|
290
|
+
Legion::Logging.info "API: created cross-team RBAC grant #{record.id} #{body[:source_team]}->#{body[:target_team]}"
|
|
291
291
|
emit_rbac_policy_changed('cross_team_grant.created', 'cross_team_grant', record.values)
|
|
292
292
|
json_response(record.values, status_code: 201)
|
|
293
293
|
rescue Legion::Rbac::Routes::InvalidTimestamp => e
|
|
294
294
|
json_error('validation_error', e.message, status_code: 422)
|
|
295
295
|
rescue Sequel::ValidationFailed => e
|
|
296
|
-
Legion::Logging.warn "API POST /api/rbac/grants/cross-team returned 422: #{e.message}"
|
|
296
|
+
Legion::Logging.warn "API POST /api/rbac/grants/cross-team returned 422: #{e.message}"
|
|
297
297
|
json_error('validation_error', e.message, status_code: 422)
|
|
298
298
|
end
|
|
299
299
|
|
|
@@ -306,7 +306,7 @@ module Legion
|
|
|
306
306
|
|
|
307
307
|
snapshot = record.values.dup
|
|
308
308
|
record.destroy
|
|
309
|
-
Legion::Logging.info "API: deleted cross-team RBAC grant #{params[:id]}"
|
|
309
|
+
Legion::Logging.info "API: deleted cross-team RBAC grant #{params[:id]}"
|
|
310
310
|
emit_rbac_policy_changed('cross_team_grant.deleted', 'cross_team_grant', snapshot)
|
|
311
311
|
json_response({ deleted: true })
|
|
312
312
|
end
|
|
@@ -382,7 +382,7 @@ module Legion
|
|
|
382
382
|
|
|
383
383
|
Legion::Events.emit(
|
|
384
384
|
'rbac.policy_changed',
|
|
385
|
-
policy_change_payload(
|
|
385
|
+
**policy_change_payload(
|
|
386
386
|
change_type: change_type,
|
|
387
387
|
target_type: target_type,
|
|
388
388
|
record_values: record_values,
|
|
@@ -390,8 +390,6 @@ module Legion
|
|
|
390
390
|
)
|
|
391
391
|
)
|
|
392
392
|
rescue StandardError => e
|
|
393
|
-
return unless defined?(Legion::Logging)
|
|
394
|
-
|
|
395
393
|
Legion::Logging.warn("API policy change event failed type=#{target_type} change=#{change_type} error=#{e.class}: #{e.message}")
|
|
396
394
|
end
|
|
397
395
|
|
data/lib/legion/rbac/version.rb
CHANGED
data/lib/legion/rbac.rb
CHANGED
|
@@ -87,10 +87,10 @@ module Legion
|
|
|
87
87
|
|
|
88
88
|
def events_enabled?
|
|
89
89
|
return false unless defined?(Legion::Events)
|
|
90
|
-
return false unless defined?(Legion::Settings)
|
|
91
90
|
|
|
92
91
|
Legion::Settings[:rbac]&.fetch(:emit_events, true) != false
|
|
93
|
-
rescue StandardError
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
handle_exception(e, level: :warn, operation: 'rbac.events_enabled')
|
|
94
94
|
false
|
|
95
95
|
end
|
|
96
96
|
|