locked-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +127 -0
  3. data/lib/locked-rb.rb +3 -0
  4. data/lib/locked.rb +60 -0
  5. data/lib/locked/api.rb +40 -0
  6. data/lib/locked/api/request.rb +37 -0
  7. data/lib/locked/api/request/build.rb +29 -0
  8. data/lib/locked/api/response.rb +40 -0
  9. data/lib/locked/client.rb +66 -0
  10. data/lib/locked/command.rb +5 -0
  11. data/lib/locked/commands/authenticate.rb +23 -0
  12. data/lib/locked/commands/identify.rb +23 -0
  13. data/lib/locked/commands/review.rb +14 -0
  14. data/lib/locked/configuration.rb +75 -0
  15. data/lib/locked/context/default.rb +40 -0
  16. data/lib/locked/context/merger.rb +14 -0
  17. data/lib/locked/context/sanitizer.rb +23 -0
  18. data/lib/locked/errors.rb +41 -0
  19. data/lib/locked/extractors/client_id.rb +17 -0
  20. data/lib/locked/extractors/headers.rb +24 -0
  21. data/lib/locked/extractors/ip.rb +18 -0
  22. data/lib/locked/failover_auth_response.rb +23 -0
  23. data/lib/locked/header_formatter.rb +9 -0
  24. data/lib/locked/review.rb +11 -0
  25. data/lib/locked/secure_mode.rb +11 -0
  26. data/lib/locked/support/hanami.rb +19 -0
  27. data/lib/locked/support/padrino.rb +19 -0
  28. data/lib/locked/support/rails.rb +13 -0
  29. data/lib/locked/support/sinatra.rb +19 -0
  30. data/lib/locked/utils.rb +55 -0
  31. data/lib/locked/utils/cloner.rb +11 -0
  32. data/lib/locked/utils/merger.rb +23 -0
  33. data/lib/locked/utils/timestamp.rb +12 -0
  34. data/lib/locked/validators/not_supported.rb +16 -0
  35. data/lib/locked/validators/present.rb +16 -0
  36. data/lib/locked/version.rb +5 -0
  37. data/spec/lib/Locked/api/request/build_spec.rb +42 -0
  38. data/spec/lib/Locked/api/request_spec.rb +59 -0
  39. data/spec/lib/Locked/api/response_spec.rb +58 -0
  40. data/spec/lib/Locked/api_spec.rb +37 -0
  41. data/spec/lib/Locked/client_spec.rb +226 -0
  42. data/spec/lib/Locked/command_spec.rb +9 -0
  43. data/spec/lib/Locked/commands/authenticate_spec.rb +95 -0
  44. data/spec/lib/Locked/commands/identify_spec.rb +87 -0
  45. data/spec/lib/Locked/commands/review_spec.rb +24 -0
  46. data/spec/lib/Locked/configuration_spec.rb +146 -0
  47. data/spec/lib/Locked/context/default_spec.rb +35 -0
  48. data/spec/lib/Locked/context/merger_spec.rb +23 -0
  49. data/spec/lib/Locked/context/sanitizer_spec.rb +27 -0
  50. data/spec/lib/Locked/extractors/client_id_spec.rb +62 -0
  51. data/spec/lib/Locked/extractors/headers_spec.rb +26 -0
  52. data/spec/lib/Locked/extractors/ip_spec.rb +27 -0
  53. data/spec/lib/Locked/header_formatter_spec.rb +25 -0
  54. data/spec/lib/Locked/review_spec.rb +19 -0
  55. data/spec/lib/Locked/secure_mode_spec.rb +9 -0
  56. data/spec/lib/Locked/utils/cloner_spec.rb +18 -0
  57. data/spec/lib/Locked/utils/merger_spec.rb +13 -0
  58. data/spec/lib/Locked/utils/timestamp_spec.rb +17 -0
  59. data/spec/lib/Locked/utils_spec.rb +156 -0
  60. data/spec/lib/Locked/validators/not_supported_spec.rb +26 -0
  61. data/spec/lib/Locked/validators/present_spec.rb +33 -0
  62. data/spec/lib/Locked/version_spec.rb +5 -0
  63. data/spec/lib/locked_spec.rb +66 -0
  64. data/spec/spec_helper.rb +22 -0
  65. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Locked::SecureMode do
4
+ it 'has signature' do
5
+ expect(described_class.signature('test')).to eql(
6
+ '02afb56304902c656fcb737cdd03de6205bb6d401da2812efd9b2d36a08af159'
7
+ )
8
+ end
9
+ 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