castle-rb 4.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0dd544ffa6fc2660fc67c058011df5b9d83a8078b48506ab611809166960f501
4
- data.tar.gz: 1720630a7f1925ba1142208114198cf176a8a3fec5c04be480aa4d2384e03de2
3
+ metadata.gz: 441cbae1aed67e419bff1683c41183abb10c3890d5a159f3365c0b4bd3855f0b
4
+ data.tar.gz: 6ef7663b21e2de6b1ef833917dd4b62ee1891f017e33f88a1cb57252e5c3bcc4
5
5
  SHA512:
6
- metadata.gz: 71320c8ff0a5dd2a137723efc76c5530449bdf4635eed38f4c5fcad8bcb9253c5b2557b9113071cd606528eb58d3d66921fa1a728e6ea123d65ab2173e71ad59
7
- data.tar.gz: d2f57e05bd976a294385808757b148248f01b2319cd99e88277bc18e104d4a2fabbcd2d1aa55057d7fc5a12e4ab7e3ec66086502c74ba6d8e5f6b8e51acfba01
6
+ metadata.gz: b54200b206ea055878d2e4a6b46f82960cda186c4b57009765c7e4c8cce28c7f00c66ad216840f8fa5412a7ed876224f285a18388620bf7df9201425a0efb055
7
+ data.tar.gz: cf9f8b5019377138fe5e61ced02b6323aa27464dc117c521fe560a6832115416bbd7e92a64a4710b96939adcd49af12feb538f3d24392d9654b14a21a48021a5
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ruby SDK for Castle
2
2
 
3
- [![Build Status](https://travis-ci.org/castle/castle-ruby.svg?branch=master)](https://travis-ci.org/castle/castle-ruby)
3
+ [![Build Status](https://circleci.com/gh/castle/castle-ruby.svg?style=shield&branch=master)](https://circleci.com/gh/castle/castle-ruby)
4
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
5
  [![Gem Version](https://badge.fury.io/rb/castle-rb.svg)](https://badge.fury.io/rb/castle-rb)
6
6
 
@@ -65,37 +65,43 @@ Castle.configure do |config|
65
65
  # Castle::RequestError is raised when timing out in milliseconds (default: 500 milliseconds)
66
66
  config.request_timeout = 2000
67
67
 
68
- # Whitelisted and Blacklisted headers are case insensitive and allow to use _ and - as a separator, http prefixes are removed
69
- # Whitelisted headers
68
+ # Allowlisted and Denylisted headers are case insensitive and allow to use _ and - as a separator, http prefixes are removed
69
+ # Allowlisted headers
70
70
  # By default, the SDK sends all HTTP headers, except for Cookie and Authorization.
71
- # If you decide to use a whitelist, the SDK will:
71
+ # If you decide to use a allowlist, the SDK will:
72
72
  # - always send the User-Agent header
73
- # - send scrubbed values of non-whitelisted headers
74
- # - send proper values of whitelisted headers.
73
+ # - send scrubbed values of non-allowlisted headers
74
+ # - send proper values of allowlisted headers.
75
75
  # @example
76
- # config.whitelisted = ['X_HEADER']
76
+ # config.allowlisted = ['X_HEADER']
77
77
  # # will send { 'User-Agent' => 'Chrome', 'X_HEADER' => 'proper value', 'Any-Other-Header' => true }
78
78
  #
79
- # We highly suggest using blacklist instead of whitelist, so that Castle can use as many data points
80
- # as possible to secure your users. If you want to use the whitelist, this is the minimal
79
+ # We highly suggest using denylist instead of allowlist, so that Castle can use as many data points
80
+ # as possible to secure your users. If you want to use the allowlist, this is the minimal
81
81
  # amount of headers we recommend:
82
- config.whitelisted = Castle::Configuration::DEFAULT_WHITELIST
82
+ config.allowlisted = Castle::Configuration::DEFAULT_ALLOWLIST
83
83
 
84
- # Blacklisted headers take precedence over whitelisted elements
85
- # We always blacklist Cookie and Authentication headers. If you use any other headers that
86
- # might contain sensitive information, you should blacklist them.
87
- config.blacklisted = ['HTTP-X-header']
84
+ # Denylisted headers take precedence over allowlisted elements
85
+ # We always denylist Cookie and Authentication headers. If you use any other headers that
86
+ # might contain sensitive information, you should denylist them.
87
+ config.denylisted = ['HTTP-X-header']
88
88
 
89
89
  # Castle needs the original IP of the client, not the IP of your proxy or load balancer.
90
90
  # The SDK will only trust the proxy chain as defined in the configuration.
91
91
  # We try to fetch the client IP based on X-Forwarded-For or Remote-Addr headers in that order,
92
92
  # but sometimes the client IP may be stored in a different header or order.
93
93
  # The SDK can be configured to look for the client IP address in headers that you specify.
94
+
95
+ # Sometimes, Cloud providers do not use consistent IP addresses to proxy requests.
96
+ # In this case, the client IP is usually preserved in a custom header. Example:
97
+ # Cloudflare preserves the client request in the 'Cf-Connecting-Ip' header.
98
+ # It would be used like so: configuration.ip_headers=['Cf-Connecting-Ip']
99
+ configuration.ip_headers = []
100
+
94
101
  # If the specified header or X-Forwarded-For default contains a proxy chain with public IP addresses,
95
102
  # then one of the following must be set
96
103
  # 1. The trusted_proxies value must match the known proxy IP's
97
104
  # 2. The trusted_proxy_depth value must be set to the number of known trusted proxies in the chain (see below)
98
- configuration.ip_headers = []
99
105
 
100
106
  # Additionally to make X-Forwarded-For and other headers work better discovering client ip address,
101
107
  # and not the address of a reverse proxy server, you can define trusted proxies
@@ -191,6 +197,25 @@ track_options = ::Castle::Client.to_options({
191
197
  CastleTrackingWorker.perform_async(request_context, track_options)
192
198
  ```
193
199
 
200
+ ## Connection reuse
201
+
202
+ If you want to reuse the connection to send multiple events:
203
+
204
+ ```ruby
205
+ Castle::API::Session.call do |http|
206
+ castle.track(
207
+ event: ::Castle::Events::LOGOUT_SUCCEEDED,
208
+ user_id: user2.id
209
+ http: http
210
+ )
211
+ castle.track(
212
+ event: ::Castle::Events::LOGIN_SUCCEEDED,
213
+ user_id: user1.id
214
+ http: http
215
+ )
216
+ end
217
+ ```
218
+
194
219
  ## Events
195
220
 
196
221
  List of Recognized Events can be found [here](https://github.com/castle/castle-ruby/tree/master/lib/castle/events.rb) or in the [docs](https://docs.castle.io/api_reference/#list-of-recognized-events)
@@ -35,6 +35,7 @@
35
35
  castle/extractors/client_id
36
36
  castle/extractors/headers
37
37
  castle/extractors/ip
38
+ castle/api/connection
38
39
  castle/api/response
39
40
  castle/api/request
40
41
  castle/api/session
@@ -19,14 +19,16 @@ module Castle
19
19
  class << self
20
20
  # @param command [String]
21
21
  # @param headers [Hash]
22
- def request(command, headers = {})
22
+ # @param http [Net::HTTP]
23
+ def request(command, headers = {}, http = nil)
23
24
  raise Castle::ConfigurationError, 'configuration is not valid' unless Castle.config.valid?
24
25
 
25
26
  begin
26
27
  Castle::API::Request.call(
27
28
  command,
28
29
  Castle.config.api_secret,
29
- headers
30
+ headers,
31
+ http
30
32
  )
31
33
  rescue *HANDLED_ERRORS => e
32
34
  # @note We need to initialize the error, as the original error is a cause for this
@@ -38,8 +40,9 @@ module Castle
38
40
 
39
41
  # @param command [String]
40
42
  # @param headers [Hash]
41
- def call(command, headers = {})
42
- Castle::API::Response.call(request(command, headers))
43
+ # @param http [Net::HTTP]
44
+ def call(command, headers = {}, http = nil)
45
+ Castle::API::Response.call(request(command, headers, http))
43
46
  end
44
47
  end
45
48
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castle
4
+ module API
5
+ # this module returns a new configured Net::HTTP object
6
+ module Connection
7
+ HTTPS_SCHEME = 'https'
8
+
9
+ class << self
10
+ def call
11
+ http = Net::HTTP.new(Castle.config.url.host, Castle.config.url.port)
12
+ http.read_timeout = Castle.config.request_timeout / 1000.0
13
+
14
+ if Castle.config.url.scheme == HTTPS_SCHEME
15
+ http.use_ssl = true
16
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
17
+ end
18
+
19
+ http
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -12,8 +12,8 @@ module Castle
12
12
  private_constant :DEFAULT_HEADERS
13
13
 
14
14
  class << self
15
- def call(command, api_secret, headers)
16
- Castle::API::Session.get.request(
15
+ def call(command, api_secret, headers, http = nil)
16
+ (http || Castle::API::Connection.call).request(
17
17
  build(
18
18
  command,
19
19
  headers.merge(DEFAULT_HEADERS),
@@ -25,7 +25,7 @@ module Castle
25
25
  def build(command, headers, api_secret)
26
26
  request_obj = Net::HTTP.const_get(
27
27
  command.method.to_s.capitalize
28
- ).new("#{Castle.config.url_prefix}/#{command.path}", headers)
28
+ ).new("#{Castle.config.url.path}/#{command.path}", headers)
29
29
 
30
30
  unless command.method == :get
31
31
  request_obj.body = ::Castle::Utils.replace_invalid_characters(
@@ -1,37 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
-
5
3
  module Castle
6
4
  module API
7
- # this class keeps http config object
8
- # and provides start/finish methods for persistent connection usage
5
+ # this module uses the Connection object
6
+ # and provides start method for persistent connection usage
9
7
  # when there is a need of sending multiple requests at once
10
- class Session
11
- include Singleton
12
-
13
- attr_accessor :http
14
-
15
- def initialize
16
- reset
17
- end
18
-
19
- def reset
20
- @http = Net::HTTP.new(Castle.config.host, Castle.config.port)
21
- @http.read_timeout = Castle.config.request_timeout / 1000.0
22
-
23
- if Castle.config.port == 443
24
- @http.use_ssl = true
25
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
26
- end
27
-
28
- @http
29
- end
8
+ module Session
9
+ HTTPS_SCHEME = 'https'
30
10
 
31
11
  class << self
32
- # @return [Net::HTTP]
33
- def get
34
- instance.http
12
+ def call(&block)
13
+ return unless block_given?
14
+
15
+ Connection.call.start(&block)
35
16
  end
36
17
  end
37
18
  end
@@ -42,9 +42,11 @@ module Castle
42
42
  return generate_do_not_track_response(options[:user_id]) unless tracked?
43
43
 
44
44
  add_timestamp_if_necessary(options)
45
- command = Castle::Commands::Authenticate.new(@context).build(options)
45
+
46
46
  begin
47
- Castle::API.call(command).merge(failover: false, failover_reason: nil)
47
+ Castle::API
48
+ .call(authenticate_command(options), {}, options[:http])
49
+ .merge(failover: false, failover_reason: nil)
48
50
  rescue Castle::RequestError, Castle::InternalServerError => e
49
51
  self.class.failover_response_or_raise(
50
52
  FailoverAuthResponse.new(options[:user_id], reason: e.to_s), e
@@ -59,8 +61,7 @@ module Castle
59
61
 
60
62
  add_timestamp_if_necessary(options)
61
63
 
62
- command = Castle::Commands::Identify.new(@context).build(options)
63
- Castle::API.call(command)
64
+ Castle::API.call(identify_command(options), {}, options[:http])
64
65
  end
65
66
 
66
67
  def track(options = {})
@@ -70,15 +71,15 @@ module Castle
70
71
 
71
72
  add_timestamp_if_necessary(options)
72
73
 
73
- command = Castle::Commands::Track.new(@context).build(options)
74
- Castle::API.call(command)
74
+ Castle::API.call(track_command(options), {}, options[:http])
75
75
  end
76
76
 
77
77
  def impersonate(options = {})
78
78
  options = Castle::Utils.deep_symbolize_keys(options || {})
79
+
79
80
  add_timestamp_if_necessary(options)
80
- command = Castle::Commands::Impersonate.new(@context).build(options)
81
- Castle::API.call(command).tap do |response|
81
+
82
+ Castle::API.call(impersonate_command(options), {}, options[:http]).tap do |response|
82
83
  raise Castle::ImpersonationFailed unless response[:success]
83
84
  end
84
85
  end
@@ -104,6 +105,26 @@ module Castle
104
105
  ).generate
105
106
  end
106
107
 
108
+ # @param options [Hash]
109
+ def authenticate_command(options)
110
+ Castle::Commands::Authenticate.new(@context).build(options)
111
+ end
112
+
113
+ # @param options [Hash]
114
+ def identify_command(options)
115
+ Castle::Commands::Identify.new(@context).build(options)
116
+ end
117
+
118
+ # @param options [Hash]
119
+ def impersonate_command(options)
120
+ Castle::Commands::Impersonate.new(@context).build(options)
121
+ end
122
+
123
+ # @param options [Hash]
124
+ def track_command(options)
125
+ Castle::Commands::Track.new(@context).build(options)
126
+ end
127
+
107
128
  def add_timestamp_if_necessary(options)
108
129
  options[:timestamp] ||= @timestamp if @timestamp
109
130
  end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
+ require 'uri'
4
5
 
5
6
  module Castle
6
7
  # manages configuration variables
7
8
  class Configuration
8
9
  include Singleton
9
10
 
10
- HOST = 'api.castle.io'
11
- PORT = 443
12
- URL_PREFIX = '/v1'
11
+ # API endpoint
12
+ URL = 'https://api.castle.io/v1'
13
13
  FAILOVER_STRATEGY = :allow
14
14
  REQUEST_TIMEOUT = 500 # in milliseconds
15
15
  FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
@@ -23,9 +23,9 @@ module Castle
23
23
  \Aunix:
24
24
  /ix].freeze
25
25
 
26
- # @note this value is not assigned as we don't recommend using a whitelist. If you need to use
26
+ # @note this value is not assigned as we don't recommend using a allowlist. If you need to use
27
27
  # one, this constant is provided as a good default.
28
- DEFAULT_WHITELIST = %w[
28
+ DEFAULT_ALLOWLIST = %w[
29
29
  Accept
30
30
  Accept-Charset
31
31
  Accept-Datetime
@@ -35,18 +35,25 @@ module Castle
35
35
  Connection
36
36
  Content-Length
37
37
  Content-Type
38
+ Dnt
38
39
  Host
39
40
  Origin
40
41
  Pragma
41
42
  Referer
42
- TE
43
+ Sec-Fetch-Dest
44
+ Sec-Fetch-Mode
45
+ Sec-Fetch-Site
46
+ Sec-Fetch-User
47
+ Te
43
48
  Upgrade-Insecure-Requests
49
+ User-Agent
44
50
  X-Castle-Client-Id
51
+ X-Requested-With
45
52
  ].freeze
46
53
 
47
- attr_accessor :host, :port, :request_timeout, :url_prefix, :trust_proxy_chain
48
- attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy, :ip_headers,
49
- :trusted_proxies, :trusted_proxy_depth
54
+ attr_accessor :request_timeout, :trust_proxy_chain
55
+ attr_reader :api_secret, :allowlisted, :denylisted, :failover_strategy, :ip_headers,
56
+ :trusted_proxies, :trusted_proxy_depth, :url
50
57
 
51
58
  def initialize
52
59
  @formatter = Castle::HeadersFormatter
@@ -56,11 +63,9 @@ module Castle
56
63
 
57
64
  def reset
58
65
  self.failover_strategy = FAILOVER_STRATEGY
59
- self.host = HOST
60
- self.port = PORT
61
- self.url_prefix = URL_PREFIX
62
- self.whitelisted = [].freeze
63
- self.blacklisted = [].freeze
66
+ self.url = URL
67
+ self.allowlisted = [].freeze
68
+ self.denylisted = [].freeze
64
69
  self.api_secret = ENV.fetch('CASTLE_API_SECRET', '')
65
70
  self.ip_headers = [].freeze
66
71
  self.trusted_proxies = [].freeze
@@ -68,16 +73,20 @@ module Castle
68
73
  self.trusted_proxy_depth = nil
69
74
  end
70
75
 
76
+ def url=(value)
77
+ @url = URI(value)
78
+ end
79
+
71
80
  def api_secret=(value)
72
81
  @api_secret = value.to_s
73
82
  end
74
83
 
75
- def whitelisted=(value)
76
- @whitelisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
84
+ def allowlisted=(value)
85
+ @allowlisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
77
86
  end
78
87
 
79
- def blacklisted=(value)
80
- @blacklisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
88
+ def denylisted=(value)
89
+ @denylisted = (value ? value.map { |header| @formatter.call(header) } : []).freeze
81
90
  end
82
91
 
83
92
  # sets ip headers
@@ -102,7 +111,7 @@ module Castle
102
111
  end
103
112
 
104
113
  def valid?
105
- !api_secret.to_s.empty? && !host.to_s.empty? && !port.to_s.empty?
114
+ !api_secret.to_s.empty? && !url.host.to_s.empty? && !url.port.to_s.empty?
106
115
  end
107
116
 
108
117
  def failover_strategy=(value)
@@ -4,18 +4,18 @@ module Castle
4
4
  module Extractors
5
5
  # used for extraction of cookies and headers from the request
6
6
  class Headers
7
- # Headers that we will never scrub, even if they land on the configuration blacklist.
8
- ALWAYS_WHITELISTED = %w[User-Agent].freeze
7
+ # Headers that we will never scrub, even if they land on the configuration denylist.
8
+ ALWAYS_ALLOWLISTED = %w[User-Agent].freeze
9
9
 
10
- # Headers that will always be scrubbed, even if whitelisted.
11
- ALWAYS_BLACKLISTED = %w[Cookie Authorization].freeze
10
+ # Headers that will always be scrubbed, even if allowlisted.
11
+ ALWAYS_DENYLISTED = %w[Cookie Authorization].freeze
12
12
 
13
- private_constant :ALWAYS_WHITELISTED, :ALWAYS_BLACKLISTED
13
+ private_constant :ALWAYS_ALLOWLISTED, :ALWAYS_DENYLISTED
14
14
 
15
15
  # @param headers [Hash]
16
16
  def initialize(headers)
17
17
  @headers = headers
18
- @no_whitelist = Castle.config.whitelisted.empty?
18
+ @no_allowlist = Castle.config.allowlisted.empty?
19
19
  end
20
20
 
21
21
  # Serialize HTTP headers
@@ -33,10 +33,10 @@ module Castle
33
33
  # @param value [String]
34
34
  # @return [TrueClass | FalseClass | String]
35
35
  def header_value(name, value)
36
- return true if ALWAYS_BLACKLISTED.include?(name)
37
- return value if ALWAYS_WHITELISTED.include?(name)
38
- return true if Castle.config.blacklisted.include?(name)
39
- return value if @no_whitelist || Castle.config.whitelisted.include?(name)
36
+ return true if ALWAYS_DENYLISTED.include?(name)
37
+ return value if ALWAYS_ALLOWLISTED.include?(name)
38
+ return true if Castle.config.denylisted.include?(name)
39
+ return value if @no_allowlist || Castle.config.allowlisted.include?(name)
40
40
 
41
41
  true
42
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- VERSION = '4.3.0'
4
+ VERSION = '5.0.0'
5
5
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::API::Connection do
4
+ describe '.call' do
5
+ subject(:class_call) { described_class.call }
6
+
7
+ context 'when ssl false' do
8
+ let(:localhost) { 'localhost' }
9
+ let(:port) { 3002 }
10
+ let(:api_url) { '/test' }
11
+
12
+ before do
13
+ Castle.config.url = 'http://localhost:3002'
14
+
15
+ allow(Net::HTTP)
16
+ .to receive(:new)
17
+ .with(localhost, port)
18
+ .and_call_original
19
+ end
20
+
21
+ it do
22
+ class_call
23
+
24
+ expect(Net::HTTP)
25
+ .to have_received(:new)
26
+ .with(localhost, port)
27
+ end
28
+
29
+ it do
30
+ expect(class_call).to be_an_instance_of(Net::HTTP)
31
+ end
32
+ end
33
+
34
+ context 'when ssl true' do
35
+ let(:localhost) { 'localhost' }
36
+ let(:port) { 443 }
37
+
38
+ before do
39
+ Castle.config.url = 'https://localhost'
40
+ end
41
+
42
+ context 'with block' do
43
+ let(:api_url) { '/test' }
44
+ let(:request) { Net::HTTP::Get.new(api_url) }
45
+
46
+ before do
47
+ allow(Net::HTTP)
48
+ .to receive(:new)
49
+ .with(localhost, port)
50
+ .and_call_original
51
+ end
52
+
53
+ it do
54
+ expect(class_call).to be_an_instance_of(Net::HTTP)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -2,29 +2,55 @@
2
2
 
3
3
  describe Castle::API::Request do
4
4
  describe '#call' do
5
- subject(:call) { described_class.call(command, api_secret, headers) }
6
-
7
- let(:session) { instance_double('Castle::API::Session') }
8
- let(:http) { instance_double('Net::HTTP') }
9
5
  let(:command) { Castle::Commands::Track.new({}).build(event: '$login.succeeded') }
10
6
  let(:headers) { {} }
11
7
  let(:api_secret) { 'secret' }
12
8
  let(:request_build) { {} }
13
9
  let(:expected_headers) { { 'Content-Type' => 'application/json' } }
10
+ let(:http) { instance_double('Net::HTTP') }
14
11
 
15
- before do
16
- allow(Castle::API::Session).to receive(:instance).and_return(session)
17
- allow(session).to receive(:http).and_return(http)
18
- allow(http).to receive(:request)
19
- allow(described_class).to receive(:build).and_return(request_build)
20
- call
21
- end
12
+ context 'without http arg provided' do
13
+ subject(:call) { described_class.call(command, api_secret, headers) }
14
+
15
+ let(:http) { instance_double('Net::HTTP') }
16
+ let(:command) { Castle::Commands::Track.new({}).build(event: '$login.succeeded') }
17
+ let(:headers) { {} }
18
+ let(:api_secret) { 'secret' }
19
+ let(:request_build) { {} }
20
+ let(:expected_headers) { { 'Content-Type' => 'application/json' } }
21
+
22
+ before do
23
+ allow(Castle::API::Connection).to receive(:call).and_return(http)
24
+ allow(http).to receive(:request)
25
+ allow(described_class).to receive(:build).and_return(request_build)
26
+ call
27
+ end
28
+
29
+ it do
30
+ expect(described_class).to have_received(:build).with(command, expected_headers, api_secret)
31
+ end
22
32
 
23
- it do
24
- expect(described_class).to have_received(:build).with(command, expected_headers, api_secret)
33
+ it { expect(http).to have_received(:request).with(request_build) }
25
34
  end
26
35
 
27
- it { expect(http).to have_received(:request).with(request_build) }
36
+ context 'with http arg provided' do
37
+ subject(:call) { described_class.call(command, api_secret, headers, http) }
38
+
39
+ before do
40
+ allow(Castle::API::Connection).to receive(:call)
41
+ allow(http).to receive(:request)
42
+ allow(described_class).to receive(:build).and_return(request_build)
43
+ call
44
+ end
45
+
46
+ it { expect(Castle::API::Connection).not_to have_received(:call) }
47
+
48
+ it do
49
+ expect(described_class).to have_received(:build).with(command, expected_headers, api_secret)
50
+ end
51
+
52
+ it { expect(http).to have_received(:request).with(request_build) }
53
+ end
28
54
  end
29
55
 
30
56
  describe '#build' do
@@ -1,47 +1,86 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::API::Session do
4
- describe '#get' do
5
- it { expect(described_class.get).to eql(described_class.get) }
6
- it { expect(described_class.get).to eql(described_class.instance.http) }
7
- end
8
-
9
- describe '#initialize' do
10
- subject(:session) { described_class.get }
11
-
12
- after do
13
- described_class.instance.reset
14
- end
15
-
4
+ describe '.call' do
16
5
  context 'when ssl false' do
6
+ let(:localhost) { 'localhost' }
7
+ let(:port) { 3002 }
8
+
17
9
  before do
18
- Castle.config.host = 'localhost'
19
- Castle.config.port = 3002
20
- described_class.instance.reset
10
+ Castle.config.url = 'http://localhost:3002'
11
+ stub_request(:get, 'localhost:3002/test').to_return(status: 200, body: '{}', headers: {})
21
12
  end
22
13
 
23
- after do
24
- Castle.config.host = Castle::Configuration::HOST
25
- Castle.config.port = Castle::Configuration::PORT
14
+ context 'with block' do
15
+ let(:api_url) { '/test' }
16
+ let(:request) { Net::HTTP::Get.new(api_url) }
17
+
18
+ before do
19
+ allow(Net::HTTP)
20
+ .to receive(:new)
21
+ .with(localhost, port)
22
+ .and_call_original
23
+
24
+ described_class.call do |http|
25
+ http.request(request)
26
+ end
27
+ end
28
+
29
+ it do
30
+ expect(Net::HTTP)
31
+ .to have_received(:new)
32
+ .with(localhost, port)
33
+ end
34
+
35
+ it do
36
+ expect(a_request(:get, 'localhost:3002/test'))
37
+ .to have_been_made.once
38
+ end
26
39
  end
27
40
 
28
- it { expect(session).to be_instance_of(Net::HTTP) }
29
- it { expect(session.address).to eq('localhost') }
30
- it { expect(session.port).to eq(3002) }
31
- it { expect(session.use_ssl?).to be false }
32
- it { expect(session.verify_mode).to be_nil }
41
+ context 'without block' do
42
+ before { described_class.call }
43
+
44
+ it do
45
+ expect(a_request(:get, 'localhost:3002/test'))
46
+ .not_to have_been_made
47
+ end
48
+ end
33
49
  end
34
50
 
35
51
  context 'when ssl true' do
52
+ let(:localhost) { 'localhost' }
53
+ let(:port) { 443 }
54
+
36
55
  before do
37
- described_class.instance.reset
56
+ Castle.config.url = 'https://localhost'
57
+ stub_request(:get, 'https://localhost/test').to_return(status: 200, body: '{}', headers: {})
38
58
  end
39
59
 
40
- it { expect(session).to be_instance_of(Net::HTTP) }
41
- it { expect(session.address).to eq(Castle.config.host) }
42
- it { expect(session.port).to eq(Castle.config.port) }
43
- it { expect(session.use_ssl?).to be true }
44
- it { expect(session.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }
60
+ context 'with block' do
61
+ let(:api_url) { '/test' }
62
+ let(:request) { Net::HTTP::Get.new(api_url) }
63
+
64
+ before do
65
+ allow(Net::HTTP)
66
+ .to receive(:new)
67
+ .with(localhost, port)
68
+ .and_call_original
69
+
70
+ allow(Net::HTTP)
71
+ .to receive(:start)
72
+
73
+ described_class.call do |http|
74
+ http.request(request)
75
+ end
76
+ end
77
+
78
+ it do
79
+ expect(Net::HTTP)
80
+ .to have_received(:new)
81
+ .with(localhost, port)
82
+ end
83
+ end
45
84
  end
46
85
  end
47
86
  end
@@ -82,10 +82,10 @@ describe Castle::Client do
82
82
  let(:impersonator) { 'test@castle.io' }
83
83
  let(:request_body) do
84
84
  { user_id: '1234', timestamp: time_auto, sent_at: time_auto,
85
- impersonator: impersonator, context: context }
85
+ properties: { impersonator: impersonator }, context: context }
86
86
  end
87
87
  let(:response_body) { { success: true }.to_json }
88
- let(:options) { { user_id: '1234', impersonator: impersonator } }
88
+ let(:options) { { user_id: '1234', properties: { impersonator: impersonator } } }
89
89
 
90
90
  context 'when used with symbol keys' do
91
91
  before { client.impersonate(options) }
@@ -36,9 +36,9 @@ describe Castle::Commands::Impersonate do
36
36
  end
37
37
 
38
38
  context 'with impersonator' do
39
- let(:payload) { default_payload.merge(impersonator: impersonator) }
39
+ let(:payload) { default_payload.merge(properties: { impersonator: impersonator }) }
40
40
  let(:command_data) do
41
- default_payload.merge(impersonator: impersonator, context: context)
41
+ default_payload.merge(properties: { impersonator: impersonator }, context: context)
42
42
  end
43
43
 
44
44
  it { expect(command.method).to be_eql(:post) }
@@ -7,25 +7,25 @@ describe Castle::Configuration do
7
7
 
8
8
  describe 'host' do
9
9
  context 'with default' do
10
- it { expect(config.host).to be_eql('api.castle.io') }
10
+ it { expect(config.url.host).to be_eql('api.castle.io') }
11
11
  end
12
12
 
13
13
  context 'with setter' do
14
- before { config.host = 'api.castle.dev' }
14
+ before { config.url = 'http://api.castle.dev/v2' }
15
15
 
16
- it { expect(config.host).to be_eql('api.castle.dev') }
16
+ it { expect(config.url.host).to be_eql('api.castle.dev') }
17
17
  end
18
18
  end
19
19
 
20
20
  describe 'post' do
21
21
  context 'with default' do
22
- it { expect(config.port).to be_eql(443) }
22
+ it { expect(config.url.port).to be_eql(443) }
23
23
  end
24
24
 
25
25
  context 'with setter' do
26
- before { config.port = 3001 }
26
+ before { config.url = 'http://api.castle.dev:3001/v2' }
27
27
 
28
- it { expect(config.port).to be_eql(3001) }
28
+ it { expect(config.url.port).to be_eql(3001) }
29
29
  end
30
30
  end
31
31
 
@@ -89,34 +89,34 @@ describe Castle::Configuration do
89
89
  end
90
90
  end
91
91
 
92
- describe 'whitelisted' do
92
+ describe 'allowlisted' do
93
93
  it do
94
- expect(config.whitelisted.size).to be_eql(0)
94
+ expect(config.allowlisted.size).to be_eql(0)
95
95
  end
96
96
 
97
97
  context 'with setter' do
98
98
  before do
99
- config.whitelisted = ['header']
99
+ config.allowlisted = ['header']
100
100
  end
101
101
 
102
102
  it do
103
- expect(config.whitelisted).to be_eql(['Header'])
103
+ expect(config.allowlisted).to be_eql(['Header'])
104
104
  end
105
105
  end
106
106
  end
107
107
 
108
- describe 'blacklisted' do
108
+ describe 'denylisted' do
109
109
  it do
110
- expect(config.blacklisted.size).to be_eql(0)
110
+ expect(config.denylisted.size).to be_eql(0)
111
111
  end
112
112
 
113
113
  context 'with setter' do
114
114
  before do
115
- config.blacklisted = ['header']
115
+ config.denylisted = ['header']
116
116
  end
117
117
 
118
118
  it do
119
- expect(config.blacklisted).to be_eql(['Header'])
119
+ expect(config.denylisted).to be_eql(['Header'])
120
120
  end
121
121
  end
122
122
  end
@@ -16,11 +16,11 @@ describe Castle::Extractors::Headers do
16
16
  end
17
17
 
18
18
  after do
19
- Castle.config.whitelisted = %w[]
20
- Castle.config.blacklisted = %w[]
19
+ Castle.config.allowlisted = %w[]
20
+ Castle.config.denylisted = %w[]
21
21
  end
22
22
 
23
- context 'when whitelist is not set in the configuration' do
23
+ context 'when allowlist is not set in the configuration' do
24
24
  let(:result) do
25
25
  {
26
26
  'Accept' => 'application/json',
@@ -36,8 +36,8 @@ describe Castle::Extractors::Headers do
36
36
  it { expect(headers).to eq(result) }
37
37
  end
38
38
 
39
- context 'when whitelist is set in the configuration' do
40
- before { Castle.config.whitelisted = %w[Accept OK] }
39
+ context 'when allowlist is set in the configuration' do
40
+ before { Castle.config.allowlisted = %w[Accept OK] }
41
41
 
42
42
  let(:result) do
43
43
  {
@@ -54,7 +54,7 @@ describe Castle::Extractors::Headers do
54
54
  it { expect(headers).to eq(result) }
55
55
  end
56
56
 
57
- context 'when blacklist is set in the configuration' do
57
+ context 'when denylist is set in the configuration' do
58
58
  context 'with a User-Agent' do
59
59
  let(:result) do
60
60
  {
@@ -68,7 +68,7 @@ describe Castle::Extractors::Headers do
68
68
  }
69
69
  end
70
70
 
71
- before { Castle.config.blacklisted = %w[User-Agent] }
71
+ before { Castle.config.denylisted = %w[User-Agent] }
72
72
 
73
73
  it { expect(headers).to eq(result) }
74
74
  end
@@ -86,16 +86,16 @@ describe Castle::Extractors::Headers do
86
86
  }
87
87
  end
88
88
 
89
- before { Castle.config.blacklisted = %w[Accept] }
89
+ before { Castle.config.denylisted = %w[Accept] }
90
90
 
91
91
  it { expect(headers).to eq(result) }
92
92
  end
93
93
  end
94
94
 
95
- context 'when a header is both whitelisted and blacklisted' do
95
+ context 'when a header is both allowlisted and denylisted' do
96
96
  before do
97
- Castle.config.whitelisted = %w[Accept]
98
- Castle.config.blacklisted = %w[Accept]
97
+ Castle.config.allowlisted = %w[Accept]
98
+ Castle.config.denylisted = %w[Accept]
99
99
  end
100
100
 
101
101
  it do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castle-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-26 00:00:00.000000000 Z
11
+ date: 2020-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -34,6 +34,7 @@ files:
34
34
  - lib/castle-rb.rb
35
35
  - lib/castle.rb
36
36
  - lib/castle/api.rb
37
+ - lib/castle/api/connection.rb
37
38
  - lib/castle/api/request.rb
38
39
  - lib/castle/api/response.rb
39
40
  - lib/castle/api/session.rb
@@ -73,6 +74,7 @@ files:
73
74
  - spec/integration/rails/support/all.rb
74
75
  - spec/integration/rails/support/application.rb
75
76
  - spec/integration/rails/support/home_controller.rb
77
+ - spec/lib/castle/api/connection_spec.rb
76
78
  - spec/lib/castle/api/request_spec.rb
77
79
  - spec/lib/castle/api/response_spec.rb
78
80
  - spec/lib/castle/api/session_spec.rb
@@ -109,7 +111,7 @@ homepage: https://castle.io
109
111
  licenses:
110
112
  - MIT
111
113
  metadata: {}
112
- post_install_message:
114
+ post_install_message:
113
115
  rdoc_options: []
114
116
  require_paths:
115
117
  - lib
@@ -125,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  version: '0'
126
128
  requirements: []
127
129
  rubygems_version: 3.1.3
128
- signing_key:
130
+ signing_key:
129
131
  specification_version: 4
130
132
  summary: Castle
131
133
  test_files:
@@ -151,6 +153,7 @@ test_files:
151
153
  - spec/lib/castle/headers_formatter_spec.rb
152
154
  - spec/lib/castle/api/session_spec.rb
153
155
  - spec/lib/castle/api/request_spec.rb
156
+ - spec/lib/castle/api/connection_spec.rb
154
157
  - spec/lib/castle/api/response_spec.rb
155
158
  - spec/lib/castle/commands/review_spec.rb
156
159
  - spec/lib/castle/commands/authenticate_spec.rb