class Twilio::Security::RequestValidator

Public Class Methods

new(auth_token = nil) click to toggle source

Initialize a Request Validator. auth_token will either be grabbed from the global Twilio object or you can pass it in here.

@param [String] auth_token Your account auth token, used to sign requests

   # File lib/twilio-ruby/security/request_validator.rb
11 def initialize(auth_token = nil)
12   @auth_token = auth_token || Twilio.auth_token
13   raise ArgumentError, 'Auth token is required' if @auth_token.nil?
14 end

Public Instance Methods

build_hash_for(body) click to toggle source

Build a SHA256 hash for a body string

@param [String] body String to hash

@return [String] A hex-encoded SHA256 of the body string

   # File lib/twilio-ruby/security/request_validator.rb
55 def build_hash_for(body)
56   hasher = OpenSSL::Digest.new('sha256')
57   hasher.hexdigest(body)
58 end
build_signature_for(url, params) click to toggle source

Build a SHA1-HMAC signature for a url and parameter hash

@param [String] url The request url, including any query parameters @param [#join] params The POST parameters

@return [String] A base64 encoded SHA1-HMAC

   # File lib/twilio-ruby/security/request_validator.rb
67 def build_signature_for(url, params)
68   data = url + params.sort.join
69   digest = OpenSSL::Digest.new('sha1')
70   Base64.strict_encode64(OpenSSL::HMAC.digest(digest, @auth_token, data))
71 end
validate(url, params, signature) click to toggle source

Validates that after hashing a request with Twilio's request-signing algorithm (www.twilio.com/docs/usage/security#validating-requests), the hash matches the signature parameter

@param [String] url The url sent to your server, including any query parameters @param [String, Hash, to_unsafe_h] params In most cases, this is the POST parameters as a hash. If you received

a bodySHA256 parameter in the query string, this parameter can instead be the POST body as a string to
validate JSON or other text-based payloads that aren't x-www-form-urlencoded.

@param [String] signature The expected signature, from the X-Twilio-Signature header of the request

@return [Boolean] whether or not the computed signature matches the signature parameter

   # File lib/twilio-ruby/security/request_validator.rb
27 def validate(url, params, signature)
28   parsed_url = URI(url)
29   url_with_port = add_port(parsed_url)
30   url_without_port = remove_port(parsed_url)
31 
32   valid_body = true # default succeed, since body not always provided
33   params_hash = body_or_hash(params)
34   unless params_hash.is_a? Enumerable
35     body_hash = URI.decode_www_form(parsed_url.query).to_h['bodySHA256']
36     params_hash = build_hash_for(params)
37     valid_body = !(params_hash.nil? || body_hash.nil?) && secure_compare(params_hash, body_hash)
38     params_hash = {}
39   end
40 
41   # Check signature of the url with and without port numbers
42   # since signature generation on the back end is inconsistent
43   valid_signature_with_port = secure_compare(build_signature_for(url_with_port, params_hash), signature)
44   valid_signature_without_port = secure_compare(build_signature_for(url_without_port, params_hash), signature)
45 
46   valid_body && (valid_signature_with_port || valid_signature_without_port)
47 end

Private Instance Methods

add_port(parsed_url) click to toggle source

Adds the standard port to the url if it doesn't already have one

@param [URI] parsed_url The parsed request url

@return [String] The URL with a port number

    # File lib/twilio-ruby/security/request_validator.rb
111 def add_port(parsed_url)
112   if parsed_url.port.nil? || parsed_url.port == parsed_url.default_port
113     build_url_with_port_for(parsed_url)
114   else
115     parsed_url.to_s
116   end
117 end
body_or_hash(params_or_body) click to toggle source

`ActionController::Parameters` no longer, as of Rails 5, inherits from `Hash` so the `sort` method, used above in `build_signature_for` is deprecated.

`to_unsafe_h` was introduced in Rails 4.2.1, before then it is still possible to sort on an ActionController::Parameters object.

We use `to_unsafe_h` as `to_h` returns a hash of the permitted parameters only and we need all the parameters to create the signature.

    # File lib/twilio-ruby/security/request_validator.rb
 97 def body_or_hash(params_or_body)
 98   if params_or_body.respond_to?(:to_unsafe_h)
 99     params_or_body.to_unsafe_h
100   else
101     params_or_body
102   end
103 end
build_url_with_port_for(parsed_url) click to toggle source

Builds the url from its component pieces, with the standard port

@param [URI] parsed_url The parsed request url

@return [String] The URL with the standard port number

    # File lib/twilio-ruby/security/request_validator.rb
136 def build_url_with_port_for(parsed_url)
137   url = ''
138 
139   url += parsed_url.scheme ? "#{parsed_url.scheme}://" : ''
140   url += parsed_url.userinfo ? "#{parsed_url.userinfo}@" : ''
141   url += parsed_url.host ? "#{parsed_url.host}:#{parsed_url.port}" : ''
142   url += parsed_url.path
143   url += parsed_url.query ? "?#{parsed_url.query}" : ''
144   url += parsed_url.fragment ? "##{parsed_url.fragment}" : ''
145 
146   url
147 end
remove_port(parsed_url) click to toggle source

Removes the port from the url

@param [URI] parsed_url The parsed request url

@return [String] The URL without a port number

    # File lib/twilio-ruby/security/request_validator.rb
125 def remove_port(parsed_url)
126   parsed_url.port = nil
127   parsed_url.to_s
128 end
secure_compare(a, b) click to toggle source

Compares two strings in constant time to avoid timing attacks. Borrowed from ActiveSupport::MessageVerifier. github.com/rails/rails/blob/master/activesupport/lib/active_support/message_verifier.rb

   # File lib/twilio-ruby/security/request_validator.rb
78 def secure_compare(a, b)
79   return false unless a.bytesize == b.bytesize
80 
81   l = a.unpack("C#{a.bytesize}")
82 
83   res = 0
84   b.each_byte { |byte| res |= byte ^ l.shift }
85   res.zero?
86 end