castle-rb 6.0.1 → 7.0.0

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.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -8
  3. data/lib/castle.rb +7 -11
  4. data/lib/castle/api.rb +7 -12
  5. data/lib/castle/api/approve_device.rb +1 -6
  6. data/lib/castle/api/authenticate.rb +10 -7
  7. data/lib/castle/api/end_impersonation.rb +3 -8
  8. data/lib/castle/api/filter.rb +37 -0
  9. data/lib/castle/api/get_device.rb +1 -6
  10. data/lib/castle/api/get_devices_for_user.rb +1 -6
  11. data/lib/castle/api/log.rb +37 -0
  12. data/lib/castle/api/report_device.rb +1 -6
  13. data/lib/castle/api/risk.rb +37 -0
  14. data/lib/castle/api/start_impersonation.rb +3 -8
  15. data/lib/castle/api/track.rb +1 -6
  16. data/lib/castle/client.rb +36 -16
  17. data/lib/castle/commands/approve_device.rb +1 -5
  18. data/lib/castle/commands/end_impersonation.rb +1 -1
  19. data/lib/castle/commands/filter.rb +23 -0
  20. data/lib/castle/commands/get_device.rb +1 -5
  21. data/lib/castle/commands/get_devices_for_user.rb +1 -5
  22. data/lib/castle/commands/{identify.rb → log.rb} +4 -3
  23. data/lib/castle/commands/report_device.rb +1 -5
  24. data/lib/castle/commands/risk.rb +23 -0
  25. data/lib/castle/commands/start_impersonation.rb +1 -1
  26. data/lib/castle/configuration.rb +18 -8
  27. data/lib/castle/core/get_connection.rb +3 -1
  28. data/lib/castle/core/process_response.rb +5 -2
  29. data/lib/castle/core/process_webhook.rb +10 -5
  30. data/lib/castle/core/send_request.rb +8 -16
  31. data/lib/castle/errors.rb +37 -13
  32. data/lib/castle/failover/prepare_response.rb +2 -7
  33. data/lib/castle/failover/strategy.rb +3 -0
  34. data/lib/castle/headers/extract.rb +4 -4
  35. data/lib/castle/headers/filter.rb +9 -6
  36. data/lib/castle/ips/extract.rb +4 -2
  37. data/lib/castle/logger.rb +3 -3
  38. data/lib/castle/payload/prepare.rb +3 -4
  39. data/lib/castle/secure_mode.rb +3 -2
  40. data/lib/castle/support/hanami.rb +2 -6
  41. data/lib/castle/support/rails.rb +1 -3
  42. data/lib/castle/utils/clean_invalid_chars.rb +1 -3
  43. data/lib/castle/verdict.rb +2 -0
  44. data/lib/castle/version.rb +1 -1
  45. data/lib/castle/webhooks/verify.rb +9 -7
  46. data/spec/integration/rails/rails_spec.rb +9 -7
  47. data/spec/integration/rails/support/home_controller.rb +26 -24
  48. data/spec/lib/castle/api/approve_device_spec.rb +3 -3
  49. data/spec/lib/castle/api/authenticate_spec.rb +20 -24
  50. data/spec/lib/castle/api/end_impersonation_spec.rb +11 -5
  51. data/spec/lib/castle/api/filter_spec.rb +5 -0
  52. data/spec/lib/castle/api/get_device_spec.rb +3 -3
  53. data/spec/lib/castle/api/get_devices_for_user_spec.rb +3 -3
  54. data/spec/lib/castle/api/log_spec.rb +5 -0
  55. data/spec/lib/castle/api/report_device_spec.rb +3 -3
  56. data/spec/lib/castle/api/risk_spec.rb +5 -0
  57. data/spec/lib/castle/api/start_impersonation_spec.rb +11 -5
  58. data/spec/lib/castle/api/track_spec.rb +11 -7
  59. data/spec/lib/castle/api_spec.rb +4 -20
  60. data/spec/lib/castle/client_id/extract_spec.rb +4 -13
  61. data/spec/lib/castle/client_spec.rb +81 -84
  62. data/spec/lib/castle/commands/authenticate_spec.rb +8 -15
  63. data/spec/lib/castle/commands/end_impersonation_spec.rb +6 -9
  64. data/spec/lib/castle/commands/{identify_spec.rb → filter_spec.rb} +41 -19
  65. data/spec/lib/castle/commands/log_spec.rb +100 -0
  66. data/spec/lib/castle/commands/risk_spec.rb +100 -0
  67. data/spec/lib/castle/commands/start_impersonation_spec.rb +6 -9
  68. data/spec/lib/castle/commands/track_spec.rb +9 -18
  69. data/spec/lib/castle/configuration_spec.rb +2 -6
  70. data/spec/lib/castle/context/get_default_spec.rb +8 -8
  71. data/spec/lib/castle/context/prepare_spec.rb +6 -7
  72. data/spec/lib/castle/core/get_connection_spec.rb +6 -22
  73. data/spec/lib/castle/core/process_response_spec.rb +1 -8
  74. data/spec/lib/castle/core/send_request_spec.rb +4 -29
  75. data/spec/lib/castle/headers/extract_spec.rb +1 -3
  76. data/spec/lib/castle/headers/filter_spec.rb +12 -11
  77. data/spec/lib/castle/ips/extract_spec.rb +4 -13
  78. data/spec/lib/castle/logger_spec.rb +2 -6
  79. data/spec/lib/castle/payload/prepare_spec.rb +5 -4
  80. data/spec/lib/castle/session_spec.rb +13 -36
  81. data/spec/lib/castle/singleton_configuration_spec.rb +2 -6
  82. data/spec/lib/castle/utils/clean_invalid_chars_spec.rb +2 -2
  83. data/spec/lib/castle/utils/merge_spec.rb +3 -1
  84. data/spec/lib/castle/validators/present_spec.rb +5 -6
  85. data/spec/lib/castle/webhooks/verify_spec.rb +8 -24
  86. data/spec/lib/castle_spec.rb +4 -10
  87. data/spec/spec_helper.rb +1 -3
  88. data/spec/support/shared_examples/action_request.rb +152 -0
  89. data/spec/support/shared_examples/configuration.rb +14 -42
  90. metadata +23 -18
  91. data/lib/castle/api/identify.rb +0 -26
  92. data/lib/castle/api/review.rb +0 -24
  93. data/lib/castle/commands/review.rb +0 -17
  94. data/lib/castle/events.rb +0 -49
  95. data/spec/lib/castle/api/identify_spec.rb +0 -68
  96. data/spec/lib/castle/api/review_spec.rb +0 -19
  97. data/spec/lib/castle/commands/review_spec.rb +0 -24
  98. data/spec/lib/castle/events_spec.rb +0 -5
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::Configuration do
4
- subject(:config) do
5
- described_class.new
6
- end
4
+ subject(:config) { described_class.new }
7
5
 
8
6
  it_behaves_like 'configuration_host'
9
7
  it_behaves_like 'configuration_request_timeout'
@@ -12,7 +10,5 @@ describe Castle::Configuration do
12
10
  it_behaves_like 'configuration_failover_strategy'
13
11
  it_behaves_like 'configuration_api_secret'
14
12
 
15
- it do
16
- expect(config.api_secret).to be_eql('')
17
- end
13
+ it { expect(config.api_secret).to be_eql('') }
18
14
  end
@@ -7,11 +7,13 @@ describe Castle::Context::GetDefault do
7
7
  let(:client_id) { 'abcd' }
8
8
 
9
9
  let(:env) do
10
- Rack::MockRequest.env_for('/',
11
- 'HTTP_X_FORWARDED_FOR' => ip,
12
- 'HTTP_ACCEPT_LANGUAGE' => 'en',
13
- 'HTTP_USER_AGENT' => 'test',
14
- 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh")
10
+ Rack::MockRequest.env_for(
11
+ '/',
12
+ 'HTTP_X_FORWARDED_FOR' => ip,
13
+ 'HTTP_ACCEPT_LANGUAGE' => 'en',
14
+ 'HTTP_USER_AGENT' => 'test',
15
+ 'HTTP_COOKIE' => "__cid=#{client_id};other=efgh"
16
+ )
15
17
  end
16
18
  let(:request) { Rack::Request.new(env) }
17
19
  let(:default_context) { subject.call }
@@ -26,9 +28,7 @@ describe Castle::Context::GetDefault do
26
28
  }
27
29
  end
28
30
 
29
- before do
30
- stub_const('Castle::VERSION', version)
31
- end
31
+ before { stub_const('Castle::VERSION', version) }
32
32
 
33
33
  it { expect(default_context[:active]).to be_eql(true) }
34
34
  it { expect(default_context[:headers]).to be_eql(result_headers) }
@@ -20,19 +20,18 @@ describe Castle::Context::Prepare do
20
20
  user_agent: ua,
21
21
  headers: headers,
22
22
  ip: ip,
23
- library: { name: 'castle-rb', version: '6.0.0' }
23
+ library: {
24
+ name: 'castle-rb',
25
+ version: '6.0.0'
26
+ }
24
27
  }
25
28
  end
26
29
 
27
30
  let(:headers) do
28
- {
29
- 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
30
- }
31
+ { 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true }
31
32
  end
32
33
 
33
- before do
34
- stub_const('Castle::VERSION', '6.0.0')
35
- end
34
+ before { stub_const('Castle::VERSION', '6.0.0') }
36
35
 
37
36
  describe '#call' do
38
37
  subject(:generated) { described_class.call(request) }
@@ -12,47 +12,31 @@ describe Castle::Core::GetConnection do
12
12
  before do
13
13
  Castle.config.base_url = 'http://localhost:3002'
14
14
 
15
- allow(Net::HTTP)
16
- .to receive(:new)
17
- .with(localhost, port)
18
- .and_call_original
15
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
19
16
  end
20
17
 
21
18
  it do
22
19
  class_call
23
20
 
24
- expect(Net::HTTP)
25
- .to have_received(:new)
26
- .with(localhost, port)
21
+ expect(Net::HTTP).to have_received(:new).with(localhost, port)
27
22
  end
28
23
 
29
- it do
30
- expect(class_call).to be_an_instance_of(Net::HTTP)
31
- end
24
+ it { expect(class_call).to be_an_instance_of(Net::HTTP) }
32
25
  end
33
26
 
34
27
  context 'when ssl true' do
35
28
  let(:localhost) { 'localhost' }
36
29
  let(:port) { 443 }
37
30
 
38
- before do
39
- Castle.config.base_url = 'https://localhost'
40
- end
31
+ before { Castle.config.base_url = 'https://localhost' }
41
32
 
42
33
  context 'with block' do
43
34
  let(:api_url) { '/test' }
44
35
  let(:request) { Net::HTTP::Get.new(api_url) }
45
36
 
46
- before do
47
- allow(Net::HTTP)
48
- .to receive(:new)
49
- .with(localhost, port)
50
- .and_call_original
51
- end
37
+ before { allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original }
52
38
 
53
- it do
54
- expect(class_call).to be_an_instance_of(Net::HTTP)
55
- end
39
+ it { expect(class_call).to be_an_instance_of(Net::HTTP) }
56
40
  end
57
41
  end
58
42
  end
@@ -38,14 +38,7 @@ describe Castle::Core::ProcessResponse do
38
38
  '{"action":"deny","user_id":"1","device_token":"abc",
39
39
  "risk_policy":{"id":"123","revision_id":"abc","name":"def","type":"bot"}}'
40
40
  end
41
- let(:response) do
42
- OpenStruct.new(
43
- {
44
- body: body,
45
- code: 200
46
- }
47
- )
48
- end
41
+ let(:response) { OpenStruct.new({ body: body, code: 200 }) }
49
42
 
50
43
  let(:result) do
51
44
  {
@@ -27,9 +27,7 @@ describe Castle::Core::SendRequest do
27
27
  end
28
28
 
29
29
  it do
30
- expect(described_class).to have_received(:build).with(
31
- command, expected_headers, config
32
- )
30
+ expect(described_class).to have_received(:build).with(command, expected_headers, config)
33
31
  end
34
32
 
35
33
  it { expect(http).to have_received(:request).with(request_build) }
@@ -48,9 +46,7 @@ describe Castle::Core::SendRequest do
48
46
  it { expect(Castle::Core::GetConnection).not_to have_received(:call) }
49
47
 
50
48
  it do
51
- expect(described_class).to have_received(:build).with(
52
- command, expected_headers, config
53
- )
49
+ expect(described_class).to have_received(:build).with(command, expected_headers, config)
54
50
  end
55
51
 
56
52
  it { expect(http).to have_received(:request).with(request_build) }
@@ -63,31 +59,10 @@ describe Castle::Core::SendRequest do
63
59
  let(:headers) { { 'SAMPLE-HEADER' => '1' } }
64
60
  let(:api_secret) { 'secret' }
65
61
 
66
- context 'when get' do
67
- let(:command) { Castle::Commands::Review.build({ review_id: review_id }) }
68
- let(:review_id) { SecureRandom.uuid }
69
-
70
- it { expect(build.body).to be_nil }
71
- it { expect(build.method).to eql('GET') }
72
- it { expect(build.path).to eql("/v1/#{command.path}") }
73
- it { expect(build.to_hash).to have_key('authorization') }
74
- it { expect(build.to_hash).to have_key('sample-header') }
75
- it { expect(build.to_hash['sample-header']).to eql(['1']) }
76
- end
77
-
78
62
  context 'when post' do
79
63
  let(:time) { Time.now.utc.iso8601(3) }
80
- let(:command) do
81
- Castle::Commands::Track.build(event: '$login.succeeded', name: "\xC4")
82
- end
83
- let(:expected_body) do
84
- {
85
- event: '$login.succeeded',
86
- name: '�',
87
- context: {},
88
- sent_at: time
89
- }
90
- end
64
+ let(:command) { Castle::Commands::Track.build(event: '$login.succeeded', name: "\xC4") }
65
+ let(:expected_body) { { event: '$login.succeeded', name: '�', context: {}, sent_at: time } }
91
66
 
92
67
  before { allow(Castle::Utils::GetTimestamp).to receive(:call).and_return(time) }
93
68
 
@@ -98,8 +98,6 @@ describe Castle::Headers::Extract do
98
98
  Castle.config.denylisted = %w[Accept]
99
99
  end
100
100
 
101
- it do
102
- expect(extract_call['Accept']).to eq(true)
103
- end
101
+ it { expect(extract_call['Accept']).to eq(true) }
104
102
  end
105
103
  end
@@ -4,17 +4,18 @@ describe Castle::Headers::Filter do
4
4
  subject(:filter_call) { described_class.new(request).call }
5
5
 
6
6
  let(:env) do
7
- result = Rack::MockRequest.env_for(
8
- '/',
9
- 'Action-Dispatch.request.content-Type' => 'application/json',
10
- 'HTTP_AUTHORIZATION' => 'Basic 123456',
11
- 'HTTP_COOKIE' => '__cid=abcd;other=efgh',
12
- 'HTTP_ACCEPT' => 'application/json',
13
- 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
14
- 'HTTP_USER_AGENT' => 'Mozilla 1234',
15
- 'TEST' => '1',
16
- 'REMOTE_ADDR' => '1.2.3.4'
17
- )
7
+ result =
8
+ Rack::MockRequest.env_for(
9
+ '/',
10
+ 'Action-Dispatch.request.content-Type' => 'application/json',
11
+ 'HTTP_AUTHORIZATION' => 'Basic 123456',
12
+ 'HTTP_COOKIE' => '__cid=abcd;other=efgh',
13
+ 'HTTP_ACCEPT' => 'application/json',
14
+ 'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
15
+ 'HTTP_USER_AGENT' => 'Mozilla 1234',
16
+ 'TEST' => '1',
17
+ 'REMOTE_ADDR' => '1.2.3.4'
18
+ )
18
19
  result[:HTTP_OK] = 'OK'
19
20
  result
20
21
  end
@@ -43,9 +43,7 @@ describe Castle::IPs::Extract do
43
43
  end
44
44
 
45
45
  context 'with all the trusted proxies' do
46
- let(:http_x_header) do
47
- '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1'
48
- end
46
+ let(:http_x_header) { '127.0.0.1,10.0.0.1,172.31.0.1,192.168.0.1' }
49
47
 
50
48
  let(:headers) { { 'Remote-Addr' => '127.0.0.1', 'X-Forwarded-For' => http_x_header } }
51
49
 
@@ -55,9 +53,7 @@ describe Castle::IPs::Extract do
55
53
  end
56
54
 
57
55
  context 'with trust_proxy_chain option' do
58
- let(:http_x_header) do
59
- '6.6.6.6, 2.2.2.3, 6.6.6.5'
60
- end
56
+ let(:http_x_header) { '6.6.6.6, 2.2.2.3, 6.6.6.5' }
61
57
 
62
58
  let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
63
59
 
@@ -69,9 +65,7 @@ describe Castle::IPs::Extract do
69
65
  end
70
66
 
71
67
  context 'with trusted_proxy_depth option' do
72
- let(:http_x_header) do
73
- '6.6.6.6, 2.2.2.3, 6.6.6.5'
74
- end
68
+ let(:http_x_header) { '6.6.6.6, 2.2.2.3, 6.6.6.5' }
75
69
 
76
70
  let(:headers) { { 'Remote-Addr' => '6.6.6.4', 'X-Forwarded-For' => http_x_header } }
77
71
 
@@ -84,10 +78,7 @@ describe Castle::IPs::Extract do
84
78
 
85
79
  context 'when list of not trusted ips provided in X_FORWARDED_FOR' do
86
80
  let(:headers) do
87
- {
88
- 'X-Forwarded-For' => '6.6.6.6, 2.2.2.3, 192.168.0.7',
89
- 'Client-Ip' => '6.6.6.6'
90
- }
81
+ { 'X-Forwarded-For' => '6.6.6.6, 2.2.2.3, 192.168.0.7', 'Client-Ip' => '6.6.6.6' }
91
82
  end
92
83
 
93
84
  it 'does not allow to spoof ip' do
@@ -7,18 +7,14 @@ class TmpLogger
7
7
  end
8
8
 
9
9
  describe Castle::Logger do
10
- subject(:log) do
11
- described_class.call(message, data)
12
- end
10
+ subject(:log) { described_class.call(message, data) }
13
11
 
14
12
  let(:message) { 'https://localhost/test:' }
15
13
  let(:integration_logger) { TmpLogger.new }
16
14
  let(:data) { { a: 1 }.to_json }
17
15
  let(:logger_message) { "[CASTLE] #{message} #{data}" }
18
16
 
19
- before do
20
- allow(integration_logger).to receive(:info).and_call_original
21
- end
17
+ before { allow(integration_logger).to receive(:info).and_call_original }
22
18
 
23
19
  describe '.call' do
24
20
  context 'without logger' do
@@ -15,9 +15,7 @@ describe Castle::Payload::Prepare do
15
15
  let(:request) { Rack::Request.new(env) }
16
16
 
17
17
  let(:headers) do
18
- {
19
- 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true
20
- }
18
+ { 'Content-Length': '0', 'User-Agent': ua, 'X-Forwarded-For': ip.to_s, 'Cookie': true }
21
19
  end
22
20
  let(:context) do
23
21
  {
@@ -26,7 +24,10 @@ describe Castle::Payload::Prepare do
26
24
  user_agent: ua,
27
25
  headers: headers,
28
26
  ip: ip,
29
- library: { name: 'castle-rb', version: '2.2.0' }
27
+ library: {
28
+ name: 'castle-rb',
29
+ version: '2.2.0'
30
+ }
30
31
  }
31
32
  end
32
33
 
@@ -16,35 +16,20 @@ describe Castle::Session do
16
16
  let(:request) { Net::HTTP::Get.new(api_url) }
17
17
 
18
18
  before do
19
- allow(Net::HTTP)
20
- .to receive(:new)
21
- .with(localhost, port)
22
- .and_call_original
23
-
24
- described_class.call do |http|
25
- http.request(request)
26
- end
27
- end
19
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
28
20
 
29
- it do
30
- expect(Net::HTTP)
31
- .to have_received(:new)
32
- .with(localhost, port)
21
+ described_class.call { |http| http.request(request) }
33
22
  end
34
23
 
35
- it do
36
- expect(a_request(:get, 'localhost:3002/test'))
37
- .to have_been_made.once
38
- end
24
+ it { expect(Net::HTTP).to have_received(:new).with(localhost, port) }
25
+
26
+ it { expect(a_request(:get, 'localhost:3002/test')).to have_been_made.once }
39
27
  end
40
28
 
41
29
  context 'without block' do
42
30
  before { described_class.call }
43
31
 
44
- it do
45
- expect(a_request(:get, 'localhost:3002/test'))
46
- .not_to have_been_made
47
- end
32
+ it { expect(a_request(:get, 'localhost:3002/test')).not_to have_been_made }
48
33
  end
49
34
  end
50
35
 
@@ -55,7 +40,9 @@ describe Castle::Session do
55
40
  before do
56
41
  Castle.config.base_url = 'https://localhost'
57
42
  stub_request(:get, 'https://localhost/test').to_return(
58
- status: 200, body: '{}', headers: {}
43
+ status: 200,
44
+ body: '{}',
45
+ headers: {}
59
46
  )
60
47
  end
61
48
 
@@ -64,24 +51,14 @@ describe Castle::Session do
64
51
  let(:request) { Net::HTTP::Get.new(api_url) }
65
52
 
66
53
  before do
67
- allow(Net::HTTP)
68
- .to receive(:new)
69
- .with(localhost, port)
70
- .and_call_original
54
+ allow(Net::HTTP).to receive(:new).with(localhost, port).and_call_original
71
55
 
72
- allow(Net::HTTP)
73
- .to receive(:start)
56
+ allow(Net::HTTP).to receive(:start)
74
57
 
75
- described_class.call do |http|
76
- http.request(request)
77
- end
58
+ described_class.call { |http| http.request(request) }
78
59
  end
79
60
 
80
- it do
81
- expect(Net::HTTP)
82
- .to have_received(:new)
83
- .with(localhost, port)
84
- end
61
+ it { expect(Net::HTTP).to have_received(:new).with(localhost, port) }
85
62
  end
86
63
  end
87
64
  end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe Castle::SingletonConfiguration do
4
- subject(:config) do
5
- described_class.instance
6
- end
4
+ subject(:config) { described_class.instance }
7
5
 
8
6
  it_behaves_like 'configuration_host'
9
7
  it_behaves_like 'configuration_request_timeout'
@@ -12,7 +10,5 @@ describe Castle::SingletonConfiguration do
12
10
  it_behaves_like 'configuration_failover_strategy'
13
11
  it_behaves_like 'configuration_api_secret'
14
12
 
15
- it do
16
- expect(config.api_secret).to be_eql('secret')
17
- end
13
+ it { expect(config.api_secret).to be_eql('secret') }
18
14
  end
@@ -56,13 +56,13 @@ describe Castle::Utils::CleanInvalidChars do
56
56
  context 'when input is an array' do
57
57
  let(:input) { ["inv\xC4lid"] * 2 }
58
58
 
59
- it { is_expected.to eq(['inv�lid', 'inv�lid']) }
59
+ it { is_expected.to eq(%w[inv�lid inv�lid]) }
60
60
  end
61
61
 
62
62
  context 'when input is a hash with array in key' do
63
63
  let(:input) { { items: ["inv\xC4lid"] * 2 } }
64
64
 
65
- it { is_expected.to eq(items: ['inv�lid', 'inv�lid']) }
65
+ it { is_expected.to eq(items: %w[inv�lid inv�lid]) }
66
66
  end
67
67
  end
68
68
  end