twilio-ruby 5.10.7 → 5.11.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGES.md +11 -0
- data/README.md +3 -4
- data/lib/twilio-ruby/rest/api/v2010/account/call/recording.rb +1 -5
- data/lib/twilio-ruby/rest/api/v2010/account/message.rb +29 -25
- data/lib/twilio-ruby/rest/api/v2010/account/usage/record.rb +9 -3
- data/lib/twilio-ruby/rest/monitor/v1.rb +1 -1
- data/lib/twilio-ruby/rest/monitor/v1/event.rb +59 -29
- data/lib/twilio-ruby/rest/preview.rb +8 -8
- data/lib/twilio-ruby/rest/preview/marketplace.rb +13 -13
- data/lib/twilio-ruby/rest/studio.rb +1 -1
- data/lib/twilio-ruby/rest/studio/v1.rb +1 -1
- data/lib/twilio-ruby/rest/studio/v1/flow.rb +11 -11
- data/lib/twilio-ruby/rest/studio/v1/flow/engagement.rb +27 -19
- data/lib/twilio-ruby/rest/studio/v1/flow/engagement/engagement_context.rb +11 -11
- data/lib/twilio-ruby/rest/studio/v1/flow/engagement/step.rb +18 -18
- data/lib/twilio-ruby/rest/studio/v1/flow/engagement/step/step_context.rb +17 -15
- data/lib/twilio-ruby/rest/video.rb +9 -9
- data/lib/twilio-ruby/rest/video/v1.rb +16 -16
- data/lib/twilio-ruby/security/request_validator.rb +48 -7
- data/lib/twilio-ruby/twiml/voice_response.rb +194 -6
- data/lib/twilio-ruby/version.rb +1 -1
- data/spec/integration/api/v2010/account/sip/ip_access_control_list/ip_address_spec.rb +4 -4
- data/spec/security/request_validator_spec.rb +34 -61
- data/spec/twiml/voice_response_spec.rb +8 -8
- metadata +3 -3
@@ -28,15 +28,6 @@ module Twilio
|
|
28
28
|
@v1 ||= V1.new self
|
29
29
|
end
|
30
30
|
|
31
|
-
##
|
32
|
-
# @param [String] sid `CJxx…xx` A system-generated 34-character string that
|
33
|
-
# uniquely identifies this Composition.
|
34
|
-
# @return [Twilio::REST::Video::V1::CompositionInstance] if sid was passed.
|
35
|
-
# @return [Twilio::REST::Video::V1::CompositionList]
|
36
|
-
def compositions(sid=:unset)
|
37
|
-
self.v1.compositions(sid)
|
38
|
-
end
|
39
|
-
|
40
31
|
##
|
41
32
|
# @return [Twilio::REST::Video::V1::CompositionSettingsInstance]
|
42
33
|
def composition_settings
|
@@ -58,6 +49,15 @@ module Twilio
|
|
58
49
|
self.v1.recording_settings()
|
59
50
|
end
|
60
51
|
|
52
|
+
##
|
53
|
+
# @param [String] sid `CJxx…xx` A system-generated 34-character string that
|
54
|
+
# uniquely identifies this Composition.
|
55
|
+
# @return [Twilio::REST::Video::V1::CompositionInstance] if sid was passed.
|
56
|
+
# @return [Twilio::REST::Video::V1::CompositionList]
|
57
|
+
def compositions(sid=:unset)
|
58
|
+
self.v1.compositions(sid)
|
59
|
+
end
|
60
|
+
|
61
61
|
##
|
62
62
|
# @param [String] sid A system-generated 34-character string that uniquely
|
63
63
|
# identifies this resource.
|
@@ -15,28 +15,13 @@ module Twilio
|
|
15
15
|
def initialize(domain)
|
16
16
|
super
|
17
17
|
@version = 'v1'
|
18
|
-
@compositions = nil
|
19
18
|
@composition_settings = nil
|
20
19
|
@recordings = nil
|
21
20
|
@recording_settings = nil
|
21
|
+
@compositions = nil
|
22
22
|
@rooms = nil
|
23
23
|
end
|
24
24
|
|
25
|
-
##
|
26
|
-
# @param [String] sid The Composition Sid that uniquely identifies the Composition
|
27
|
-
# to fetch.
|
28
|
-
# @return [Twilio::REST::Video::V1::CompositionContext] if sid was passed.
|
29
|
-
# @return [Twilio::REST::Video::V1::CompositionList]
|
30
|
-
def compositions(sid=:unset)
|
31
|
-
if sid.nil?
|
32
|
-
raise ArgumentError, 'sid cannot be nil'
|
33
|
-
elsif sid == :unset
|
34
|
-
@compositions ||= CompositionList.new self
|
35
|
-
else
|
36
|
-
CompositionContext.new(self, sid)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
25
|
##
|
41
26
|
# @return [Twilio::REST::Video::V1::CompositionSettingsContext]
|
42
27
|
def composition_settings
|
@@ -64,6 +49,21 @@ module Twilio
|
|
64
49
|
@recording_settings ||= RecordingSettingsContext.new self
|
65
50
|
end
|
66
51
|
|
52
|
+
##
|
53
|
+
# @param [String] sid The Composition Sid that uniquely identifies the Composition
|
54
|
+
# to fetch.
|
55
|
+
# @return [Twilio::REST::Video::V1::CompositionContext] if sid was passed.
|
56
|
+
# @return [Twilio::REST::Video::V1::CompositionList]
|
57
|
+
def compositions(sid=:unset)
|
58
|
+
if sid.nil?
|
59
|
+
raise ArgumentError, 'sid cannot be nil'
|
60
|
+
elsif sid == :unset
|
61
|
+
@compositions ||= CompositionList.new self
|
62
|
+
else
|
63
|
+
CompositionContext.new(self, sid)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
67
|
##
|
68
68
|
# @param [String] sid The sid
|
69
69
|
# @return [Twilio::REST::Video::V1::RoomContext] if sid was passed.
|
@@ -3,17 +3,58 @@
|
|
3
3
|
module Twilio
|
4
4
|
module Security
|
5
5
|
class RequestValidator
|
6
|
+
##
|
7
|
+
# Initialize a Request Validator. auth_token will either be grabbed from the global Twilio object or you can
|
8
|
+
# pass it in here.
|
9
|
+
#
|
10
|
+
# @param [String] auth_token Your account auth token, used to sign requests
|
6
11
|
def initialize(auth_token = nil)
|
7
12
|
@auth_token = auth_token || Twilio.auth_token
|
8
13
|
raise ArgumentError, 'Auth token is required' if @auth_token.nil?
|
9
14
|
end
|
10
15
|
|
16
|
+
##
|
17
|
+
# Validates that after hashing a request with Twilio's request-signing algorithm
|
18
|
+
# (https://www.twilio.com/docs/usage/security#validating-requests), the hash matches the signature parameter
|
19
|
+
#
|
20
|
+
# @param [String] url The url sent to your server, including any query parameters
|
21
|
+
# @param [String, Hash, #to_unsafe_h] params In most cases, this is the POST parameters as a hash. If you received
|
22
|
+
# a bodySHA256 parameter in the query string, this parameter can instead be the POST body as a string to
|
23
|
+
# validate JSON or other text-based payloads that aren't x-www-form-urlencoded.
|
24
|
+
# @param [String] signature The expected signature, from the X-Twilio-Signature header of the request
|
25
|
+
#
|
26
|
+
# @return [Boolean] whether or not the computed signature matches the signature parameter
|
11
27
|
def validate(url, params, signature)
|
12
|
-
params_hash =
|
13
|
-
|
14
|
-
|
28
|
+
params_hash = body_or_hash(params)
|
29
|
+
if params_hash.is_a? Enumerable
|
30
|
+
expected = build_signature_for(url, params_hash)
|
31
|
+
secure_compare(expected, signature)
|
32
|
+
else
|
33
|
+
expected_signature = build_signature_for(url, {})
|
34
|
+
body_hash = URI.decode_www_form(URI(url).query).to_h['bodySHA256']
|
35
|
+
expected_hash = build_hash_for(params)
|
36
|
+
secure_compare(expected_signature, signature) && secure_compare(expected_hash, body_hash)
|
37
|
+
end
|
15
38
|
end
|
16
39
|
|
40
|
+
##
|
41
|
+
# Build a SHA256 hash for a body string
|
42
|
+
#
|
43
|
+
# @param [String] body String to hash
|
44
|
+
#
|
45
|
+
# @return [String] A base64-encoded SHA256 of the body string
|
46
|
+
def build_hash_for(body)
|
47
|
+
hasher = OpenSSL::Digest.new('sha256')
|
48
|
+
Base64.encode64(hasher.digest(body)).strip
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Build a SHA1-HMAC signature for a url and parameter hash
|
53
|
+
#
|
54
|
+
# @param [String] url The request url, including any query parameters
|
55
|
+
# @param [#join] params The POST parameters
|
56
|
+
#
|
57
|
+
# @return [String] A base64 encoded SHA1-HMAC
|
17
58
|
def build_signature_for(url, params)
|
18
59
|
data = url + params.sort.join
|
19
60
|
digest = OpenSSL::Digest.new('sha1')
|
@@ -44,11 +85,11 @@ module Twilio
|
|
44
85
|
#
|
45
86
|
# We use `to_unsafe_h` as `to_h` returns a hash of the permitted
|
46
87
|
# parameters only and we need all the parameters to create the signature.
|
47
|
-
def
|
48
|
-
if
|
49
|
-
|
88
|
+
def body_or_hash(params_or_body)
|
89
|
+
if params_or_body.respond_to?(:to_unsafe_h)
|
90
|
+
params_or_body.to_unsafe_h
|
50
91
|
else
|
51
|
-
|
92
|
+
params_or_body
|
52
93
|
end
|
53
94
|
end
|
54
95
|
end
|
@@ -175,8 +175,11 @@ module Twilio
|
|
175
175
|
# loop:: Times to loop message
|
176
176
|
# language:: Message langauge
|
177
177
|
# keyword_args:: additional attributes
|
178
|
-
def say(message, voice: nil, loop: nil, language: nil, **keyword_args)
|
179
|
-
|
178
|
+
def say(message: nil, voice: nil, loop: nil, language: nil, **keyword_args)
|
179
|
+
say = Say.new(message: message, voice: voice, loop: loop, language: language, **keyword_args)
|
180
|
+
|
181
|
+
yield(say) if block_given?
|
182
|
+
append(say)
|
180
183
|
end
|
181
184
|
|
182
185
|
##
|
@@ -207,10 +210,192 @@ module Twilio
|
|
207
210
|
##
|
208
211
|
# <Say> TwiML Verb
|
209
212
|
class Say < TwiML
|
210
|
-
def initialize(message, **keyword_args)
|
213
|
+
def initialize(message: nil, **keyword_args)
|
211
214
|
super(**keyword_args)
|
212
215
|
@name = 'Say'
|
213
|
-
@value = message
|
216
|
+
@value = message unless message.nil?
|
217
|
+
yield(self) if block_given?
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Create a new <Break> element
|
222
|
+
# strength:: Set a pause based on strength
|
223
|
+
# time:: Set a pause to a specific length of time in seconds or milliseconds, available values: [number]s, [number]ms
|
224
|
+
# keyword_args:: additional attributes
|
225
|
+
def break(strength: nil, time: nil, **keyword_args)
|
226
|
+
append(SsmlBreak.new(strength: strength, time: time, **keyword_args))
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Create a new <Emphasis> element
|
231
|
+
# words:: Words to emphasize
|
232
|
+
# level:: Specify the degree of emphasis
|
233
|
+
# keyword_args:: additional attributes
|
234
|
+
def emphasis(words, level: nil, **keyword_args)
|
235
|
+
append(SsmlEmphasis.new(words, level: level, **keyword_args))
|
236
|
+
end
|
237
|
+
|
238
|
+
##
|
239
|
+
# Create a new <P> element
|
240
|
+
# words:: Words to speak
|
241
|
+
# keyword_args:: additional attributes
|
242
|
+
def p(words, **keyword_args)
|
243
|
+
append(SsmlP.new(words, **keyword_args))
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
# Create a new <Phoneme> element
|
248
|
+
# words:: Words to speak
|
249
|
+
# alphabet:: Specify the phonetic alphabet
|
250
|
+
# ph:: Specifiy the phonetic symbols for pronunciation
|
251
|
+
# keyword_args:: additional attributes
|
252
|
+
def phoneme(words, alphabet: nil, ph: nil, **keyword_args)
|
253
|
+
append(SsmlPhoneme.new(words, alphabet: alphabet, ph: ph, **keyword_args))
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Create a new <Prosody> element
|
258
|
+
# words:: Words to speak
|
259
|
+
# volume:: Specify the volume, available values: default, silent, x-soft, soft, medium, loud, x-loud, +ndB, -ndB
|
260
|
+
# rate:: Specify the rate, available values: x-slow, slow, medium, fast, x-fast, n%
|
261
|
+
# pitch:: Specify the pitch, available values: default, x-low, low, medium, high, x-high, +n%, -n%
|
262
|
+
# keyword_args:: additional attributes
|
263
|
+
def prosody(words, volume: nil, rate: nil, pitch: nil, **keyword_args)
|
264
|
+
append(SsmlProsody.new(words, volume: volume, rate: rate, pitch: pitch, **keyword_args))
|
265
|
+
end
|
266
|
+
|
267
|
+
##
|
268
|
+
# Create a new <S> element
|
269
|
+
# words:: Words to speak
|
270
|
+
# keyword_args:: additional attributes
|
271
|
+
def s(words, **keyword_args)
|
272
|
+
append(SsmlS.new(words, **keyword_args))
|
273
|
+
end
|
274
|
+
|
275
|
+
##
|
276
|
+
# Create a new <Say-As> element
|
277
|
+
# words:: Words to be interpreted
|
278
|
+
# interpret-as:: Specify the type of words are spoken
|
279
|
+
# role:: Specify the format of the date when interpret-as is set to date
|
280
|
+
# keyword_args:: additional attributes
|
281
|
+
def say_as(words, interpretAs: nil, role: nil, **keyword_args)
|
282
|
+
append(SsmlSayAs.new(words, interpretAs: interpretAs, role: role, **keyword_args))
|
283
|
+
end
|
284
|
+
|
285
|
+
##
|
286
|
+
# Create a new <Sub> element
|
287
|
+
# words:: Words to be substituted
|
288
|
+
# alias:: Substitute a different word (or pronunciation) for selected text such as an acronym or abbreviation
|
289
|
+
# keyword_args:: additional attributes
|
290
|
+
def sub(words, aliasAttribute: nil, **keyword_args)
|
291
|
+
append(SsmlSub.new(words, aliasAttribute: aliasAttribute, **keyword_args))
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# Create a new <W> element
|
296
|
+
# words:: Words to speak
|
297
|
+
# role:: Customize the pronunciation of words by specifying the word’s part of speech or alternate meaning
|
298
|
+
# keyword_args:: additional attributes
|
299
|
+
def w(words, role: nil, **keyword_args)
|
300
|
+
append(SsmlW.new(words, role: role, **keyword_args))
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Improving Pronunciation by Specifying Parts of Speech in <Say>
|
306
|
+
class SsmlW < TwiML
|
307
|
+
def initialize(words, **keyword_args)
|
308
|
+
super(**keyword_args)
|
309
|
+
@name = 'W'
|
310
|
+
@value = words
|
311
|
+
yield(self) if block_given?
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# Pronouncing Acronyms and Abbreviations in <Say>
|
317
|
+
class SsmlSub < TwiML
|
318
|
+
def initialize(words, **keyword_args)
|
319
|
+
super(**keyword_args)
|
320
|
+
@name = 'Sub'
|
321
|
+
@value = words
|
322
|
+
yield(self) if block_given?
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# Controlling How Special Types of Words Are Spoken in <Say>
|
328
|
+
class SsmlSayAs < TwiML
|
329
|
+
def initialize(words, **keyword_args)
|
330
|
+
super(**keyword_args)
|
331
|
+
@name = 'Say-As'
|
332
|
+
@value = words
|
333
|
+
yield(self) if block_given?
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# Adding A Pause Between Sentences in <Say>
|
339
|
+
class SsmlS < TwiML
|
340
|
+
def initialize(words, **keyword_args)
|
341
|
+
super(**keyword_args)
|
342
|
+
@name = 'S'
|
343
|
+
@value = words
|
344
|
+
yield(self) if block_given?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# Controling Volume, Speaking Rate, and Pitch in <Say>
|
350
|
+
class SsmlProsody < TwiML
|
351
|
+
def initialize(words, **keyword_args)
|
352
|
+
super(**keyword_args)
|
353
|
+
@name = 'Prosody'
|
354
|
+
@value = words
|
355
|
+
yield(self) if block_given?
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
##
|
360
|
+
# Using Phonetic Pronunciation in <Say>
|
361
|
+
class SsmlPhoneme < TwiML
|
362
|
+
def initialize(words, **keyword_args)
|
363
|
+
super(**keyword_args)
|
364
|
+
@name = 'Phoneme'
|
365
|
+
@value = words
|
366
|
+
yield(self) if block_given?
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
##
|
371
|
+
# Adding a Pause Between Paragraphs in <Say>
|
372
|
+
class SsmlP < TwiML
|
373
|
+
def initialize(words, **keyword_args)
|
374
|
+
super(**keyword_args)
|
375
|
+
@name = 'P'
|
376
|
+
@value = words
|
377
|
+
yield(self) if block_given?
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
##
|
382
|
+
# Emphasizing Words in <Say>
|
383
|
+
class SsmlEmphasis < TwiML
|
384
|
+
def initialize(words, **keyword_args)
|
385
|
+
super(**keyword_args)
|
386
|
+
@name = 'Emphasis'
|
387
|
+
@value = words
|
388
|
+
yield(self) if block_given?
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
##
|
393
|
+
# Adding a Pause in <Say>
|
394
|
+
class SsmlBreak < TwiML
|
395
|
+
def initialize(**keyword_args)
|
396
|
+
super(**keyword_args)
|
397
|
+
@name = 'Break'
|
398
|
+
|
214
399
|
yield(self) if block_given?
|
215
400
|
end
|
216
401
|
end
|
@@ -320,8 +505,11 @@ module Twilio
|
|
320
505
|
# loop:: Times to loop message
|
321
506
|
# language:: Message langauge
|
322
507
|
# keyword_args:: additional attributes
|
323
|
-
def say(message, voice: nil, loop: nil, language: nil, **keyword_args)
|
324
|
-
|
508
|
+
def say(message: nil, voice: nil, loop: nil, language: nil, **keyword_args)
|
509
|
+
say = Say.new(message: message, voice: voice, loop: loop, language: language, **keyword_args)
|
510
|
+
|
511
|
+
yield(say) if block_given?
|
512
|
+
append(say)
|
325
513
|
end
|
326
514
|
|
327
515
|
##
|
data/lib/twilio-ruby/version.rb
CHANGED
@@ -41,7 +41,7 @@ describe 'IpAddress' do
|
|
41
41
|
"date_updated": "Mon, 20 Jul 2015 17:27:10 +0000",
|
42
42
|
"friendly_name": "friendly_name",
|
43
43
|
"ip_access_control_list_sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
44
|
-
"ip_address": "
|
44
|
+
"ip_address": "192.168.1.1",
|
45
45
|
"cidr_prefix_length": 32,
|
46
46
|
"sid": "IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
47
47
|
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/SIP/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IpAddresses/IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"
|
@@ -120,7 +120,7 @@ describe 'IpAddress' do
|
|
120
120
|
"date_updated": "Mon, 20 Jul 2015 17:27:10 +0000",
|
121
121
|
"friendly_name": "friendly_name",
|
122
122
|
"ip_access_control_list_sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
123
|
-
"ip_address": "
|
123
|
+
"ip_address": "192.168.1.1",
|
124
124
|
"cidr_prefix_length": 32,
|
125
125
|
"sid": "IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
126
126
|
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/SIP/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IpAddresses/IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"
|
@@ -164,7 +164,7 @@ describe 'IpAddress' do
|
|
164
164
|
"date_updated": "Mon, 20 Jul 2015 17:27:10 +0000",
|
165
165
|
"friendly_name": "friendly_name",
|
166
166
|
"ip_access_control_list_sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
167
|
-
"ip_address": "
|
167
|
+
"ip_address": "192.168.1.1",
|
168
168
|
"cidr_prefix_length": 32,
|
169
169
|
"sid": "IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
170
170
|
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/SIP/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IpAddresses/IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"
|
@@ -208,7 +208,7 @@ describe 'IpAddress' do
|
|
208
208
|
"date_updated": "Mon, 20 Jul 2015 17:27:10 +0000",
|
209
209
|
"friendly_name": "friendly_name",
|
210
210
|
"ip_access_control_list_sid": "ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
211
|
-
"ip_address": "
|
211
|
+
"ip_address": "192.168.1.1",
|
212
212
|
"cidr_prefix_length": 32,
|
213
213
|
"sid": "IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
214
214
|
"uri": "/2010-04-01/Accounts/ACaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/SIP/IpAccessControlLists/ALaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/IpAddresses/IPaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json"
|
@@ -30,84 +30,57 @@ describe Twilio::Security::RequestValidator do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
describe 'validations' do
|
33
|
-
let(:token) { '
|
33
|
+
let(:token) { '12345' }
|
34
34
|
|
35
35
|
let(:validator) { Twilio::Security::RequestValidator.new token }
|
36
36
|
|
37
|
-
let(:
|
37
|
+
let(:url) { 'https://mycompany.com/myapp.php?foo=1&bar=2' }
|
38
38
|
|
39
|
-
let(:
|
39
|
+
let(:default_signature) { 'RSOYDt4T1cUTdK1PDd93/VVr8B8=' }
|
40
40
|
|
41
|
-
let(:
|
41
|
+
let(:body) { '{"property": "value", "boolean": true}' }
|
42
|
+
|
43
|
+
let(:body_hash) { 'Ch/3Y02as7ldtcmi3+lBbkFQKyg6gMfPGWMmMvluZiA=' }
|
44
|
+
|
45
|
+
let(:body_hash_encoded) { body_hash.gsub('+', '%2B').gsub('=', '%3D') }
|
46
|
+
|
47
|
+
let(:params) do
|
42
48
|
{
|
43
|
-
'
|
44
|
-
'
|
45
|
-
'
|
46
|
-
'
|
47
|
-
'
|
48
|
-
'Caller' => '+14153595711',
|
49
|
-
'CallerZip' => '94108',
|
50
|
-
'CallerCountry' => 'US',
|
51
|
-
'From' => '+14153595711',
|
52
|
-
'FromCity' => 'SAN FRANCISCO',
|
53
|
-
'CallerCity' => 'SAN FRANCISCO',
|
54
|
-
'To' => '+14157669926',
|
55
|
-
'FromZip' => '94108',
|
56
|
-
'FromCountry' => 'US',
|
57
|
-
'ToCity' => '',
|
58
|
-
'CallStatus' => 'ringing',
|
59
|
-
'CalledCity' => '',
|
60
|
-
'CallerState' => 'CA',
|
61
|
-
'CalledZip' => '',
|
62
|
-
'ToZip' => '',
|
63
|
-
'ToCountry' => 'US',
|
64
|
-
'CallSid' => 'CA136d09cd59a3c0ec8dbff44da5c03f31',
|
65
|
-
'CalledCountry' => 'US',
|
66
|
-
'Called' => '+14157669926',
|
67
|
-
'ApiVersion' => '2010-04-01',
|
68
|
-
'ApplicationSid' => 'AP44efecad51364e80b133bb7c07eb8204'
|
49
|
+
'From' => '+14158675309',
|
50
|
+
'To' => '+18005551212',
|
51
|
+
'CallSid' => 'CA1234567890ABCDE',
|
52
|
+
'Caller' => '+14158675309',
|
53
|
+
'Digits' => '1234'
|
69
54
|
}
|
70
55
|
end
|
71
56
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
'SmsStatus' => 'received',
|
82
|
-
'FromZip' => '94107',
|
83
|
-
'FromCountry' => 'US',
|
84
|
-
'To' => '+14158141819',
|
85
|
-
'ToCity' => 'SAN FRANCISCO',
|
86
|
-
'ToZip' => '94105',
|
87
|
-
'ToCountry' => 'US',
|
88
|
-
'ApiVersion' => '2010-04-01',
|
89
|
-
'SmsSid' => 'SM2003cbd5e6a3701999aa3e5f20ff2787'
|
90
|
-
}
|
57
|
+
it 'should build a correct signature' do
|
58
|
+
expect(validator.build_signature_for(url, params)).to eq(default_signature)
|
59
|
+
end
|
60
|
+
it 'should validate an authentic Twilio request' do
|
61
|
+
expect(validator.validate(url, params, default_signature)).to eq(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should not validate a request with wrong signature' do
|
65
|
+
expect(validator.validate(url, params, 'fake_signature')).to eq(false)
|
91
66
|
end
|
92
67
|
|
93
|
-
it 'should
|
94
|
-
|
95
|
-
expect(validator.validate(voice_url, voice_params, signature)).to eq(true)
|
68
|
+
it 'should hash body correctly' do
|
69
|
+
expect(validator.build_hash_for(body)).to eq(body_hash)
|
96
70
|
end
|
97
71
|
|
98
|
-
it 'should validate
|
99
|
-
|
100
|
-
expect(validator.validate(
|
72
|
+
it 'should validate requests with body correctly' do
|
73
|
+
url_with_hash = "#{url}&bodySHA256=#{body_hash_encoded}"
|
74
|
+
expect(validator.validate(url_with_hash, body, 'afcFvPLPYT8mg/JyIVkdnqQKa2s=')).to eq(true)
|
101
75
|
end
|
102
76
|
|
103
|
-
it 'should
|
104
|
-
|
105
|
-
expect(validator.validate(
|
77
|
+
it 'should validate with no other GET parameters' do
|
78
|
+
url_with_hash = "https://mycompany.com/myapp.php?bodySHA256=#{body_hash_encoded}"
|
79
|
+
expect(validator.validate(url_with_hash, body, 'DXnNFCj8DJ/hZmiSg4UzaDHw5Og=')).to eq(true)
|
106
80
|
end
|
107
81
|
|
108
|
-
it 'should
|
109
|
-
|
110
|
-
expect(validator.validate(sms_url, sms_params, signature)).to eq(false)
|
82
|
+
it 'should fail validation with body but no signature' do
|
83
|
+
expect(validator.validate(url, body, default_signature)).to eq(false)
|
111
84
|
end
|
112
85
|
end
|
113
86
|
end
|