castle-rb 3.6.0 → 3.6.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10971e0d6aaf51fd108b6a4316e938fd223146eb65bf1d1216c585816a0d8e2c
4
- data.tar.gz: 52bae4bd484b9ccc79d6665f2a80c3839532442f4850c9fa4b1a0a7768fc3426
3
+ metadata.gz: 4b34bb0722019eb7b8249772c5f05a0e51de7e230cb00cb12a12a1ccd5c7cb1f
4
+ data.tar.gz: 97c7084f179397163ccbf652f46651892f47fbfe17622bd8a2ed3c5388c93f05
5
5
  SHA512:
6
- metadata.gz: fa31103ea67d58adb41aad80a1e925173892d73c9cad31b1028bf38194044128c9d3bedfaf399050603749e0f84ae87e48edb922124c99b9cd99b063a65734dd
7
- data.tar.gz: c203a28c0bb5bde14212282e3565123d41797ea3af151cc5e7405666bea63f58b98decb0807d7fb95de262e066f1f154d3448b1d9714545964842a78fefc0e75
6
+ metadata.gz: c2187bb33e94733bb99b49e33a999fadbc5bc42c18afb84ba1117880350e020e0415000ac0fa819ca3fe356171648e970a413479bb04558b1e9cdb588a68012e
7
+ data.tar.gz: 41edcf28a794675f760d3be4322fc920ae7458047907407074626fb97a1698c14b88bf2b15a0dc7d784f537ff1ada225c421cd3c38e6017bf5b4a07a09a90a14
data/README.md CHANGED
@@ -67,15 +67,24 @@ Castle.configure do |config|
67
67
 
68
68
  # Whitelisted and Blacklisted headers are case insensitive and allow to use _ and - as a separator, http prefixes are removed
69
69
  # Whitelisted headers
70
- # @note In case of the whitelist, we won't send the values of other headers but we will send their names
71
- config.whitelisted = ['X_HEADER']
72
- # or append to default
73
- config.whitelisted += ['http-x-header']
74
-
75
- # Blacklisted headers take advantage over whitelisted elements
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:
72
+ # - always send the User-Agent header
73
+ # - send scrubbed values of non-whitelisted headers
74
+ # - send proper values of whitelisted headers.
75
+ # @example
76
+ # config.whitelisted = ['X_HEADER']
77
+ # # will send { 'User-Agent' => 'Chrome', 'X_HEADER' => 'proper value', 'Any-Other-Header' => true }
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
81
+ # amount of headers we recommend:
82
+ config.whitelisted = Castle::Configuration::DEFAULT_WHITELIST
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.
76
87
  config.blacklisted = ['HTTP-X-header']
77
- # or append to default
78
- config.blacklisted += ['X_HEADER']
79
88
  end
80
89
  ```
81
90
 
@@ -9,22 +9,27 @@ module Castle
9
9
  FAILOVER_STRATEGY = :allow
10
10
  REQUEST_TIMEOUT = 500 # in milliseconds
11
11
  FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
12
- WHITELISTED = [
13
- 'User-Agent',
14
- 'Accept-Language',
15
- 'Accept-Encoding',
16
- 'Accept-Charset',
17
- 'Accept',
18
- 'Accept-Datetime',
19
- 'X-Forwarded-For',
20
- 'Forwarded',
21
- 'X-Forwarded',
22
- 'X-Real-IP',
23
- 'REMOTE_ADDR',
24
- 'X-Forwarded-For',
25
- 'CF_CONNECTING_IP'
12
+
13
+ # @note this value is not assigned as we don't recommend using a whitelist. If you need to use
14
+ # one, this constant is provided as a good default.
15
+ DEFAULT_WHITELIST = %w[
16
+ Accept
17
+ Accept-Charset
18
+ Accept-Datetime
19
+ Accept-Encoding
20
+ Accept-Language
21
+ Cache-Control
22
+ Connection
23
+ Content-Length
24
+ Content-Type
25
+ Host
26
+ Origin
27
+ Pragma
28
+ Referer
29
+ TE
30
+ Upgrade-Insecure-Requests
31
+ X-Castle-Client-Id
26
32
  ].freeze
27
- BLACKLISTED = ['HTTP_COOKIE'].freeze
28
33
 
29
34
  attr_accessor :host, :port, :request_timeout, :url_prefix
30
35
  attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy
@@ -36,8 +41,8 @@ module Castle
36
41
  self.host = HOST
37
42
  self.port = PORT
38
43
  self.url_prefix = URL_PREFIX
39
- self.whitelisted = WHITELISTED
40
- self.blacklisted = BLACKLISTED
44
+ self.whitelisted = [].freeze
45
+ self.blacklisted = [].freeze
41
46
  self.api_secret = ''
42
47
  end
43
48
 
@@ -4,23 +4,45 @@ 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_INCLUDED_HEADERS = %w[User-Agent].freeze
9
+
10
+ # Headers that will always be scrubbed, even if whitelisted.
11
+ ALWAYS_SCRUBBED_HEADERS = %w[Cookie Authorization].freeze
12
+
13
+ # Rack does not add the HTTP_ prefix to Content-Length for some reason
14
+ CONTENT_LENGTH = 'CONTENT_LENGTH'
15
+
16
+ # Prefix that Rack adds for HTTP headers
17
+ HTTP_HEADER_PREFIX = 'HTTP_'
18
+
19
+ private_constant :ALWAYS_INCLUDED_HEADERS, :ALWAYS_SCRUBBED_HEADERS,
20
+ :CONTENT_LENGTH, :HTTP_HEADER_PREFIX
21
+
22
+ # @param request [Rack::Request]
7
23
  def initialize(request)
8
- @request = request
9
- @request_env = @request.env
24
+ @request_env = request.env
10
25
  @formatter = HeaderFormatter.new
11
26
  end
12
27
 
13
28
  # Serialize HTTP headers
29
+ # @return [Hash]
14
30
  def call
15
- @request_env.keys.each_with_object({}) do |header, acc|
16
- name = @formatter.call(header)
31
+ @request_env.keys.each_with_object({}) do |env_header, acc|
32
+ next unless env_header.start_with?(HTTP_HEADER_PREFIX) || env_header == CONTENT_LENGTH
33
+
34
+ header = @formatter.call(env_header)
17
35
 
18
- if Castle.config.whitelisted.include?(name) && !Castle.config.blacklisted.include?(name)
19
- acc[name] = @request_env[header]
36
+ if ALWAYS_SCRUBBED_HEADERS.include?(header)
37
+ acc[header] = true
38
+ elsif ALWAYS_INCLUDED_HEADERS.include?(header)
39
+ acc[header] = @request_env[env_header]
40
+ elsif Castle.config.blacklisted.include?(header)
41
+ acc[header] = true
42
+ elsif Castle.config.whitelisted.empty? || Castle.config.whitelisted.include?(header)
43
+ acc[header] = @request_env[env_header]
20
44
  else
21
- # When a header is not whitelisted or blacklisted, we're not suppose to send
22
- # it's value but we should send it's name to indicate it's presence
23
- acc[name] = true
45
+ acc[header] = true
24
46
  end
25
47
  end
26
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castle
4
- VERSION = '3.6.0'
4
+ VERSION = '3.6.1'
5
5
  end
@@ -22,13 +22,7 @@ describe Castle::Client do
22
22
 
23
23
  let(:headers) do
24
24
  {
25
- 'Rack.version': true, 'Rack.input': true, 'Rack.errors': true,
26
- 'Rack.multithread': true, 'Rack.multiprocess': true, 'Rack.run-Once': true,
27
- 'Request-Method': true, 'Server-Name': true, 'Server-Port': true,
28
- 'Query-String': true, 'Path-Info': true, 'Rack.url-Scheme': true,
29
- 'Https': true, 'Script-Name': true, 'Content-Length': true,
30
- 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Rack.request.cookie-Hash': true,
31
- 'Rack.request.cookie-String': true, 'Cookie': true
25
+ 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
32
26
  }
33
27
  end
34
28
  let(:context) do
@@ -77,7 +77,7 @@ describe Castle::Configuration do
77
77
 
78
78
  describe 'whitelisted' do
79
79
  it do
80
- expect(config.whitelisted.size).to be_eql(13)
80
+ expect(config.whitelisted.size).to be_eql(0)
81
81
  end
82
82
 
83
83
  context 'with setter' do
@@ -88,19 +88,11 @@ describe Castle::Configuration do
88
88
  expect(config.whitelisted).to be_eql(['Header'])
89
89
  end
90
90
  end
91
-
92
- context 'when appending' do
93
- before do
94
- config.whitelisted += ['header']
95
- end
96
- it { expect(config.whitelisted).to be_include('Header') }
97
- it { expect(config.whitelisted.size).to be_eql(14) }
98
- end
99
91
  end
100
92
 
101
93
  describe 'blacklisted' do
102
94
  it do
103
- expect(config.blacklisted.size).to be_eql(1)
95
+ expect(config.blacklisted.size).to be_eql(0)
104
96
  end
105
97
 
106
98
  context 'with setter' do
@@ -111,14 +103,6 @@ describe Castle::Configuration do
111
103
  expect(config.blacklisted).to be_eql(['Header'])
112
104
  end
113
105
  end
114
-
115
- context 'when appending' do
116
- before do
117
- config.blacklisted += ['header']
118
- end
119
- it { expect(config.blacklisted).to be_include('Header') }
120
- it { expect(config.blacklisted.size).to be_eql(2) }
121
- end
122
106
  end
123
107
 
124
108
  describe 'failover_strategy' do
@@ -9,8 +9,8 @@ describe Castle::Context::Default do
9
9
  let(:env) do
10
10
  Rack::MockRequest.env_for('/',
11
11
  'HTTP_X_FORWARDED_FOR' => ip,
12
- 'HTTP-Accept-Language' => 'en',
13
- 'HTTP-User-Agent' => 'test',
12
+ 'HTTP_ACCEPT_LANGUAGE' => 'en',
13
+ 'HTTP_USER_AGENT' => 'test',
14
14
  'HTTP_COOKIE' => "__cid=#{cookie_id};other=efgh")
15
15
  end
16
16
  let(:request) { Rack::Request.new(env) }
@@ -23,18 +23,17 @@ describe Castle::Context::Default do
23
23
 
24
24
  it { expect(default_context[:active]).to be_eql(true) }
25
25
  it { expect(default_context[:origin]).to be_eql('web') }
26
- it {
26
+
27
+ it do
27
28
  expect(default_context[:headers]).to be_eql(
28
- 'Rack.version' => true, 'Rack.input' => true, 'Rack.errors' => true,
29
- 'Rack.multithread' => true, 'Rack.multiprocess' => true, 'Rack.run-Once' => true,
30
- 'Request-Method' => true, 'Server-Name' => true, 'Server-Port' => true,
31
- 'Query-String' => true, 'Path-Info' => true, 'Rack.url-Scheme' => true,
32
- 'Https' => true, 'Script-Name' => true, 'Content-Length' => true,
33
- 'X-Forwarded-For' => '1.2.3.4', 'Accept-Language' => 'en', 'User-Agent' => 'test',
34
- 'Rack.request.cookie-Hash' => true, 'Rack.request.cookie-String' => true,
29
+ 'X-Forwarded-For' => '1.2.3.4',
30
+ 'Accept-Language' => 'en',
31
+ 'User-Agent' => 'test',
32
+ 'Content-Length' => '0',
35
33
  'Cookie' => true
36
34
  )
37
- }
35
+ end
36
+
38
37
  it { expect(default_context[:ip]).to be_eql(ip) }
39
38
  it { expect(default_context[:library][:name]).to be_eql('castle-rb') }
40
39
  it { expect(default_context[:library][:version]).to be_eql(version) }
@@ -1,32 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::Extractors::Headers do
4
- subject(:extractor) { described_class.new(request) }
4
+ subject(:headers) { described_class.new(request).call }
5
5
 
6
6
  let(:client_id) { 'abcd' }
7
7
  let(:env) do
8
- Rack::MockRequest.env_for('/',
9
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
10
- 'HTTP_OK' => 'OK',
11
- 'TEST' => '1',
12
- 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh")
8
+ Rack::MockRequest.env_for(
9
+ '/',
10
+ 'Action-Dispatch.request.content-Type' => 'application/json',
11
+ 'HTTP_AUTHORIZATION' => 'Basic 123456',
12
+ 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh",
13
+ 'HTTP_OK' => 'OK',
14
+ 'HTTP_ACCEPT' => 'application/json',
15
+ 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
16
+ 'HTTP_USER_AGENT' => 'Mozilla 1234',
17
+ 'TEST' => '1'
18
+ )
13
19
  end
14
20
  let(:request) { Rack::Request.new(env) }
15
21
 
16
- describe 'extract http headers with whitelisted and blacklisted support' do
22
+ context 'when whitelist is not set in the configuration' do
23
+ it do
24
+ is_expected.to eq('Accept' => 'application/json',
25
+ 'Authorization' => true,
26
+ 'Cookie' => true,
27
+ 'Content-Length' => '0',
28
+ 'Ok' => 'OK',
29
+ 'User-Agent' => 'Mozilla 1234',
30
+ 'X-Forwarded-For' => '1.2.3.4')
31
+ end
32
+ end
33
+
34
+ context 'when whitelist is set in the configuration' do
35
+ before { Castle.config.whitelisted = %w[Accept OK] }
36
+
37
+ it do
38
+ is_expected.to eq('Accept' => 'application/json',
39
+ 'Authorization' => true,
40
+ 'Cookie' => true,
41
+ 'Content-Length' => true,
42
+ 'Ok' => 'OK',
43
+ 'User-Agent' => 'Mozilla 1234',
44
+ 'X-Forwarded-For' => true)
45
+ end
46
+ end
47
+
48
+ context 'when blacklist is set in the configuration' do
49
+ context 'with a User-Agent' do
50
+ before { Castle.config.blacklisted = %w[User-Agent] }
51
+
52
+ it do
53
+ is_expected.to eq('Accept' => 'application/json',
54
+ 'Authorization' => true,
55
+ 'Cookie' => true,
56
+ 'Content-Length' => '0',
57
+ 'Ok' => 'OK',
58
+ 'User-Agent' => 'Mozilla 1234',
59
+ 'X-Forwarded-For' => '1.2.3.4')
60
+ end
61
+ end
62
+
63
+ context 'with a different header' do
64
+ before { Castle.config.blacklisted = %w[Accept] }
65
+
66
+ it do
67
+ is_expected.to eq('Accept' => true,
68
+ 'Authorization' => true,
69
+ 'Cookie' => true,
70
+ 'Content-Length' => '0',
71
+ 'Ok' => 'OK',
72
+ 'User-Agent' => 'Mozilla 1234',
73
+ 'X-Forwarded-For' => '1.2.3.4')
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when a header is both whitelisted and blacklisted' do
17
79
  before do
18
- Castle.config.whitelisted += ['TEST']
80
+ Castle.config.whitelisted = %w[Accept]
81
+ Castle.config.blacklisted = %w[Accept]
19
82
  end
83
+
20
84
  it do
21
- expect(extractor.call).to eql(
22
- 'Test' => '1', 'Ok' => true, 'Rack.version' => true,
23
- 'Rack.input' => true, 'Rack.errors' => true, 'Rack.multithread' => true,
24
- 'Rack.multiprocess' => true, 'Rack.run-Once' => true, 'Request-Method' => true,
25
- 'Server-Name' => true, 'Server-Port' => true, 'Query-String' => true,
26
- 'Path-Info' => true, 'Rack.url-Scheme' => true, 'Https' => true,
27
- 'Script-Name' => true, 'Content-Length' => true, 'X-Forwarded-For' => '1.2.3.4',
28
- 'Cookie' => true
29
- )
85
+ expect(headers['Accept']).to eq(true)
30
86
  end
31
87
  end
32
88
  end
@@ -12,11 +12,14 @@ Coveralls.wear!
12
12
 
13
13
  require 'castle'
14
14
 
15
- Castle.configure do |config|
16
- config.api_secret = 'secret'
17
- end
18
-
19
15
  WebMock.disable_net_connect!(allow_localhost: true)
20
16
 
21
17
  RSpec.configure do |config|
18
+ config.before do
19
+ Castle.instance_variable_set(:@configuration, Castle::Configuration.new)
20
+
21
+ Castle.configure do |cfg|
22
+ cfg.api_secret = 'secret'
23
+ end
24
+ end
22
25
  end
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: 3.6.0
4
+ version: 3.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Brissmyr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-07 00:00:00.000000000 Z
11
+ date: 2020-01-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Castle protects your users from account compromise
14
14
  email: johan@castle.io