Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## master

- [#101](github.com/castle/castle-ruby/pull/103) added `impersonate` method with `user_id`, `impersonator` and `context` options

## 3.3.1 (2018-01-22)

**Enhancements:**
Expand Down
1 change: 1 addition & 0 deletions lib/castle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
require 'castle/commands/authenticate'
require 'castle/commands/track'
require 'castle/commands/review'
require 'castle/commands/impersonate'
require 'castle/configuration'
require 'castle/failover_auth_response'
require 'castle/client'
Expand Down
8 changes: 8 additions & 0 deletions lib/castle/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ def track(options = {})
@api.request(command)
end

def impersonate(options = {})
options = Castle::Utils.deep_symbolize_keys(options || {})

return unless tracked?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this. Doesn't apply to impersonate

command = Castle::Commands::Impersonate.new(@context).build(options)
@api.request(command)
end

def disable_tracking
@do_not_track = true
end
Expand Down
40 changes: 40 additions & 0 deletions lib/castle/commands/impersonate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module Castle
module Commands
# builder for impersonate command
class Impersonate
def initialize(context)
@context = context
end

def build(options = {})
validate!(options)
context = ContextMerger.call(@context, options[:context])
context = ContextSanitizer.call(context)

validate_context!(context)

Castle::Command.new('impersonate',
options.merge(context: context),
:post)
end

private

def validate!(options)
%i[user_id].each do |key|
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From impersonate endpoint:

requires :user_id, type: String
requires :user_agent, type: String
requires :ip, type: String
optional :impersonator, type: String
optional :client_id, type: String

Please update validation

Copy link
Copy Markdown
Contributor Author

@bartes bartes Jan 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lluft - I'm changing the endpoint . It will look like this before merging this:

        requires :user_id, type: String
        optional :impersonator, type: String, desc: 'Impersonator email'
        optional :context, type: Hash, desc: 'Request context. (includes Ip, User-Agent, ClientId)'
        optional :timestamp, desc: 'Timestamp of the event', types: [String, Time]
        optional :sent_at, type: String
        optional :received_at, type: String

next unless options[key].to_s.empty?
raise Castle::InvalidParametersError, "#{key} is missing or empty"
end
end

def validate_context!(context)
%i[user_agent ip].each do |key|
next unless context[key].to_s.empty?
raise Castle::InvalidParametersError, "#{key} is missing or empty"
end
end
end
end
end
49 changes: 34 additions & 15 deletions spec/lib/castle/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
describe Castle::Client do
let(:ip) { '1.2.3.4' }
let(:cookie_id) { 'abcd' }
let(:ua) { 'Chrome' }
let(:env) do
Rack::MockRequest.env_for(
'/',
'HTTP_USER_AGENT' => ua,
'HTTP_X_FORWARDED_FOR' => ip,
'HTTP_COOKIE' => "__cid=#{cookie_id};other=efgh"
)
Expand All @@ -18,13 +20,14 @@
end
let(:client_with_no_timestamp) { described_class.new(request_to_context) }

let(:headers) { { 'X-Forwarded-For' => ip.to_s } }
let(:headers) { { 'X-Forwarded-For' => ip.to_s, 'User-Agent' => ua } }
let(:context) do
{
client_id: 'abcd',
active: true,
origin: 'web',
headers: { 'X-Forwarded-For': ip.to_s },
user_agent: ua,
headers: { 'X-Forwarded-For': ip.to_s, 'User-Agent': ua },
ip: ip,
library: { name: 'castle-rb', version: '2.2.0' }
}
Expand All @@ -42,9 +45,7 @@
).to_return(status: 200, body: '{}', headers: {})
end

after do
Timecop.return
end
after { Timecop.return }

describe 'parses the request' do
before do
Expand Down Expand Up @@ -72,6 +73,24 @@
end
end

describe 'impersonate' do
let(:impersonator) { 'test@castle.io' }
let(:request_body) do
{ user_id: '1234', impersonator: impersonator, context: context }
end
let(:options) { { user_id: '1234', impersonator: impersonator } }

before { client.impersonate(options) }

context 'when used with symbol keys' do
it do
assert_requested :post, 'https://api.castle.io/v1/impersonate', times: 1 do |req|
JSON.parse(req.body) == JSON.parse(request_body.to_json)
end
end
end
end

describe 'identify' do
let(:request_body) do
{ user_id: '1234', timestamp: time_auto,
Expand All @@ -80,7 +99,7 @@

before { client.identify(options) }

context 'symbol keys' do
context 'when used with symbol keys' do
let(:options) { { user_id: '1234', traits: { name: 'Jo' } } }

it do
Expand Down Expand Up @@ -119,7 +138,7 @@
end
end

context 'string keys' do
context 'when used with string keys' do
let(:options) { { 'user_id' => '1234', 'traits' => { 'name' => 'Jo' } } }

it do
Expand All @@ -138,7 +157,7 @@
timestamp: time_auto, sent_at: time_auto }
end

context 'symbol keys' do
context 'when used with symbol keys' do
before { request_response }

it do
Expand Down Expand Up @@ -177,7 +196,7 @@
end
end

context 'string keys' do
context 'when used with string keys' do
let(:options) { { 'event' => '$login.succeeded', 'user_id' => '1234' } }

before { request_response }
Expand All @@ -189,7 +208,7 @@
end
end

context 'tracking enabled' do
context 'when tracking enabled' do
before { request_response }

it do
Expand All @@ -202,7 +221,7 @@
it { expect(request_response[:failover_reason]).to be_nil }
end

context 'tracking disabled' do
context 'when tracking disabled' do
before do
client.disable_tracking
request_response
Expand Down Expand Up @@ -260,7 +279,7 @@

before { client.track(options) }

context 'symbol keys' do
context 'when used with symbol keys' do
let(:options) { { event: '$login.succeeded', user_id: '1234' } }

it do
Expand Down Expand Up @@ -299,7 +318,7 @@
end
end

context 'string keys' do
context 'when used with string keys' do
let(:options) { { 'event' => '$login.succeeded', 'user_id' => '1234' } }

it do
Expand All @@ -311,13 +330,13 @@
end

describe 'tracked?' do
context 'off' do
context 'when off' do
before { client.disable_tracking }

it { expect(client).not_to be_tracked }
end

context 'on' do
context 'when on' do
before { client.enable_tracking }

it { expect(client).to be_tracked }
Expand Down
9 changes: 2 additions & 7 deletions spec/lib/castle/commands/authenticate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@
let(:time_now) { Time.now }
let(:time_auto) { time_now.utc.iso8601(3) }

before do
Timecop.freeze(time_now)
end

after do
Timecop.return
end
before { Timecop.freeze(time_now) }
after { Timecop.return }

describe '.build' do
subject(:command) { instance.build(payload) }
Expand Down
9 changes: 2 additions & 7 deletions spec/lib/castle/commands/identify_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@
let(:time_now) { Time.now }
let(:time_auto) { time_now.utc.iso8601(3) }

before do
Timecop.freeze(time_now)
end

after do
Timecop.return
end
before { Timecop.freeze(time_now) }
after { Timecop.return }

describe '.build' do
subject(:command) { instance.build(payload) }
Expand Down
99 changes: 99 additions & 0 deletions spec/lib/castle/commands/impersonate_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

describe Castle::Commands::Impersonate do
subject(:instance) { described_class.new(context) }

let(:context) { { user_agent: 'test', ip: '127.0.0.1', client_id: 'test' } }
let(:impersonator) { 'test@castle.io' }
let(:default_payload) { { user_id: '1234' } }

let(:time_now) { Time.now }
let(:time_auto) { time_now.utc.iso8601(3) }

before { Timecop.freeze(time_now) }
after { Timecop.return }

describe '.build' do
subject(:command) { instance.build(payload) }

context 'with simple merger' do
let(:payload) { default_payload.merge(context: { test: { test2: '1' } }) }
let(:command_data) do
default_payload.merge(
context: {
test: { test2: '1' },
user_agent: 'test',
ip: '127.0.0.1',
client_id: 'test'
}
)
end

it { expect(command.method).to be_eql(:post) }
it { expect(command.path).to be_eql('impersonate') }
it { expect(command.data).to be_eql(command_data) }
end

context 'with impersonator' do
let(:payload) { default_payload.merge(impersonator: impersonator) }
let(:command_data) do
default_payload.merge(impersonator: impersonator, context: context)
end

it { expect(command.method).to be_eql(:post) }
it { expect(command.path).to be_eql('impersonate') }
it { expect(command.data).to be_eql(command_data) }
end

context 'when active true' do
let(:payload) { default_payload.merge(context: { active: true }) }
let(:command_data) do
default_payload.merge(context: context.merge(active: true))
end

it { expect(command.method).to be_eql(:post) }
it { expect(command.path).to be_eql('impersonate') }
it { expect(command.data).to be_eql(command_data) }
end

context 'when active false' do
let(:payload) { default_payload.merge(context: { active: false }) }
let(:command_data) do
default_payload.merge(context: context.merge(active: false))
end

it { expect(command.method).to be_eql(:post) }
it { expect(command.path).to be_eql('impersonate') }
it { expect(command.data).to be_eql(command_data) }
end

context 'when active string' do
let(:payload) { default_payload.merge(context: { active: 'string' }) }
let(:command_data) { default_payload.merge(context: context) }

it { expect(command.method).to be_eql(:post) }
it { expect(command.path).to be_eql('impersonate') }
it { expect(command.data).to be_eql(command_data) }
end
end

describe '#validate!' do
subject(:validate!) { instance.build(payload) }

context 'when user_id not present' do
let(:payload) { {} }

it do
expect do
validate!
end.to raise_error(Castle::InvalidParametersError, 'user_id is missing or empty')
end
end

context 'when user_id present' do
let(:payload) { { user_id: '1234' } }

it { expect { validate! }.not_to raise_error }
end
end
end
9 changes: 2 additions & 7 deletions spec/lib/castle/commands/track_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@
let(:time_now) { Time.now }
let(:time_auto) { time_now.utc.iso8601(3) }

before do
Timecop.freeze(time_now)
end

after do
Timecop.return
end
before { Timecop.freeze(time_now) }
after { Timecop.return }

describe '#build' do
subject(:command) { instance.build(payload) }
Expand Down
9 changes: 2 additions & 7 deletions spec/lib/castle/utils/timestamp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@
let(:time_string) { '2018-01-10T14:14:24.407Z' }
let(:time) { Time.parse(time_string) }

before do
Timecop.freeze(time)
end

after do
Timecop.return
end
before { Timecop.freeze(time) }
after { Timecop.return }

describe '#call' do
it do
Expand Down