castle-rb 3.6.2 → 4.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -5
  3. data/lib/castle.rb +3 -1
  4. data/lib/castle/client.rb +20 -15
  5. data/lib/castle/configuration.rb +30 -3
  6. data/lib/castle/context/default.rb +36 -18
  7. data/lib/castle/context/sanitizer.rb +1 -0
  8. data/lib/castle/events.rb +49 -0
  9. data/lib/castle/extractors/client_id.rb +7 -3
  10. data/lib/castle/extractors/headers.rb +24 -30
  11. data/lib/castle/extractors/ip.rb +49 -5
  12. data/lib/castle/header_filter.rb +35 -0
  13. data/lib/castle/header_formatter.rb +3 -0
  14. data/lib/castle/validators/not_supported.rb +1 -0
  15. data/lib/castle/validators/present.rb +1 -0
  16. data/lib/castle/version.rb +1 -1
  17. data/spec/integration/rails/rails_spec.rb +61 -0
  18. data/spec/integration/rails/support/all.rb +6 -0
  19. data/spec/integration/rails/support/application.rb +15 -0
  20. data/spec/integration/rails/support/home_controller.rb +21 -0
  21. data/spec/lib/castle/api/request/build_spec.rb +4 -2
  22. data/spec/lib/castle/api/request_spec.rb +1 -0
  23. data/spec/lib/castle/client_spec.rb +5 -3
  24. data/spec/lib/castle/commands/authenticate_spec.rb +1 -0
  25. data/spec/lib/castle/commands/identify_spec.rb +1 -0
  26. data/spec/lib/castle/commands/impersonate_spec.rb +1 -0
  27. data/spec/lib/castle/commands/track_spec.rb +1 -0
  28. data/spec/lib/castle/configuration_spec.rb +18 -2
  29. data/spec/lib/castle/context/default_spec.rb +10 -11
  30. data/spec/lib/castle/events_spec.rb +5 -0
  31. data/spec/lib/castle/extractors/client_id_spec.rb +2 -1
  32. data/spec/lib/castle/extractors/headers_spec.rb +66 -49
  33. data/spec/lib/castle/extractors/ip_spec.rb +56 -12
  34. data/spec/lib/castle/header_filter_spec.rb +38 -0
  35. data/spec/lib/castle/header_formatter_spec.rb +1 -1
  36. data/spec/lib/castle/utils/cloner_spec.rb +1 -0
  37. data/spec/lib/castle/utils/timestamp_spec.rb +3 -4
  38. data/spec/lib/castle/utils_spec.rb +1 -1
  39. data/spec/lib/castle/validators/not_supported_spec.rb +1 -3
  40. metadata +31 -3
@@ -1,78 +1,95 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::Extractors::Headers do
4
- subject(:headers) { described_class.new(request).call }
4
+ subject(:headers) { described_class.new(formatted_headers).call }
5
5
 
6
6
  let(:client_id) { 'abcd' }
7
- let(:env) do
8
- result = 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_ACCEPT' => 'application/json',
14
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
15
- 'HTTP_USER_AGENT' => 'Mozilla 1234',
16
- 'TEST' => '1'
17
- )
18
- result[:HTTP_OK] = 'OK'
19
- result
7
+ let(:formatted_headers) do
8
+ {
9
+ 'Content-Length' => '0',
10
+ 'Authorization' => 'Basic 123456',
11
+ 'Cookie' => '__cid=abcd;other=efgh',
12
+ 'Ok' => 'OK',
13
+ 'Accept' => 'application/json',
14
+ 'X-Forwarded-For' => '1.2.3.4',
15
+ 'User-Agent' => 'Mozilla 1234'
16
+ }
17
+ end
18
+
19
+ after do
20
+ Castle.config.whitelisted = %w[]
21
+ Castle.config.blacklisted = %w[]
20
22
  end
21
- let(:request) { Rack::Request.new(env) }
22
23
 
23
24
  context 'when whitelist is not set in the configuration' do
24
- it do
25
- is_expected.to eq('Accept' => 'application/json',
26
- 'Authorization' => true,
27
- 'Cookie' => true,
28
- 'Content-Length' => '0',
29
- 'Ok' => 'OK',
30
- 'User-Agent' => 'Mozilla 1234',
31
- 'X-Forwarded-For' => '1.2.3.4')
25
+ let(:result) do
26
+ {
27
+ 'Accept' => 'application/json',
28
+ 'Authorization' => true,
29
+ 'Cookie' => true,
30
+ 'Content-Length' => '0',
31
+ 'Ok' => 'OK',
32
+ 'User-Agent' => 'Mozilla 1234',
33
+ 'X-Forwarded-For' => '1.2.3.4'
34
+ }
32
35
  end
36
+
37
+ it { expect(headers).to eq(result) }
33
38
  end
34
39
 
35
40
  context 'when whitelist is set in the configuration' do
36
41
  before { Castle.config.whitelisted = %w[Accept OK] }
37
42
 
38
- it do
39
- is_expected.to eq('Accept' => 'application/json',
40
- 'Authorization' => true,
41
- 'Cookie' => true,
42
- 'Content-Length' => true,
43
- 'Ok' => 'OK',
44
- 'User-Agent' => 'Mozilla 1234',
45
- 'X-Forwarded-For' => true)
43
+ let(:result) do
44
+ {
45
+ 'Accept' => 'application/json',
46
+ 'Authorization' => true,
47
+ 'Cookie' => true,
48
+ 'Content-Length' => true,
49
+ 'Ok' => 'OK',
50
+ 'User-Agent' => 'Mozilla 1234',
51
+ 'X-Forwarded-For' => true
52
+ }
46
53
  end
54
+
55
+ it { expect(headers).to eq(result) }
47
56
  end
48
57
 
49
58
  context 'when blacklist is set in the configuration' do
50
59
  context 'with a User-Agent' do
60
+ let(:result) do
61
+ {
62
+ 'Accept' => 'application/json',
63
+ 'Authorization' => true,
64
+ 'Cookie' => true,
65
+ 'Content-Length' => '0',
66
+ 'Ok' => 'OK',
67
+ 'User-Agent' => 'Mozilla 1234',
68
+ 'X-Forwarded-For' => '1.2.3.4'
69
+ }
70
+ end
71
+
51
72
  before { Castle.config.blacklisted = %w[User-Agent] }
52
73
 
53
- it do
54
- is_expected.to eq('Accept' => 'application/json',
55
- 'Authorization' => true,
56
- 'Cookie' => true,
57
- 'Content-Length' => '0',
58
- 'Ok' => 'OK',
59
- 'User-Agent' => 'Mozilla 1234',
60
- 'X-Forwarded-For' => '1.2.3.4')
61
- end
74
+ it { expect(headers).to eq(result) }
62
75
  end
63
76
 
64
77
  context 'with a different header' do
78
+ let(:result) do
79
+ {
80
+ 'Accept' => true,
81
+ 'Authorization' => true,
82
+ 'Cookie' => true,
83
+ 'Content-Length' => '0',
84
+ 'Ok' => 'OK',
85
+ 'User-Agent' => 'Mozilla 1234',
86
+ 'X-Forwarded-For' => '1.2.3.4'
87
+ }
88
+ end
89
+
65
90
  before { Castle.config.blacklisted = %w[Accept] }
66
91
 
67
- it do
68
- is_expected.to eq('Accept' => true,
69
- 'Authorization' => true,
70
- 'Cookie' => true,
71
- 'Content-Length' => '0',
72
- 'Ok' => 'OK',
73
- 'User-Agent' => 'Mozilla 1234',
74
- 'X-Forwarded-For' => '1.2.3.4')
75
- end
92
+ it { expect(headers).to eq(result) }
76
93
  end
77
94
  end
78
95
 
@@ -1,27 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::Extractors::IP do
4
- subject(:extractor) { described_class.new(request) }
5
-
6
- let(:request) { Rack::Request.new(env) }
4
+ subject(:extractor) { described_class.new(headers) }
7
5
 
8
6
  describe 'ip' do
9
7
  context 'when regular ip' do
10
- let(:env) { Rack::MockRequest.env_for('/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5') }
8
+ let(:headers) { { 'X-Forwarded-For' => '1.2.3.5' } }
11
9
 
12
10
  it { expect(extractor.call).to eql('1.2.3.5') }
13
11
  end
14
12
 
15
- context 'when cf remote_ip' do
16
- let(:env) do
17
- Rack::MockRequest.env_for(
18
- '/',
19
- 'HTTP_CF_CONNECTING_IP' => '1.2.3.4',
20
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.5'
21
- )
13
+ context 'when we need to use other ip header' do
14
+ let(:headers) do
15
+ { 'Cf-Connecting-Ip' => '1.2.3.4', 'X-Forwarded-For' => '1.1.1.1, 1.2.2.2, 1.2.3.5' }
16
+ end
17
+
18
+ context 'with uppercase format' do
19
+ before { Castle.config.ip_headers = %w[CF_CONNECTING_IP] }
20
+
21
+ it { expect(extractor.call).to eql('1.2.3.4') }
22
+ end
23
+
24
+ context 'with regular format' do
25
+ before { Castle.config.ip_headers = %w[Cf-Connecting-Ip] }
26
+
27
+ it { expect(extractor.call).to eql('1.2.3.4') }
28
+ end
29
+
30
+ context 'with value from trusted proxies' do
31
+ before do
32
+ Castle.config.ip_headers = %w[Cf-Connecting-Ip]
33
+ Castle.config.trusted_proxies = %w[1.2.3.4]
34
+ end
35
+
36
+ it { expect(extractor.call).to eql('1.2.3.5') }
22
37
  end
38
+ end
23
39
 
24
- it { expect(extractor.call).to eql('1.2.3.4') }
40
+ context 'with all the trusted proxies' do
41
+ let(:http_x_header) do
42
+ '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1,::1,fd00::,localhost,unix,unix:/tmp/sock'
43
+ end
44
+
45
+ let(:headers) { { 'Remote-Addr' => '127.0.0.1', 'X-Forwarded-For' => http_x_header } }
46
+
47
+ it 'fallbacks to remote_addr even if trusted proxy' do
48
+ expect(extractor.call).to eql('127.0.0.1')
49
+ end
50
+ end
51
+
52
+ context 'when list of not trusted ips provided in X_FORWARDED_FOR' do
53
+ let(:headers) do
54
+ {
55
+ 'X-Forwarded-For' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
56
+ 'Client-Ip' => '6.6.6.6'
57
+ }
58
+ end
59
+
60
+ it 'does not allow to spoof ip' do
61
+ expect(extractor.call).to eql('2.2.2.3')
62
+ end
63
+
64
+ context 'when marked 2.2.2.3 as trusted proxy' do
65
+ before { Castle.config.trusted_proxies = [/^2.2.2.\d$/] }
66
+
67
+ it { expect(extractor.call).to eql('6.6.6.6') }
68
+ end
25
69
  end
26
70
  end
27
71
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Castle::HeaderFilter do
4
+ subject(:headers) { described_class.new(request).call }
5
+
6
+ let(:client_id) { 'abcd' }
7
+ let(:env) do
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
+ 'REMOTE_ADDR' => '1.2.3.4'
19
+ )
20
+ end
21
+ let(:filtered) do
22
+ {
23
+ 'Accept' => 'application/json',
24
+ 'Authorization' => 'Basic 123456',
25
+ 'Cookie' => "__cid=#{client_id};other=efgh",
26
+ 'Content-Length' => '0',
27
+ 'Ok' => 'OK',
28
+ 'User-Agent' => 'Mozilla 1234',
29
+ 'Remote-Addr' => '1.2.3.4',
30
+ 'X-Forwarded-For' => '1.2.3.4'
31
+ }
32
+ end
33
+ let(:request) { Rack::Request.new(env) }
34
+
35
+ context 'with list of header' do
36
+ it { expect(headers).to eq(filtered) }
37
+ end
38
+ end
@@ -19,7 +19,7 @@ describe Castle::HeaderFormatter do
19
19
  expect(formatter.call('httpX_teST')).to be_eql('Httpx-Test')
20
20
  end
21
21
 
22
- it 'removes HTTP_' do
22
+ it 'capitalizes' do
23
23
  expect(formatter.call(:clearance)).to be_eql('Clearance')
24
24
  end
25
25
  end
@@ -10,6 +10,7 @@ describe Castle::Utils::Cloner do
10
10
  let(:cloned) { cloner.call(first) }
11
11
 
12
12
  before { cloned }
13
+
13
14
  it do
14
15
  nested[:test] = 'sample'
15
16
  expect(cloned).to be_eql(result)
@@ -1,17 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::Utils::Timestamp do
4
- subject { described_class.call }
4
+ subject(:timestamp) { described_class.call }
5
5
 
6
6
  let(:time_string) { '2018-01-10T14:14:24.407Z' }
7
7
  let(:time) { Time.parse(time_string) }
8
8
 
9
9
  before { Timecop.freeze(time) }
10
+
10
11
  after { Timecop.return }
11
12
 
12
13
  describe '#call' do
13
- it do
14
- is_expected.to eql(time_string)
15
- end
14
+ it { expect(timestamp).to eql(time_string) }
16
15
  end
17
16
  end
@@ -48,7 +48,7 @@ describe Castle::Utils do
48
48
  end
49
49
  end
50
50
 
51
- describe '#deep_symbolize_keys' do
51
+ describe '#cloner' do
52
52
  subject { described_class.deep_symbolize_keys!(Castle::Utils::Cloner.call(hash)) }
53
53
 
54
54
  context 'when nested_symbols' do
@@ -8,9 +8,7 @@ describe Castle::Validators::NotSupported do
8
8
  let(:keys) { %i[first second] }
9
9
 
10
10
  it do
11
- expect do
12
- call
13
- end.to raise_error(
11
+ expect { call }.to raise_error(
14
12
  Castle::InvalidParametersError,
15
13
  'first is/are not supported'
16
14
  )
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castle-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.2
4
+ version: 4.0.0
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-04-24 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2020-03-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: appraisal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Castle protects your users from account compromise
14
28
  email: johan@castle.io
15
29
  executables: []
@@ -35,10 +49,12 @@ files:
35
49
  - lib/castle/context/merger.rb
36
50
  - lib/castle/context/sanitizer.rb
37
51
  - lib/castle/errors.rb
52
+ - lib/castle/events.rb
38
53
  - lib/castle/extractors/client_id.rb
39
54
  - lib/castle/extractors/headers.rb
40
55
  - lib/castle/extractors/ip.rb
41
56
  - lib/castle/failover_auth_response.rb
57
+ - lib/castle/header_filter.rb
42
58
  - lib/castle/header_formatter.rb
43
59
  - lib/castle/review.rb
44
60
  - lib/castle/secure_mode.rb
@@ -53,6 +69,10 @@ files:
53
69
  - lib/castle/validators/not_supported.rb
54
70
  - lib/castle/validators/present.rb
55
71
  - lib/castle/version.rb
72
+ - spec/integration/rails/rails_spec.rb
73
+ - spec/integration/rails/support/all.rb
74
+ - spec/integration/rails/support/application.rb
75
+ - spec/integration/rails/support/home_controller.rb
56
76
  - spec/lib/castle/api/request/build_spec.rb
57
77
  - spec/lib/castle/api/request_spec.rb
58
78
  - spec/lib/castle/api/response_spec.rb
@@ -68,9 +88,11 @@ files:
68
88
  - spec/lib/castle/context/default_spec.rb
69
89
  - spec/lib/castle/context/merger_spec.rb
70
90
  - spec/lib/castle/context/sanitizer_spec.rb
91
+ - spec/lib/castle/events_spec.rb
71
92
  - spec/lib/castle/extractors/client_id_spec.rb
72
93
  - spec/lib/castle/extractors/headers_spec.rb
73
94
  - spec/lib/castle/extractors/ip_spec.rb
95
+ - spec/lib/castle/header_filter_spec.rb
74
96
  - spec/lib/castle/header_formatter_spec.rb
75
97
  - spec/lib/castle/review_spec.rb
76
98
  - spec/lib/castle/secure_mode_spec.rb
@@ -108,6 +130,10 @@ specification_version: 4
108
130
  summary: Castle
109
131
  test_files:
110
132
  - spec/spec_helper.rb
133
+ - spec/integration/rails/support/application.rb
134
+ - spec/integration/rails/support/all.rb
135
+ - spec/integration/rails/support/home_controller.rb
136
+ - spec/integration/rails/rails_spec.rb
111
137
  - spec/lib/castle_spec.rb
112
138
  - spec/lib/castle/review_spec.rb
113
139
  - spec/lib/castle/client_spec.rb
@@ -125,6 +151,7 @@ test_files:
125
151
  - spec/lib/castle/api/request_spec.rb
126
152
  - spec/lib/castle/api/response_spec.rb
127
153
  - spec/lib/castle/api/request/build_spec.rb
154
+ - spec/lib/castle/header_filter_spec.rb
128
155
  - spec/lib/castle/commands/review_spec.rb
129
156
  - spec/lib/castle/commands/authenticate_spec.rb
130
157
  - spec/lib/castle/commands/track_spec.rb
@@ -137,3 +164,4 @@ test_files:
137
164
  - spec/lib/castle/extractors/client_id_spec.rb
138
165
  - spec/lib/castle/utils_spec.rb
139
166
  - spec/lib/castle/secure_mode_spec.rb
167
+ - spec/lib/castle/events_spec.rb