locked-rb 0.0.1
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 +7 -0
- data/README.md +127 -0
- data/lib/locked-rb.rb +3 -0
- data/lib/locked.rb +60 -0
- data/lib/locked/api.rb +40 -0
- data/lib/locked/api/request.rb +37 -0
- data/lib/locked/api/request/build.rb +29 -0
- data/lib/locked/api/response.rb +40 -0
- data/lib/locked/client.rb +66 -0
- data/lib/locked/command.rb +5 -0
- data/lib/locked/commands/authenticate.rb +23 -0
- data/lib/locked/commands/identify.rb +23 -0
- data/lib/locked/commands/review.rb +14 -0
- data/lib/locked/configuration.rb +75 -0
- data/lib/locked/context/default.rb +40 -0
- data/lib/locked/context/merger.rb +14 -0
- data/lib/locked/context/sanitizer.rb +23 -0
- data/lib/locked/errors.rb +41 -0
- data/lib/locked/extractors/client_id.rb +17 -0
- data/lib/locked/extractors/headers.rb +24 -0
- data/lib/locked/extractors/ip.rb +18 -0
- data/lib/locked/failover_auth_response.rb +23 -0
- data/lib/locked/header_formatter.rb +9 -0
- data/lib/locked/review.rb +11 -0
- data/lib/locked/secure_mode.rb +11 -0
- data/lib/locked/support/hanami.rb +19 -0
- data/lib/locked/support/padrino.rb +19 -0
- data/lib/locked/support/rails.rb +13 -0
- data/lib/locked/support/sinatra.rb +19 -0
- data/lib/locked/utils.rb +55 -0
- data/lib/locked/utils/cloner.rb +11 -0
- data/lib/locked/utils/merger.rb +23 -0
- data/lib/locked/utils/timestamp.rb +12 -0
- data/lib/locked/validators/not_supported.rb +16 -0
- data/lib/locked/validators/present.rb +16 -0
- data/lib/locked/version.rb +5 -0
- data/spec/lib/Locked/api/request/build_spec.rb +42 -0
- data/spec/lib/Locked/api/request_spec.rb +59 -0
- data/spec/lib/Locked/api/response_spec.rb +58 -0
- data/spec/lib/Locked/api_spec.rb +37 -0
- data/spec/lib/Locked/client_spec.rb +226 -0
- data/spec/lib/Locked/command_spec.rb +9 -0
- data/spec/lib/Locked/commands/authenticate_spec.rb +95 -0
- data/spec/lib/Locked/commands/identify_spec.rb +87 -0
- data/spec/lib/Locked/commands/review_spec.rb +24 -0
- data/spec/lib/Locked/configuration_spec.rb +146 -0
- data/spec/lib/Locked/context/default_spec.rb +35 -0
- data/spec/lib/Locked/context/merger_spec.rb +23 -0
- data/spec/lib/Locked/context/sanitizer_spec.rb +27 -0
- data/spec/lib/Locked/extractors/client_id_spec.rb +62 -0
- data/spec/lib/Locked/extractors/headers_spec.rb +26 -0
- data/spec/lib/Locked/extractors/ip_spec.rb +27 -0
- data/spec/lib/Locked/header_formatter_spec.rb +25 -0
- data/spec/lib/Locked/review_spec.rb +19 -0
- data/spec/lib/Locked/secure_mode_spec.rb +9 -0
- data/spec/lib/Locked/utils/cloner_spec.rb +18 -0
- data/spec/lib/Locked/utils/merger_spec.rb +13 -0
- data/spec/lib/Locked/utils/timestamp_spec.rb +17 -0
- data/spec/lib/Locked/utils_spec.rb +156 -0
- data/spec/lib/Locked/validators/not_supported_spec.rb +26 -0
- data/spec/lib/Locked/validators/present_spec.rb +33 -0
- data/spec/lib/Locked/version_spec.rb +5 -0
- data/spec/lib/locked_spec.rb +66 -0
- data/spec/spec_helper.rb +22 -0
- metadata +133 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Extractors::ClientId do
|
4
|
+
subject(:extractor) { described_class.new(request, cookies) }
|
5
|
+
|
6
|
+
let(:client_id_cookie) { 'abcd' }
|
7
|
+
let(:client_id_header) { 'abcde' }
|
8
|
+
let(:cookies) { request.cookies }
|
9
|
+
let(:request) { Rack::Request.new(env) }
|
10
|
+
let(:env) do
|
11
|
+
Rack::MockRequest.env_for('/', headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'with client_id' do
|
15
|
+
let(:headers) do
|
16
|
+
{
|
17
|
+
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
18
|
+
'HTTP_COOKIE' => "__cid=#{client_id_cookie};other=efgh"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
it do
|
23
|
+
expect(extractor.call).to eql(client_id_cookie)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with X-Locked-Client-Id header' do
|
28
|
+
let(:headers) do
|
29
|
+
{
|
30
|
+
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
31
|
+
'HTTP_X_LOCKED_CLIENT_ID' => client_id_header
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'appends the client_id' do
|
36
|
+
expect(extractor.call).to eql(client_id_header)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when cookies undefined' do
|
41
|
+
let(:cookies) { nil }
|
42
|
+
let(:headers) { {} }
|
43
|
+
|
44
|
+
it do
|
45
|
+
expect(extractor.call).to eql('')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with X-Locked-Client-Id header and cookies client' do
|
50
|
+
let(:headers) do
|
51
|
+
{
|
52
|
+
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
53
|
+
'HTTP_X_LOCKED_CLIENT_ID' => client_id_header,
|
54
|
+
'HTTP_COOKIE' => "__cid=#{client_id_cookie};other=efgh"
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'appends the client_id' do
|
59
|
+
expect(extractor.call).to eql(client_id_header)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Extractors::Headers do
|
4
|
+
subject(:extractor) { described_class.new(request) }
|
5
|
+
|
6
|
+
let(:client_id) { 'abcd' }
|
7
|
+
let(:env) do
|
8
|
+
Rack::MockRequest.env_for('/',
|
9
|
+
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
10
|
+
'HTTP_OK' => 'OK',
|
11
|
+
'TEST' => '1',
|
12
|
+
'HTTP_COOKIE' => "__cid=#{client_id};other=efgh")
|
13
|
+
end
|
14
|
+
let(:request) { Rack::Request.new(env) }
|
15
|
+
|
16
|
+
describe 'extract http headers with whitelisted and blacklisted support' do
|
17
|
+
before do
|
18
|
+
Locked.config.whitelisted += ['TEST']
|
19
|
+
end
|
20
|
+
it do
|
21
|
+
expect(extractor.call).to eql(
|
22
|
+
'X-Forwarded-For' => '1.2.3.4', 'Test' => '1'
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Extractors::IP do
|
4
|
+
subject(:extractor) { described_class.new(request) }
|
5
|
+
|
6
|
+
let(:request) { Rack::Request.new(env) }
|
7
|
+
|
8
|
+
describe 'ip' do
|
9
|
+
context 'when regular ip' do
|
10
|
+
let(:env) { Rack::MockRequest.env_for('/', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5') }
|
11
|
+
|
12
|
+
it { expect(extractor.call).to eql('1.2.3.5') }
|
13
|
+
end
|
14
|
+
|
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
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
it { expect(extractor.call).to eql('1.2.3.4') }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::HeaderFormatter do
|
4
|
+
subject(:formatter) { described_class.new }
|
5
|
+
|
6
|
+
it 'removes HTTP_' do
|
7
|
+
expect(formatter.call('HTTP_X_TEST')).to be_eql('X-Test')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'capitalizes header' do
|
11
|
+
expect(formatter.call('X_TEST')).to be_eql('X-Test')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'ignores letter case and -_ divider' do
|
15
|
+
expect(formatter.call('http-X_teST')).to be_eql('X-Test')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'does not remove http if there is no _- char' do
|
19
|
+
expect(formatter.call('httpX_teST')).to be_eql('Httpx-Test')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'removes HTTP_' do
|
23
|
+
expect(formatter.call(:clearance)).to be_eql('Clearance')
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Review do
|
4
|
+
# before do
|
5
|
+
# stub_request(:any, /locked.jp/).with(
|
6
|
+
# basic_auth: ['', 'key']
|
7
|
+
# ).to_return(status: 200, body: '{}', headers: {})
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# describe '#retrieve' do
|
11
|
+
# subject(:retrieve) { described_class.retrieve(review_id) }
|
12
|
+
#
|
13
|
+
# let(:review_id) { '1234' }
|
14
|
+
#
|
15
|
+
# before { retrieve }
|
16
|
+
#
|
17
|
+
# it { assert_requested :get, "https://locked.jp/api/v1/client/reviews/#{review_id}", times: 1 }
|
18
|
+
# end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Utils::Cloner do
|
4
|
+
subject(:cloner) { described_class }
|
5
|
+
|
6
|
+
describe 'call' do
|
7
|
+
let(:nested) { { c: '3' } }
|
8
|
+
let(:first) { { test: { test1: { c: '4' }, test2: nested, a: '1', b: '2' } } }
|
9
|
+
let(:result) { { test: { test1: { c: '4' }, test2: { c: '3' }, a: '1', b: '2' } } }
|
10
|
+
let(:cloned) { cloner.call(first) }
|
11
|
+
|
12
|
+
before { cloned }
|
13
|
+
it do
|
14
|
+
nested[:test] = 'sample'
|
15
|
+
expect(cloned).to be_eql(result)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Utils::Merger do
|
4
|
+
subject(:merger) { described_class }
|
5
|
+
|
6
|
+
describe 'call' do
|
7
|
+
let(:first) { { test: { test1: { c: '4' }, test2: { c: '3' }, a: '1', b: '2' } } }
|
8
|
+
let(:second) { { test2: '2', test: { 'test1' => { d: '5' }, test2: '6', a: nil, b: '3' } } }
|
9
|
+
let(:result) { { test2: '2', test: { test1: { c: '4', d: '5' }, test2: '6', b: '3' } } }
|
10
|
+
|
11
|
+
it { expect(merger.call(first, second)).to be_eql(result) }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Utils::Timestamp do
|
4
|
+
subject { described_class.call }
|
5
|
+
|
6
|
+
let(:time_string) { '2018-01-10T14:14:24.407Z' }
|
7
|
+
let(:time) { Time.parse(time_string) }
|
8
|
+
|
9
|
+
before { Timecop.freeze(time) }
|
10
|
+
after { Timecop.return }
|
11
|
+
|
12
|
+
describe '#call' do
|
13
|
+
it do
|
14
|
+
is_expected.to eql(time_string)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Utils do
|
4
|
+
let(:nested_strings) { { 'a' => { 'b' => { 'c' => 3 } } } }
|
5
|
+
let(:nested_symbols) { { a: { b: { c: 3 } } } }
|
6
|
+
let(:nested_mixed) { { 'a' => { b: { 'c' => 3 } } } }
|
7
|
+
let(:string_array_of_hashes) { { 'a' => [{ 'b' => 2 }, { 'c' => 3 }, 4] } }
|
8
|
+
let(:symbol_array_of_hashes) { { a: [{ b: 2 }, { c: 3 }, 4] } }
|
9
|
+
let(:mixed_array_of_hashes) { { a: [{ b: 2 }, { 'c' => 3 }, 4] } }
|
10
|
+
|
11
|
+
describe '#deep_symbolize_keys' do
|
12
|
+
subject { described_class.deep_symbolize_keys(hash) }
|
13
|
+
|
14
|
+
context 'when nested_symbols' do
|
15
|
+
let(:hash) { nested_symbols }
|
16
|
+
|
17
|
+
it { is_expected.to eq(nested_symbols) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when nested_strings' do
|
21
|
+
let(:hash) { nested_strings }
|
22
|
+
|
23
|
+
it { is_expected.to eq(nested_symbols) }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when nested_mixed' do
|
27
|
+
let(:hash) { nested_mixed }
|
28
|
+
|
29
|
+
it { is_expected.to eq(nested_symbols) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when string_array_of_hashes' do
|
33
|
+
let(:hash) { string_array_of_hashes }
|
34
|
+
|
35
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when symbol_array_of_hashes' do
|
39
|
+
let(:hash) { symbol_array_of_hashes }
|
40
|
+
|
41
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when mixed_array_of_hashes' do
|
45
|
+
let(:hash) { mixed_array_of_hashes }
|
46
|
+
|
47
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#deep_symbolize_keys' do
|
52
|
+
subject { described_class.deep_symbolize_keys!(Locked::Utils::Cloner.call(hash)) }
|
53
|
+
|
54
|
+
context 'when nested_symbols' do
|
55
|
+
let(:hash) { nested_symbols }
|
56
|
+
|
57
|
+
it { is_expected.to eq(nested_symbols) }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when nested_strings' do
|
61
|
+
let(:hash) { nested_strings }
|
62
|
+
|
63
|
+
it { is_expected.to eq(nested_symbols) }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when nested_mixed' do
|
67
|
+
let(:hash) { nested_mixed }
|
68
|
+
|
69
|
+
it { is_expected.to eq(nested_symbols) }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when string_array_of_hashes' do
|
73
|
+
let(:hash) { string_array_of_hashes }
|
74
|
+
|
75
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when symbol_array_of_hashes' do
|
79
|
+
let(:hash) { symbol_array_of_hashes }
|
80
|
+
|
81
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when mixed_array_of_hashes' do
|
85
|
+
let(:hash) { mixed_array_of_hashes }
|
86
|
+
|
87
|
+
it { is_expected.to eq(symbol_array_of_hashes) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '::replace_invalid_characters' do
|
92
|
+
subject { described_class.replace_invalid_characters(input) }
|
93
|
+
|
94
|
+
context 'when input is a string' do
|
95
|
+
let(:input) { '1234' }
|
96
|
+
|
97
|
+
it { is_expected.to eq input }
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when input is an array' do
|
101
|
+
let(:input) { [1, 2, 3, '4'] }
|
102
|
+
|
103
|
+
it { is_expected.to eq input }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when input is a hash' do
|
107
|
+
let(:input) { { user_id: 1 } }
|
108
|
+
|
109
|
+
it { is_expected.to eq input }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when input is nil' do
|
113
|
+
let(:input) { nil }
|
114
|
+
|
115
|
+
it { is_expected.to eq input }
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'when input is a nested hash' do
|
119
|
+
let(:input) { { user: { id: 1 } } }
|
120
|
+
|
121
|
+
it { is_expected.to eq input }
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'with invalid UTF-8 characters' do
|
125
|
+
context 'when input is a hash' do
|
126
|
+
let(:input) { { user_id: "inv\xC4lid" } }
|
127
|
+
|
128
|
+
it { is_expected.to eq(user_id: 'inv�lid') }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when input is a nested hash' do
|
132
|
+
let(:input) { { user: { id: "inv\xC4lid" } } }
|
133
|
+
|
134
|
+
it { is_expected.to eq(user: { id: 'inv�lid' }) }
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'when input is an array of hashes' do
|
138
|
+
let(:input) { [{ user: "inv\xC4lid" }] * 2 }
|
139
|
+
|
140
|
+
it { is_expected.to eq([{ user: 'inv�lid' }, { user: 'inv�lid' }]) }
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'when input is an array' do
|
144
|
+
let(:input) { ["inv\xC4lid"] * 2 }
|
145
|
+
|
146
|
+
it { is_expected.to eq(['inv�lid', 'inv�lid']) }
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when input is a hash with array in key' do
|
150
|
+
let(:input) { { items: ["inv\xC4lid"] * 2 } }
|
151
|
+
|
152
|
+
it { is_expected.to eq(items: ['inv�lid', 'inv�lid']) }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Validators::NotSupported do
|
4
|
+
describe '#call' do
|
5
|
+
subject(:call) { described_class.call({ first: 1 }, keys) }
|
6
|
+
|
7
|
+
context 'when keys is present' do
|
8
|
+
let(:keys) { %i[first second] }
|
9
|
+
|
10
|
+
it do
|
11
|
+
expect do
|
12
|
+
call
|
13
|
+
end.to raise_error(
|
14
|
+
Locked::InvalidParametersError,
|
15
|
+
'first is/are not supported'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when key is not present' do
|
21
|
+
let(:keys) { %i[second third] }
|
22
|
+
|
23
|
+
it { expect { call }.not_to raise_error }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe Locked::Validators::Present do
|
4
|
+
describe '#call' do
|
5
|
+
subject(:call) { described_class.call({ first: 1, second: '2', invalid: '' }, keys) }
|
6
|
+
|
7
|
+
context 'when keys is not present' do
|
8
|
+
let(:keys) { %i[second third] }
|
9
|
+
|
10
|
+
it do
|
11
|
+
expect do
|
12
|
+
call
|
13
|
+
end.to raise_error(Locked::InvalidParametersError, 'third is missing or empty')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when keys is empty' do
|
18
|
+
let(:keys) { %i[second invalid] }
|
19
|
+
|
20
|
+
it do
|
21
|
+
expect do
|
22
|
+
call
|
23
|
+
end.to raise_error(Locked::InvalidParametersError, 'invalid is missing or empty')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when key is present' do
|
28
|
+
let(:keys) { %i[first second] }
|
29
|
+
|
30
|
+
it { expect { call }.not_to raise_error }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|