1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
require 'openssl'
require 'base64'
module OAuth
module Helper
extend self
# Escape +value+ by URL encoding all non-reserved character.
#
# See Also: {OAuth core spec version 1.0, section 5.1}[http://oauth.net/core/1.0#rfc.section.5.1]
def escape(value)
_escape(value.to_s.to_str)
rescue ArgumentError
_escape(value.to_s.to_str.force_encoding(Encoding::UTF_8))
end
def _escape(string)
URI::DEFAULT_PARSER.escape(string, OAuth::RESERVED_CHARACTERS)
end
def unescape(value)
URI::DEFAULT_PARSER.unescape(value.gsub('+', '%2B'))
end
# Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
# characters removed.
def generate_key(size=32)
Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
end
alias_method :generate_nonce, :generate_key
def generate_timestamp #:nodoc:
Time.now.to_i.to_s
end
# Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical
# byte value ordering. If two or more parameters share the same name, they are sorted by their value.
# Parameters are concatenated in their sorted order into a single string. For each parameter, the name
# is separated from the corresponding value by an "=" character, even if the value is empty. Each
# name-value pair is separated by an "&" character.
#
# See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
def normalize(params)
params.sort.map do |k, values|
if values.is_a?(Array)
# make sure the array has an element so we don't lose the key
values << nil if values.empty?
# multiple values were provided for a single key
values.sort.collect do |v|
[escape(k),escape(v)] * "="
end
elsif values.is_a?(Hash)
normalize_nested_query(values, k)
else
[escape(k),escape(values)] * "="
end
end * "&"
end
#Returns a string representation of the Hash like in URL query string
# build_nested_query({:level_1 => {:level_2 => ['value_1','value_2']}}, 'prefix'))
# #=> ["prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_1", "prefix%5Blevel_1%5D%5Blevel_2%5D%5B%5D=value_2"]
def normalize_nested_query(value, prefix = nil)
case value
when Array
value.map do |v|
normalize_nested_query(v, "#{prefix}[]")
end.flatten.sort
when Hash
value.map do |k, v|
normalize_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
end.flatten.sort
else
[escape(prefix), escape(value)] * "="
end
end
# Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and
# removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a
# valid hash. Does not validate the keys or values.
#
# hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate'])
# hash['oauth_timestamp']
# #=>"1234567890"
#
def parse_header(header)
# decompose
params = header[6,header.length].split(/[,=&]/)
# odd number of arguments - must be a malformed header.
raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0
params.map! do |v|
# strip and unescape
val = unescape(v.strip)
# strip quotes
val.sub(/^\"(.*)\"$/, '\1')
end
# convert into a Hash
Hash[*params.flatten]
end
def stringify_keys(hash)
new_h = {}
hash.each do |k, v|
new_h[k.to_s] = v.is_a?(Hash) ? stringify_keys(v) : v
end
new_h
end
end
end
|