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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6478ad1652831cea81595098617c0c74fb6d2f1697d09098b87b2c7131ab3a02
4
- data.tar.gz: 254f990d422aa7d1fee557e78ffd328cc6800aea723c0841169803cf8ece059d
3
+ metadata.gz: d7826dfa439ee726cf91d308b8cd9eee895a48d53a425ac24926765525c23760
4
+ data.tar.gz: cec6eee4ac9ca13157b48a10a23f837c18f684bf3621cbe41fb89655291e493c
5
5
  SHA512:
6
- metadata.gz: 304c8351ee2b95fbb8b306dc410f065dfc80237b0018d9ba123ba4b6101ed7130f2aa067012afd453439a990e9360f8e30a419861cafbea72ffed16a239b1e8e
7
- data.tar.gz: 99a2e12550a6b7ba9e8927e1e87354755870466ddc602d5b728803ea104ff2ac25267d427cdeacf3b242fcc313663ff400f570b695a1686edaa89f1520d3a13e
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.1.3)
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.1)
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.1.9)
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.9.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.15.0)
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.4)
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.1.3
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.1.3'
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.1.3.gem
47
+ gem install ./freeclimb-4.2.0.gem
48
48
  ```
49
- (for development, run `gem install --dev ./freeclimb-4.1.3.gem` to install the development dependencies)
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.openapi_types
225
- percl_hash = attributes.each_with_object({}) do |(attr, _type), hash|
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[attr] = value.compact.map { |v| v.is_a?(PerclCommand) ? v.to_percl_hash() : v }
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[attr] = value.to_percl_hash()
230
+ hash[percl_attr] = value.to_percl_hash()
231
231
  elsif !value.nil?
232
- hash[attr] = value
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
@@ -11,5 +11,5 @@ OpenAPI Generator version: 5.4.0
11
11
  =end
12
12
 
13
13
  module Freeclimb
14
- VERSION = '4.1.3'
14
+ VERSION = '4.2.0'
15
15
  end
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.
@@ -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
- script = Freeclimb::PerclScript.new(commands:[say])
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.1.3
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-03-13 00:00:00.000000000 Z
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/update_conference_participant_request_spec.rb
455
- - spec/models/request_type_spec.rb
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/get_speech_spec.rb
488
- - spec/models/get_digits_spec.rb
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/message_result_spec.rb
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/message_request_spec.rb
497
- - spec/models/park_spec.rb
498
- - spec/models/update_conference_request_status_spec.rb
499
- - spec/models/unpark_spec.rb
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/application_request_spec.rb
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/filter_logs_request_spec.rb
504
- - spec/models/hangup_spec.rb
505
- - spec/models/percl_command_spec.rb
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/account_request_spec.rb
518
- - spec/models/queue_list_spec.rb
519
- - spec/models/queue_member_spec.rb
520
- - spec/models/play_early_media_spec.rb
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/incoming_number_request_spec.rb
532
- - spec/models/say_spec.rb
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/set_listen_spec.rb
538
- - spec/models/recording_list_spec.rb
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