freeclimb 4.1.3 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +6 -6
- data/README.md +28 -5
- data/lib/freeclimb/models/percl_command.rb +5 -5
- data/lib/freeclimb/utils/request_verifier.rb +61 -0
- data/lib/freeclimb/utils/signature_information.rb +45 -0
- data/lib/freeclimb/version.rb +1 -1
- data/lib/freeclimb.rb +4 -0
- data/spec/quickstart_spec.rb +3 -2
- data/spec/utils/request_verifier_spec.rb +148 -0
- data/spec/utils/signature_information_spec.rb +38 -0
- metadata +77 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7826dfa439ee726cf91d308b8cd9eee895a48d53a425ac24926765525c23760
|
4
|
+
data.tar.gz: cec6eee4ac9ca13157b48a10a23f837c18f684bf3621cbe41fb89655291e493c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f6600bdf1b690008a0c6dfffb24de9e180e5e58600103e6e556dff32243fde20bd7f8d84ce5fcc1211804487fa82c68fd3f000b70bb1a4126278d4edbbcd537
|
7
|
+
data.tar.gz: c5e79a2cfbf443175d929f7b6ee271ae8a4c8202c93f7823cc8212a1a97ce0aff0b424f93a2ab022c63a3757cd2405bbfc81ab8b72de24bc7eb2835731d47e25
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,22 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
9
9
|
|
10
10
|
None
|
11
11
|
|
12
|
+
<a name="4.2.0"></a>
|
13
|
+
|
14
|
+
## [4.2.0] - 2023-04-03
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- Introduce signing secret verification class (RequestVerifier) - https://docs.freeclimb.com/docs/validating-requests-from-freeclimb#how-to-verify-requests-manually
|
19
|
+
|
20
|
+
<a name="4.1.4"></a>
|
21
|
+
|
22
|
+
## [4.1.4] - 2023-03-31
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- PerclScript.to_json serialization fixed
|
27
|
+
|
12
28
|
<a name="4.1.3"></a>
|
13
29
|
|
14
30
|
## [4.1.3] - 2023-03-13
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
freeclimb (4.
|
4
|
+
freeclimb (4.2.0)
|
5
5
|
typhoeus (~> 1.0, >= 1.0.1)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activesupport (7.0.
|
10
|
+
activesupport (7.0.4.3)
|
11
11
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
12
|
i18n (>= 1.6, < 2)
|
13
13
|
minitest (>= 5.1)
|
@@ -17,7 +17,7 @@ GEM
|
|
17
17
|
ast (2.4.0)
|
18
18
|
byebug (11.1.1)
|
19
19
|
coderay (1.1.2)
|
20
|
-
concurrent-ruby (1.
|
20
|
+
concurrent-ruby (1.2.2)
|
21
21
|
crack (0.4.5)
|
22
22
|
rexml
|
23
23
|
diff-lcs (1.3)
|
@@ -28,11 +28,11 @@ GEM
|
|
28
28
|
activesupport (>= 5.0.0)
|
29
29
|
ffi (1.15.5)
|
30
30
|
hashdiff (1.0.1)
|
31
|
-
i18n (1.
|
31
|
+
i18n (1.12.0)
|
32
32
|
concurrent-ruby (~> 1.0)
|
33
33
|
jaro_winkler (1.5.4)
|
34
34
|
method_source (0.9.2)
|
35
|
-
minitest (5.
|
35
|
+
minitest (5.18.0)
|
36
36
|
parallel (1.19.1)
|
37
37
|
parser (2.7.0.2)
|
38
38
|
ast (~> 2.4.0)
|
@@ -71,7 +71,7 @@ GEM
|
|
71
71
|
ruby-progressbar (1.10.1)
|
72
72
|
typhoeus (1.4.0)
|
73
73
|
ethon (>= 0.9.0)
|
74
|
-
tzinfo (2.0.
|
74
|
+
tzinfo (2.0.6)
|
75
75
|
concurrent-ruby (~> 1.0)
|
76
76
|
unicode-display_width (1.5.0)
|
77
77
|
webmock (3.14.0)
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ FreeClimb is a cloud-based application programming interface (API) that puts the
|
|
7
7
|
This SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
8
8
|
|
9
9
|
- API version: 1.0.0
|
10
|
-
- Package version: 4.
|
10
|
+
- Package version: 4.2.0
|
11
11
|
- Build package: org.openapitools.codegen.languages.RubyClientCodegen
|
12
12
|
For more information, please visit [https://www.freeclimb.com/support/](https://www.freeclimb.com/support/)
|
13
13
|
|
@@ -15,7 +15,7 @@ For more information, please visit [https://www.freeclimb.com/support/](https://
|
|
15
15
|
|
16
16
|
Add this to the Gemfile:
|
17
17
|
|
18
|
-
gem 'freeclimb', '~> 4.
|
18
|
+
gem 'freeclimb', '~> 4.2.0'
|
19
19
|
|
20
20
|
and run from your terminal
|
21
21
|
|
@@ -44,9 +44,9 @@ gem build freeclimb.gemspec
|
|
44
44
|
Then either install the gem locally:
|
45
45
|
|
46
46
|
```shell
|
47
|
-
gem install ./freeclimb-4.
|
47
|
+
gem install ./freeclimb-4.2.0.gem
|
48
48
|
```
|
49
|
-
(for development, run `gem install --dev ./freeclimb-4.
|
49
|
+
(for development, run `gem install --dev ./freeclimb-4.2.0.gem` to install the development dependencies)
|
50
50
|
|
51
51
|
## Getting Started
|
52
52
|
|
@@ -285,7 +285,30 @@ Class | Method | HTTP request | Description
|
|
285
285
|
|
286
286
|
- **Type**: HTTP basic authentication
|
287
287
|
|
288
|
+
<a name="documentation-for-verify-request-signature"></a>
|
289
|
+
|
290
|
+
## Documentation for verifying request signature
|
291
|
+
|
292
|
+
- To verify the signature request, we will need to use the verifySignatureRequest method within the Request Verifier class
|
293
|
+
|
294
|
+
Freeclimb::RequestVerifier.verify_request_signature(requestBody, requestHeader, signingSecret, tolerance)
|
295
|
+
|
296
|
+
This is a method that you can call directly from the request verifier class, it will throw exceptions depending on whether all parts of the request signature is valid otherwise it will throw a specific error message depending on which request signature part is causing issues
|
297
|
+
|
298
|
+
This method requires a requestBody of type string, a requestHeader of type string, a signingSecret of type string, and a tolerance value of type int
|
299
|
+
|
300
|
+
Example code down below
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
class RequestVerifier
|
304
|
+
def verify_request_signature_example()
|
305
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
306
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
307
|
+
tolerance = (5 * 60)
|
308
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
309
|
+
Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance)
|
310
|
+
```
|
288
311
|
|
289
312
|
## Getting Help
|
290
313
|
|
291
|
-
If you are experiencing difficulties, [contact support](https://freeclimb.com/support).
|
314
|
+
If you are experiencing difficulties, [contact support](https://freeclimb.com/support).
|
@@ -221,15 +221,15 @@ module Freeclimb
|
|
221
221
|
end
|
222
222
|
def to_percl_hash()
|
223
223
|
command = self.command
|
224
|
-
attributes = self.class.
|
225
|
-
percl_hash = attributes.each_with_object({}) do |(attr,
|
224
|
+
attributes = self.class.attribute_map
|
225
|
+
percl_hash = attributes.each_with_object({}) do |(attr, percl_attr), hash|
|
226
226
|
value = self.send(attr)
|
227
227
|
if value.is_a?(Array)
|
228
|
-
hash[
|
228
|
+
hash[percl_attr] = value.compact.map { |v| v.is_a?(PerclCommand) ? v.to_percl_hash() : v }
|
229
229
|
elsif value.is_a?(PerclCommand)
|
230
|
-
hash[
|
230
|
+
hash[percl_attr] = value.to_percl_hash()
|
231
231
|
elsif !value.nil?
|
232
|
-
hash[
|
232
|
+
hash[percl_attr] = value
|
233
233
|
end
|
234
234
|
end
|
235
235
|
result = {}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Freeclimb
|
2
|
+
class RequestVerifier
|
3
|
+
class << self
|
4
|
+
@@DEFAULT_TOLERANCE = 5 * 60 * 1000
|
5
|
+
|
6
|
+
def verify_request_signature(request_body, request_header, signing_secret, tolerance=DEFAULT_TOLERANCE)
|
7
|
+
request_verifier_object = Freeclimb::RequestVerifier.new()
|
8
|
+
request_verifier_object.instance_eval{ check_request_body(request_body) }
|
9
|
+
request_verifier_object.instance_eval{ check_request_header(request_header) }
|
10
|
+
request_verifier_object.instance_eval{ check_signing_secret(signing_secret) }
|
11
|
+
request_verifier_object.instance_eval{ check_tolerance(tolerance) }
|
12
|
+
info = Freeclimb::SignatureInformation.new(request_header)
|
13
|
+
request_verifier_object.instance_eval{ verify_tolerance(info, tolerance) }
|
14
|
+
request_verifier_object.instance_eval{ verify_signature(info, request_body, signing_secret)}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check_request_body(request_body)
|
21
|
+
if request_body == "" || request_body == nil
|
22
|
+
raise 'Request Body cannot be empty or null'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_request_header(request_header)
|
27
|
+
if request_header == "" || request_header == nil
|
28
|
+
raise 'Error with request header, Request header is empty'
|
29
|
+
elsif !(request_header.include? "t")
|
30
|
+
raise 'Error with request header, timestamp is not present'
|
31
|
+
elsif !(request_header.include? "v1")
|
32
|
+
raise 'Error with request header, signatures are not present'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_signing_secret(signing_secret)
|
37
|
+
if signing_secret == "" || signing_secret == nil
|
38
|
+
raise 'Signing secret cannot be empty or null'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_tolerance(tolerance)
|
43
|
+
if tolerance <= 0 || !(tolerance.is_a? Integer)
|
44
|
+
raise 'Tolerance value must be a positive integer'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def verify_tolerance(info, tolerance)
|
49
|
+
currentTime = info.get_current_unix_time()
|
50
|
+
if !info.is_request_time_valid(tolerance)
|
51
|
+
raise "Request time exceeded tolerance threshold. Request: " + info.request_timestamp.to_s + ", CurrentTime: " + currentTime.to_s + ", tolerance: " + tolerance.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_signature(info, request_body, signing_secret)
|
56
|
+
if !info.is_signature_safe(request_body, signing_secret)
|
57
|
+
raise "Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Freeclimb
|
5
|
+
class SignatureInformation
|
6
|
+
attr_accessor :request_timestamp
|
7
|
+
attr_accessor :signatures
|
8
|
+
|
9
|
+
def initialize(request_header)
|
10
|
+
@request_timestamp = 0
|
11
|
+
@signatures = []
|
12
|
+
signatureHeader = request_header.try(:split, ",")
|
13
|
+
signatureHeader.each { |signature|
|
14
|
+
header, value = signature.try(:split, "=")
|
15
|
+
if header == "t"
|
16
|
+
@request_timestamp = value.to_i
|
17
|
+
elsif header == "v1"
|
18
|
+
@signatures.append(value)
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_request_time_valid(tolerance)
|
24
|
+
currentTime = self.get_current_unix_time()
|
25
|
+
timeCalculation = @request_timestamp + tolerance
|
26
|
+
return (timeCalculation) < currentTime
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_signature_safe(request_body, signing_secret)
|
30
|
+
hashValue = self.compute_hash(request_body, signing_secret)
|
31
|
+
return @signatures.include? hashValue
|
32
|
+
end
|
33
|
+
|
34
|
+
def compute_hash(request_body, signing_secret)
|
35
|
+
data = @request_timestamp.to_s + "." + request_body
|
36
|
+
return OpenSSL::HMAC.hexdigest('sha256', signing_secret, data)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_current_unix_time()
|
40
|
+
return DateTime.now.strftime('%s').to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
private :compute_hash
|
44
|
+
end
|
45
|
+
end
|
data/lib/freeclimb/version.rb
CHANGED
data/lib/freeclimb.rb
CHANGED
@@ -148,6 +148,10 @@ require 'freeclimb/models/unpark'
|
|
148
148
|
# APIs
|
149
149
|
require 'freeclimb/api/default_api'
|
150
150
|
|
151
|
+
#Utils
|
152
|
+
require 'freeclimb/utils/signature_information'
|
153
|
+
require 'freeclimb/utils/request_verifier'
|
154
|
+
|
151
155
|
module Freeclimb
|
152
156
|
class << self
|
153
157
|
# Customize default settings for the SDK using block.
|
data/spec/quickstart_spec.rb
CHANGED
@@ -3,8 +3,9 @@ require 'json'
|
|
3
3
|
describe "quickstart" do
|
4
4
|
it "generates percl to say the text 'Hello, World!'" do
|
5
5
|
say = Freeclimb::Say.new(text:'Hello, World!')
|
6
|
-
|
6
|
+
get_speech = Freeclimb::GetSpeech.new(action_url: 'https://example.com/update')
|
7
|
+
script = Freeclimb::PerclScript.new(commands:[say, get_speech])
|
7
8
|
json = script.to_json()
|
8
|
-
expect(json).to eq("[{\"Say\":{\"text\":\"Hello, World!\",\"loop\":1}}]")
|
9
|
+
expect(json).to eq("[{\"Say\":{\"text\":\"Hello, World!\",\"loop\":1}},{\"GetSpeech\":{\"actionUrl\":\"https://example.com/update\"}}]")
|
9
10
|
end
|
10
11
|
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'RequestVerifier' do
|
4
|
+
before do
|
5
|
+
@request_verifier_object = Freeclimb::RequestVerifier.new()
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#check_request_body' do
|
9
|
+
context 'Request Body is empty' do
|
10
|
+
it 'throws "Request Body cannot be empty or null"' do
|
11
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
12
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
13
|
+
tolerance = 5 * 60
|
14
|
+
request_body = ""
|
15
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Request Body cannot be empty or null")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context 'Request Body is nil' do
|
19
|
+
it 'throws "Request Body cannot be empty or null"' do
|
20
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
21
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
22
|
+
tolerance = 5 * 60
|
23
|
+
request_body = nil
|
24
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Request Body cannot be empty or null")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#check_request_header' do
|
30
|
+
context 'signatures are not present' do
|
31
|
+
it 'throws "Error with request header, signatures are not present"' do
|
32
|
+
request_header = "t=1679944186,"
|
33
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
34
|
+
tolerance = 5 * 60
|
35
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
36
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Error with request header, signatures are not present")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context 'timestamp is not present' do
|
40
|
+
it 'throws "Error with request header, timestamp is not present"' do
|
41
|
+
request_header = "v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
42
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
43
|
+
tolerance = 5 * 60
|
44
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
45
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Error with request header, timestamp is not present")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context 'Request header is empty' do
|
49
|
+
it 'throws "Error with request header, Request header is empty"' do
|
50
|
+
request_header = ""
|
51
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
52
|
+
tolerance = 5 * 60
|
53
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
54
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Error with request header, Request header is empty")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#check_signing_secret' do
|
60
|
+
context 'Signing secret is empty' do
|
61
|
+
it 'throws "Signing secret cannot be empty or null"' do
|
62
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
63
|
+
signing_secret = ""
|
64
|
+
tolerance = 5 * 60
|
65
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
66
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Signing secret cannot be empty or null")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'Signing secret is nil' do
|
70
|
+
it 'throws "Signing secret cannot be empty or null"' do
|
71
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
72
|
+
signing_secret = nil
|
73
|
+
tolerance = 5 * 60
|
74
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
75
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Signing secret cannot be empty or null")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#check_tolerance' do
|
81
|
+
context 'Tolerance value is a negative value' do
|
82
|
+
it 'throws "Tolerance value must be a positive integer"' do
|
83
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
84
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
85
|
+
tolerance = -5
|
86
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
87
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Tolerance value must be a positive integer")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
context 'Tolerance value is 0' do
|
91
|
+
it 'throws "Tolerance value must be a positive integer"' do
|
92
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
93
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
94
|
+
tolerance = 0
|
95
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
96
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Tolerance value must be a positive integer")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
context 'Tolerance value is NaN' do
|
100
|
+
it 'throws "Tolerance value must be a positive integer"' do
|
101
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
102
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
103
|
+
tolerance = Float::NAN
|
104
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
105
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Tolerance value must be a positive integer")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#verify_tolerance' do
|
111
|
+
context 'Request plus tolerance is not less than the current datetime' do
|
112
|
+
it 'throws "Request time exceeded tolerance threshold. Request: 1900871395, CurrentTime: currentTimeValue, tolerance, toleranceValue"' do
|
113
|
+
currentTime = DateTime.now.strftime('%s').to_i
|
114
|
+
request_header = "t=1900871395,v1=1d798c86e977ff734dec3a8b8d67fe8621dcc1df46ef4212e0bfe2e122b01bfd,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
115
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
116
|
+
tolerance = (5 * 60)
|
117
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
118
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Request time exceeded tolerance threshold. Request: 1900871395" + ", CurrentTime: " + currentTime.to_s + ", tolerance: " + tolerance.to_s)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#verify_signature' do
|
124
|
+
context 'Signature request is unverified, signing secret does not exist in signatures, potential typo' do
|
125
|
+
it 'throws "Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret"' do
|
126
|
+
currentTime = DateTime.now.strftime('%s').to_i
|
127
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
128
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7794"
|
129
|
+
tolerance = (5 * 60)
|
130
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
131
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.to raise_error("Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '#verify_request_signature' do
|
137
|
+
context 'Request is valid' do
|
138
|
+
it 'No errors are thrown' do
|
139
|
+
request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
140
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
141
|
+
tolerance = (5 * 60)
|
142
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
143
|
+
expect { Freeclimb::RequestVerifier.verify_request_signature(request_body, request_header, signing_secret, tolerance) }.not_to raise_error
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'SignatureInformation' do
|
4
|
+
before do
|
5
|
+
@request_header = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8"
|
6
|
+
@signature_information_object = Freeclimb::SignatureInformation.new(@request_header)
|
7
|
+
end
|
8
|
+
describe '#is_request_time_valid' do
|
9
|
+
context 'request time is within tolerance threshold' do
|
10
|
+
it 'returns true' do
|
11
|
+
tolerance = 5 * 60
|
12
|
+
expect(@signature_information_object.is_request_time_valid(tolerance)).to be true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
context 'request time is not within tolerance threshold' do
|
16
|
+
it 'returns false since it does not match condition of request time being within tolerance threshold' do
|
17
|
+
tolerance = 5 * 60 * 10000
|
18
|
+
expect(@signature_information_object.is_request_time_valid(tolerance)).to be false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
describe '#is_signature_safe' do
|
23
|
+
context 'signingSecret exists in signature array' do
|
24
|
+
it 'returns true' do
|
25
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
26
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793"
|
27
|
+
expect(@signature_information_object.is_signature_safe(request_body, signing_secret)).to be true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
context 'signingSecret does not exists in signature array' do
|
31
|
+
it 'returns false since it does not match condition of signingSecret being within signature array' do
|
32
|
+
request_body = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}"
|
33
|
+
signing_secret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7794"
|
34
|
+
expect(@signature_information_object.is_signature_safe(request_body, signing_secret)).to be false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freeclimb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OpenAPI-Generator
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typhoeus
|
@@ -330,6 +330,8 @@ files:
|
|
330
330
|
- lib/freeclimb/models/update_conference_participant_request.rb
|
331
331
|
- lib/freeclimb/models/update_conference_request.rb
|
332
332
|
- lib/freeclimb/models/update_conference_request_status.rb
|
333
|
+
- lib/freeclimb/utils/request_verifier.rb
|
334
|
+
- lib/freeclimb/utils/signature_information.rb
|
333
335
|
- lib/freeclimb/version.rb
|
334
336
|
- openapi.json
|
335
337
|
- spec/api/default_api_spec.rb
|
@@ -423,6 +425,8 @@ files:
|
|
423
425
|
- spec/models/update_conference_request_status_spec.rb
|
424
426
|
- spec/quickstart_spec.rb
|
425
427
|
- spec/spec_helper.rb
|
428
|
+
- spec/utils/request_verifier_spec.rb
|
429
|
+
- spec/utils/signature_information_spec.rb
|
426
430
|
homepage: https://freeclimb.com
|
427
431
|
licenses:
|
428
432
|
- Unlicense
|
@@ -451,90 +455,92 @@ test_files:
|
|
451
455
|
- spec/api_client_spec.rb
|
452
456
|
- spec/configuration_spec.rb
|
453
457
|
- spec/factories.rb
|
454
|
-
- spec/models/
|
455
|
-
- spec/models/
|
456
|
-
- spec/models/message_status_spec.rb
|
457
|
-
- spec/models/available_number_list_spec.rb
|
458
|
-
- spec/models/mutable_resource_model_spec.rb
|
459
|
-
- spec/models/add_to_conference_spec.rb
|
460
|
-
- spec/models/account_type_spec.rb
|
461
|
-
- spec/models/queue_request_spec.rb
|
462
|
-
- spec/models/language_spec.rb
|
463
|
-
- spec/models/start_record_call_spec.rb
|
464
|
-
- spec/models/remove_from_conference_spec.rb
|
465
|
-
- spec/models/message_direction_spec.rb
|
466
|
-
- spec/models/application_result_spec.rb
|
467
|
-
- spec/models/log_result_spec.rb
|
468
|
-
- spec/models/terminate_conference_spec.rb
|
469
|
-
- spec/models/create_conference_request_spec.rb
|
470
|
-
- spec/models/enqueue_spec.rb
|
471
|
-
- spec/models/out_dial_spec.rb
|
458
|
+
- spec/models/recording_result_spec.rb
|
459
|
+
- spec/models/pagination_model_spec.rb
|
472
460
|
- spec/models/redirect_spec.rb
|
473
|
-
- spec/models/conference_list_spec.rb
|
474
|
-
- spec/models/call_list_spec.rb
|
475
|
-
- spec/models/incoming_number_list_spec.rb
|
476
|
-
- spec/models/record_utterance_spec.rb
|
477
|
-
- spec/models/set_talk_spec.rb
|
478
|
-
- spec/models/log_level_spec.rb
|
479
|
-
- spec/models/log_list_spec.rb
|
480
461
|
- spec/models/percl_script_spec.rb
|
462
|
+
- spec/models/get_digits_spec.rb
|
463
|
+
- spec/models/dequeue_spec.rb
|
464
|
+
- spec/models/update_conference_participant_request_spec.rb
|
465
|
+
- spec/models/log_result_spec.rb
|
481
466
|
- spec/models/answered_by_spec.rb
|
482
|
-
- spec/models/reject_spec.rb
|
483
|
-
- spec/models/grammar_file_built_in_spec.rb
|
484
467
|
- spec/models/grammar_type_spec.rb
|
485
|
-
- spec/models/if_machine_spec.rb
|
486
468
|
- spec/models/account_result_spec.rb
|
487
|
-
- spec/models/
|
488
|
-
- spec/models/
|
469
|
+
- spec/models/incoming_number_request_spec.rb
|
470
|
+
- spec/models/filter_logs_request_spec.rb
|
471
|
+
- spec/models/message_status_spec.rb
|
472
|
+
- spec/models/start_record_call_spec.rb
|
473
|
+
- spec/models/conference_participant_result_spec.rb
|
474
|
+
- spec/models/message_result_spec.rb
|
475
|
+
- spec/models/record_utterance_term_reason_spec.rb
|
476
|
+
- spec/models/sms_spec.rb
|
489
477
|
- spec/models/get_speech_reason_spec.rb
|
478
|
+
- spec/models/message_request_spec.rb
|
479
|
+
- spec/models/account_status_spec.rb
|
480
|
+
- spec/models/out_dial_spec.rb
|
481
|
+
- spec/models/call_direction_spec.rb
|
482
|
+
- spec/models/language_spec.rb
|
483
|
+
- spec/models/play_beep_spec.rb
|
484
|
+
- spec/models/conference_status_spec.rb
|
485
|
+
- spec/models/request_type_spec.rb
|
486
|
+
- spec/models/terminate_conference_spec.rb
|
487
|
+
- spec/models/conference_result_spec.rb
|
488
|
+
- spec/models/create_conference_request_spec.rb
|
489
|
+
- spec/models/buy_incoming_number_request_spec.rb
|
490
|
+
- spec/models/reject_spec.rb
|
491
|
+
- spec/models/messages_list_spec.rb
|
492
|
+
- spec/models/hangup_spec.rb
|
493
|
+
- spec/models/queue_member_spec.rb
|
494
|
+
- spec/models/percl_command_spec.rb
|
495
|
+
- spec/models/machine_type_spec.rb
|
496
|
+
- spec/models/unpark_spec.rb
|
497
|
+
- spec/models/application_request_spec.rb
|
498
|
+
- spec/models/account_request_spec.rb
|
499
|
+
- spec/models/incoming_number_result_spec.rb
|
490
500
|
- spec/models/queue_result_spec.rb
|
491
|
-
- spec/models/
|
501
|
+
- spec/models/record_utterance_spec.rb
|
492
502
|
- spec/models/available_number_spec.rb
|
493
|
-
- spec/models/call_direction_spec.rb
|
494
|
-
- spec/models/account_status_spec.rb
|
495
503
|
- spec/models/pause_spec.rb
|
496
|
-
- spec/models/
|
497
|
-
- spec/models/
|
498
|
-
- spec/models/
|
499
|
-
- spec/models/
|
504
|
+
- spec/models/conference_participant_list_spec.rb
|
505
|
+
- spec/models/set_listen_spec.rb
|
506
|
+
- spec/models/available_number_list_spec.rb
|
507
|
+
- spec/models/add_to_conference_spec.rb
|
508
|
+
- spec/models/enqueue_spec.rb
|
509
|
+
- spec/models/application_result_spec.rb
|
510
|
+
- spec/models/call_result_spec.rb
|
511
|
+
- spec/models/update_call_request_spec.rb
|
500
512
|
- spec/models/call_status_spec.rb
|
501
|
-
- spec/models/
|
513
|
+
- spec/models/grammar_file_built_in_spec.rb
|
514
|
+
- spec/models/capabilities_spec.rb
|
515
|
+
- spec/models/park_spec.rb
|
516
|
+
- spec/models/queue_request_spec.rb
|
517
|
+
- spec/models/message_direction_spec.rb
|
518
|
+
- spec/models/mutable_resource_model_spec.rb
|
519
|
+
- spec/models/update_conference_request_spec.rb
|
520
|
+
- spec/models/recording_list_spec.rb
|
502
521
|
- spec/models/application_list_spec.rb
|
503
|
-
- spec/models/
|
504
|
-
- spec/models/
|
505
|
-
- spec/models/
|
506
|
-
- spec/models/update_call_request_status_spec.rb
|
507
|
-
- spec/models/messages_list_spec.rb
|
508
|
-
- spec/models/recording_result_spec.rb
|
509
|
-
- spec/models/buy_incoming_number_request_spec.rb
|
510
|
-
- spec/models/update_call_request_spec.rb
|
511
|
-
- spec/models/conference_status_spec.rb
|
512
|
-
- spec/models/make_call_request_spec.rb
|
513
|
-
- spec/models/conference_participant_result_spec.rb
|
514
|
-
- spec/models/conference_result_spec.rb
|
522
|
+
- spec/models/play_early_media_spec.rb
|
523
|
+
- spec/models/remove_from_conference_spec.rb
|
524
|
+
- spec/models/account_type_spec.rb
|
515
525
|
- spec/models/queue_result_status_spec.rb
|
516
526
|
- spec/models/send_digits_spec.rb
|
517
|
-
- spec/models/
|
518
|
-
- spec/models/
|
519
|
-
- spec/models/
|
520
|
-
- spec/models/
|
521
|
-
- spec/models/machine_type_spec.rb
|
522
|
-
- spec/models/capabilities_spec.rb
|
527
|
+
- spec/models/say_spec.rb
|
528
|
+
- spec/models/log_level_spec.rb
|
529
|
+
- spec/models/update_conference_request_status_spec.rb
|
530
|
+
- spec/models/make_call_request_spec.rb
|
523
531
|
- spec/models/create_conference_spec.rb
|
524
|
-
- spec/models/record_utterance_term_reason_spec.rb
|
525
|
-
- spec/models/pagination_model_spec.rb
|
526
|
-
- spec/models/play_beep_spec.rb
|
527
|
-
- spec/models/sms_spec.rb
|
528
|
-
- spec/models/conference_participant_list_spec.rb
|
529
|
-
- spec/models/update_conference_request_spec.rb
|
530
532
|
- spec/models/play_spec.rb
|
531
|
-
- spec/models/
|
532
|
-
- spec/models/
|
533
|
-
- spec/models/dequeue_spec.rb
|
534
|
-
- spec/models/incoming_number_result_spec.rb
|
535
|
-
- spec/models/call_result_spec.rb
|
533
|
+
- spec/models/get_speech_spec.rb
|
534
|
+
- spec/models/log_list_spec.rb
|
536
535
|
- spec/models/queue_member_list_spec.rb
|
537
|
-
- spec/models/
|
538
|
-
- spec/models/
|
536
|
+
- spec/models/conference_list_spec.rb
|
537
|
+
- spec/models/update_call_request_status_spec.rb
|
538
|
+
- spec/models/queue_list_spec.rb
|
539
|
+
- spec/models/call_list_spec.rb
|
540
|
+
- spec/models/set_talk_spec.rb
|
541
|
+
- spec/models/incoming_number_list_spec.rb
|
542
|
+
- spec/models/if_machine_spec.rb
|
539
543
|
- spec/quickstart_spec.rb
|
540
544
|
- spec/spec_helper.rb
|
545
|
+
- spec/utils/signature_information_spec.rb
|
546
|
+
- spec/utils/request_verifier_spec.rb
|