castle-rb 3.3.1 → 3.4.0.pre.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|