atlas_rb 1.2.1 → 1.2.2
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/.version +1 -1
- data/CHANGELOG.md +36 -0
- data/Gemfile.lock +1 -1
- data/README.md +40 -0
- data/lib/atlas_rb/audit_event.rb +109 -0
- data/lib/atlas_rb.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 29da2e41c70d17572c6c24002ba0ff25beb2ad6cdd940dee157a83133c86efaf
|
|
4
|
+
data.tar.gz: 9e16fed8e22867c37647a12de104135d78f57995ba5e6decd4929ac6a402bae7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ba5a0216a5cc5dacce4f6822078eb07d71aa4e14b31142c83dd823c0ec177efa92e1712eac0b235c125870611dcd4633817066c5ee70a98d6f85adbec614a7f
|
|
7
|
+
data.tar.gz: b176fc377d6d802e356c5f0a0fd487c6c899d4b5e323e3017eb773fd11e06dd8ebb415329a4c4e81aef5f55f0086602fc33a6cb82c4699709e0a3209c542a48d
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Added — `AtlasRb::AuditEvent.emit` (session-scoped audit events)
|
|
6
|
+
|
|
7
|
+
A new binding for Atlas's `POST /audit_events` endpoint, which records an
|
|
8
|
+
AuditEvent with a **null `resource_id`** — an event not tied to any
|
|
9
|
+
resource write. This is the gem's half of the impersonation audit trail
|
|
10
|
+
(acting-as / view-as): the session lifecycle lives entirely in the calling
|
|
11
|
+
application, and a view-as session performs no writes, so neither leaves a
|
|
12
|
+
per-resource event for `Resource.history` to surface.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
AtlasRb::AuditEvent.emit(
|
|
16
|
+
action: "impersonation_started", # or "impersonation_ended"
|
|
17
|
+
actor_nuid: admin_nuid,
|
|
18
|
+
on_behalf_of_nuid: target_nuid,
|
|
19
|
+
mode: "acting_as" # or "view_as"
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- The recorded principals (`actor_nuid`, `on_behalf_of_nuid`), `mode`, and
|
|
24
|
+
an optional free-form `payload:` travel in the request **body**, not in
|
|
25
|
+
ambient headers — so the call is self-describing even when fired as a
|
|
26
|
+
session is being torn down (e.g. `impersonation_ended`).
|
|
27
|
+
- The request authenticates via the standard `connection` (system token),
|
|
28
|
+
with the `User: NUID` header pinned to `actor_nuid` so the server-side
|
|
29
|
+
admin gate holds regardless of ambient `Current` state.
|
|
30
|
+
- `on_behalf_of_nuid`, `mode`, and `payload` are omitted from the body when
|
|
31
|
+
blank, leaving room for future, mode-less session events. Atlas stamps
|
|
32
|
+
`occurred_at` server-side.
|
|
33
|
+
- Authorization errors (`401` / `403`) surface as raw Faraday responses,
|
|
34
|
+
matching `Resource.history`.
|
|
35
|
+
|
|
36
|
+
Depends on the matching Atlas-side `POST /audit_events` emit endpoint
|
|
37
|
+
(nullable resource scope, admin-gated); see the impersonation gap report.
|
|
38
|
+
|
|
3
39
|
## 1.2.1
|
|
4
40
|
|
|
5
41
|
### Added — typed errors for re-parent / linked-member rejections
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -113,6 +113,7 @@ Community → Collection → Work
|
|
|
113
113
|
| `AtlasRb::Blob` | The binary bytes; supports streaming downloads. |
|
|
114
114
|
| `AtlasRb::Authentication` | NUID → user record / group lookup. |
|
|
115
115
|
| `AtlasRb::Resource` | Generic resolver, permissions lookup, and audit-event history. |
|
|
116
|
+
| `AtlasRb::AuditEvent` | Emit a session-scoped (resource-less) audit event, e.g. impersonation. |
|
|
116
117
|
| `AtlasRb::Reset` | Test-only — wipes Atlas state via `GET /reset`. |
|
|
117
118
|
|
|
118
119
|
## Namespace gradient: regular / Admin / System
|
|
@@ -205,6 +206,45 @@ result["events"].first["action"] # => "update"
|
|
|
205
206
|
Authorization errors (`401` / `403`) are not caught here — they surface as
|
|
206
207
|
raw Faraday responses for the calling application's rescue layer.
|
|
207
208
|
|
|
209
|
+
### Session-scoped audit events
|
|
210
|
+
|
|
211
|
+
`Resource.history` only reads events that hang off a resource write. Some
|
|
212
|
+
auditable events have no resource to attach to — most notably impersonation
|
|
213
|
+
sessions (acting-as / view-as): the session lifecycle lives in the calling
|
|
214
|
+
app, and a view-as session performs no writes at all, so it would otherwise
|
|
215
|
+
leave no audit trail. `AtlasRb::AuditEvent.emit` wraps Atlas's
|
|
216
|
+
`POST /audit_events` endpoint, which records an event with a **null
|
|
217
|
+
`resource_id`**.
|
|
218
|
+
|
|
219
|
+
The principals and metadata travel in the request body — not inferred from
|
|
220
|
+
ambient headers — so the call is self-describing even when fired as a
|
|
221
|
+
session is being torn down. The request is admin-gated server-side; the
|
|
222
|
+
`User: NUID` header is pinned to the `actor_nuid` you pass.
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
# An admin starts acting as another user:
|
|
226
|
+
AtlasRb::AuditEvent.emit(
|
|
227
|
+
action: "impersonation_started",
|
|
228
|
+
actor_nuid: Current.nuid, # the admin (also the User: header)
|
|
229
|
+
on_behalf_of_nuid: target_nuid, # the impersonated user
|
|
230
|
+
mode: "acting_as" # or "view_as"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# …and later ends the session:
|
|
234
|
+
AtlasRb::AuditEvent.emit(
|
|
235
|
+
action: "impersonation_ended",
|
|
236
|
+
actor_nuid: admin_nuid,
|
|
237
|
+
on_behalf_of_nuid: target_nuid,
|
|
238
|
+
mode: "acting_as"
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`on_behalf_of_nuid`, `mode`, and `payload:` (free-form metadata) are
|
|
243
|
+
omitted from the body when blank, so `emit` can also carry future,
|
|
244
|
+
mode-less session events. Atlas stamps `occurred_at` server-side.
|
|
245
|
+
Authorization errors (`401` / `403`) surface as raw Faraday responses,
|
|
246
|
+
matching `Resource.history`.
|
|
247
|
+
|
|
208
248
|
### Re-parenting
|
|
209
249
|
|
|
210
250
|
`reparent` moves a resource to a new structural parent, binding Atlas's
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtlasRb
|
|
4
|
+
# Session-scoped AuditEvent emit — record an audit event that is **not**
|
|
5
|
+
# tied to a resource write.
|
|
6
|
+
#
|
|
7
|
+
# ## Why this exists
|
|
8
|
+
#
|
|
9
|
+
# Every other AuditEvent in Atlas is a side effect of a resource mutation
|
|
10
|
+
# (`create`, `update`, `tombstone`, …) and is read back per-resource via
|
|
11
|
+
# {AtlasRb::Resource.history} (`GET /resources/<id>/history`). Some
|
|
12
|
+
# auditable events have **no resource to hang on**: impersonation sessions.
|
|
13
|
+
# An admin starting/ending an acting-as or view-as session is a security
|
|
14
|
+
# event worth recording, but view-as performs no writes at all, and the
|
|
15
|
+
# session lifecycle lives entirely in the calling application (a cookie),
|
|
16
|
+
# so there is no resource mutation to attach the event to.
|
|
17
|
+
#
|
|
18
|
+
# This binding wraps Atlas's `POST /audit_events` emit endpoint, which
|
|
19
|
+
# writes an AuditEvent with a **null `resource_id`** (a session-scoped
|
|
20
|
+
# event). The recorded principals are passed explicitly in the body rather
|
|
21
|
+
# than inferred from request headers, so the call is self-describing and
|
|
22
|
+
# does not depend on the ambient {AtlasRb.config} identity being intact at
|
|
23
|
+
# emit time (it may not be — e.g. an `impersonation_ended` emit fires as
|
|
24
|
+
# the session is being torn down).
|
|
25
|
+
#
|
|
26
|
+
# ## Authorization
|
|
27
|
+
#
|
|
28
|
+
# The endpoint is system-token + admin-gated. The request authenticates via
|
|
29
|
+
# the standard {FaradayHelper#connection} — the `Authorization: Bearer`
|
|
30
|
+
# token plus a `User: NUID <admin>` header pinned to `actor_nuid` — and
|
|
31
|
+
# Atlas gates the emit to admin operators. `actor_nuid` is passed
|
|
32
|
+
# explicitly (rather than left to {AtlasRb.config}.default_nuid) so the
|
|
33
|
+
# admin gate holds even when the ambient session identity is mid-teardown,
|
|
34
|
+
# as it is for an `impersonation_ended` emit. The same `actor_nuid` is the
|
|
35
|
+
# principal recorded on the event.
|
|
36
|
+
#
|
|
37
|
+
# @example Recording the start of an acting-as session (from Cerberus)
|
|
38
|
+
# AtlasRb::AuditEvent.emit(
|
|
39
|
+
# action: "impersonation_started",
|
|
40
|
+
# actor_nuid: Current.nuid, # the admin
|
|
41
|
+
# on_behalf_of_nuid: target_nuid, # the impersonated user
|
|
42
|
+
# mode: "acting_as"
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
# @example Recording the end of a view-as session
|
|
46
|
+
# AtlasRb::AuditEvent.emit(
|
|
47
|
+
# action: "impersonation_ended",
|
|
48
|
+
# actor_nuid: admin_nuid,
|
|
49
|
+
# on_behalf_of_nuid: target_nuid,
|
|
50
|
+
# mode: "view_as"
|
|
51
|
+
# )
|
|
52
|
+
class AuditEvent
|
|
53
|
+
extend AtlasRb::FaradayHelper
|
|
54
|
+
|
|
55
|
+
# Atlas REST endpoint for the session-scoped emit.
|
|
56
|
+
# @api private
|
|
57
|
+
ROUTE = "/audit_events"
|
|
58
|
+
|
|
59
|
+
# Emit a session-scoped AuditEvent (one with no resource scope).
|
|
60
|
+
#
|
|
61
|
+
# Wraps `POST /audit_events`. The principals and metadata travel in the
|
|
62
|
+
# JSON body — not in request headers — so the recorded event does not
|
|
63
|
+
# depend on the ambient {AtlasRb.config} identity. The request still
|
|
64
|
+
# authenticates as the configured caller (system token + the admin
|
|
65
|
+
# `User:` header); Atlas admin-gates the endpoint server-side.
|
|
66
|
+
#
|
|
67
|
+
# Atlas stamps `occurred_at` server-side, so it is not part of the body.
|
|
68
|
+
#
|
|
69
|
+
# Authorization errors (`401` / `403`) are intentionally **not** caught
|
|
70
|
+
# here — they surface as raw Faraday responses for the calling
|
|
71
|
+
# application's rescue layer to translate, matching
|
|
72
|
+
# {AtlasRb::Resource.history}.
|
|
73
|
+
#
|
|
74
|
+
# @param action [String] the audit action, e.g. `"impersonation_started"`
|
|
75
|
+
# or `"impersonation_ended"`.
|
|
76
|
+
# @param actor_nuid [String] NUID of the principal performing the action
|
|
77
|
+
# (the admin, in the impersonation flow).
|
|
78
|
+
# @param on_behalf_of_nuid [String, nil] NUID of the attribution target
|
|
79
|
+
# (the impersonated user). Omitted from the body when `nil`.
|
|
80
|
+
# @param mode [String, nil] session mode, e.g. `"acting_as"` or
|
|
81
|
+
# `"view_as"`. Omitted from the body when `nil` so the binding can also
|
|
82
|
+
# carry future, mode-less session events.
|
|
83
|
+
# @param payload [Hash] optional free-form metadata to record alongside
|
|
84
|
+
# the event. Omitted from the body when empty.
|
|
85
|
+
# @return [AtlasRb::Mash] the parsed envelope returned by
|
|
86
|
+
# `POST /audit_events` (the created event).
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# AtlasRb::AuditEvent.emit(
|
|
90
|
+
# action: "impersonation_started",
|
|
91
|
+
# actor_nuid: "000000001",
|
|
92
|
+
# on_behalf_of_nuid: "000000123",
|
|
93
|
+
# mode: "acting_as"
|
|
94
|
+
# )
|
|
95
|
+
def self.emit(action:, actor_nuid:, on_behalf_of_nuid: nil, mode: nil, payload: {})
|
|
96
|
+
body = {
|
|
97
|
+
action: action,
|
|
98
|
+
actor_nuid: actor_nuid,
|
|
99
|
+
on_behalf_of_nuid: on_behalf_of_nuid,
|
|
100
|
+
mode: mode,
|
|
101
|
+
payload: (payload unless payload.nil? || payload.empty?)
|
|
102
|
+
}.compact
|
|
103
|
+
|
|
104
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
105
|
+
connection({}, actor_nuid).post(ROUTE, JSON.dump(body))&.body
|
|
106
|
+
))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/atlas_rb.rb
CHANGED
|
@@ -23,6 +23,7 @@ require_relative "atlas_rb/admin/work"
|
|
|
23
23
|
require_relative "atlas_rb/admin/collection"
|
|
24
24
|
require_relative "atlas_rb/admin/community"
|
|
25
25
|
require_relative "atlas_rb/system/user"
|
|
26
|
+
require_relative "atlas_rb/audit_event"
|
|
26
27
|
|
|
27
28
|
# Ruby client for the Atlas API — Northeastern University's institutional
|
|
28
29
|
# digital repository.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atlas_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cliff
|
|
@@ -119,6 +119,7 @@ files:
|
|
|
119
119
|
- lib/atlas_rb/admin/collection.rb
|
|
120
120
|
- lib/atlas_rb/admin/community.rb
|
|
121
121
|
- lib/atlas_rb/admin/work.rb
|
|
122
|
+
- lib/atlas_rb/audit_event.rb
|
|
122
123
|
- lib/atlas_rb/authentication.rb
|
|
123
124
|
- lib/atlas_rb/blob.rb
|
|
124
125
|
- lib/atlas_rb/collection.rb
|