castle-rb 2.3.2 → 3.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 +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
|
-
[](https://travis-ci.org/castle/castle-ruby)
|
4
|
+
[](https://coveralls.io/github/castle/castle-ruby?branch=coveralls)
|
5
|
+
[](https://badge.fury.io/rb/castle-rb)
|
6
|
+
[](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
|