castle-rb 3.3.1 → 3.4.0.pre.rc.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 +4 -4
- data/lib/castle.rb +1 -0
- data/lib/castle/client.rb +8 -0
- data/lib/castle/commands/impersonate.rb +40 -0
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/client_spec.rb +34 -15
- data/spec/lib/castle/commands/authenticate_spec.rb +2 -7
- data/spec/lib/castle/commands/identify_spec.rb +2 -7
- data/spec/lib/castle/commands/impersonate_spec.rb +99 -0
- data/spec/lib/castle/commands/track_spec.rb +2 -7
- data/spec/lib/castle/utils/timestamp_spec.rb +2 -7
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ccb1875a2285b0243e021b7af9c65d9e85ca3339a9a9545a005cb7647d1c947
|
4
|
+
data.tar.gz: 050dc35cdcea679018cdd5d590e05a12248544c7f6de90651134d6ab1348baad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35e8261649b45f2a60cc39c0e8e2824d23d5f42aeee081c85c2f009588c3e127e767610d6c7ed39cfeed932009c213f85c42c1f95072df4588d93937d61d32a6
|
7
|
+
data.tar.gz: 1a88b649dc9b80208e26368f41fe28d19e84b9614b6ca87f36c960e3c83199061e341d77fe7a58946927080f753008a654ef915108d41691d109fb254f7b9cd8
|
data/lib/castle.rb
CHANGED
@@ -19,6 +19,7 @@ require 'castle/commands/identify'
|
|
19
19
|
require 'castle/commands/authenticate'
|
20
20
|
require 'castle/commands/track'
|
21
21
|
require 'castle/commands/review'
|
22
|
+
require 'castle/commands/impersonate'
|
22
23
|
require 'castle/configuration'
|
23
24
|
require 'castle/failover_auth_response'
|
24
25
|
require 'castle/client'
|
data/lib/castle/client.rb
CHANGED
@@ -66,6 +66,14 @@ module Castle
|
|
66
66
|
@api.request(command)
|
67
67
|
end
|
68
68
|
|
69
|
+
def impersonate(options = {})
|
70
|
+
options = Castle::Utils.deep_symbolize_keys(options || {})
|
71
|
+
|
72
|
+
return unless tracked?
|
73
|
+
command = Castle::Commands::Impersonate.new(@context).build(options)
|
74
|
+
@api.request(command)
|
75
|
+
end
|
76
|
+
|
69
77
|
def disable_tracking
|
70
78
|
@do_not_track = true
|
71
79
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
module Commands
|
5
|
+
# builder for impersonate command
|
6
|
+
class Impersonate
|
7
|
+
def initialize(context)
|
8
|
+
@context = context
|
9
|
+
end
|
10
|
+
|
11
|
+
def build(options = {})
|
12
|
+
validate!(options)
|
13
|
+
context = ContextMerger.call(@context, options[:context])
|
14
|
+
context = ContextSanitizer.call(context)
|
15
|
+
|
16
|
+
validate_context!(context)
|
17
|
+
|
18
|
+
Castle::Command.new('impersonate',
|
19
|
+
options.merge(context: context),
|
20
|
+
:post)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def validate!(options)
|
26
|
+
%i[user_id].each do |key|
|
27
|
+
next unless options[key].to_s.empty?
|
28
|
+
raise Castle::InvalidParametersError, "#{key} is missing or empty"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_context!(context)
|
33
|
+
%i[user_agent ip].each do |key|
|
34
|
+
next unless context[key].to_s.empty?
|
35
|
+
raise Castle::InvalidParametersError, "#{key} is missing or empty"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/castle/version.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
describe Castle::Client do
|
4
4
|
let(:ip) { '1.2.3.4' }
|
5
5
|
let(:cookie_id) { 'abcd' }
|
6
|
+
let(:ua) { 'Chrome' }
|
6
7
|
let(:env) do
|
7
8
|
Rack::MockRequest.env_for(
|
8
9
|
'/',
|
10
|
+
'HTTP_USER_AGENT' => ua,
|
9
11
|
'HTTP_X_FORWARDED_FOR' => ip,
|
10
12
|
'HTTP_COOKIE' => "__cid=#{cookie_id};other=efgh"
|
11
13
|
)
|
@@ -18,13 +20,14 @@ describe Castle::Client do
|
|
18
20
|
end
|
19
21
|
let(:client_with_no_timestamp) { described_class.new(request_to_context) }
|
20
22
|
|
21
|
-
let(:headers) { { 'X-Forwarded-For' => ip.to_s } }
|
23
|
+
let(:headers) { { 'X-Forwarded-For' => ip.to_s, 'User-Agent' => ua } }
|
22
24
|
let(:context) do
|
23
25
|
{
|
24
26
|
client_id: 'abcd',
|
25
27
|
active: true,
|
26
28
|
origin: 'web',
|
27
|
-
|
29
|
+
user_agent: ua,
|
30
|
+
headers: { 'X-Forwarded-For': ip.to_s, 'User-Agent': ua },
|
28
31
|
ip: ip,
|
29
32
|
library: { name: 'castle-rb', version: '2.2.0' }
|
30
33
|
}
|
@@ -42,9 +45,7 @@ describe Castle::Client do
|
|
42
45
|
).to_return(status: 200, body: '{}', headers: {})
|
43
46
|
end
|
44
47
|
|
45
|
-
after
|
46
|
-
Timecop.return
|
47
|
-
end
|
48
|
+
after { Timecop.return }
|
48
49
|
|
49
50
|
describe 'parses the request' do
|
50
51
|
before do
|
@@ -72,6 +73,24 @@ describe Castle::Client do
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
76
|
+
describe 'impersonate' do
|
77
|
+
let(:impersonator) { 'test@castle.io' }
|
78
|
+
let(:request_body) do
|
79
|
+
{ user_id: '1234', impersonator: impersonator, context: context }
|
80
|
+
end
|
81
|
+
let(:options) { { user_id: '1234', impersonator: impersonator } }
|
82
|
+
|
83
|
+
before { client.impersonate(options) }
|
84
|
+
|
85
|
+
context 'when used with symbol keys' do
|
86
|
+
it do
|
87
|
+
assert_requested :post, 'https://api.castle.io/v1/impersonate', times: 1 do |req|
|
88
|
+
JSON.parse(req.body) == JSON.parse(request_body.to_json)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
75
94
|
describe 'identify' do
|
76
95
|
let(:request_body) do
|
77
96
|
{ user_id: '1234', timestamp: time_auto,
|
@@ -80,7 +99,7 @@ describe Castle::Client do
|
|
80
99
|
|
81
100
|
before { client.identify(options) }
|
82
101
|
|
83
|
-
context 'symbol keys' do
|
102
|
+
context 'when used with symbol keys' do
|
84
103
|
let(:options) { { user_id: '1234', traits: { name: 'Jo' } } }
|
85
104
|
|
86
105
|
it do
|
@@ -119,7 +138,7 @@ describe Castle::Client do
|
|
119
138
|
end
|
120
139
|
end
|
121
140
|
|
122
|
-
context 'string keys' do
|
141
|
+
context 'when used with string keys' do
|
123
142
|
let(:options) { { 'user_id' => '1234', 'traits' => { 'name' => 'Jo' } } }
|
124
143
|
|
125
144
|
it do
|
@@ -138,7 +157,7 @@ describe Castle::Client do
|
|
138
157
|
timestamp: time_auto, sent_at: time_auto }
|
139
158
|
end
|
140
159
|
|
141
|
-
context 'symbol keys' do
|
160
|
+
context 'when used with symbol keys' do
|
142
161
|
before { request_response }
|
143
162
|
|
144
163
|
it do
|
@@ -177,7 +196,7 @@ describe Castle::Client do
|
|
177
196
|
end
|
178
197
|
end
|
179
198
|
|
180
|
-
context 'string keys' do
|
199
|
+
context 'when used with string keys' do
|
181
200
|
let(:options) { { 'event' => '$login.succeeded', 'user_id' => '1234' } }
|
182
201
|
|
183
202
|
before { request_response }
|
@@ -189,7 +208,7 @@ describe Castle::Client do
|
|
189
208
|
end
|
190
209
|
end
|
191
210
|
|
192
|
-
context 'tracking enabled' do
|
211
|
+
context 'when tracking enabled' do
|
193
212
|
before { request_response }
|
194
213
|
|
195
214
|
it do
|
@@ -202,7 +221,7 @@ describe Castle::Client do
|
|
202
221
|
it { expect(request_response[:failover_reason]).to be_nil }
|
203
222
|
end
|
204
223
|
|
205
|
-
context 'tracking disabled' do
|
224
|
+
context 'when tracking disabled' do
|
206
225
|
before do
|
207
226
|
client.disable_tracking
|
208
227
|
request_response
|
@@ -260,7 +279,7 @@ describe Castle::Client do
|
|
260
279
|
|
261
280
|
before { client.track(options) }
|
262
281
|
|
263
|
-
context 'symbol keys' do
|
282
|
+
context 'when used with symbol keys' do
|
264
283
|
let(:options) { { event: '$login.succeeded', user_id: '1234' } }
|
265
284
|
|
266
285
|
it do
|
@@ -299,7 +318,7 @@ describe Castle::Client do
|
|
299
318
|
end
|
300
319
|
end
|
301
320
|
|
302
|
-
context 'string keys' do
|
321
|
+
context 'when used with string keys' do
|
303
322
|
let(:options) { { 'event' => '$login.succeeded', 'user_id' => '1234' } }
|
304
323
|
|
305
324
|
it do
|
@@ -311,13 +330,13 @@ describe Castle::Client do
|
|
311
330
|
end
|
312
331
|
|
313
332
|
describe 'tracked?' do
|
314
|
-
context 'off' do
|
333
|
+
context 'when off' do
|
315
334
|
before { client.disable_tracking }
|
316
335
|
|
317
336
|
it { expect(client).not_to be_tracked }
|
318
337
|
end
|
319
338
|
|
320
|
-
context 'on' do
|
339
|
+
context 'when on' do
|
321
340
|
before { client.enable_tracking }
|
322
341
|
|
323
342
|
it { expect(client).to be_tracked }
|
@@ -9,13 +9,8 @@ describe Castle::Commands::Authenticate do
|
|
9
9
|
let(:time_now) { Time.now }
|
10
10
|
let(:time_auto) { time_now.utc.iso8601(3) }
|
11
11
|
|
12
|
-
before
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
after do
|
17
|
-
Timecop.return
|
18
|
-
end
|
12
|
+
before { Timecop.freeze(time_now) }
|
13
|
+
after { Timecop.return }
|
19
14
|
|
20
15
|
describe '.build' do
|
21
16
|
subject(:command) { instance.build(payload) }
|
@@ -9,13 +9,8 @@ describe Castle::Commands::Identify do
|
|
9
9
|
let(:time_now) { Time.now }
|
10
10
|
let(:time_auto) { time_now.utc.iso8601(3) }
|
11
11
|
|
12
|
-
before
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
after do
|
17
|
-
Timecop.return
|
18
|
-
end
|
12
|
+
before { Timecop.freeze(time_now) }
|
13
|
+
after { Timecop.return }
|
19
14
|
|
20
15
|
describe '.build' do
|
21
16
|
subject(:command) { instance.build(payload) }
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Castle::Commands::Impersonate do
|
4
|
+
subject(:instance) { described_class.new(context) }
|
5
|
+
|
6
|
+
let(:context) { { user_agent: 'test', ip: '127.0.0.1', client_id: 'test' } }
|
7
|
+
let(:impersonator) { 'test@castle.io' }
|
8
|
+
let(:default_payload) { { user_id: '1234' } }
|
9
|
+
|
10
|
+
let(:time_now) { Time.now }
|
11
|
+
let(:time_auto) { time_now.utc.iso8601(3) }
|
12
|
+
|
13
|
+
before { Timecop.freeze(time_now) }
|
14
|
+
after { Timecop.return }
|
15
|
+
|
16
|
+
describe '.build' do
|
17
|
+
subject(:command) { instance.build(payload) }
|
18
|
+
|
19
|
+
context 'with simple merger' do
|
20
|
+
let(:payload) { default_payload.merge(context: { test: { test2: '1' } }) }
|
21
|
+
let(:command_data) do
|
22
|
+
default_payload.merge(
|
23
|
+
context: {
|
24
|
+
test: { test2: '1' },
|
25
|
+
user_agent: 'test',
|
26
|
+
ip: '127.0.0.1',
|
27
|
+
client_id: 'test'
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it { expect(command.method).to be_eql(:post) }
|
33
|
+
it { expect(command.path).to be_eql('impersonate') }
|
34
|
+
it { expect(command.data).to be_eql(command_data) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with impersonator' do
|
38
|
+
let(:payload) { default_payload.merge(impersonator: impersonator) }
|
39
|
+
let(:command_data) do
|
40
|
+
default_payload.merge(impersonator: impersonator, context: context)
|
41
|
+
end
|
42
|
+
|
43
|
+
it { expect(command.method).to be_eql(:post) }
|
44
|
+
it { expect(command.path).to be_eql('impersonate') }
|
45
|
+
it { expect(command.data).to be_eql(command_data) }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when active true' do
|
49
|
+
let(:payload) { default_payload.merge(context: { active: true }) }
|
50
|
+
let(:command_data) do
|
51
|
+
default_payload.merge(context: context.merge(active: true))
|
52
|
+
end
|
53
|
+
|
54
|
+
it { expect(command.method).to be_eql(:post) }
|
55
|
+
it { expect(command.path).to be_eql('impersonate') }
|
56
|
+
it { expect(command.data).to be_eql(command_data) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when active false' do
|
60
|
+
let(:payload) { default_payload.merge(context: { active: false }) }
|
61
|
+
let(:command_data) do
|
62
|
+
default_payload.merge(context: context.merge(active: false))
|
63
|
+
end
|
64
|
+
|
65
|
+
it { expect(command.method).to be_eql(:post) }
|
66
|
+
it { expect(command.path).to be_eql('impersonate') }
|
67
|
+
it { expect(command.data).to be_eql(command_data) }
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when active string' do
|
71
|
+
let(:payload) { default_payload.merge(context: { active: 'string' }) }
|
72
|
+
let(:command_data) { default_payload.merge(context: context) }
|
73
|
+
|
74
|
+
it { expect(command.method).to be_eql(:post) }
|
75
|
+
it { expect(command.path).to be_eql('impersonate') }
|
76
|
+
it { expect(command.data).to be_eql(command_data) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#validate!' do
|
81
|
+
subject(:validate!) { instance.build(payload) }
|
82
|
+
|
83
|
+
context 'when user_id not present' do
|
84
|
+
let(:payload) { {} }
|
85
|
+
|
86
|
+
it do
|
87
|
+
expect do
|
88
|
+
validate!
|
89
|
+
end.to raise_error(Castle::InvalidParametersError, 'user_id is missing or empty')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when user_id present' do
|
94
|
+
let(:payload) { { user_id: '1234' } }
|
95
|
+
|
96
|
+
it { expect { validate! }.not_to raise_error }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -9,13 +9,8 @@ describe Castle::Commands::Track do
|
|
9
9
|
let(:time_now) { Time.now }
|
10
10
|
let(:time_auto) { time_now.utc.iso8601(3) }
|
11
11
|
|
12
|
-
before
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
after do
|
17
|
-
Timecop.return
|
18
|
-
end
|
12
|
+
before { Timecop.freeze(time_now) }
|
13
|
+
after { Timecop.return }
|
19
14
|
|
20
15
|
describe '#build' do
|
21
16
|
subject(:command) { instance.build(payload) }
|
@@ -6,13 +6,8 @@ describe Castle::Utils::Timestamp do
|
|
6
6
|
let(:time_string) { '2018-01-10T14:14:24.407Z' }
|
7
7
|
let(:time) { Time.parse(time_string) }
|
8
8
|
|
9
|
-
before
|
10
|
-
|
11
|
-
end
|
12
|
-
|
13
|
-
after do
|
14
|
-
Timecop.return
|
15
|
-
end
|
9
|
+
before { Timecop.freeze(time) }
|
10
|
+
after { Timecop.return }
|
16
11
|
|
17
12
|
describe '#call' do
|
18
13
|
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: 3.
|
4
|
+
version: 3.4.0.pre.rc.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: 2018-01-
|
11
|
+
date: 2018-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Castle protects your users from account compromise
|
14
14
|
email: johan@castle.io
|
@@ -24,6 +24,7 @@ files:
|
|
24
24
|
- lib/castle/command.rb
|
25
25
|
- lib/castle/commands/authenticate.rb
|
26
26
|
- lib/castle/commands/identify.rb
|
27
|
+
- lib/castle/commands/impersonate.rb
|
27
28
|
- lib/castle/commands/review.rb
|
28
29
|
- lib/castle/commands/track.rb
|
29
30
|
- lib/castle/configuration.rb
|
@@ -54,6 +55,7 @@ files:
|
|
54
55
|
- spec/lib/castle/command_spec.rb
|
55
56
|
- spec/lib/castle/commands/authenticate_spec.rb
|
56
57
|
- spec/lib/castle/commands/identify_spec.rb
|
58
|
+
- spec/lib/castle/commands/impersonate_spec.rb
|
57
59
|
- spec/lib/castle/commands/review_spec.rb
|
58
60
|
- spec/lib/castle/commands/track_spec.rb
|
59
61
|
- spec/lib/castle/configuration_spec.rb
|
@@ -90,9 +92,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
92
|
version: '0'
|
91
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
94
|
requirements:
|
93
|
-
- - "
|
95
|
+
- - ">"
|
94
96
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
97
|
+
version: 1.3.1
|
96
98
|
requirements: []
|
97
99
|
rubyforge_project:
|
98
100
|
rubygems_version: 2.7.4
|
@@ -119,6 +121,7 @@ test_files:
|
|
119
121
|
- spec/lib/castle/commands/review_spec.rb
|
120
122
|
- spec/lib/castle/commands/authenticate_spec.rb
|
121
123
|
- spec/lib/castle/commands/track_spec.rb
|
124
|
+
- spec/lib/castle/commands/impersonate_spec.rb
|
122
125
|
- spec/lib/castle/commands/identify_spec.rb
|
123
126
|
- spec/lib/castle/context_merger_spec.rb
|
124
127
|
- spec/lib/castle/extractors/ip_spec.rb
|