class Twilio::Security::RequestValidator
Public Class Methods
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 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 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
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
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
`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
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
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
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