castle-rb 2.3.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +55 -9
- data/lib/castle.rb +17 -7
- data/lib/castle/api.rb +20 -22
- data/lib/castle/client.rb +50 -19
- data/lib/castle/command.rb +5 -0
- data/lib/castle/commands/authenticate.rb +25 -0
- data/lib/castle/commands/identify.rb +30 -0
- data/lib/castle/commands/review.rb +13 -0
- data/lib/castle/commands/track.rb +25 -0
- data/lib/castle/commands/with_context.rb +28 -0
- data/lib/castle/configuration.rb +46 -14
- data/lib/castle/context_merger.rb +13 -0
- data/lib/castle/default_context.rb +28 -0
- data/lib/castle/errors.rb +2 -0
- data/lib/castle/extractors/client_id.rb +4 -14
- data/lib/castle/extractors/headers.rb +6 -18
- data/lib/castle/failover_auth_response.rb +21 -0
- data/lib/castle/header_formatter.rb +9 -0
- data/lib/castle/request.rb +7 -13
- data/lib/castle/response.rb +2 -0
- data/lib/castle/review.rb +11 -0
- data/lib/castle/secure_mode.rb +11 -0
- data/lib/castle/support/hanami.rb +19 -0
- data/lib/castle/support/padrino.rb +1 -1
- data/lib/castle/support/rails.rb +1 -1
- data/lib/castle/support/sinatra.rb +4 -2
- data/lib/castle/utils.rb +55 -0
- data/lib/castle/utils/cloner.rb +11 -0
- data/lib/castle/utils/merger.rb +23 -0
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/api_spec.rb +16 -25
- data/spec/lib/castle/client_spec.rb +175 -39
- data/spec/lib/castle/command_spec.rb +9 -0
- data/spec/lib/castle/commands/authenticate_spec.rb +106 -0
- data/spec/lib/castle/commands/identify_spec.rb +85 -0
- data/spec/lib/castle/commands/review_spec.rb +24 -0
- data/spec/lib/castle/commands/track_spec.rb +107 -0
- data/spec/lib/castle/configuration_spec.rb +75 -27
- data/spec/lib/castle/context_merger_spec.rb +34 -0
- data/spec/lib/castle/default_context_spec.rb +35 -0
- data/spec/lib/castle/extractors/client_id_spec.rb +13 -5
- data/spec/lib/castle/extractors/headers_spec.rb +6 -5
- data/spec/lib/castle/extractors/ip_spec.rb +2 -9
- data/spec/lib/castle/header_formatter_spec.rb +21 -0
- data/spec/lib/castle/request_spec.rb +12 -9
- data/spec/lib/castle/response_spec.rb +1 -3
- data/spec/lib/castle/review_spec.rb +23 -0
- data/spec/lib/castle/secure_mode_spec.rb +9 -0
- data/spec/lib/castle/utils/cloner_spec.rb +18 -0
- data/spec/lib/castle/utils/merger_spec.rb +13 -0
- data/spec/lib/castle/utils_spec.rb +156 -0
- data/spec/lib/castle/version_spec.rb +1 -5
- data/spec/lib/castle_spec.rb +8 -15
- data/spec/spec_helper.rb +3 -9
- metadata +46 -12
- data/lib/castle/cookie_store.rb +0 -52
- data/lib/castle/headers.rb +0 -39
- data/lib/castle/support.rb +0 -11
- data/lib/castle/system.rb +0 -36
- data/spec/lib/castle/headers_spec.rb +0 -82
- data/spec/lib/castle/system_spec.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 42c05b0f98dc4b62b7dd63aa69c0f78b3470c4e3a9432a454b1d7ca7918dbc17
|
4
|
+
data.tar.gz: a8761230e18ee47bb2337878774f637bea68ddd1d96bdc55827ec475d29a0026
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08a34db09b0d770ae486c520d71d5643c07aa436643679303f679bafbb7bf783222282db54dcfcbf36217a47401a19f73aaca49a3534e2919f5378f26fead199'
|
7
|
+
data.tar.gz: f502877e392ec738ff2425bb98ba4c8b9bdaa73a2ca9025dad7b2c51f7e20e4c50544f5275df10a9f83639fc8410feae33787bdd8b312b5d98017e40541f5fa9
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Ruby SDK for Castle
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/castle/castle-ruby.
|
4
|
-
[![
|
5
|
-
[![
|
3
|
+
[![Build Status](https://travis-ci.org/castle/castle-ruby.svg?branch=master)](https://travis-ci.org/castle/castle-ruby)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/castle/castle-ruby/badge.svg?branch=coveralls)](https://coveralls.io/github/castle/castle-ruby?branch=coveralls)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/castle-rb.svg)](https://badge.fury.io/rb/castle-rb)
|
6
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/castle/castle-ruby.svg)](https://gemnasium.com/github.com/castle/castle-ruby)
|
6
7
|
|
7
8
|
**[Castle](https://castle.io) adds real-time monitoring of your authentication stack, instantly notifying you and your users on potential account hijacks.**
|
8
9
|
|
@@ -20,7 +21,35 @@ Load and configure the library with your Castle API secret in an initializer or
|
|
20
21
|
Castle.api_secret = 'YOUR_API_SECRET'
|
21
22
|
```
|
22
23
|
|
23
|
-
A Castle client instance will be made available as `castle` in your
|
24
|
+
A Castle client instance will be made available as `castle` in your
|
25
|
+
|
26
|
+
* Rails controllers when you add `require 'castle/support/rails'`
|
27
|
+
|
28
|
+
* Padrino controllers when you add `require 'castle/support/padrino'`
|
29
|
+
|
30
|
+
* 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)
|
31
|
+
|
32
|
+
```
|
33
|
+
require 'castle/support/sinatra'
|
34
|
+
|
35
|
+
class ApplicationController < Sinatra::Base
|
36
|
+
register Sinatra::Castle
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
* Hanami when you add `require 'castle/support/hanami'` and include `Castle::Hanami` to your Hanami application
|
41
|
+
|
42
|
+
```
|
43
|
+
require 'castle/support/hanami'
|
44
|
+
|
45
|
+
module Web
|
46
|
+
class Application < Hanami::Application
|
47
|
+
include Castle::Hanami
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
The client will automatically configure the [request context](https://api.castle.io/docs#request-context) for each request.
|
24
53
|
|
25
54
|
## Documentation
|
26
55
|
|
@@ -34,7 +63,8 @@ A Castle client instance will be made available as `castle` in your Rails, Sinat
|
|
34
63
|
begin
|
35
64
|
castle.track(
|
36
65
|
name: '$login.succeeded',
|
37
|
-
user_id: user.id
|
66
|
+
user_id: user.id
|
67
|
+
)
|
38
68
|
rescue Castle::Error => e
|
39
69
|
puts e.message
|
40
70
|
end
|
@@ -47,10 +77,26 @@ Castle.configure do |config|
|
|
47
77
|
# Same as setting it through Castle.api_secret
|
48
78
|
config.api_secret = 'secret'
|
49
79
|
|
50
|
-
#
|
51
|
-
config.
|
80
|
+
# For authenticate method you can set failover strategies: allow(default), deny, challenge, throw
|
81
|
+
config.failover_strategy = :deny
|
52
82
|
|
53
|
-
#
|
54
|
-
config.
|
83
|
+
# Castle::RequestError is raised when timing out in seconds (default: 500 milliseconds)
|
84
|
+
config.request_timeout = 2000
|
85
|
+
|
86
|
+
# Whitelisted and Blacklisted headers are case insensitive and allow to use _ and - as a separator, http prefixes are removed
|
87
|
+
# Whitelisted headers
|
88
|
+
config.whitelisted = ['X_HEADER']
|
89
|
+
# or append to default
|
90
|
+
config.whitelisted += ['http-x-header']
|
91
|
+
|
92
|
+
# Blacklisted headers take advantage over whitelisted elements
|
93
|
+
config.blacklisted = ['HTTP-X-header']
|
94
|
+
# or append to default
|
95
|
+
config.blacklisted += ['X_HEADER']
|
55
96
|
end
|
56
97
|
```
|
98
|
+
|
99
|
+
|
100
|
+
## Signature
|
101
|
+
|
102
|
+
`Castle::SecureMode.signature(user_id)` will create a signed user_id.
|
data/lib/castle.rb
CHANGED
@@ -2,21 +2,33 @@
|
|
2
2
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'net/http'
|
5
|
+
require 'json'
|
5
6
|
|
6
7
|
require 'castle/version'
|
7
|
-
|
8
|
+
require 'castle/errors'
|
9
|
+
require 'castle/command'
|
10
|
+
require 'castle/utils'
|
11
|
+
require 'castle/utils/merger'
|
12
|
+
require 'castle/utils/cloner'
|
13
|
+
require 'castle/context_merger'
|
14
|
+
require 'castle/default_context'
|
15
|
+
require 'castle/commands/with_context'
|
16
|
+
require 'castle/commands/identify'
|
17
|
+
require 'castle/commands/authenticate'
|
18
|
+
require 'castle/commands/track'
|
19
|
+
require 'castle/commands/review'
|
8
20
|
require 'castle/configuration'
|
21
|
+
require 'castle/failover_auth_response'
|
9
22
|
require 'castle/client'
|
10
|
-
require 'castle/
|
11
|
-
require 'castle/
|
23
|
+
require 'castle/header_formatter'
|
24
|
+
require 'castle/secure_mode'
|
12
25
|
require 'castle/extractors/client_id'
|
13
26
|
require 'castle/extractors/headers'
|
14
27
|
require 'castle/extractors/ip'
|
15
|
-
require 'castle/headers'
|
16
28
|
require 'castle/response'
|
17
29
|
require 'castle/request'
|
30
|
+
require 'castle/review'
|
18
31
|
require 'castle/api'
|
19
|
-
require 'castle/cookie_store'
|
20
32
|
|
21
33
|
# main sdk module
|
22
34
|
module Castle
|
@@ -38,5 +50,3 @@ module Castle
|
|
38
50
|
end
|
39
51
|
end
|
40
52
|
end
|
41
|
-
|
42
|
-
require 'castle/support'
|
data/lib/castle/api.rb
CHANGED
@@ -3,32 +3,27 @@
|
|
3
3
|
module Castle
|
4
4
|
# this class is responsible for making requests to api
|
5
5
|
class API
|
6
|
-
def initialize(
|
6
|
+
def initialize(headers = {})
|
7
7
|
@config = Castle.config
|
8
|
-
@config_api_endpoint = @config.api_endpoint
|
9
8
|
@http = prepare_http
|
10
|
-
@headers =
|
9
|
+
@headers = headers.merge('Content-Type' => 'application/json')
|
11
10
|
end
|
12
11
|
|
13
|
-
def
|
14
|
-
request = Castle::Request.new(@headers).
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
request = Castle::Request.new(@headers).build(endpoint, args, method)
|
12
|
+
def request(command)
|
13
|
+
request = Castle::Request.new(@headers).build(
|
14
|
+
command.path,
|
15
|
+
command.data,
|
16
|
+
command.method
|
17
|
+
)
|
20
18
|
perform_request(request)
|
21
19
|
end
|
22
20
|
|
23
21
|
private
|
24
22
|
|
25
23
|
def prepare_http
|
26
|
-
http = Net::HTTP.new(
|
27
|
-
|
28
|
-
|
29
|
-
)
|
30
|
-
http.read_timeout = @config.request_timeout
|
31
|
-
prepare_http_for_ssl(http) if @config_api_endpoint.scheme == 'https'
|
24
|
+
http = Net::HTTP.new(@config.host, @config.port)
|
25
|
+
http.read_timeout = @config.request_timeout / 1000.0
|
26
|
+
prepare_http_for_ssl(http) if @config.port == 443
|
32
27
|
http
|
33
28
|
end
|
34
29
|
|
@@ -37,12 +32,15 @@ module Castle
|
|
37
32
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
38
33
|
end
|
39
34
|
|
40
|
-
def perform_request(
|
41
|
-
Castle::
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
35
|
+
def perform_request(request)
|
36
|
+
raise Castle::ConfigurationError, 'configuration is not valid' unless @config.valid?
|
37
|
+
begin
|
38
|
+
Castle::Response.new(@http.request(request)).parse
|
39
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
40
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
41
|
+
Net::ProtocolError
|
42
|
+
raise Castle::RequestError, 'Castle API connection error'
|
43
|
+
end
|
46
44
|
end
|
47
45
|
end
|
48
46
|
end
|
data/lib/castle/client.rb
CHANGED
@@ -4,40 +4,71 @@ module Castle
|
|
4
4
|
class Client
|
5
5
|
attr_accessor :api
|
6
6
|
|
7
|
-
def initialize(request,
|
8
|
-
@do_not_track =
|
9
|
-
|
10
|
-
|
11
|
-
headers = Extractors::Headers.new(request).call
|
12
|
-
@api = API.new(cookie_id, ip, headers)
|
7
|
+
def initialize(request, options = {})
|
8
|
+
@do_not_track = default_tracking(options)
|
9
|
+
@context = setup_context(request, options[:cookies], options[:context])
|
10
|
+
@api = API.new
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
16
|
-
|
17
|
-
end
|
13
|
+
def authenticate(options = {})
|
14
|
+
options = Castle::Utils.deep_symbolize_keys(options || {})
|
18
15
|
|
19
|
-
|
20
|
-
|
16
|
+
if tracked?
|
17
|
+
command = Castle::Commands::Authenticate.new(@context).build(options)
|
18
|
+
begin
|
19
|
+
@api.request(command).merge('failover' => false, 'failover_reason' => nil)
|
20
|
+
rescue Castle::RequestError, Castle::InternalServerError => error
|
21
|
+
failover_response_or_raise(FailoverAuthResponse.new(options[:user_id], reason: error.to_s), error)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
FailoverAuthResponse.new(options[:user_id], strategy: :allow, reason: 'Castle set to do not track.').generate
|
25
|
+
end
|
21
26
|
end
|
22
27
|
|
23
|
-
def
|
24
|
-
|
28
|
+
def identify(options = {})
|
29
|
+
options = Castle::Utils.deep_symbolize_keys(options || {})
|
30
|
+
|
31
|
+
return unless tracked?
|
32
|
+
|
33
|
+
command = Castle::Commands::Identify.new(@context).build(options)
|
34
|
+
@api.request(command)
|
25
35
|
end
|
26
36
|
|
27
|
-
def track(
|
28
|
-
|
37
|
+
def track(options = {})
|
38
|
+
options = Castle::Utils.deep_symbolize_keys(options || {})
|
39
|
+
|
40
|
+
return unless tracked?
|
41
|
+
|
42
|
+
command = Castle::Commands::Track.new(@context).build(options)
|
43
|
+
@api.request(command)
|
29
44
|
end
|
30
45
|
|
31
|
-
def
|
46
|
+
def disable_tracking
|
32
47
|
@do_not_track = true
|
33
48
|
end
|
34
49
|
|
35
|
-
def
|
50
|
+
def enable_tracking
|
36
51
|
@do_not_track = false
|
37
52
|
end
|
38
53
|
|
39
|
-
def
|
40
|
-
|
54
|
+
def tracked?
|
55
|
+
!@do_not_track
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def setup_context(request, cookies, additional_context)
|
61
|
+
default_context = Castle::DefaultContext.new(request, cookies).call
|
62
|
+
Castle::ContextMerger.new(default_context).call(additional_context || {})
|
63
|
+
end
|
64
|
+
|
65
|
+
def failover_response_or_raise(failover_response, error)
|
66
|
+
return failover_response.generate unless Castle.config.failover_strategy == :throw
|
67
|
+
raise error
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_tracking(options)
|
71
|
+
options.key?(:do_not_track) ? options[:do_not_track] : false
|
41
72
|
end
|
42
73
|
end
|
43
74
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
class Authenticate
|
6
|
+
include WithContext
|
7
|
+
|
8
|
+
def build(options = {})
|
9
|
+
validate!(options)
|
10
|
+
build_context!(options)
|
11
|
+
|
12
|
+
Castle::Command.new('authenticate', options, :post)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate!(options)
|
18
|
+
%i[event user_id].each do |key|
|
19
|
+
next unless options[key].to_s.empty?
|
20
|
+
raise Castle::InvalidParametersError, "#{key} is missing or empty"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
class Identify
|
6
|
+
include WithContext
|
7
|
+
|
8
|
+
def build(options = {})
|
9
|
+
validate!(options)
|
10
|
+
build_context!(options)
|
11
|
+
|
12
|
+
Castle::Command.new('identify', options, :post)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate!(options)
|
18
|
+
%i[user_id].each do |key|
|
19
|
+
next unless options[key].to_s.empty?
|
20
|
+
raise Castle::InvalidParametersError, "#{key} is missing or empty"
|
21
|
+
end
|
22
|
+
|
23
|
+
if options[:properties]
|
24
|
+
raise Castle::InvalidParametersError,
|
25
|
+
'properties are not supported in identify calls'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
class Review
|
6
|
+
def build(review_id)
|
7
|
+
raise Castle::InvalidParametersError if review_id.nil? || review_id.to_s.empty?
|
8
|
+
|
9
|
+
Castle::Command.new("reviews/#{review_id}", nil, :get)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
class Track
|
6
|
+
include WithContext
|
7
|
+
|
8
|
+
def build(options = {})
|
9
|
+
validate!(options)
|
10
|
+
build_context!(options)
|
11
|
+
|
12
|
+
Castle::Command.new('track', options, :post)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validate!(options)
|
18
|
+
%i[event].each do |key|
|
19
|
+
next unless options[key].to_s.empty?
|
20
|
+
raise Castle::InvalidParametersError, "#{key} is missing or empty"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
module WithContext
|
6
|
+
def initialize(context)
|
7
|
+
@context_merger = ContextMerger.new(context)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def build_context!(options)
|
13
|
+
sanitize_active_mode!(options)
|
14
|
+
options[:context] = merge_context(options[:context])
|
15
|
+
end
|
16
|
+
|
17
|
+
def sanitize_active_mode!(options)
|
18
|
+
return unless options[:context] && options[:context].key?(:active)
|
19
|
+
return if [true, false].include?(options[:context][:active])
|
20
|
+
options[:context].delete(:active)
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge_context(request_context)
|
24
|
+
@context_merger.call(request_context || {})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/castle/configuration.rb
CHANGED
@@ -3,37 +3,69 @@
|
|
3
3
|
module Castle
|
4
4
|
# manages configuration variables
|
5
5
|
class Configuration
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
REQUEST_TIMEOUT = 500 # in milliseconds
|
7
|
+
FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
|
8
|
+
WHITELISTED = [
|
9
|
+
'User-Agent',
|
10
|
+
'Accept-Language',
|
11
|
+
'Accept-Encoding',
|
12
|
+
'Accept-Charset',
|
13
|
+
'Accept',
|
14
|
+
'Accept-Datetime',
|
15
|
+
'X-Forwarded-For',
|
16
|
+
'Forwarded',
|
17
|
+
'X-Forwarded',
|
18
|
+
'X-Real-IP',
|
19
|
+
'REMOTE_ADDR'
|
20
|
+
].freeze
|
9
21
|
|
10
|
-
|
11
|
-
|
22
|
+
BLACKLISTED = ['HTTP_COOKIE'].freeze
|
23
|
+
|
24
|
+
attr_accessor :host, :port, :request_timeout, :url_prefix
|
25
|
+
attr_reader :api_secret, :host, :port, :whitelisted, :blacklisted, :failover_strategy
|
12
26
|
|
13
27
|
def initialize
|
28
|
+
@formatter = Castle::HeaderFormatter.new
|
14
29
|
@request_timeout = REQUEST_TIMEOUT
|
15
|
-
self.
|
30
|
+
self.failover_strategy = :allow
|
31
|
+
self.host = 'api.castle.io'
|
32
|
+
self.port = 443
|
33
|
+
self.url_prefix = 'v1'
|
34
|
+
self.whitelisted = WHITELISTED
|
35
|
+
self.blacklisted = BLACKLISTED
|
16
36
|
self.api_secret = ''
|
17
37
|
end
|
18
38
|
|
19
|
-
def api_endpoint=(value)
|
20
|
-
@api_endpoint = URI(
|
21
|
-
ENV.fetch('CASTLE_API_ENDPOINT', value)
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
39
|
def api_secret=(value)
|
26
40
|
@api_secret = ENV.fetch('CASTLE_API_SECRET', value).to_s
|
27
41
|
end
|
28
42
|
|
43
|
+
def whitelisted=(value)
|
44
|
+
@whitelisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def blacklisted=(value)
|
48
|
+
@blacklisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid?
|
52
|
+
!api_secret.to_s.empty? && !host.to_s.empty? && !port.to_s.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def failover_strategy=(value)
|
56
|
+
@failover_strategy = FAILOVER_STRATEGIES.detect { |strategy| strategy == value.to_sym }
|
57
|
+
raise Castle::ConfigurationError, 'unrecognized failover strategy' if @failover_strategy.nil?
|
58
|
+
@failover_strategy
|
59
|
+
end
|
60
|
+
|
29
61
|
private
|
30
62
|
|
31
63
|
def respond_to_missing?(method_name, _include_private)
|
32
64
|
/^(\w+)=$/ =~ method_name
|
33
65
|
end
|
34
66
|
|
35
|
-
def method_missing(
|
36
|
-
raise Castle::ConfigurationError,
|
67
|
+
def method_missing(m, *_args)
|
68
|
+
raise Castle::ConfigurationError, "there is no such a config #{m}"
|
37
69
|
end
|
38
70
|
end
|
39
71
|
end
|