Module: GoCardless::Utils

Extended by:
Utils
Included in:
Utils
Defined in:
lib/gocardless/utils.rb

Instance Method Summary collapse

Instance Method Details

#camelize(str) ⇒ Object

String Helpers


8
9
10
# File 'lib/gocardless/utils.rb', line 8

def camelize(str)
  str.split('_').map(&:capitalize).join
end

#flatten_params(obj, ns = nil) ⇒ Array

Flatten a hash containing nested hashes and arrays to a non-nested array of key-value pairs.

Examples:

flatten_params(a: 'b')
# => [['a', 'b']]

flatten_params(a: ['b', 'c'])
# => [['a[]', 'b'], ['a[]', 'c']]

flatten_params(a: {b: 'c'})
# => [['a[b]', 'c']]

Parameters:

  • obj (Hash)

    the hash to flatten

Returns:

  • (Array)

    an array of key-value pairs (arrays of two strings)


58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/gocardless/utils.rb', line 58

def flatten_params(obj, ns=nil)
  case obj
  when Hash
    pairs = obj.map { |k,v| flatten_params(v, ns ? "#{ns}[#{k}]" : k) }
    pairs.empty? ? [] : pairs.inject(&:+)
  when Array
    obj.map { |v| flatten_params(v, "#{ns}[]") }.inject(&:+) || []
  when Time
    [[ns.to_s, iso_format_time(obj)]]
  else
    [[ns.to_s, obj.to_s]]
  end
end

#iso_format_time(time) ⇒ String

Format a Time object according to ISO 8601, and convert to UTC.

Parameters:

  • time (Time)

    the time object to format

Returns:

  • (String)

    the ISO-formatted time


120
121
122
123
124
125
# File 'lib/gocardless/utils.rb', line 120

def iso_format_time(time)
  return time unless time.is_a? Time or time.is_a? Date
  time = time.getutc if time.is_a? Time
  time = time.new_offset(0) if time.is_a? DateTime
  time.strftime('%Y-%m-%dT%H:%M:%SZ')
end

#normalize_params(params) ⇒ Object

Generate a percent-encoded query string from an object. The object may have nested arrays and objects as values. Ordinary top-level key-value pairs will be of the form “name=Bob”, arrays will result in “cars[]=BMW&cars=Fiat”, and nested objects will look like “user=Bob&user=50”. All keys and values will be percent-encoded according to RFC5849 §3.6 and parameters will be normalised according to RFC5849 §3.4.1.3.2.


79
80
81
82
83
# File 'lib/gocardless/utils.rb', line 79

def normalize_params(params)
  flatten_params(params).sort.map do |pair|
    pair.map { |item| percent_encode(item) } * '='
  end * '&'
end

#percent_encode(str) ⇒ String

Percent encode a string according to RFC 5849 (section 3.6)

Parameters:

  • str (String)

    the string to encode

Returns:

  • (String)

    str the encoded string


38
39
40
# File 'lib/gocardless/utils.rb', line 38

def percent_encode(str)
  URI.encode(str, /[^a-zA-Z0-9\-\.\_\~]/)
end

#secure_compare(a, b) ⇒ Boolean

Given two strings, compare them in constant time (for the length of the string). This can avoid timing attacks when used to compare signed parameters. Borrowed from ActiveSupport::MessageVerifier. github.com/rails/rails/blob/master/activesupport/lib/active_support/message_verifier.rb

Parameters:

  • the (String)

    first string to compare

  • this (String)

    second string to compare

Returns:

  • (Boolean)

    the result of the comparison


106
107
108
109
110
111
112
113
114
# File 'lib/gocardless/utils.rb', line 106

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  l = a.unpack("C#{a.bytesize}")

  res = 0
  b.each_byte { |byte| res |= byte ^ l.shift }
  res == 0
end

#sign_params(params, key) ⇒ String

Given a Hash of parameters, normalize them (flatten and convert to a string), then generate the HMAC-SHA-256 signature using the provided key.

Parameters:

  • params (Hash)

    the parameters to sign

  • key (String)

    the key to sign the params with

Returns:

  • (String)

    the resulting signature


91
92
93
94
95
# File 'lib/gocardless/utils.rb', line 91

def sign_params(params, key)
  msg = Utils.normalize_params(params)
  digest = OpenSSL::Digest.new('sha256')
  OpenSSL::HMAC.hexdigest(digest, key, msg)
end

#singularize(str) ⇒ Object


16
17
18
19
# File 'lib/gocardless/utils.rb', line 16

def singularize(str)
  # This should probably be a bit more robust
  str.sub(/s$/, '').sub(/i$/, 'us')
end

#stringify_times(obj) ⇒ Hash

Recursively ISO format all time and date values.

Parameters:

  • obj (Hash)

    the object containing date or time objects

Returns:

  • (Hash)

    the object with ISO-formatted time stings


131
132
133
134
135
136
137
138
139
140
# File 'lib/gocardless/utils.rb', line 131

def stringify_times(obj)
  case obj
  when Hash
    Hash[obj.map { |k,v| [k, stringify_times(v)] }]
  when Array
    obj.map { |v| stringify_times(v) }
  else
    iso_format_time(obj)
  end
end

#symbolize_keys(hash) ⇒ Object

Hash Helpers


22
23
24
# File 'lib/gocardless/utils.rb', line 22

def symbolize_keys(hash)
  symbolize_keys! hash.dup
end

#symbolize_keys!(hash) ⇒ Object


26
27
28
29
30
31
32
# File 'lib/gocardless/utils.rb', line 26

def symbolize_keys!(hash)
  hash.keys.each do |key|
    sym_key = key.to_s.to_sym rescue key
    hash[sym_key] = hash.delete(key) unless hash.key?(sym_key)
  end
  hash
end

#underscore(str) ⇒ Object


12
13
14
# File 'lib/gocardless/utils.rb', line 12

def underscore(str)
  str.gsub(/(.)([A-Z])/) { "#{$1}_#{$2.downcase}" }.downcase
end