castle-rb 4.0.0 → 4.1.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 +4 -4
- data/lib/castle/api.rb +12 -6
- data/lib/castle/api/request.rb +15 -10
- data/lib/castle/api/session.rb +39 -0
- data/lib/castle/client.rb +4 -4
- data/lib/castle/configuration.rb +8 -2
- data/lib/castle/context/default.rb +1 -1
- data/lib/castle/extractors/ip.rb +7 -1
- data/lib/castle/{header_filter.rb → headers_filter.rb} +7 -7
- data/lib/castle/headers_formatter.rb +22 -0
- data/lib/castle/version.rb +1 -1
- data/spec/lib/castle/api/request_spec.rb +42 -30
- data/spec/lib/castle/api_spec.rb +4 -4
- data/spec/lib/castle/configuration_spec.rb +3 -2
- data/spec/lib/castle/context/default_spec.rb +3 -2
- data/spec/lib/castle/extractors/client_id_spec.rb +1 -1
- data/spec/lib/castle/extractors/headers_spec.rb +0 -1
- data/spec/lib/castle/extractors/ip_spec.rb +5 -0
- data/spec/lib/castle/{header_filter_spec.rb → headers_filter_spec.rb} +4 -5
- data/spec/lib/castle/{header_formatter_spec.rb → headers_formatter_spec.rb} +2 -2
- data/spec/spec_helper.rb +1 -2
- metadata +9 -11
- data/lib/castle/api/request/build.rb +0 -27
- data/lib/castle/header_formatter.rb +0 -12
- data/spec/lib/castle/api/request/build_spec.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0d6aa4996eb091692c0a7dfcf9658b2b26fb011cfa94103604a79e60d401ac0
|
4
|
+
data.tar.gz: 8e7eb4c21cf3edd7a3845fc946857dd25ad1031d75dfa8cf8cb8b911b928eebe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bdf75588225fd282a36d1e37c1ee3eb69ecfab3f3e50a2c27d695f8c7c7ad1a2bccdb8b097dbe9ff361c17481c9449808d9c26adf56924eb249a18ffb2e9a71
|
7
|
+
data.tar.gz: '089b2507cd099a863b9592c8a194f5971f2126a7ff2892d0f211ace1a9b06261872a9a13d98a72b617f59815d70d87a4c49ca9e348c06031554a3a8cbe905140'
|
data/lib/castle.rb
CHANGED
@@ -29,15 +29,15 @@
|
|
29
29
|
castle/configuration
|
30
30
|
castle/failover_auth_response
|
31
31
|
castle/client
|
32
|
-
castle/
|
33
|
-
castle/
|
32
|
+
castle/headers_filter
|
33
|
+
castle/headers_formatter
|
34
34
|
castle/secure_mode
|
35
35
|
castle/extractors/client_id
|
36
36
|
castle/extractors/headers
|
37
37
|
castle/extractors/ip
|
38
38
|
castle/api/response
|
39
39
|
castle/api/request
|
40
|
-
castle/api/
|
40
|
+
castle/api/session
|
41
41
|
castle/review
|
42
42
|
castle/api
|
43
43
|
].each(&method(:require))
|
@@ -54,7 +54,7 @@ module Castle
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def config
|
57
|
-
|
57
|
+
Configuration.instance
|
58
58
|
end
|
59
59
|
|
60
60
|
def api_secret=(api_secret)
|
data/lib/castle/api.rb
CHANGED
@@ -17,16 +17,16 @@ module Castle
|
|
17
17
|
private_constant :HANDLED_ERRORS
|
18
18
|
|
19
19
|
class << self
|
20
|
+
# @param command [String]
|
21
|
+
# @param headers [Hash]
|
20
22
|
def request(command, headers = {})
|
21
23
|
raise Castle::ConfigurationError, 'configuration is not valid' unless Castle.config.valid?
|
22
24
|
|
23
25
|
begin
|
24
|
-
Castle::API::
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
headers
|
29
|
-
)
|
26
|
+
Castle::API::Request.call(
|
27
|
+
command,
|
28
|
+
Castle.config.api_secret,
|
29
|
+
headers
|
30
30
|
)
|
31
31
|
rescue *HANDLED_ERRORS => e
|
32
32
|
# @note We need to initialize the error, as the original error is a cause for this
|
@@ -35,6 +35,12 @@ module Castle
|
|
35
35
|
raise Castle::RequestError.new(e) # rubocop:disable Style/RaiseArgs
|
36
36
|
end
|
37
37
|
end
|
38
|
+
|
39
|
+
# @param command [String]
|
40
|
+
# @param headers [Hash]
|
41
|
+
def call(command, headers = {})
|
42
|
+
Castle::API::Response.call(request(command, headers))
|
43
|
+
end
|
38
44
|
end
|
39
45
|
end
|
40
46
|
end
|
data/lib/castle/api/request.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Castle
|
4
|
-
# this class is responsible for making requests to api
|
5
4
|
module API
|
5
|
+
# this class is responsible for making requests to api
|
6
6
|
module Request
|
7
7
|
# Default headers that we add to passed ones
|
8
8
|
DEFAULT_HEADERS = {
|
@@ -13,8 +13,8 @@ module Castle
|
|
13
13
|
|
14
14
|
class << self
|
15
15
|
def call(command, api_secret, headers)
|
16
|
-
|
17
|
-
|
16
|
+
Castle::API::Session.get.request(
|
17
|
+
build(
|
18
18
|
command,
|
19
19
|
headers.merge(DEFAULT_HEADERS),
|
20
20
|
api_secret
|
@@ -22,14 +22,19 @@ module Castle
|
|
22
22
|
)
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
def build(command, headers, api_secret)
|
26
|
+
request_obj = Net::HTTP.const_get(
|
27
|
+
command.method.to_s.capitalize
|
28
|
+
).new("#{Castle.config.url_prefix}/#{command.path}", headers)
|
29
|
+
|
30
|
+
unless command.method == :get
|
31
|
+
request_obj.body = ::Castle::Utils.replace_invalid_characters(
|
32
|
+
command.data
|
33
|
+
).to_json
|
31
34
|
end
|
32
|
-
|
35
|
+
|
36
|
+
request_obj.basic_auth('', api_secret)
|
37
|
+
request_obj
|
33
38
|
end
|
34
39
|
end
|
35
40
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Castle
|
6
|
+
module API
|
7
|
+
# this class keeps http config object
|
8
|
+
# and provides start/finish methods for persistent connection usage
|
9
|
+
# when there is a need of sending multiple requests at once
|
10
|
+
class Session
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
attr_accessor :http
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
reset
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset
|
20
|
+
@http = Net::HTTP.new(Castle.config.host, Castle.config.port)
|
21
|
+
@http.read_timeout = Castle.config.request_timeout / 1000.0
|
22
|
+
|
23
|
+
if Castle.config.port == 443
|
24
|
+
@http.use_ssl = true
|
25
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
26
|
+
end
|
27
|
+
|
28
|
+
@http
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# @return [Net::HTTP]
|
33
|
+
def get
|
34
|
+
instance.http
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/castle/client.rb
CHANGED
@@ -44,7 +44,7 @@ module Castle
|
|
44
44
|
add_timestamp_if_necessary(options)
|
45
45
|
command = Castle::Commands::Authenticate.new(@context).build(options)
|
46
46
|
begin
|
47
|
-
Castle::API.
|
47
|
+
Castle::API.call(command).merge(failover: false, failover_reason: nil)
|
48
48
|
rescue Castle::RequestError, Castle::InternalServerError => e
|
49
49
|
self.class.failover_response_or_raise(
|
50
50
|
FailoverAuthResponse.new(options[:user_id], reason: e.to_s), e
|
@@ -60,7 +60,7 @@ module Castle
|
|
60
60
|
add_timestamp_if_necessary(options)
|
61
61
|
|
62
62
|
command = Castle::Commands::Identify.new(@context).build(options)
|
63
|
-
Castle::API.
|
63
|
+
Castle::API.call(command)
|
64
64
|
end
|
65
65
|
|
66
66
|
def track(options = {})
|
@@ -71,14 +71,14 @@ module Castle
|
|
71
71
|
add_timestamp_if_necessary(options)
|
72
72
|
|
73
73
|
command = Castle::Commands::Track.new(@context).build(options)
|
74
|
-
Castle::API.
|
74
|
+
Castle::API.call(command)
|
75
75
|
end
|
76
76
|
|
77
77
|
def impersonate(options = {})
|
78
78
|
options = Castle::Utils.deep_symbolize_keys(options || {})
|
79
79
|
add_timestamp_if_necessary(options)
|
80
80
|
command = Castle::Commands::Impersonate.new(@context).build(options)
|
81
|
-
Castle::API.
|
81
|
+
Castle::API.call(command).tap do |response|
|
82
82
|
raise Castle::ImpersonationFailed unless response[:success]
|
83
83
|
end
|
84
84
|
end
|
data/lib/castle/configuration.rb
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
module Castle
|
4
4
|
# manages configuration variables
|
5
5
|
class Configuration
|
6
|
+
include Singleton
|
7
|
+
|
6
8
|
HOST = 'api.castle.io'
|
7
9
|
PORT = 443
|
8
|
-
URL_PREFIX = 'v1'
|
10
|
+
URL_PREFIX = '/v1'
|
9
11
|
FAILOVER_STRATEGY = :allow
|
10
12
|
REQUEST_TIMEOUT = 500 # in milliseconds
|
11
13
|
FAILOVER_STRATEGIES = %i[allow deny challenge throw].freeze
|
@@ -44,8 +46,12 @@ module Castle
|
|
44
46
|
attr_reader :api_secret, :whitelisted, :blacklisted, :failover_strategy, :ip_headers, :trusted_proxies
|
45
47
|
|
46
48
|
def initialize
|
47
|
-
@formatter = Castle::
|
49
|
+
@formatter = Castle::HeadersFormatter
|
48
50
|
@request_timeout = REQUEST_TIMEOUT
|
51
|
+
reset
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset
|
49
55
|
self.failover_strategy = FAILOVER_STRATEGY
|
50
56
|
self.host = HOST
|
51
57
|
self.port = PORT
|
data/lib/castle/extractors/ip.rb
CHANGED
@@ -45,7 +45,13 @@ module Castle
|
|
45
45
|
# @param ips [Array<String>]
|
46
46
|
# @return [Array<String>]
|
47
47
|
def remove_proxies(ips)
|
48
|
-
ips.reject { |ip|
|
48
|
+
ips.reject { |ip| proxy?(ip) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param ip [String]
|
52
|
+
# @return [Boolean]
|
53
|
+
def proxy?(ip)
|
54
|
+
@proxies.any? { |proxy| proxy.match(ip) }
|
49
55
|
end
|
50
56
|
|
51
57
|
# @param header [String]
|
@@ -2,23 +2,23 @@
|
|
2
2
|
|
3
3
|
module Castle
|
4
4
|
# used for preparing valuable headers list
|
5
|
-
class
|
5
|
+
class HeadersFilter
|
6
6
|
# headers filter
|
7
7
|
# HTTP_ - this is how Rack prefixes incoming HTTP headers
|
8
8
|
# CONTENT_LENGTH - for responses without Content-Length or Transfer-Encoding header
|
9
9
|
# REMOTE_ADDR - ip address header returned by web server
|
10
|
-
VALUABLE_HEADERS = /^
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
VALUABLE_HEADERS = /^
|
11
|
+
HTTP(?:_|-).*|
|
12
|
+
CONTENT(?:_|-)LENGTH|
|
13
|
+
REMOTE(?:_|-)ADDR
|
14
|
+
$/xi.freeze
|
15
15
|
|
16
16
|
private_constant :VALUABLE_HEADERS
|
17
17
|
|
18
18
|
# @param request [Rack::Request]
|
19
19
|
def initialize(request)
|
20
20
|
@request_env = request.env
|
21
|
-
@formatter =
|
21
|
+
@formatter = HeadersFormatter
|
22
22
|
end
|
23
23
|
|
24
24
|
# Serialize HTTP headers
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Castle
|
4
|
+
# formats header name
|
5
|
+
class HeadersFormatter
|
6
|
+
class << self
|
7
|
+
# @param header [String]
|
8
|
+
# @return [String]
|
9
|
+
def call(header)
|
10
|
+
format(header.to_s.gsub(/^HTTP(?:_|-)/i, ''))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# @param header [String]
|
16
|
+
# @return [String]
|
17
|
+
def format(header)
|
18
|
+
header.split(/_|-/).map(&:capitalize).join('-')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/castle/version.rb
CHANGED
@@ -4,57 +4,69 @@ describe Castle::API::Request do
|
|
4
4
|
describe '#call' do
|
5
5
|
subject(:call) { described_class.call(command, api_secret, headers) }
|
6
6
|
|
7
|
+
let(:session) { instance_double('Castle::API::Session') }
|
7
8
|
let(:http) { instance_double('Net::HTTP') }
|
8
|
-
let(:request_build) { instance_double('Castle::API::Request::Build') }
|
9
9
|
let(:command) { Castle::Commands::Track.new({}).build(event: '$login.succeeded') }
|
10
10
|
let(:headers) { {} }
|
11
11
|
let(:api_secret) { 'secret' }
|
12
|
+
let(:request_build) { {} }
|
12
13
|
let(:expected_headers) { { 'Content-Type' => 'application/json' } }
|
13
14
|
|
14
15
|
before do
|
15
|
-
allow(
|
16
|
-
allow(
|
17
|
-
allow(
|
18
|
-
|
19
|
-
.and_return(request_build)
|
16
|
+
allow(Castle::API::Session).to receive(:instance).and_return(session)
|
17
|
+
allow(session).to receive(:http).and_return(http)
|
18
|
+
allow(http).to receive(:request)
|
19
|
+
allow(described_class).to receive(:build).and_return(request_build)
|
20
20
|
call
|
21
21
|
end
|
22
22
|
|
23
23
|
it do
|
24
|
-
expect(
|
25
|
-
.with(command, expected_headers, api_secret)
|
24
|
+
expect(described_class).to have_received(:build).with(command, expected_headers, api_secret)
|
26
25
|
end
|
27
26
|
|
28
27
|
it { expect(http).to have_received(:request).with(request_build) }
|
29
28
|
end
|
30
29
|
|
31
|
-
describe '#
|
32
|
-
subject(:
|
30
|
+
describe '#build' do
|
31
|
+
subject(:build) { described_class.build(command, headers, api_secret) }
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
Castle.config.host = 'localhost'
|
37
|
-
Castle.config.port = 3002
|
38
|
-
end
|
33
|
+
let(:headers) { { 'SAMPLE-HEADER' => '1' } }
|
34
|
+
let(:api_secret) { 'secret' }
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
36
|
+
context 'when get' do
|
37
|
+
let(:command) { Castle::Commands::Review.build(review_id) }
|
38
|
+
let(:review_id) { SecureRandom.uuid }
|
44
39
|
|
45
|
-
it { expect(
|
46
|
-
it { expect(
|
47
|
-
it { expect(
|
48
|
-
it { expect(
|
49
|
-
it { expect(
|
40
|
+
it { expect(build.body).to be_nil }
|
41
|
+
it { expect(build.method).to eql('GET') }
|
42
|
+
it { expect(build.path).to eql("/v1/#{command.path}") }
|
43
|
+
it { expect(build.to_hash).to have_key('authorization') }
|
44
|
+
it { expect(build.to_hash).to have_key('sample-header') }
|
45
|
+
it { expect(build.to_hash['sample-header']).to eql(['1']) }
|
50
46
|
end
|
51
47
|
|
52
|
-
context 'when
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
48
|
+
context 'when post' do
|
49
|
+
let(:time) { Time.now.utc.iso8601(3) }
|
50
|
+
let(:command) do
|
51
|
+
Castle::Commands::Track.new({}).build(event: '$login.succeeded', name: "\xC4")
|
52
|
+
end
|
53
|
+
let(:expected_body) do
|
54
|
+
{
|
55
|
+
event: '$login.succeeded',
|
56
|
+
name: '�',
|
57
|
+
context: {},
|
58
|
+
sent_at: time
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
before { allow(Castle::Utils::Timestamp).to receive(:call).and_return(time) }
|
63
|
+
|
64
|
+
it { expect(build.body).to be_eql(expected_body.to_json) }
|
65
|
+
it { expect(build.method).to eql('POST') }
|
66
|
+
it { expect(build.path).to eql("/v1/#{command.path}") }
|
67
|
+
it { expect(build.to_hash).to have_key('authorization') }
|
68
|
+
it { expect(build.to_hash).to have_key('sample-header') }
|
69
|
+
it { expect(build.to_hash['sample-header']).to eql(['1']) }
|
58
70
|
end
|
59
71
|
end
|
60
72
|
end
|
data/spec/lib/castle/api_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
describe Castle::API do
|
4
|
-
subject(:
|
4
|
+
subject(:call) { described_class.call(command) }
|
5
5
|
|
6
6
|
let(:command) { Castle::Commands::Track.new({}).build(event: '$login.succeeded') }
|
7
7
|
|
@@ -10,7 +10,7 @@ describe Castle::API do
|
|
10
10
|
|
11
11
|
it do
|
12
12
|
expect do
|
13
|
-
|
13
|
+
call
|
14
14
|
end.to raise_error(Castle::RequestError)
|
15
15
|
end
|
16
16
|
end
|
@@ -20,7 +20,7 @@ describe Castle::API do
|
|
20
20
|
|
21
21
|
it do
|
22
22
|
expect do
|
23
|
-
|
23
|
+
call
|
24
24
|
end.to raise_error(Castle::BadRequestError)
|
25
25
|
end
|
26
26
|
end
|
@@ -30,7 +30,7 @@ describe Castle::API do
|
|
30
30
|
|
31
31
|
it do
|
32
32
|
expect do
|
33
|
-
|
33
|
+
call
|
34
34
|
end.to raise_error(Castle::ConfigurationError)
|
35
35
|
end
|
36
36
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
describe Castle::Configuration do
|
4
4
|
subject(:config) do
|
5
|
-
described_class.
|
5
|
+
described_class.instance
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'host' do
|
@@ -38,6 +38,7 @@ describe Castle::Configuration do
|
|
38
38
|
allow(ENV).to receive(:fetch).with(
|
39
39
|
'CASTLE_API_SECRET', ''
|
40
40
|
).and_return(secret_key_env)
|
41
|
+
config.reset
|
41
42
|
end
|
42
43
|
|
43
44
|
it do
|
@@ -66,7 +67,7 @@ describe Castle::Configuration do
|
|
66
67
|
end
|
67
68
|
|
68
69
|
it do
|
69
|
-
expect(config.api_secret).to be_eql('')
|
70
|
+
expect(config.api_secret).to be_eql('secret')
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
@@ -4,14 +4,14 @@ describe Castle::Context::Default do
|
|
4
4
|
subject { described_class.new(request, nil) }
|
5
5
|
|
6
6
|
let(:ip) { '1.2.3.4' }
|
7
|
-
let(:
|
7
|
+
let(:client_id) { 'abcd' }
|
8
8
|
|
9
9
|
let(:env) do
|
10
10
|
Rack::MockRequest.env_for('/',
|
11
11
|
'HTTP_X_FORWARDED_FOR' => ip,
|
12
12
|
'HTTP_ACCEPT_LANGUAGE' => 'en',
|
13
13
|
'HTTP_USER_AGENT' => 'test',
|
14
|
-
'HTTP_COOKIE' => "__cid=#{
|
14
|
+
'HTTP_COOKIE' => "__cid=#{client_id};other=efgh")
|
15
15
|
end
|
16
16
|
let(:request) { Rack::Request.new(env) }
|
17
17
|
let(:default_context) { subject.call }
|
@@ -34,6 +34,7 @@ describe Castle::Context::Default do
|
|
34
34
|
it { expect(default_context[:origin]).to be_eql('web') }
|
35
35
|
it { expect(default_context[:headers]).to be_eql(result_headers) }
|
36
36
|
it { expect(default_context[:ip]).to be_eql(ip) }
|
37
|
+
it { expect(default_context[:client_id]).to be_eql(client_id) }
|
37
38
|
it { expect(default_context[:library][:name]).to be_eql('castle-rb') }
|
38
39
|
it { expect(default_context[:library][:version]).to be_eql(version) }
|
39
40
|
it { expect(default_context[:user_agent]).to be_eql('test') }
|
@@ -3,7 +3,7 @@
|
|
3
3
|
describe Castle::Extractors::ClientId do
|
4
4
|
subject(:extractor) { described_class.new(formatted_headers, cookies) }
|
5
5
|
|
6
|
-
let(:formatted_headers) { Castle::
|
6
|
+
let(:formatted_headers) { Castle::HeadersFilter.new(request).call }
|
7
7
|
let(:client_id_cookie) { 'abcd' }
|
8
8
|
let(:client_id_header) { 'abcde' }
|
9
9
|
let(:cookies) { request.cookies }
|
@@ -4,6 +4,11 @@ describe Castle::Extractors::IP do
|
|
4
4
|
subject(:extractor) { described_class.new(headers) }
|
5
5
|
|
6
6
|
describe 'ip' do
|
7
|
+
after do
|
8
|
+
Castle.config.ip_headers = []
|
9
|
+
Castle.config.trusted_proxies = []
|
10
|
+
end
|
11
|
+
|
7
12
|
context 'when regular ip' do
|
8
13
|
let(:headers) { { 'X-Forwarded-For' => '1.2.3.5' } }
|
9
14
|
|
@@ -1,16 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
describe Castle::
|
3
|
+
describe Castle::HeadersFilter do
|
4
4
|
subject(:headers) { described_class.new(request).call }
|
5
5
|
|
6
|
-
let(:client_id) { 'abcd' }
|
7
6
|
let(:env) do
|
8
7
|
Rack::MockRequest.env_for(
|
9
8
|
'/',
|
10
9
|
'Action-Dispatch.request.content-Type' => 'application/json',
|
11
10
|
'HTTP_AUTHORIZATION' => 'Basic 123456',
|
12
|
-
'HTTP_COOKIE' =>
|
13
|
-
'
|
11
|
+
'HTTP_COOKIE' => '__cid=abcd;other=efgh',
|
12
|
+
'http-ok' => 'OK',
|
14
13
|
'HTTP_ACCEPT' => 'application/json',
|
15
14
|
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
16
15
|
'HTTP_USER_AGENT' => 'Mozilla 1234',
|
@@ -22,7 +21,7 @@ describe Castle::HeaderFilter do
|
|
22
21
|
{
|
23
22
|
'Accept' => 'application/json',
|
24
23
|
'Authorization' => 'Basic 123456',
|
25
|
-
'Cookie' =>
|
24
|
+
'Cookie' => '__cid=abcd;other=efgh',
|
26
25
|
'Content-Length' => '0',
|
27
26
|
'Ok' => 'OK',
|
28
27
|
'User-Agent' => 'Mozilla 1234',
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
describe Castle::
|
4
|
-
subject(:formatter) { described_class
|
3
|
+
describe Castle::HeadersFormatter do
|
4
|
+
subject(:formatter) { described_class }
|
5
5
|
|
6
6
|
it 'removes HTTP_' do
|
7
7
|
expect(formatter.call('HTTP_X_TEST')).to be_eql('X-Test')
|
data/spec/spec_helper.rb
CHANGED
@@ -16,8 +16,7 @@ WebMock.disable_net_connect!(allow_localhost: true)
|
|
16
16
|
|
17
17
|
RSpec.configure do |config|
|
18
18
|
config.before do
|
19
|
-
Castle.
|
20
|
-
|
19
|
+
Castle.config.reset
|
21
20
|
Castle.configure do |cfg|
|
22
21
|
cfg.api_secret = 'secret'
|
23
22
|
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: 4.
|
4
|
+
version: 4.1.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-03-
|
11
|
+
date: 2020-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: appraisal
|
@@ -35,8 +35,8 @@ files:
|
|
35
35
|
- lib/castle.rb
|
36
36
|
- lib/castle/api.rb
|
37
37
|
- lib/castle/api/request.rb
|
38
|
-
- lib/castle/api/request/build.rb
|
39
38
|
- lib/castle/api/response.rb
|
39
|
+
- lib/castle/api/session.rb
|
40
40
|
- lib/castle/client.rb
|
41
41
|
- lib/castle/command.rb
|
42
42
|
- lib/castle/commands/authenticate.rb
|
@@ -54,8 +54,8 @@ files:
|
|
54
54
|
- lib/castle/extractors/headers.rb
|
55
55
|
- lib/castle/extractors/ip.rb
|
56
56
|
- lib/castle/failover_auth_response.rb
|
57
|
-
- lib/castle/
|
58
|
-
- lib/castle/
|
57
|
+
- lib/castle/headers_filter.rb
|
58
|
+
- lib/castle/headers_formatter.rb
|
59
59
|
- lib/castle/review.rb
|
60
60
|
- lib/castle/secure_mode.rb
|
61
61
|
- lib/castle/support/hanami.rb
|
@@ -73,7 +73,6 @@ files:
|
|
73
73
|
- spec/integration/rails/support/all.rb
|
74
74
|
- spec/integration/rails/support/application.rb
|
75
75
|
- spec/integration/rails/support/home_controller.rb
|
76
|
-
- spec/lib/castle/api/request/build_spec.rb
|
77
76
|
- spec/lib/castle/api/request_spec.rb
|
78
77
|
- spec/lib/castle/api/response_spec.rb
|
79
78
|
- spec/lib/castle/api_spec.rb
|
@@ -92,8 +91,8 @@ files:
|
|
92
91
|
- spec/lib/castle/extractors/client_id_spec.rb
|
93
92
|
- spec/lib/castle/extractors/headers_spec.rb
|
94
93
|
- spec/lib/castle/extractors/ip_spec.rb
|
95
|
-
- spec/lib/castle/
|
96
|
-
- spec/lib/castle/
|
94
|
+
- spec/lib/castle/headers_filter_spec.rb
|
95
|
+
- spec/lib/castle/headers_formatter_spec.rb
|
97
96
|
- spec/lib/castle/review_spec.rb
|
98
97
|
- spec/lib/castle/secure_mode_spec.rb
|
99
98
|
- spec/lib/castle/utils/cloner_spec.rb
|
@@ -136,6 +135,7 @@ test_files:
|
|
136
135
|
- spec/integration/rails/rails_spec.rb
|
137
136
|
- spec/lib/castle_spec.rb
|
138
137
|
- spec/lib/castle/review_spec.rb
|
138
|
+
- spec/lib/castle/headers_filter_spec.rb
|
139
139
|
- spec/lib/castle/client_spec.rb
|
140
140
|
- spec/lib/castle/context/default_spec.rb
|
141
141
|
- spec/lib/castle/context/merger_spec.rb
|
@@ -143,15 +143,13 @@ test_files:
|
|
143
143
|
- spec/lib/castle/api_spec.rb
|
144
144
|
- spec/lib/castle/configuration_spec.rb
|
145
145
|
- spec/lib/castle/version_spec.rb
|
146
|
-
- spec/lib/castle/header_formatter_spec.rb
|
147
146
|
- spec/lib/castle/utils/cloner_spec.rb
|
148
147
|
- spec/lib/castle/utils/timestamp_spec.rb
|
149
148
|
- spec/lib/castle/utils/merger_spec.rb
|
150
149
|
- spec/lib/castle/command_spec.rb
|
150
|
+
- spec/lib/castle/headers_formatter_spec.rb
|
151
151
|
- spec/lib/castle/api/request_spec.rb
|
152
152
|
- spec/lib/castle/api/response_spec.rb
|
153
|
-
- spec/lib/castle/api/request/build_spec.rb
|
154
|
-
- spec/lib/castle/header_filter_spec.rb
|
155
153
|
- spec/lib/castle/commands/review_spec.rb
|
156
154
|
- spec/lib/castle/commands/authenticate_spec.rb
|
157
155
|
- spec/lib/castle/commands/track_spec.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
module API
|
5
|
-
# generate api request
|
6
|
-
module Request
|
7
|
-
module Build
|
8
|
-
class << self
|
9
|
-
def call(command, headers, api_secret)
|
10
|
-
request = Net::HTTP.const_get(
|
11
|
-
command.method.to_s.capitalize
|
12
|
-
).new("/#{Castle.config.url_prefix}/#{command.path}", headers)
|
13
|
-
|
14
|
-
unless command.method == :get
|
15
|
-
request.body = ::Castle::Utils.replace_invalid_characters(
|
16
|
-
command.data
|
17
|
-
).to_json
|
18
|
-
end
|
19
|
-
|
20
|
-
request.basic_auth('', api_secret)
|
21
|
-
request
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castle
|
4
|
-
# formats header name
|
5
|
-
class HeaderFormatter
|
6
|
-
# @param header [String]
|
7
|
-
# @return [String]
|
8
|
-
def call(header)
|
9
|
-
header.to_s.gsub(/^HTTP(?:_|-)/i, '').split(/_|-/).map(&:capitalize).join('-')
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
describe Castle::API::Request::Build do
|
4
|
-
subject(:call) { described_class.call(command, headers, api_secret) }
|
5
|
-
|
6
|
-
let(:headers) { { 'SAMPLE-HEADER' => '1' } }
|
7
|
-
let(:api_secret) { 'secret' }
|
8
|
-
|
9
|
-
describe 'call' do
|
10
|
-
context 'when get' do
|
11
|
-
let(:command) { Castle::Commands::Review.build(review_id) }
|
12
|
-
let(:review_id) { SecureRandom.uuid }
|
13
|
-
|
14
|
-
it { expect(call.body).to be_nil }
|
15
|
-
it { expect(call.method).to eql('GET') }
|
16
|
-
it { expect(call.path).to eql("/v1/#{command.path}") }
|
17
|
-
it { expect(call.to_hash).to have_key('authorization') }
|
18
|
-
it { expect(call.to_hash).to have_key('sample-header') }
|
19
|
-
it { expect(call.to_hash['sample-header']).to eql(['1']) }
|
20
|
-
end
|
21
|
-
|
22
|
-
context 'when post' do
|
23
|
-
let(:time) { Time.now.utc.iso8601(3) }
|
24
|
-
let(:command) do
|
25
|
-
Castle::Commands::Track.new({}).build(event: '$login.succeeded', name: "\xC4")
|
26
|
-
end
|
27
|
-
let(:expected_body) do
|
28
|
-
{
|
29
|
-
event: '$login.succeeded',
|
30
|
-
name: '�',
|
31
|
-
context: {},
|
32
|
-
sent_at: time
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
before { allow(Castle::Utils::Timestamp).to receive(:call).and_return(time) }
|
37
|
-
|
38
|
-
it { expect(call.body).to be_eql(expected_body.to_json) }
|
39
|
-
it { expect(call.method).to eql('POST') }
|
40
|
-
it { expect(call.path).to eql("/v1/#{command.path}") }
|
41
|
-
it { expect(call.to_hash).to have_key('authorization') }
|
42
|
-
it { expect(call.to_hash).to have_key('sample-header') }
|
43
|
-
it { expect(call.to_hash['sample-header']).to eql(['1']) }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|