atlas-auth 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +132 -0
- data/lib/atlas/authorization.rb +78 -0
- data/lib/atlas/client.rb +124 -0
- data/lib/atlas/error.rb +92 -0
- data/lib/atlas/handshake.rb +241 -0
- data/lib/atlas/jwks_cache.rb +138 -0
- data/lib/atlas/pagination.rb +52 -0
- data/lib/atlas/resources/actions.rb +46 -0
- data/lib/atlas/resources/actor_tokens.rb +18 -0
- data/lib/atlas/resources/api_keys.rb +38 -0
- data/lib/atlas/resources/attack_protection.rb +18 -0
- data/lib/atlas/resources/audit_logs.rb +14 -0
- data/lib/atlas/resources/base.rb +32 -0
- data/lib/atlas/resources/billing.rb +35 -0
- data/lib/atlas/resources/bot_signals.rb +26 -0
- data/lib/atlas/resources/branding.rb +24 -0
- data/lib/atlas/resources/data_subject_requests.rb +28 -0
- data/lib/atlas/resources/domains.rb +26 -0
- data/lib/atlas/resources/email_templates.rb +29 -0
- data/lib/atlas/resources/fga.rb +146 -0
- data/lib/atlas/resources/import_export.rb +35 -0
- data/lib/atlas/resources/instance.rb +18 -0
- data/lib/atlas/resources/instance_security.rb +46 -0
- data/lib/atlas/resources/invitations.rb +22 -0
- data/lib/atlas/resources/jwt_templates.rb +32 -0
- data/lib/atlas/resources/localizations.rb +30 -0
- data/lib/atlas/resources/log_streams.rb +35 -0
- data/lib/atlas/resources/lti_platforms.rb +30 -0
- data/lib/atlas/resources/managed_waf.rb +35 -0
- data/lib/atlas/resources/messaging.rb +32 -0
- data/lib/atlas/resources/network_acls.rb +30 -0
- data/lib/atlas/resources/oauth_clients.rb +55 -0
- data/lib/atlas/resources/oauth_providers.rb +44 -0
- data/lib/atlas/resources/organizations.rb +140 -0
- data/lib/atlas/resources/radius_clients.rb +30 -0
- data/lib/atlas/resources/rate_limit.rb +18 -0
- data/lib/atlas/resources/resource_servers.rb +30 -0
- data/lib/atlas/resources/restrictions.rb +29 -0
- data/lib/atlas/resources/risk_based_mfa.rb +18 -0
- data/lib/atlas/resources/roles.rb +55 -0
- data/lib/atlas/resources/scim_provisioning.rb +46 -0
- data/lib/atlas/resources/scim_tokens.rb +23 -0
- data/lib/atlas/resources/sessions.rb +29 -0
- data/lib/atlas/resources/sign_in_tokens.rb +17 -0
- data/lib/atlas/resources/sms_templates.rb +37 -0
- data/lib/atlas/resources/sso_connections.rb +35 -0
- data/lib/atlas/resources/sso_onboarding.rb +37 -0
- data/lib/atlas/resources/tokens.rb +15 -0
- data/lib/atlas/resources/users.rb +121 -0
- data/lib/atlas/resources/waitlist.rb +19 -0
- data/lib/atlas/resources/webhooks.rb +35 -0
- data/lib/atlas/transport.rb +158 -0
- data/lib/atlas/verifier.rb +227 -0
- data/lib/atlas/version.rb +6 -0
- data/lib/atlas.rb +41 -0
- metadata +148 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 80f3e5a3fa6f404e3416096b1315e90babd798263583e3f61a47a33c59bf21b6
|
|
4
|
+
data.tar.gz: 9f243710f91725c971972e8216e54348dd23009bb60ec0189929b168d7f0775c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 00ed1612a4c79bc241ec64c2d1a9d49cd637292c1da84baaf8efbb480ed1afcf344f1ad556aca5825a0ddb629da8f6ad095f1dc6fa90f1303e710ad79c23f4c7
|
|
7
|
+
data.tar.gz: 4e11a62f2eb27a06a15f8512d4d0a9bc85083fc7cdbf058754113b1112affb1be4dc37345c494625cb514ab57a48cd9bd01782b1178e77c13a973ad87c96e3af
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Atlas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Atlas Ruby SDK
|
|
2
|
+
|
|
3
|
+
The official **Ruby backend SDK** for [Atlas](https://atlasauth.net) — a typed client over the `sk_` Backend API (BAPI), plus local session-token verification. It is the Ruby peer of the TypeScript `@atlasauth/backend` and Python `atlas-backend` SDKs and covers the full management surface (all 45 resource namespaces).
|
|
4
|
+
|
|
5
|
+
> This is a **server-side** SDK. It uses your instance **secret key** (`sk_...`) and must never ship to a browser or mobile app. For client-side sign-in flows use the JS, Swift, Kotlin, or Flutter SDKs.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Gemfile
|
|
11
|
+
gem "atlas-auth"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bundle install
|
|
16
|
+
# or
|
|
17
|
+
gem install atlas-auth
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Ruby 3.0+. The only runtime dependency is [`jwt`](https://rubygems.org/gems/jwt) (for session-token verification); everything else is standard library.
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "atlas"
|
|
26
|
+
|
|
27
|
+
atlas = Atlas::Client.new(ENV.fetch("ATLAS_SECRET_KEY")) # "sk_live_..."
|
|
28
|
+
|
|
29
|
+
# Create a user
|
|
30
|
+
user = atlas.users.create(email_address: "ada@example.com", password: "•••••••")
|
|
31
|
+
|
|
32
|
+
# Read one, list many
|
|
33
|
+
atlas.users.get(user["id"])
|
|
34
|
+
page = atlas.users.list(limit: 20)
|
|
35
|
+
|
|
36
|
+
# Walk every page of a cursor-paginated list
|
|
37
|
+
Atlas.paginate(atlas.organizations).each { |org| puts org["name"] }
|
|
38
|
+
all_users = Atlas.collect(atlas.users, status: "active")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Configuration
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
Atlas::Client.new(
|
|
45
|
+
"sk_live_...",
|
|
46
|
+
api_url: "https://api.atlasauth.net", # default: https://api.atlasauth.net
|
|
47
|
+
open_timeout: 30,
|
|
48
|
+
read_timeout: 30,
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every request is authenticated with `Authorization: Bearer <secret_key>`; the key is never logged or placed in a URL.
|
|
53
|
+
|
|
54
|
+
### Idempotency
|
|
55
|
+
|
|
56
|
+
Write operations accept an `idempotency_key:` so a retried request is applied at most once:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
atlas.users.create({ email_address: "ada@example.com" }, idempotency_key: "signup-42")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Errors
|
|
63
|
+
|
|
64
|
+
Any non-2xx response raises a typed `Atlas::APIError` carrying the HTTP status and the full `{ errors: [...] }` envelope. Status codes map to subclasses so you can rescue precisely:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
begin
|
|
68
|
+
atlas.users.get("user_missing")
|
|
69
|
+
rescue Atlas::NotFoundError => e
|
|
70
|
+
e.status # => 404
|
|
71
|
+
e.code # => "NOT_FOUND" (first error's stable code)
|
|
72
|
+
e.errors # => [{ "code" => "NOT_FOUND", "message" => "..." }]
|
|
73
|
+
rescue Atlas::APIError => e
|
|
74
|
+
# BadRequestError, AuthenticationError, PermissionError, ConflictError,
|
|
75
|
+
# RateLimitError, ServerError, ... all inherit from APIError.
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Session-token verification
|
|
80
|
+
|
|
81
|
+
Verify Atlas session JWTs **locally** — no round trip per request. The JWKS is cached in-process with kid-miss refetch throttled to once per minute (§7.3).
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
backend = Atlas::Backend.new(
|
|
85
|
+
jwks_url: "https://api.atlasauth.net/v1/jwks",
|
|
86
|
+
issuer: "https://your-instance.atlasauth.net",
|
|
87
|
+
authorized_parties: ["https://app.example.com"], # optional azp allowlist
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
result = backend.verify(session_jwt)
|
|
91
|
+
if result.ok?
|
|
92
|
+
result.claims["sub"] # the user id
|
|
93
|
+
result.protect(permission: "org:billing:manage")
|
|
94
|
+
else
|
|
95
|
+
result.reason # :invalid, :malformed, :no_keys, :unauthorized_party
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# From a request's headers (Authorization: Bearer <jwt> or the __session cookie):
|
|
99
|
+
backend.authenticate_request(request.headers)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`verify` is the fast local path. `verify_online` asks Atlas whether the session is still live (a round trip; fails closed on an outage).
|
|
103
|
+
|
|
104
|
+
### Rails
|
|
105
|
+
|
|
106
|
+
Wire a shared client in an initializer:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# config/initializers/atlas.rb
|
|
110
|
+
ATLAS = Atlas::Client.new(Rails.application.credentials.atlas_secret_key)
|
|
111
|
+
BACKEND = Atlas::Backend.new(
|
|
112
|
+
jwks_url: "https://api.atlasauth.net/v1/jwks",
|
|
113
|
+
issuer: "https://your-instance.atlasauth.net",
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then verify in a `before_action` and branch on `BACKEND.authenticate_request(request.headers)`.
|
|
118
|
+
|
|
119
|
+
## Resources
|
|
120
|
+
|
|
121
|
+
All 45 namespaces are available on the client: `users`, `sessions`, `organizations`, `roles`, `permissions`, `oauth_clients`, `resource_servers`, `sso_connections`, `scim_tokens`, `scim_provisioning`, `domains`, `waitlist`, `allowlist`, `blocklist`, `attack_protection`, `actor_tokens`, `invitations`, `webhooks`, `sign_in_tokens`, `audit_logs`, `jwt_templates`, `api_keys`, `oauth_providers`, `sso_onboarding`, `fga`, `rate_limit_policy`, `risk_based_mfa`, `bot_signals`, `network_acls`, `managed_waf`, `log_streams`, `branding`, `email_templates`, `sms_templates`, `localizations`, `actions`, `billing`, `messaging`, `import_export`, `data_subject_requests`, `radius_clients`, `lti_platforms`, `instance`, `instance_security`, `tokens`.
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
bundle install
|
|
127
|
+
rake test # or: ruby -Ilib -Itest test/client_test.rb
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atlas
|
|
4
|
+
# Raised by +protect+ when a claims set fails an authorization condition. An
|
|
5
|
+
# app maps this to a 401/403. Mirrors +@atlas/authz+'s +ForbiddenError+.
|
|
6
|
+
class ForbiddenError < Error
|
|
7
|
+
# @return [Symbol] why the check failed (:unauthenticated, :permission, :role, ...).
|
|
8
|
+
attr_reader :reason
|
|
9
|
+
# @return [Hash] the condition that was evaluated.
|
|
10
|
+
attr_reader :condition
|
|
11
|
+
|
|
12
|
+
def initialize(reason, condition = {})
|
|
13
|
+
@reason = reason
|
|
14
|
+
@condition = condition
|
|
15
|
+
super("Forbidden: #{reason}")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The shared authorization primitive — the Ruby peer of +@atlas/authz+.
|
|
20
|
+
#
|
|
21
|
+
# A condition is a hash; an empty condition means "signed in". Supported keys
|
|
22
|
+
# (string or symbol):
|
|
23
|
+
# * +:permission+ — the claims must grant this org permission.
|
|
24
|
+
# * +:role+ — the claims' org role must equal this.
|
|
25
|
+
# * +:any_permission+ — at least one of these permissions is granted.
|
|
26
|
+
# * +:all_permissions+ — every one of these permissions is granted.
|
|
27
|
+
module Authorization
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# True when the claims satisfy the condition.
|
|
31
|
+
def has_from_claims?(claims, condition = {})
|
|
32
|
+
evaluate(claims, condition)[:allowed]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Evaluate a condition, returning +{ allowed:, reason: }+.
|
|
36
|
+
def evaluate(claims, condition = {})
|
|
37
|
+
condition ||= {}
|
|
38
|
+
permission = fetch(condition, :permission)
|
|
39
|
+
role = fetch(condition, :role)
|
|
40
|
+
any_permission = fetch(condition, :any_permission)
|
|
41
|
+
all_permissions = fetch(condition, :all_permissions)
|
|
42
|
+
|
|
43
|
+
# Empty condition: signed in is enough.
|
|
44
|
+
if permission.nil? && role.nil? && any_permission.nil? && all_permissions.nil?
|
|
45
|
+
return { allowed: true, reason: nil }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
granted = Array(fetch(claims, :org_permissions))
|
|
49
|
+
org_role = fetch(claims, :org_role)
|
|
50
|
+
|
|
51
|
+
return deny(:permission) if permission && !granted.include?(permission)
|
|
52
|
+
return deny(:role) if role && org_role != role
|
|
53
|
+
if any_permission && (Array(any_permission) & granted).empty?
|
|
54
|
+
return deny(:permission)
|
|
55
|
+
end
|
|
56
|
+
if all_permissions && !(Array(all_permissions) - granted).empty?
|
|
57
|
+
return deny(:permission)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
{ allowed: true, reason: nil }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def deny(reason)
|
|
64
|
+
{ allowed: false, reason: reason }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Look a key up under both its symbol and string form.
|
|
68
|
+
def fetch(hash, key)
|
|
69
|
+
return nil unless hash
|
|
70
|
+
|
|
71
|
+
if hash.key?(key)
|
|
72
|
+
hash[key]
|
|
73
|
+
elsif hash.key?(key.to_s)
|
|
74
|
+
hash[key.to_s]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/atlas/client.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "transport"
|
|
4
|
+
|
|
5
|
+
require_relative "resources/users"
|
|
6
|
+
require_relative "resources/sessions"
|
|
7
|
+
require_relative "resources/organizations"
|
|
8
|
+
require_relative "resources/roles"
|
|
9
|
+
require_relative "resources/oauth_clients"
|
|
10
|
+
require_relative "resources/resource_servers"
|
|
11
|
+
require_relative "resources/sso_connections"
|
|
12
|
+
require_relative "resources/scim_tokens"
|
|
13
|
+
require_relative "resources/domains"
|
|
14
|
+
require_relative "resources/waitlist"
|
|
15
|
+
require_relative "resources/restrictions"
|
|
16
|
+
require_relative "resources/attack_protection"
|
|
17
|
+
require_relative "resources/actor_tokens"
|
|
18
|
+
require_relative "resources/invitations"
|
|
19
|
+
require_relative "resources/webhooks"
|
|
20
|
+
require_relative "resources/sign_in_tokens"
|
|
21
|
+
require_relative "resources/audit_logs"
|
|
22
|
+
require_relative "resources/jwt_templates"
|
|
23
|
+
require_relative "resources/api_keys"
|
|
24
|
+
require_relative "resources/oauth_providers"
|
|
25
|
+
require_relative "resources/sso_onboarding"
|
|
26
|
+
require_relative "resources/scim_provisioning"
|
|
27
|
+
require_relative "resources/fga"
|
|
28
|
+
require_relative "resources/rate_limit"
|
|
29
|
+
require_relative "resources/risk_based_mfa"
|
|
30
|
+
require_relative "resources/bot_signals"
|
|
31
|
+
require_relative "resources/network_acls"
|
|
32
|
+
require_relative "resources/managed_waf"
|
|
33
|
+
require_relative "resources/log_streams"
|
|
34
|
+
require_relative "resources/branding"
|
|
35
|
+
require_relative "resources/email_templates"
|
|
36
|
+
require_relative "resources/sms_templates"
|
|
37
|
+
require_relative "resources/localizations"
|
|
38
|
+
require_relative "resources/actions"
|
|
39
|
+
require_relative "resources/billing"
|
|
40
|
+
require_relative "resources/messaging"
|
|
41
|
+
require_relative "resources/import_export"
|
|
42
|
+
require_relative "resources/data_subject_requests"
|
|
43
|
+
require_relative "resources/radius_clients"
|
|
44
|
+
require_relative "resources/lti_platforms"
|
|
45
|
+
require_relative "resources/instance"
|
|
46
|
+
require_relative "resources/instance_security"
|
|
47
|
+
require_relative "resources/tokens"
|
|
48
|
+
|
|
49
|
+
module Atlas
|
|
50
|
+
# The typed management client for the Atlas Backend API — the secret-key
|
|
51
|
+
# surface, the Ruby peer of TypeScript's +createAtlasClient+.
|
|
52
|
+
#
|
|
53
|
+
# Each namespace is one memoized accessor handing the shared, config-bound
|
|
54
|
+
# transport to a resource class, so this file reads as a table of contents for
|
|
55
|
+
# the whole surface.
|
|
56
|
+
#
|
|
57
|
+
# atlas = Atlas::Client.new("sk_live_...")
|
|
58
|
+
# user = atlas.users.create(email_address: "ada@example.com")
|
|
59
|
+
# Atlas.paginate(atlas.organizations).each { |org| puts org["name"] }
|
|
60
|
+
class Client
|
|
61
|
+
# @param secret_key [String] the instance secret key (+sk_...+). Sent as
|
|
62
|
+
# +Authorization: Bearer <key>+ on every request; never logged, never in a URL.
|
|
63
|
+
# @param api_url [String] base URL of the instance's Backend API.
|
|
64
|
+
# @param http [#call, nil] injectable requester (tests, custom transport).
|
|
65
|
+
# @param open_timeout [Numeric] connect timeout, seconds.
|
|
66
|
+
# @param read_timeout [Numeric] read timeout, seconds.
|
|
67
|
+
def initialize(secret_key, api_url: DEFAULT_API_URL, http: nil,
|
|
68
|
+
open_timeout: 30, read_timeout: 30)
|
|
69
|
+
@transport = Transport.new(
|
|
70
|
+
secret_key: secret_key, api_url: api_url, http: http,
|
|
71
|
+
open_timeout: open_timeout, read_timeout: read_timeout
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Atlas::Transport] the underlying config-bound transport.
|
|
76
|
+
attr_reader :transport
|
|
77
|
+
|
|
78
|
+
def users = @users ||= Resources::Users.new(@transport)
|
|
79
|
+
def sessions = @sessions ||= Resources::Sessions.new(@transport)
|
|
80
|
+
def organizations = @organizations ||= Resources::Organizations.new(@transport)
|
|
81
|
+
def roles = @roles ||= Resources::Roles.new(@transport)
|
|
82
|
+
def permissions = @permissions ||= Resources::Permissions.new(@transport)
|
|
83
|
+
def oauth_clients = @oauth_clients ||= Resources::OAuthClients.new(@transport)
|
|
84
|
+
def resource_servers = @resource_servers ||= Resources::ResourceServers.new(@transport)
|
|
85
|
+
def sso_connections = @sso_connections ||= Resources::SsoConnections.new(@transport)
|
|
86
|
+
def scim_tokens = @scim_tokens ||= Resources::ScimTokens.new(@transport)
|
|
87
|
+
def domains = @domains ||= Resources::Domains.new(@transport)
|
|
88
|
+
def waitlist = @waitlist ||= Resources::Waitlist.new(@transport)
|
|
89
|
+
def allowlist = @allowlist ||= Resources::Restriction.new(@transport, "/v1/allowlist_identifiers")
|
|
90
|
+
def blocklist = @blocklist ||= Resources::Restriction.new(@transport, "/v1/blocklist_identifiers")
|
|
91
|
+
def attack_protection = @attack_protection ||= Resources::AttackProtection.new(@transport)
|
|
92
|
+
def actor_tokens = @actor_tokens ||= Resources::ActorTokens.new(@transport)
|
|
93
|
+
def invitations = @invitations ||= Resources::Invitations.new(@transport)
|
|
94
|
+
def webhooks = @webhooks ||= Resources::Webhooks.new(@transport)
|
|
95
|
+
def sign_in_tokens = @sign_in_tokens ||= Resources::SignInTokens.new(@transport)
|
|
96
|
+
def audit_logs = @audit_logs ||= Resources::AuditLogs.new(@transport)
|
|
97
|
+
def jwt_templates = @jwt_templates ||= Resources::JwtTemplates.new(@transport)
|
|
98
|
+
def api_keys = @api_keys ||= Resources::ApiKeys.new(@transport)
|
|
99
|
+
def oauth_providers = @oauth_providers ||= Resources::OAuthProviders.new(@transport)
|
|
100
|
+
def sso_onboarding = @sso_onboarding ||= Resources::SsoOnboarding.new(@transport)
|
|
101
|
+
def scim_provisioning = @scim_provisioning ||= Resources::ScimProvisioning.new(@transport)
|
|
102
|
+
def fga = @fga ||= Resources::Fga.new(@transport)
|
|
103
|
+
def rate_limit_policy = @rate_limit_policy ||= Resources::RateLimitPolicy.new(@transport)
|
|
104
|
+
def risk_based_mfa = @risk_based_mfa ||= Resources::RiskBasedMfa.new(@transport)
|
|
105
|
+
def bot_signals = @bot_signals ||= Resources::BotSignals.new(@transport)
|
|
106
|
+
def network_acls = @network_acls ||= Resources::NetworkAcls.new(@transport)
|
|
107
|
+
def managed_waf = @managed_waf ||= Resources::ManagedWaf.new(@transport)
|
|
108
|
+
def log_streams = @log_streams ||= Resources::LogStreams.new(@transport)
|
|
109
|
+
def branding = @branding ||= Resources::Branding.new(@transport)
|
|
110
|
+
def email_templates = @email_templates ||= Resources::EmailTemplates.new(@transport)
|
|
111
|
+
def sms_templates = @sms_templates ||= Resources::SmsTemplates.new(@transport)
|
|
112
|
+
def localizations = @localizations ||= Resources::Localizations.new(@transport)
|
|
113
|
+
def actions = @actions ||= Resources::Actions.new(@transport)
|
|
114
|
+
def billing = @billing ||= Resources::Billing.new(@transport)
|
|
115
|
+
def messaging = @messaging ||= Resources::Messaging.new(@transport)
|
|
116
|
+
def import_export = @import_export ||= Resources::ImportExport.new(@transport)
|
|
117
|
+
def data_subject_requests = @data_subject_requests ||= Resources::DataSubjectRequests.new(@transport)
|
|
118
|
+
def radius_clients = @radius_clients ||= Resources::RadiusClients.new(@transport)
|
|
119
|
+
def lti_platforms = @lti_platforms ||= Resources::LtiPlatforms.new(@transport)
|
|
120
|
+
def instance = @instance ||= Resources::Instance.new(@transport)
|
|
121
|
+
def instance_security = @instance_security ||= Resources::InstanceSecurity.new(@transport)
|
|
122
|
+
def tokens = @tokens ||= Resources::Tokens.new(@transport)
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/atlas/error.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atlas
|
|
4
|
+
# Base class for every error the SDK raises. Rescue +Atlas::Error+ to catch
|
|
5
|
+
# anything the gem can throw.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Raised before a request goes out when the client is misconfigured
|
|
9
|
+
# (e.g. a missing secret key).
|
|
10
|
+
class ConfigurationError < Error; end
|
|
11
|
+
|
|
12
|
+
# Raised on any non-2xx response from the Backend API.
|
|
13
|
+
#
|
|
14
|
+
# Atlas answers a failed BAPI call with the §9.1 envelope:
|
|
15
|
+
#
|
|
16
|
+
# { "errors": [ { "code", "message", "param?", "meta?" } ] }
|
|
17
|
+
#
|
|
18
|
+
# +code+ is the stable, machine-readable part of that contract — integrators
|
|
19
|
+
# branch on it (+LAST_ADMIN+, +NOT_FOUND+, +SCOPE_MISSING+, ...) — so it is
|
|
20
|
+
# surfaced first-class here rather than buried in a parsed body. The raw
|
|
21
|
+
# +errors+ array and the HTTP +status+ are both kept so a caller can inspect
|
|
22
|
+
# +param+/+meta+ (e.g. a rate limit's +retry_after+) when they need to.
|
|
23
|
+
class APIError < Error
|
|
24
|
+
# @return [Integer] HTTP status of the failed response.
|
|
25
|
+
attr_reader :status
|
|
26
|
+
|
|
27
|
+
# @return [Array<Hash>] the full §9.1 error envelope, in order. Each item
|
|
28
|
+
# carries string keys "code", "message", and optionally "param"/"meta".
|
|
29
|
+
attr_reader :errors
|
|
30
|
+
|
|
31
|
+
def initialize(status, errors, message = nil)
|
|
32
|
+
@status = status
|
|
33
|
+
@errors = errors || []
|
|
34
|
+
first = @errors.first
|
|
35
|
+
super(message || (first && (first["message"] || first[:message])) ||
|
|
36
|
+
"Atlas API request failed with status #{status}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The first error's stable code — the field callers branch on most.
|
|
40
|
+
# @return [String, nil]
|
|
41
|
+
def code
|
|
42
|
+
first = @errors.first
|
|
43
|
+
first && (first["code"] || first[:code])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# True when any error in the envelope carries the given stable code.
|
|
47
|
+
def has_code?(code)
|
|
48
|
+
@errors.any? { |e| (e["code"] || e[:code]) == code }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A short human summary for logs. Never includes the secret key.
|
|
52
|
+
def to_s
|
|
53
|
+
"#{self.class.name}: status=#{@status} code=#{code.inspect} #{super}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 400 — the request was malformed or a parameter was invalid.
|
|
58
|
+
class BadRequestError < APIError; end
|
|
59
|
+
# 401 — the secret key is missing, wrong, or revoked.
|
|
60
|
+
class AuthenticationError < APIError; end
|
|
61
|
+
# 403 — authenticated but not permitted (e.g. scope missing).
|
|
62
|
+
class PermissionError < APIError; end
|
|
63
|
+
# 404 — the resource does not exist for this instance.
|
|
64
|
+
class NotFoundError < APIError; end
|
|
65
|
+
# 409 — a conflict/terminal-state error (e.g. re-actioning a DSAR).
|
|
66
|
+
class ConflictError < APIError; end
|
|
67
|
+
# 422 — the request was well-formed but semantically rejected.
|
|
68
|
+
class UnprocessableEntityError < APIError; end
|
|
69
|
+
# 429 — rate limited. Inspect +errors.first["meta"]["retry_after"]+.
|
|
70
|
+
class RateLimitError < APIError; end
|
|
71
|
+
# 5xx — an Atlas-side failure.
|
|
72
|
+
class ServerError < APIError; end
|
|
73
|
+
|
|
74
|
+
module ErrorFactory
|
|
75
|
+
# Map an HTTP status onto the most specific error class.
|
|
76
|
+
STATUS_MAP = {
|
|
77
|
+
400 => BadRequestError,
|
|
78
|
+
401 => AuthenticationError,
|
|
79
|
+
403 => PermissionError,
|
|
80
|
+
404 => NotFoundError,
|
|
81
|
+
409 => ConflictError,
|
|
82
|
+
422 => UnprocessableEntityError,
|
|
83
|
+
429 => RateLimitError
|
|
84
|
+
}.freeze
|
|
85
|
+
|
|
86
|
+
# Build the right typed error from a status + parsed error items.
|
|
87
|
+
def self.build(status, errors)
|
|
88
|
+
klass = STATUS_MAP[status] || (status >= 500 ? ServerError : APIError)
|
|
89
|
+
klass.new(status, errors)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|