twilio-ruby 5.10.7 → 5.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|