atlas_rb 1.8.11 → 1.9.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 +4 -4
- data/.version +1 -1
- data/Gemfile.lock +1 -1
- data/lib/atlas_rb/authentication.rb +9 -4
- data/lib/atlas_rb/configuration.rb +11 -2
- data/lib/atlas_rb/faraday_helper.rb +27 -12
- data/lib/atlas_rb/system/user.rb +24 -15
- data/lib/atlas_rb/system/work.rb +48 -0
- data/lib/atlas_rb/user.rb +55 -9
- data/lib/atlas_rb.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe879cca7166684164dce3dc7651f22b68c83bd36d4463aaf70d1078147b26f8
|
|
4
|
+
data.tar.gz: ab88520172776ef649214806b30b2153d1804e960672e7c6ed4237a33ce9eb22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89879f3e03444288baa4e04004bd83e706ace04652eca337cb85d7e665816d49d4ce2d006a598c3505317bff78106e647b477bcbd77fdd0408eda6c0632fa8ca
|
|
7
|
+
data.tar.gz: de9bf34fe94ff2192733ac5c21040b6b1dc8d1b941270398a8edfa0231d4c41fa5ccc69ba17cb68797b40c1a0c6263b187811200c9de13166543cd4bc9632bd8
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.9.1
|
data/Gemfile.lock
CHANGED
|
@@ -17,7 +17,14 @@ module AtlasRb
|
|
|
17
17
|
|
|
18
18
|
# Look up the Atlas user record for an NUID.
|
|
19
19
|
#
|
|
20
|
+
# A NUID can hold several accounts (a person's staff/student logins). Pass
|
|
21
|
+
# `email:` to act as — and read back — a specific one; it rides as a signed
|
|
22
|
+
# `acct` claim so `GET /user` resolves that account. Omit it and Atlas
|
|
23
|
+
# returns the person's preferred account.
|
|
24
|
+
#
|
|
20
25
|
# @param nuid [String] the user's Northeastern University ID.
|
|
26
|
+
# @param email [String, nil] optional account email to act as (the `acct`
|
|
27
|
+
# selector); nil resolves the preferred account.
|
|
21
28
|
# @return [Hash] the user record returned by `GET /user`, including at
|
|
22
29
|
# minimum `"id"`, `"name"`, and `"groups"`.
|
|
23
30
|
# @raise [JSON::ParserError] if the response body is not valid JSON
|
|
@@ -26,10 +33,8 @@ module AtlasRb
|
|
|
26
33
|
# @example
|
|
27
34
|
# AtlasRb::Authentication.login("001234567")
|
|
28
35
|
# # => { "id" => 42, "name" => "Jane Doe", "groups" => [...] }
|
|
29
|
-
def self.login(nuid)
|
|
30
|
-
|
|
31
|
-
# need hash - id, name, token => ...
|
|
32
|
-
AtlasRb::Mash.new(JSON.parse(connection({}, nuid).get('/user')&.body))
|
|
36
|
+
def self.login(nuid, email: nil)
|
|
37
|
+
AtlasRb::Mash.new(JSON.parse(connection({}, nuid, account: email).get('/user')&.body))
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
# Fetch only the group memberships for an NUID.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module AtlasRb
|
|
4
4
|
# Holds gem-wide configuration registered via {AtlasRb.configure}.
|
|
5
5
|
#
|
|
6
|
-
# The configuration model is deliberately tiny:
|
|
7
|
-
# accept callables. The gem stays consumer-agnostic — it knows nothing
|
|
6
|
+
# The configuration model is deliberately tiny: a handful of slots, all of
|
|
7
|
+
# which accept callables. The gem stays consumer-agnostic — it knows nothing
|
|
8
8
|
# about Rails, Devise, or any host application's request lifecycle — and
|
|
9
9
|
# instead lets the consumer hand it lambdas that resolve the per-request
|
|
10
10
|
# ambient context when a request is about to go out.
|
|
@@ -45,6 +45,15 @@ module AtlasRb
|
|
|
45
45
|
# to send no `On-Behalf-Of:` header.
|
|
46
46
|
attr_accessor :default_on_behalf_of
|
|
47
47
|
|
|
48
|
+
# @return [Proc, nil] callable returning the ambient account email when a
|
|
49
|
+
# call is made without an explicit `account:` kwarg. A person's NUID can
|
|
50
|
+
# hold several accounts (staff/student logins); this names which one is
|
|
51
|
+
# acting, signed into the assertion as an `acct` claim (companion to
|
|
52
|
+
# {#default_nuid}). Typically `-> { Current.account_email }` in a Rails
|
|
53
|
+
# host. `nil` (the default) signs no `acct` — Atlas then resolves the
|
|
54
|
+
# person's preferred account.
|
|
55
|
+
attr_accessor :default_account
|
|
56
|
+
|
|
48
57
|
# Relay signing. When set, the regular relay path *signs* a short-lived
|
|
49
58
|
# assertion (ES256, `sub` = acting nuid) — identity is proven, not asserted.
|
|
50
59
|
# This is the relay credential: with no signing key configured (and no
|
|
@@ -76,6 +76,10 @@ module AtlasRb
|
|
|
76
76
|
# claim (acting-as / view-as). When `nil`, falls through to
|
|
77
77
|
# `AtlasRb.config.default_on_behalf_of&.call`; if that is also nil, no
|
|
78
78
|
# `obo` claim is added.
|
|
79
|
+
# @param account [String, nil] optional account email carried as a signed
|
|
80
|
+
# `acct` claim, naming which of the NUID's accounts is acting. When `nil`,
|
|
81
|
+
# falls through to `AtlasRb.config.default_account&.call`; if that is also
|
|
82
|
+
# nil, no `acct` claim is added and Atlas resolves the preferred account.
|
|
79
83
|
# @param idempotency_key [String, nil] optional UUID to send in the
|
|
80
84
|
# `Idempotency-Key` header. Used by retry-safe create flows (currently
|
|
81
85
|
# `POST /works`, `POST /file_sets`, `POST /files`) to deduplicate replays
|
|
@@ -92,8 +96,8 @@ module AtlasRb
|
|
|
92
96
|
#
|
|
93
97
|
# @example Fetching a community
|
|
94
98
|
# AtlasRb::Community.connection({}).get('/communities/abc123')
|
|
95
|
-
def connection(params, nuid=nil, on_behalf_of: nil, idempotency_key: nil, auth: :required)
|
|
96
|
-
headers = auth_headers(nuid, on_behalf_of, optional: auth == :optional)
|
|
99
|
+
def connection(params, nuid=nil, on_behalf_of: nil, account: nil, idempotency_key: nil, auth: :required)
|
|
100
|
+
headers = auth_headers(nuid, on_behalf_of, account: account, optional: auth == :optional)
|
|
97
101
|
.merge("Content-Type" => "application/json")
|
|
98
102
|
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
99
103
|
|
|
@@ -135,8 +139,8 @@ module AtlasRb
|
|
|
135
139
|
# "scan.pdf")
|
|
136
140
|
# }
|
|
137
141
|
# AtlasRb::Blob.multipart.post('/files/', payload)
|
|
138
|
-
def multipart(nuid=nil, on_behalf_of: nil, idempotency_key: nil)
|
|
139
|
-
headers = auth_headers(nuid, on_behalf_of)
|
|
142
|
+
def multipart(nuid=nil, on_behalf_of: nil, account: nil, idempotency_key: nil)
|
|
143
|
+
headers = auth_headers(nuid, on_behalf_of, account: account)
|
|
140
144
|
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
141
145
|
|
|
142
146
|
Faraday.new(
|
|
@@ -192,11 +196,13 @@ module AtlasRb
|
|
|
192
196
|
# Used exclusively by classes under {AtlasRb::System}.
|
|
193
197
|
#
|
|
194
198
|
# @param params [Hash] query-string / body params.
|
|
199
|
+
# @param on_behalf_of [String, nil] optional NUID sent as a plain (not
|
|
200
|
+
# signed) `On-Behalf-Of` header — this path is already backend-only.
|
|
195
201
|
# @return [Faraday::Connection] a system-authenticated connection.
|
|
196
202
|
# @raise [RuntimeError] if the credential is not configured.
|
|
197
203
|
# @raise [NameError] if `Rails` is not loaded (the gem assumes a
|
|
198
204
|
# Rails host for system-path calls).
|
|
199
|
-
def system_connection(params = {})
|
|
205
|
+
def system_connection(params = {}, on_behalf_of: nil)
|
|
200
206
|
token = Rails.application.credentials.atlas_system_token ||
|
|
201
207
|
raise("atlas_rb: Rails.application.credentials.atlas_system_token not configured")
|
|
202
208
|
|
|
@@ -205,6 +211,7 @@ module AtlasRb
|
|
|
205
211
|
"Authorization" => "Bearer #{token}",
|
|
206
212
|
"User" => "NUID #{AtlasRb::System::NUID}"
|
|
207
213
|
}
|
|
214
|
+
headers["On-Behalf-Of"] = "NUID #{on_behalf_of}" if on_behalf_of
|
|
208
215
|
|
|
209
216
|
Faraday.new(
|
|
210
217
|
url: ENV.fetch("ATLAS_URL", nil),
|
|
@@ -212,6 +219,10 @@ module AtlasRb
|
|
|
212
219
|
headers: headers
|
|
213
220
|
) do |f|
|
|
214
221
|
instrument(f)
|
|
222
|
+
# Narrowly path-scoped (see each class) — a no-op for System::User /
|
|
223
|
+
# System::Token, and gives System::Work the same typed errors as #connection.
|
|
224
|
+
f.use AtlasRb::Middleware::RaiseOnStaleResource
|
|
225
|
+
f.use AtlasRb::Middleware::RaiseOnResourceError
|
|
215
226
|
f.response :follow_redirects
|
|
216
227
|
f.adapter Faraday.default_adapter
|
|
217
228
|
end
|
|
@@ -243,14 +254,15 @@ module AtlasRb
|
|
|
243
254
|
# is only for endpoints Atlas serves with `require_auth` skipped (`GET
|
|
244
255
|
# /reset`); every normal endpoint leaves `optional` false so a
|
|
245
256
|
# misconfiguration fails loudly rather than silently going unauthenticated.
|
|
246
|
-
def auth_headers(nuid, on_behalf_of, optional: false)
|
|
257
|
+
def auth_headers(nuid, on_behalf_of, account: nil, optional: false)
|
|
247
258
|
jwt = ENV.fetch("ATLAS_JWT", nil)
|
|
248
259
|
return { "Authorization" => "Bearer #{jwt}" } if jwt
|
|
249
260
|
|
|
250
261
|
nuid ||= AtlasRb.config.default_nuid&.call
|
|
251
262
|
on_behalf_of ||= AtlasRb.config.default_on_behalf_of&.call
|
|
263
|
+
account ||= AtlasRb.config.default_account&.call
|
|
252
264
|
|
|
253
|
-
headers = signed_relay_headers(nuid, on_behalf_of)
|
|
265
|
+
headers = signed_relay_headers(nuid, on_behalf_of, account)
|
|
254
266
|
return headers if headers
|
|
255
267
|
return {} if optional
|
|
256
268
|
|
|
@@ -262,14 +274,16 @@ module AtlasRb
|
|
|
262
274
|
# A signed-assertion Authorization header (sub = acting nuid), or nil when
|
|
263
275
|
# signing isn't configured or there is no acting nuid to put in `sub`.
|
|
264
276
|
# Acting-as is carried IN the assertion as a signed `obo` claim (Atlas
|
|
265
|
-
# honours it on the assertion path; a header obo is ignored there).
|
|
266
|
-
|
|
277
|
+
# honours it on the assertion path; a header obo is ignored there). When a
|
|
278
|
+
# NUID holds several accounts, `account` (email) rides as a signed `acct`
|
|
279
|
+
# claim naming which one is acting; absent, Atlas picks the preferred.
|
|
280
|
+
def signed_relay_headers(nuid, on_behalf_of, account = nil)
|
|
267
281
|
return nil unless nuid
|
|
268
282
|
|
|
269
283
|
key = assertion_signing_key
|
|
270
284
|
return nil unless key
|
|
271
285
|
|
|
272
|
-
{ "Authorization" => "Bearer #{signed_assertion(nuid.to_s, key, on_behalf_of)}" }
|
|
286
|
+
{ "Authorization" => "Bearer #{signed_assertion(nuid.to_s, key, on_behalf_of, account)}" }
|
|
273
287
|
end
|
|
274
288
|
|
|
275
289
|
# Mint a Cerberus relay assertion for `nuid`, signed ES256 with `key`. The
|
|
@@ -278,11 +292,12 @@ module AtlasRb
|
|
|
278
292
|
# for an Atlas-side one-time cache. When `on_behalf_of` is given, it rides as
|
|
279
293
|
# a SIGNED `obo` claim — acting-as that can't be forged onto a stolen
|
|
280
294
|
# assertion (Atlas admin-gates the operator and ignores any header obo here).
|
|
281
|
-
def signed_assertion(nuid, key, on_behalf_of = nil)
|
|
295
|
+
def signed_assertion(nuid, key, on_behalf_of = nil, account = nil)
|
|
282
296
|
now = Time.now.to_i
|
|
283
297
|
payload = { "iss" => ASSERTION_ISSUER, "aud" => ASSERTION_AUDIENCE, "sub" => nuid,
|
|
284
298
|
"iat" => now, "exp" => now + ASSERTION_TTL, "jti" => SecureRandom.uuid,
|
|
285
|
-
"obo"
|
|
299
|
+
"obo" => on_behalf_of&.to_s, # obo only when acting-as
|
|
300
|
+
"acct" => account&.to_s }.compact # acct only when a specific account is named
|
|
286
301
|
JWT.encode(payload, key, "ES256", { kid: assertion_signing_kid })
|
|
287
302
|
end
|
|
288
303
|
|
data/lib/atlas_rb/system/user.rb
CHANGED
|
@@ -27,9 +27,10 @@ module AtlasRb
|
|
|
27
27
|
NUID = "000000000"
|
|
28
28
|
|
|
29
29
|
# SSO-callback user provisioning. Finds the Atlas `User` row keyed on
|
|
30
|
-
# the supplied
|
|
31
|
-
# `groups` with the IdP-asserted set. Full replace, not
|
|
32
|
-
# the IdP assertion is authoritative.
|
|
30
|
+
# the supplied email — the account key — (creating it if missing) and
|
|
31
|
+
# replaces its `groups` with the IdP-asserted set. Full replace, not
|
|
32
|
+
# merge — the IdP assertion is authoritative. Keying on email keeps a
|
|
33
|
+
# person's staff/student logins as distinct accounts under one NUID.
|
|
33
34
|
#
|
|
34
35
|
# Always authenticates via {FaradayHelper#system_connection}, so the
|
|
35
36
|
# caller has no way to act as a non-system principal. Atlas allows
|
|
@@ -37,32 +38,40 @@ module AtlasRb
|
|
|
37
38
|
class User
|
|
38
39
|
extend AtlasRb::FaradayHelper
|
|
39
40
|
|
|
40
|
-
# Find-or-create the User keyed on
|
|
41
|
+
# Find-or-create the User keyed on email and replace its groups.
|
|
41
42
|
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
43
|
+
# Email is the account key: a person's staff and student logins share a
|
|
44
|
+
# NUID but present a different email each, so keying on email keeps them
|
|
45
|
+
# as distinct accounts instead of collapsing (last-write-wins) on NUID.
|
|
46
|
+
#
|
|
47
|
+
# @param email [String] the account key — the login's `mail`.
|
|
48
|
+
# This is the *subject* of the operation, not the actor; the actor is
|
|
49
|
+
# always the system fixture.
|
|
50
|
+
# @param nuid [String, nil] the person's NUID (the grouping thread that
|
|
51
|
+
# ties a person's accounts together). Forwarded when available.
|
|
45
52
|
# @param groups [Array<String>] full group set; replaces, not merges.
|
|
46
53
|
# @param name [String, nil] forwarded if the SSO callback has it;
|
|
47
54
|
# Atlas treats this field as optional.
|
|
48
|
-
# @param
|
|
49
|
-
#
|
|
55
|
+
# @param affiliation [String, nil] the login's unscoped-affiliation, a
|
|
56
|
+
# human label for the account (e.g. "staff"/"student"); optional.
|
|
50
57
|
# @return [AtlasRb::Mash] the resulting User record (`id`, `nuid`,
|
|
51
|
-
# `name`, `email`, `role`, `groups`).
|
|
58
|
+
# `name`, `email`, `role`, `groups`, `affiliation`, `preferred`).
|
|
52
59
|
#
|
|
53
60
|
# @example From Cerberus's SSO callback
|
|
54
61
|
# AtlasRb::System::User.find_or_create(
|
|
62
|
+
# email: "j.doe@northeastern.edu",
|
|
55
63
|
# nuid: "001234567",
|
|
56
64
|
# groups: ["northeastern:staff", "drs:editors"],
|
|
57
65
|
# name: "Jane Doe",
|
|
58
|
-
#
|
|
66
|
+
# affiliation: "staff"
|
|
59
67
|
# )
|
|
60
|
-
def self.find_or_create(
|
|
68
|
+
def self.find_or_create(email:, nuid: nil, groups: [], name: nil, affiliation: nil)
|
|
61
69
|
body = { groups: groups }
|
|
62
|
-
body[:
|
|
63
|
-
body[:
|
|
70
|
+
body[:nuid] = nuid if nuid
|
|
71
|
+
body[:name] = name if name
|
|
72
|
+
body[:affiliation] = affiliation if affiliation
|
|
64
73
|
|
|
65
|
-
response = system_connection.put("/users/
|
|
74
|
+
response = system_connection.put("/users/by_email/#{email}", body.to_json)
|
|
66
75
|
AtlasRb::Mash.new(JSON.parse(response.body))["user"]
|
|
67
76
|
end
|
|
68
77
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AtlasRb
|
|
4
|
+
module System
|
|
5
|
+
# Showcase publishing: link a freshly-created Work into a depositor's
|
|
6
|
+
# featured showcase Collection on their behalf, without granting the
|
|
7
|
+
# depositor themselves standing edit rights on that shared Collection.
|
|
8
|
+
#
|
|
9
|
+
# Atlas scopes this narrowly on both sides (see Atlas's `Ability`): the
|
|
10
|
+
# target Collection must be `featured`, and the Work must belong to the
|
|
11
|
+
# `on_behalf_of` NUID — never an arbitrary or private Work. Cerberus's
|
|
12
|
+
# `WorkDeposit#create_published` ("Publish to my community") is the one
|
|
13
|
+
# caller today.
|
|
14
|
+
#
|
|
15
|
+
# Always authenticates via {FaradayHelper#system_connection}, so there is
|
|
16
|
+
# no way to issue this as a regular user.
|
|
17
|
+
class Work
|
|
18
|
+
extend AtlasRb::FaradayHelper
|
|
19
|
+
|
|
20
|
+
# @param work_id [String] the Work ID to link.
|
|
21
|
+
# @param collection_id [String] the (must-be-featured) showcase
|
|
22
|
+
# Collection to link the Work into.
|
|
23
|
+
# @param on_behalf_of [String] the depositor NUID this write is
|
|
24
|
+
# attributed to — required (unlike the regular, human-facing
|
|
25
|
+
# {AtlasRb::Work.add_linked_member}), since a :system call with no
|
|
26
|
+
# attribution defeats the point of this path, and Atlas's Ability
|
|
27
|
+
# grant is itself conditioned on it matching the Work's depositor.
|
|
28
|
+
# @return [Array<String>] the Work's full set of linked Collection
|
|
29
|
+
# noids after the add.
|
|
30
|
+
# @raise [AtlasRb::StaleResourceError] optimistic-lock conflict.
|
|
31
|
+
# @raise [AtlasRb::LinkedMemberError] structural rejection (HTTP 422) —
|
|
32
|
+
# non-Collection target, tombstoned work/target, already a structural
|
|
33
|
+
# member.
|
|
34
|
+
# @raise [AtlasRb::ForbiddenError] Atlas refused the link (HTTP 403) —
|
|
35
|
+
# e.g. the target Collection isn't featured, or on_behalf_of doesn't
|
|
36
|
+
# own the Work.
|
|
37
|
+
#
|
|
38
|
+
# @example From Cerberus's publish-to-showcase deposit branch
|
|
39
|
+
# AtlasRb::System::Work.add_linked_member(work.id, showcase.id, on_behalf_of: depositor_nuid)
|
|
40
|
+
def self.add_linked_member(work_id, collection_id, on_behalf_of:)
|
|
41
|
+
JSON.parse(
|
|
42
|
+
system_connection({ collection_id: collection_id }, on_behalf_of: on_behalf_of)
|
|
43
|
+
.post("/works/#{work_id}/linked_members")&.body
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/atlas_rb/user.rb
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module AtlasRb
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# User directory lookups (typeahead search, NUID → name resolution) plus a
|
|
5
|
+
# person's own account management — enumerating the accounts that share their
|
|
6
|
+
# NUID and choosing a preferred (default) one.
|
|
6
7
|
#
|
|
7
8
|
# This is a **user-context** binding: calls authenticate as the acting
|
|
8
9
|
# user via the standard relay-signing path (the acting NUID is signed into
|
|
9
10
|
# the assertion `sub`), like every other top-level class. It is
|
|
10
11
|
# deliberately *not* part of
|
|
11
12
|
# {AtlasRb::System} — that namespace is structurally reserved for
|
|
12
|
-
# system-token calls ({System::User.find_or_create}), and
|
|
13
|
-
#
|
|
13
|
+
# system-token calls ({System::User.find_or_create}), and these are ordinary
|
|
14
|
+
# logged-in-user capabilities.
|
|
14
15
|
#
|
|
15
|
-
#
|
|
16
|
-
# only (no email, role, or groups), and rows with role
|
|
17
|
-
# `guest`, or `system` are never returned.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
16
|
+
# The directory lookups enforce minimal disclosure: every entry carries
|
|
17
|
+
# `nuid` + `name` only (no email, role, or groups), and rows with role
|
|
18
|
+
# `anonymous`, `guest`, or `system` are never returned. The account methods
|
|
19
|
+
# ({.accounts} / {.set_preferred}) *do* disclose email/groups/affiliation, so
|
|
20
|
+
# Atlas limits them to the person themselves (matching NUID), an admin, or the
|
|
21
|
+
# system principal. Per the layering principle the gem adds nothing on top —
|
|
22
|
+
# no caching, no name parsing, no result shaping; presentation belongs to the
|
|
23
|
+
# host application.
|
|
20
24
|
class User
|
|
21
25
|
extend AtlasRb::FaradayHelper
|
|
22
26
|
|
|
@@ -88,5 +92,47 @@ module AtlasRb
|
|
|
88
92
|
connection({ nuids: Array(nuids).join(",") }, nuid).get(ROUTE)&.body
|
|
89
93
|
).map { |entry| AtlasRb::Mash.new(entry) }
|
|
90
94
|
end
|
|
95
|
+
|
|
96
|
+
# List the accounts sharing a NUID — a person's staff/student logins.
|
|
97
|
+
#
|
|
98
|
+
# Unlike the directory lookups this discloses each account's email,
|
|
99
|
+
# affiliation label, role, stored group set, and which one is preferred —
|
|
100
|
+
# the data the login "you have more than one account" check and the My DRS
|
|
101
|
+
# accounts panel render from. Atlas limits it to the person themselves, an
|
|
102
|
+
# admin, or the system principal; others get a `403`.
|
|
103
|
+
#
|
|
104
|
+
# @param target_nuid [String] the NUID whose accounts to list — the
|
|
105
|
+
# *subject*, distinct from the acting `nuid:` kwarg.
|
|
106
|
+
# @param nuid [String, nil] optional acting user's NUID (signed into the
|
|
107
|
+
# assertion `sub`); on the BYO-JWT path identity lives in the token.
|
|
108
|
+
# @return [AtlasRb::Mash] the envelope: `nuid` plus an `accounts` array,
|
|
109
|
+
# each entry carrying `email`, `name`, `affiliation`, `role`, `groups`,
|
|
110
|
+
# and `preferred`.
|
|
111
|
+
#
|
|
112
|
+
# @example Login-time multi-account detection
|
|
113
|
+
# AtlasRb::User.accounts("001234567", nuid: "001234567")["accounts"].size # => 2
|
|
114
|
+
def self.accounts(target_nuid, nuid: nil)
|
|
115
|
+
AtlasRb::Mash.new(JSON.parse(
|
|
116
|
+
connection({}, nuid).get("#{ROUTE}/by_nuid/#{target_nuid}/accounts")&.body
|
|
117
|
+
))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Set the preferred (default) account for a NUID — the account chosen when
|
|
121
|
+
# a login names no specific one. Same self/admin/system scope as {.accounts}.
|
|
122
|
+
#
|
|
123
|
+
# @param target_nuid [String] the NUID whose default is being set.
|
|
124
|
+
# @param email [String] the account to make preferred; must be one of the
|
|
125
|
+
# NUID's accounts (else Atlas returns `404`).
|
|
126
|
+
# @param nuid [String, nil] optional acting user's NUID (signed into `sub`).
|
|
127
|
+
# @return [AtlasRb::Mash] the updated account record under `"user"`.
|
|
128
|
+
#
|
|
129
|
+
# @example
|
|
130
|
+
# AtlasRb::User.set_preferred("001234567", email: "j.doe@husky.neu.edu",
|
|
131
|
+
# nuid: "001234567")
|
|
132
|
+
def self.set_preferred(target_nuid, email:, nuid: nil)
|
|
133
|
+
response = connection({}, nuid)
|
|
134
|
+
.put("#{ROUTE}/by_nuid/#{target_nuid}/preferred_account", { email: email }.to_json)
|
|
135
|
+
AtlasRb::Mash.new(JSON.parse(response.body))["user"]
|
|
136
|
+
end
|
|
91
137
|
end
|
|
92
138
|
end
|
data/lib/atlas_rb.rb
CHANGED
|
@@ -31,6 +31,7 @@ require_relative "atlas_rb/admin/community"
|
|
|
31
31
|
require_relative "atlas_rb/system"
|
|
32
32
|
require_relative "atlas_rb/system/user"
|
|
33
33
|
require_relative "atlas_rb/system/token"
|
|
34
|
+
require_relative "atlas_rb/system/work"
|
|
34
35
|
require_relative "atlas_rb/audit_event"
|
|
35
36
|
|
|
36
37
|
# Ruby client for the Atlas API — Northeastern University's institutional
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: atlas_rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cliff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -152,6 +152,7 @@ files:
|
|
|
152
152
|
- lib/atlas_rb/system.rb
|
|
153
153
|
- lib/atlas_rb/system/token.rb
|
|
154
154
|
- lib/atlas_rb/system/user.rb
|
|
155
|
+
- lib/atlas_rb/system/work.rb
|
|
155
156
|
- lib/atlas_rb/user.rb
|
|
156
157
|
- lib/atlas_rb/version.rb
|
|
157
158
|
- lib/atlas_rb/work.rb
|