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 +4 -4
- data/README.md +17 -8
- data/lib/castle/configuration.rb +22 -17
- data/lib/castle/extractors/headers.rb +31 -9
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/client_spec.rb +1 -7
- data/spec/lib/castle/configuration_spec.rb +2 -18
- data/spec/lib/castle/context/default_spec.rb +10 -11
- data/spec/lib/castle/extractors/headers_spec.rb +73 -17
- data/spec/spec_helper.rb +7 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b34bb0722019eb7b8249772c5f05a0e51de7e230cb00cb12a12a1ccd5c7cb1f
|
4
|
+
data.tar.gz: 97c7084f179397163ccbf652f46651892f47fbfe17622bd8a2ed3c5388c93f05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
71
|
-
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
#
|
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
|
|
data/lib/castle/configuration.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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 =
|
40
|
-
self.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
|
-
@
|
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 |
|
16
|
-
|
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
|
19
|
-
acc[
|
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
|
-
|
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
|
data/lib/castle/version.rb
CHANGED
@@ -22,13 +22,7 @@ describe Castle::Client do
|
|
22
22
|
|
23
23
|
let(:headers) do
|
24
24
|
{
|
25
|
-
'
|
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(
|
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(
|
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
|
-
'
|
13
|
-
'
|
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
|
-
|
26
|
+
|
27
|
+
it do
|
27
28
|
expect(default_context[:headers]).to be_eql(
|
28
|
-
'
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
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(:
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
80
|
+
Castle.config.whitelisted = %w[Accept]
|
81
|
+
Castle.config.blacklisted = %w[Accept]
|
19
82
|
end
|
83
|
+
|
20
84
|
it do
|
21
|
-
expect(
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -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.
|
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-
|
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
|