castle-rb 7.1.2 → 8.0.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/README.md +14 -159
- data/lib/castle/api/authenticate.rb +3 -12
- data/lib/castle/api/end_impersonation.rb +1 -3
- data/lib/castle/api/filter.rb +2 -10
- data/lib/castle/api/log.rb +2 -10
- data/lib/castle/api/risk.rb +2 -10
- data/lib/castle/api/start_impersonation.rb +1 -3
- data/lib/castle/api/track.rb +1 -3
- data/lib/castle/client.rb +1 -5
- data/lib/castle/commands/log.rb +1 -5
- data/lib/castle/commands/risk.rb +1 -5
- data/lib/castle/configuration.rb +2 -6
- data/lib/castle/core/process_response.rb +25 -2
- data/lib/castle/core/process_webhook.rb +4 -7
- data/lib/castle/core/send_request.rb +1 -3
- data/lib/castle/errors.rb +8 -0
- data/lib/castle/headers/extract.rb +1 -3
- data/lib/castle/headers/filter.rb +2 -3
- data/lib/castle/payload/prepare.rb +1 -2
- data/lib/castle/session.rb +1 -1
- data/lib/castle/version.rb +1 -1
- data/lib/castle.rb +1 -3
- data/spec/integration/rails/rails_spec.rb +9 -3
- data/spec/integration/rails/support/application.rb +2 -2
- data/spec/integration/rails/support/home_controller.rb +4 -30
- data/spec/lib/castle/api/approve_device_spec.rb +2 -6
- data/spec/lib/castle/api/authenticate_spec.rb +22 -25
- data/spec/lib/castle/api/end_impersonation_spec.rb +8 -14
- data/spec/lib/castle/api/get_device_spec.rb +1 -3
- data/spec/lib/castle/api/get_devices_for_user_spec.rb +1 -3
- data/spec/lib/castle/api/report_device_spec.rb +2 -6
- data/spec/lib/castle/api/start_impersonation_spec.rb +8 -14
- data/spec/lib/castle/api/track_spec.rb +9 -16
- data/spec/lib/castle/client_id/extract_spec.rb +2 -9
- data/spec/lib/castle/client_spec.rb +37 -78
- data/spec/lib/castle/command_spec.rb +3 -3
- data/spec/lib/castle/commands/approve_device_spec.rb +2 -2
- data/spec/lib/castle/commands/authenticate_spec.rb +17 -24
- data/spec/lib/castle/commands/end_impersonation_spec.rb +14 -21
- data/spec/lib/castle/commands/filter_spec.rb +15 -15
- data/spec/lib/castle/commands/get_device_spec.rb +2 -2
- data/spec/lib/castle/commands/get_devices_for_user_spec.rb +2 -2
- data/spec/lib/castle/commands/log_spec.rb +15 -15
- data/spec/lib/castle/commands/report_device_spec.rb +2 -2
- data/spec/lib/castle/commands/risk_spec.rb +15 -15
- data/spec/lib/castle/commands/start_impersonation_spec.rb +14 -21
- data/spec/lib/castle/commands/track_spec.rb +19 -24
- data/spec/lib/castle/configuration_spec.rb +1 -1
- data/spec/lib/castle/context/get_default_spec.rb +9 -8
- data/spec/lib/castle/context/prepare_spec.rb +3 -4
- data/spec/lib/castle/core/process_response_spec.rb +3 -6
- data/spec/lib/castle/core/process_webhook_spec.rb +12 -6
- data/spec/lib/castle/core/send_request_spec.rb +7 -11
- data/spec/lib/castle/failover/strategy_spec.rb +5 -5
- data/spec/lib/castle/headers/extract_spec.rb +1 -1
- data/spec/lib/castle/headers/filter_spec.rb +6 -3
- data/spec/lib/castle/headers/format_spec.rb +5 -5
- data/spec/lib/castle/ips/extract_spec.rb +2 -6
- data/spec/lib/castle/logger_spec.rb +2 -1
- data/spec/lib/castle/payload/prepare_spec.rb +4 -7
- data/spec/lib/castle/secure_mode_spec.rb +1 -3
- data/spec/lib/castle/session_spec.rb +1 -5
- data/spec/lib/castle/singleton_configuration_spec.rb +1 -1
- data/spec/lib/castle/utils/clone_spec.rb +1 -1
- data/spec/lib/castle/utils/merge_spec.rb +2 -4
- data/spec/lib/castle/validators/not_supported_spec.rb +1 -6
- data/spec/lib/castle/validators/present_spec.rb +2 -9
- data/spec/lib/castle/verdict_spec.rb +3 -3
- data/spec/lib/castle/webhooks/verify_spec.rb +12 -6
- data/spec/lib/castle_spec.rb +5 -7
- data/spec/support/shared_examples/action_request.rb +37 -25
- data/spec/support/shared_examples/configuration.rb +14 -16
- metadata +48 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9dc06eac5803e36c8576d9938442ba1c1f6bb852420c7a736044127deea50b8
|
4
|
+
data.tar.gz: 5d89738c670359a0345d51b41d0137d614622002711f20665238a4c289f28bab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 118236690b20da223208e043cb4aafea5c1d9870e0d2e44d87106a45122652ca894f8bfbc79bf465e474923a026b9b2253a6c7a1ab2c2f5157b0b21d51459d6c
|
7
|
+
data.tar.gz: 223933e1de62111635ba5d22fed064541a1d54aee9c9374fc8524a808b27b623245ee0d5240a42d0eff4ac72e214cc4f61ce44bb72f15591d3944790d8bc3e0d
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://coveralls.io/github/castle/castle-ruby?branch=coveralls)
|
5
5
|
[](https://badge.fury.io/rb/castle-rb)
|
6
6
|
|
7
|
-
**[Castle](https://castle.io) analyzes
|
7
|
+
**[Castle](https://castle.io) analyzes user behavior in web and mobile apps to stop fraud before it happens.**
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -26,11 +26,11 @@ Castle.api_secret = 'YOUR_API_SECRET'
|
|
26
26
|
|
27
27
|
A Castle client instance will be made available as `castle` in your
|
28
28
|
|
29
|
-
|
29
|
+
- Rails controllers when you add `require 'castle/support/rails'`
|
30
30
|
|
31
|
-
|
31
|
+
- Padrino controllers when you add `require 'castle/support/padrino'`
|
32
32
|
|
33
|
-
|
33
|
+
- Sinatra app when you add `require 'castle/support/sinatra'` (and additionally explicitly add `register Sinatra::Castle` to your `Sinatra::Base` class if you have a modular application)
|
34
34
|
|
35
35
|
```ruby
|
36
36
|
require 'castle/support/sinatra'
|
@@ -40,7 +40,7 @@ class ApplicationController < Sinatra::Base
|
|
40
40
|
end
|
41
41
|
```
|
42
42
|
|
43
|
-
|
43
|
+
- Hanami when you add `require 'castle/support/hanami'` and include `Castle::Hanami` to your Hanami application
|
44
44
|
|
45
45
|
```ruby
|
46
46
|
require 'castle/support/hanami'
|
@@ -119,8 +119,10 @@ Castle.configure do |config|
|
|
119
119
|
# you can achieve this by listing all the proxies ip defined by string or regular expressions
|
120
120
|
# in the trusted_proxies setting
|
121
121
|
config.trusted_proxies = []
|
122
|
+
|
122
123
|
# or by providing number of trusted proxies used in the chain
|
123
124
|
config.trusted_proxy_depth = 0
|
125
|
+
|
124
126
|
# note that you must pick one approach over the other.
|
125
127
|
|
126
128
|
# If there is no possibility to define options above and there is no other header that holds the client IP,
|
@@ -138,10 +140,11 @@ It is also possible to define multiple configs within one application.
|
|
138
140
|
|
139
141
|
```ruby
|
140
142
|
# Initialize new instance of Castle::Configuration
|
141
|
-
config =
|
142
|
-
|
143
|
-
|
144
|
-
|
143
|
+
config =
|
144
|
+
Castle::Configuration.new.tap do |c|
|
145
|
+
# and set any attribute
|
146
|
+
c.api_secret = 'YOUR_API_SECRET'
|
147
|
+
end
|
145
148
|
```
|
146
149
|
|
147
150
|
After a successful setup, you can pass the config to any API command as follows:
|
@@ -150,159 +153,11 @@ After a successful setup, you can pass the config to any API command as follows:
|
|
150
153
|
::Castle::API::GetDevice.call(device_token: device_token, config: config)
|
151
154
|
```
|
152
155
|
|
153
|
-
##
|
154
|
-
|
155
|
-
The client will automatically configure the context for each request.
|
156
|
-
|
157
|
-
### Overriding Default Context Properties
|
158
|
-
|
159
|
-
If you need to modify the event context properties or if you desire to add additional properties such as user traits to the context, you can pass the properties along with the other data. For example:
|
160
|
-
```ruby
|
161
|
-
{
|
162
|
-
event: '$login.succeeded',
|
163
|
-
user_id: user.id,
|
164
|
-
properties: {
|
165
|
-
key: 'value'
|
166
|
-
},
|
167
|
-
user_traits: {
|
168
|
-
key: 'value'
|
169
|
-
},
|
170
|
-
context: {
|
171
|
-
section: 'mobile'
|
172
|
-
}
|
173
|
-
}
|
174
|
-
```
|
175
|
-
|
176
|
-
## Tracking
|
177
|
-
|
178
|
-
Here is a simple example of a track event.
|
179
|
-
|
180
|
-
```ruby
|
181
|
-
begin
|
182
|
-
castle.track(
|
183
|
-
event: '$login.succeeded',
|
184
|
-
user_id: user.id
|
185
|
-
)
|
186
|
-
rescue Castle::Error => e
|
187
|
-
puts e.message
|
188
|
-
end
|
189
|
-
```
|
190
|
-
|
191
|
-
## Signature
|
156
|
+
## Usage
|
192
157
|
|
193
|
-
|
194
|
-
|
195
|
-
## Async tracking
|
196
|
-
|
197
|
-
By default Castle sends requests synchronously. To eg. use Sidekiq to send requests in a background worker you can pass data to the worker:
|
198
|
-
|
199
|
-
#### castle_tracking_worker.rb
|
200
|
-
|
201
|
-
```ruby
|
202
|
-
class CastleTrackingWorker
|
203
|
-
include Sidekiq::Worker
|
204
|
-
|
205
|
-
def perform(payload = {})
|
206
|
-
::Castle::API::Track.call(payload)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
```
|
210
|
-
|
211
|
-
#### tracking_controller.rb
|
212
|
-
|
213
|
-
```ruby
|
214
|
-
payload = ::Castle::Payload::Prepare.call(
|
215
|
-
{
|
216
|
-
event: '$login.succeeded',
|
217
|
-
user_id: user.id,
|
218
|
-
properties: {
|
219
|
-
key: 'value'
|
220
|
-
},
|
221
|
-
user_traits: {
|
222
|
-
key: 'value'
|
223
|
-
}
|
224
|
-
},
|
225
|
-
request
|
226
|
-
)
|
227
|
-
CastleTrackingWorker.perform_async(payload)
|
228
|
-
```
|
229
|
-
|
230
|
-
## Connection reuse
|
231
|
-
|
232
|
-
If you want to reuse the connection to send multiple events:
|
233
|
-
|
234
|
-
```ruby
|
235
|
-
Castle::Session.call do |http|
|
236
|
-
castle.track(
|
237
|
-
event: '$logout.succeeded',
|
238
|
-
user_id: user2.id
|
239
|
-
http: http
|
240
|
-
)
|
241
|
-
castle.track(
|
242
|
-
event: '$login.succeeded',
|
243
|
-
user_id: user1.id
|
244
|
-
http: http
|
245
|
-
)
|
246
|
-
end
|
247
|
-
```
|
248
|
-
|
249
|
-
## Events
|
250
|
-
|
251
|
-
List of Recognized Events can be found in the [docs](https://docs.castle.io/v1/reference/events/)
|
252
|
-
|
253
|
-
## Device management
|
254
|
-
|
255
|
-
This SDK allows issuing requests to [Castle's Device Management Endpoints](https://docs.castle.io/v1/reference/api-reference/#devices). Use these endpoints for admin-level management of end-user devices (i.e., for an internal dashboard).
|
256
|
-
|
257
|
-
Fetching device data, approving a device, reporting a device requires a valid `device_token`.
|
258
|
-
|
259
|
-
```ruby
|
260
|
-
# Get device data
|
261
|
-
::Castle::API::GetDevice.call(device_token: device_token)
|
262
|
-
# Approve a device
|
263
|
-
::Castle::API::ApproveDevice.call(device_token: device_token)
|
264
|
-
# Report a device
|
265
|
-
::Castle::API::ReportDevice.call(device_token: device_token)
|
266
|
-
```
|
267
|
-
|
268
|
-
#### castle_device_reporting_worker.rb
|
269
|
-
|
270
|
-
```ruby
|
271
|
-
class CastleDeviceReportingWorker
|
272
|
-
include Sidekiq::Worker
|
273
|
-
|
274
|
-
def perform(device_token)
|
275
|
-
::Castle::API::ReportDevice.call(device_token: device_token)
|
276
|
-
end
|
277
|
-
end
|
278
|
-
```
|
279
|
-
|
280
|
-
Fetching available devices that belong to a given user requires a valid `user_id`.
|
281
|
-
|
282
|
-
```ruby
|
283
|
-
# Get user's devices data
|
284
|
-
::Castle::API::GetDevicesForUser.call(user_id: user.id)
|
285
|
-
```
|
286
|
-
|
287
|
-
## Impersonation mode
|
288
|
-
|
289
|
-
https://castle.io/docs/impersonation_mode
|
158
|
+
See [documentation](https://docs.castle.io/docs/) for how to use this SDK with the Castle APIs
|
290
159
|
|
291
160
|
## Exceptions
|
292
161
|
|
293
162
|
`Castle::Error` will be thrown if the Castle API returns a 400 or a 500 level HTTP response.
|
294
163
|
You can also choose to catch a more [finegrained error](https://github.com/castle/castle-ruby/blob/master/lib/castle/errors.rb).
|
295
|
-
|
296
|
-
## Webhooks
|
297
|
-
|
298
|
-
Castle uses webhooks to notify about `$incident.confirmed` or `$review.opened` events. Each webhook has `X-Castle-Signature` header that allows verifying webhook's source.
|
299
|
-
|
300
|
-
```ruby
|
301
|
-
# Verify the webhook, passed as a Request object
|
302
|
-
::Castle::Webhooks::Verify.call(webhook_request)
|
303
|
-
# Castle::WebhookVerificationError is raised when the signature is not matching
|
304
|
-
```
|
305
|
-
|
306
|
-
## Documentation
|
307
|
-
|
308
|
-
[Official Castle docs](https://docs.castle.io/)
|
@@ -7,26 +7,17 @@ module Castle
|
|
7
7
|
# @param options [Hash]
|
8
8
|
# return [Hash]
|
9
9
|
def call(options = {})
|
10
|
-
unless options[:no_symbolize]
|
11
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
12
|
-
end
|
10
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
13
11
|
options.delete(:no_symbolize)
|
14
12
|
http = options.delete(:http)
|
15
13
|
config = options.delete(:config) || Castle.config
|
16
14
|
|
17
|
-
response =
|
18
|
-
Castle::API.call(Castle::Commands::Authenticate.build(options), {}, http, config)
|
15
|
+
response = Castle::API.call(Castle::Commands::Authenticate.build(options), {}, http, config)
|
19
16
|
response.merge(failover: false, failover_reason: nil)
|
20
17
|
rescue Castle::RequestError, Castle::InternalServerError => e
|
21
18
|
unless config.failover_strategy == :throw
|
22
19
|
strategy = (config || Castle.config).failover_strategy
|
23
|
-
return(
|
24
|
-
Castle::Failover::PrepareResponse.new(
|
25
|
-
options[:user_id],
|
26
|
-
reason: e.to_s,
|
27
|
-
strategy: strategy
|
28
|
-
).call
|
29
|
-
)
|
20
|
+
return(Castle::Failover::PrepareResponse.new(options[:user_id], reason: e.to_s, strategy: strategy).call)
|
30
21
|
end
|
31
22
|
|
32
23
|
raise e
|
@@ -7,9 +7,7 @@ module Castle
|
|
7
7
|
class << self
|
8
8
|
# @param options [Hash]
|
9
9
|
def call(options = {})
|
10
|
-
unless options[:no_symbolize]
|
11
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
12
|
-
end
|
10
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
13
11
|
options.delete(:no_symbolize)
|
14
12
|
http = options.delete(:http)
|
15
13
|
config = options.delete(:config) || Castle.config
|
data/lib/castle/api/filter.rb
CHANGED
@@ -8,9 +8,7 @@ module Castle
|
|
8
8
|
# @param options [Hash]
|
9
9
|
# return [Hash]
|
10
10
|
def call(options = {})
|
11
|
-
unless options[:no_symbolize]
|
12
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
13
|
-
end
|
11
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
14
12
|
options.delete(:no_symbolize)
|
15
13
|
http = options.delete(:http)
|
16
14
|
config = options.delete(:config) || Castle.config
|
@@ -20,13 +18,7 @@ module Castle
|
|
20
18
|
rescue Castle::RequestError, Castle::InternalServerError => e
|
21
19
|
unless config.failover_strategy == :throw
|
22
20
|
strategy = (config || Castle.config).failover_strategy
|
23
|
-
return(
|
24
|
-
Castle::Failover::PrepareResponse.new(
|
25
|
-
options[:user][:id],
|
26
|
-
reason: e.to_s,
|
27
|
-
strategy: strategy
|
28
|
-
).call
|
29
|
-
)
|
21
|
+
return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
|
30
22
|
end
|
31
23
|
|
32
24
|
raise e
|
data/lib/castle/api/log.rb
CHANGED
@@ -8,9 +8,7 @@ module Castle
|
|
8
8
|
# @param options [Hash]
|
9
9
|
# return [Hash]
|
10
10
|
def call(options = {})
|
11
|
-
unless options[:no_symbolize]
|
12
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
13
|
-
end
|
11
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
14
12
|
options.delete(:no_symbolize)
|
15
13
|
http = options.delete(:http)
|
16
14
|
config = options.delete(:config) || Castle.config
|
@@ -20,13 +18,7 @@ module Castle
|
|
20
18
|
rescue Castle::RequestError, Castle::InternalServerError => e
|
21
19
|
unless config.failover_strategy == :throw
|
22
20
|
strategy = (config || Castle.config).failover_strategy
|
23
|
-
return(
|
24
|
-
Castle::Failover::PrepareResponse.new(
|
25
|
-
options[:user][:id],
|
26
|
-
reason: e.to_s,
|
27
|
-
strategy: strategy
|
28
|
-
).call
|
29
|
-
)
|
21
|
+
return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
|
30
22
|
end
|
31
23
|
|
32
24
|
raise e
|
data/lib/castle/api/risk.rb
CHANGED
@@ -8,9 +8,7 @@ module Castle
|
|
8
8
|
# @param options [Hash]
|
9
9
|
# return [Hash]
|
10
10
|
def call(options = {})
|
11
|
-
unless options[:no_symbolize]
|
12
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
13
|
-
end
|
11
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
14
12
|
options.delete(:no_symbolize)
|
15
13
|
http = options.delete(:http)
|
16
14
|
config = options.delete(:config) || Castle.config
|
@@ -20,13 +18,7 @@ module Castle
|
|
20
18
|
rescue Castle::RequestError, Castle::InternalServerError => e
|
21
19
|
unless config.failover_strategy == :throw
|
22
20
|
strategy = (config || Castle.config).failover_strategy
|
23
|
-
return(
|
24
|
-
Castle::Failover::PrepareResponse.new(
|
25
|
-
options[:user][:id],
|
26
|
-
reason: e.to_s,
|
27
|
-
strategy: strategy
|
28
|
-
).call
|
29
|
-
)
|
21
|
+
return(Castle::Failover::PrepareResponse.new(options[:user][:id], reason: e.to_s, strategy: strategy).call)
|
30
22
|
end
|
31
23
|
|
32
24
|
raise e
|
@@ -7,9 +7,7 @@ module Castle
|
|
7
7
|
class << self
|
8
8
|
# @param options [Hash]
|
9
9
|
def call(options = {})
|
10
|
-
unless options[:no_symbolize]
|
11
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
12
|
-
end
|
10
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
13
11
|
options.delete(:no_symbolize)
|
14
12
|
http = options.delete(:http)
|
15
13
|
config = options.delete(:config) || Castle.config
|
data/lib/castle/api/track.rb
CHANGED
@@ -6,9 +6,7 @@ module Castle
|
|
6
6
|
class << self
|
7
7
|
# @param options [Hash]
|
8
8
|
def call(options = {})
|
9
|
-
unless options[:no_symbolize]
|
10
|
-
options = Castle::Utils::DeepSymbolizeKeys.call(options || {})
|
11
|
-
end
|
9
|
+
options = Castle::Utils::DeepSymbolizeKeys.call(options || {}) unless options[:no_symbolize]
|
12
10
|
options.delete(:no_symbolize)
|
13
11
|
http = options.delete(:http)
|
14
12
|
config = options.delete(:config) || Castle.config
|
data/lib/castle/client.rb
CHANGED
@@ -123,11 +123,7 @@ module Castle
|
|
123
123
|
|
124
124
|
# @param user_id [String, Boolean]
|
125
125
|
def generate_do_not_track_response(user_id)
|
126
|
-
Castle::Failover::PrepareResponse.new(
|
127
|
-
user_id,
|
128
|
-
strategy: :allow,
|
129
|
-
reason: 'Castle is set to do not track.'
|
130
|
-
).call
|
126
|
+
Castle::Failover::PrepareResponse.new(user_id, strategy: :allow, reason: 'Castle is set to do not track.').call
|
131
127
|
end
|
132
128
|
|
133
129
|
# @param options [Hash]
|
data/lib/castle/commands/log.rb
CHANGED
@@ -10,11 +10,7 @@ module Castle
|
|
10
10
|
def build(options = {})
|
11
11
|
context = Castle::Context::Sanitize.call(options[:context])
|
12
12
|
|
13
|
-
Castle::Command.new(
|
14
|
-
'log',
|
15
|
-
options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
|
16
|
-
:post
|
17
|
-
)
|
13
|
+
Castle::Command.new('log', options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call), :post)
|
18
14
|
end
|
19
15
|
end
|
20
16
|
end
|
data/lib/castle/commands/risk.rb
CHANGED
@@ -10,11 +10,7 @@ module Castle
|
|
10
10
|
def build(options = {})
|
11
11
|
context = Castle::Context::Sanitize.call(options[:context])
|
12
12
|
|
13
|
-
Castle::Command.new(
|
14
|
-
'risk',
|
15
|
-
options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call),
|
16
|
-
:post
|
17
|
-
)
|
13
|
+
Castle::Command.new('risk', options.merge(context: context, sent_at: Castle::Utils::GetTimestamp.call), :post)
|
18
14
|
end
|
19
15
|
end
|
20
16
|
end
|
data/lib/castle/configuration.rb
CHANGED
@@ -45,7 +45,6 @@ module Castle
|
|
45
45
|
Te
|
46
46
|
Upgrade-Insecure-Requests
|
47
47
|
User-Agent
|
48
|
-
X-Castle-Client-Id
|
49
48
|
X-Requested-With
|
50
49
|
].freeze
|
51
50
|
|
@@ -105,9 +104,7 @@ module Castle
|
|
105
104
|
# sets trusted proxies
|
106
105
|
# @param value [Array<String,Regexp>]
|
107
106
|
def trusted_proxies=(value)
|
108
|
-
unless value.is_a?(Array)
|
109
|
-
raise Castle::ConfigurationError, 'trusted proxies must be an Array'
|
110
|
-
end
|
107
|
+
raise Castle::ConfigurationError, 'trusted proxies must be an Array' unless value.is_a?(Array)
|
111
108
|
|
112
109
|
@trusted_proxies = value
|
113
110
|
end
|
@@ -122,8 +119,7 @@ module Castle
|
|
122
119
|
end
|
123
120
|
|
124
121
|
def failover_strategy=(value)
|
125
|
-
@failover_strategy =
|
126
|
-
Castle::Failover::STRATEGIES.detect { |strategy| strategy == value.to_sym }
|
122
|
+
@failover_strategy = Castle::Failover::STRATEGIES.detect { |strategy| strategy == value.to_sym }
|
127
123
|
raise Castle::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
|
128
124
|
end
|
129
125
|
|
@@ -10,9 +10,11 @@ module Castle
|
|
10
10
|
403 => Castle::ForbiddenError,
|
11
11
|
404 => Castle::NotFoundError,
|
12
12
|
419 => Castle::UserUnauthorizedError,
|
13
|
-
|
13
|
+
429 => Castle::RateLimitError
|
14
14
|
}.freeze
|
15
15
|
|
16
|
+
INVALID_REQUEST_TOKEN = 'invalid_request_token'
|
17
|
+
|
16
18
|
class << self
|
17
19
|
# @param response [Response]
|
18
20
|
# @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
|
@@ -36,8 +38,29 @@ module Castle
|
|
36
38
|
|
37
39
|
raise Castle::InternalServerError if response.code.to_i.between?(500, 599)
|
38
40
|
|
41
|
+
raise_error422(response) if response.code.to_i == 422
|
42
|
+
|
39
43
|
error = RESPONSE_ERRORS.fetch(response.code.to_i, Castle::ApiError)
|
40
|
-
|
44
|
+
|
45
|
+
raise error
|
46
|
+
end
|
47
|
+
|
48
|
+
def raise_error422(response)
|
49
|
+
if response.body
|
50
|
+
begin
|
51
|
+
parsed_body = JSON.parse(response.body, symbolize_names: true)
|
52
|
+
if parsed_body.is_a?(Hash) && parsed_body.key?(:type)
|
53
|
+
if parsed_body[:type] == INVALID_REQUEST_TOKEN
|
54
|
+
raise Castle::InvalidRequestTokenError, parsed_body[:message]
|
55
|
+
end
|
56
|
+
|
57
|
+
raise Castle::InvalidParametersError, parsed_body[:message]
|
58
|
+
end
|
59
|
+
rescue JSON::ParserError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
raise Castle::InvalidParametersError
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
@@ -10,14 +10,11 @@ module Castle
|
|
10
10
|
# @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
|
11
11
|
# @return [String]
|
12
12
|
def call(webhook, config = nil)
|
13
|
-
webhook
|
14
|
-
.
|
15
|
-
.read
|
16
|
-
.tap do |result|
|
17
|
-
raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
|
13
|
+
webhook.body.read.tap do |result|
|
14
|
+
raise Castle::ApiError, 'Invalid webhook from Castle API' if result.blank?
|
18
15
|
|
19
|
-
|
20
|
-
|
16
|
+
Castle::Logger.call('webhook:', result.to_s, config)
|
17
|
+
end
|
21
18
|
end
|
22
19
|
end
|
23
20
|
end
|
@@ -15,9 +15,7 @@ module Castle
|
|
15
15
|
# @param http [Net::HTTP]
|
16
16
|
# @param config [Castle::Configuration, Castle::SingletonConfiguration, nil]
|
17
17
|
def call(command, headers, http = nil, config = nil)
|
18
|
-
(http || Castle::Core::GetConnection.call).request(
|
19
|
-
build(command, headers.merge(DEFAULT_HEADERS), config)
|
20
|
-
)
|
18
|
+
(http || Castle::Core::GetConnection.call).request(build(command, headers.merge(DEFAULT_HEADERS), config))
|
21
19
|
end
|
22
20
|
|
23
21
|
# @param command [String]
|
data/lib/castle/errors.rb
CHANGED
@@ -53,10 +53,18 @@ module Castle
|
|
53
53
|
class InvalidParametersError < Castle::ApiError
|
54
54
|
end
|
55
55
|
|
56
|
+
# api error invalid param 422 (invalid token)
|
57
|
+
class InvalidRequestTokenError < Castle::ApiError
|
58
|
+
end
|
59
|
+
|
56
60
|
# api error unauthorized 401
|
57
61
|
class UnauthorizedError < Castle::ApiError
|
58
62
|
end
|
59
63
|
|
64
|
+
# api error too many requests 429
|
65
|
+
class RateLimitError < Castle::ApiError
|
66
|
+
end
|
67
|
+
|
60
68
|
# all internal server errors
|
61
69
|
class InternalServerError < Castle::ApiError
|
62
70
|
end
|
@@ -23,9 +23,7 @@ module Castle
|
|
23
23
|
# Serialize HTTP headers
|
24
24
|
# @return [Hash]
|
25
25
|
def call
|
26
|
-
@headers.each_with_object({})
|
27
|
-
acc[name] = header_value(name, value)
|
28
|
-
end
|
26
|
+
@headers.each_with_object({}) { |(name, value), acc| acc[name] = header_value(name, value) }
|
29
27
|
end
|
30
28
|
|
31
29
|
private
|
@@ -12,8 +12,7 @@ module Castle
|
|
12
12
|
HTTP(?:_|-).*|
|
13
13
|
CONTENT(?:_|-)LENGTH|
|
14
14
|
REMOTE(?:_|-)ADDR
|
15
|
-
$/xi
|
16
|
-
.freeze
|
15
|
+
$/xi.freeze
|
17
16
|
|
18
17
|
private_constant :VALUABLE_HEADERS
|
19
18
|
|
@@ -32,7 +31,7 @@ module Castle
|
|
32
31
|
next unless header_name.match(VALUABLE_HEADERS)
|
33
32
|
|
34
33
|
formatted_name = @header_format.call(header_name)
|
35
|
-
acc[formatted_name] = @request_env[header_name]
|
34
|
+
acc[formatted_name] = @request_env[header_name].to_s
|
36
35
|
end
|
37
36
|
end
|
38
37
|
end
|
@@ -12,8 +12,7 @@ module Castle
|
|
12
12
|
def call(payload_options, request, options = {})
|
13
13
|
context = Castle::Context::Prepare.call(request, payload_options.merge(options))
|
14
14
|
|
15
|
-
payload =
|
16
|
-
Castle::Utils::DeepSymbolizeKeys.call(payload_options || {}).merge(context: context)
|
15
|
+
payload = Castle::Utils::DeepSymbolizeKeys.call(payload_options || {}).merge(context: context)
|
17
16
|
payload[:timestamp] ||= Castle::Utils::GetTimestamp.call
|
18
17
|
|
19
18
|
warn '[DEPRECATION] use user_traits instead of traits key' if payload.key?(:traits)
|
data/lib/castle/session.rb
CHANGED
data/lib/castle/version.rb
CHANGED
data/lib/castle.rb
CHANGED
@@ -67,9 +67,7 @@
|
|
67
67
|
module Castle
|
68
68
|
class << self
|
69
69
|
def configure(config_hash = nil)
|
70
|
-
(config_hash || {}).each
|
71
|
-
config.send("#{config_name}=", config_value)
|
72
|
-
end
|
70
|
+
(config_hash || {}).each { |config_name, config_value| config.send(:"#{config_name}=", config_value) }
|
73
71
|
|
74
72
|
yield(config) if block_given?
|
75
73
|
end
|
@@ -24,11 +24,12 @@ RSpec.describe HomeController, type: :request do
|
|
24
24
|
'Accept' =>
|
25
25
|
'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
26
26
|
'Authorization' => true,
|
27
|
-
'Content-Length' => '0',
|
28
27
|
'Cookie' => true,
|
28
|
+
'Content-Length' => '0',
|
29
29
|
'Host' => 'www.example.com',
|
30
30
|
'X-Forwarded-For' => '5.5.5.5, 1.2.3.4',
|
31
|
-
'Remote-Addr' => '127.0.0.1'
|
31
|
+
'Remote-Addr' => '127.0.0.1',
|
32
|
+
'Version' => 'HTTP/1.0'
|
32
33
|
},
|
33
34
|
'ip' => '1.2.3.4',
|
34
35
|
'library' => {
|
@@ -40,7 +41,12 @@ RSpec.describe HomeController, type: :request do
|
|
40
41
|
end
|
41
42
|
let(:now) { Time.now }
|
42
43
|
let(:headers) do
|
43
|
-
{
|
44
|
+
{
|
45
|
+
'HTTP_AUTHORIZATION' => 'Basic 123',
|
46
|
+
'HTTP_X_FORWARDED_FOR' => '5.5.5.5, 1.2.3.4',
|
47
|
+
'HTTP_VERSION' => 'HTTP/1.0',
|
48
|
+
'HTTP_CONTENT_LENGTH' => '0'
|
49
|
+
}
|
44
50
|
end
|
45
51
|
|
46
52
|
before do
|
@@ -3,8 +3,8 @@
|
|
3
3
|
require 'action_controller/railtie'
|
4
4
|
|
5
5
|
class TestApp < Rails::Application
|
6
|
-
|
7
|
-
|
6
|
+
credentials.secret_token = 'secret_token'
|
7
|
+
credentials.secret_key_base = 'secret_key_base'
|
8
8
|
|
9
9
|
config.logger = Logger.new($stdout)
|
10
10
|
Rails.logger = config.logger
|