-
Notifications
You must be signed in to change notification settings - Fork 4
/
roauth.rb
113 lines (96 loc) · 3.91 KB
/
roauth.rb
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 "base64"
require "openssl"
require "uri"
module ROAuth
class UnsupportedSignatureMethod < Exception; end
class MissingOAuthParams < Exception; end
# Supported {signature methods}[http://oauth.net/core/1.0/#signing_process];
SIGNATURE_METHODS = {"HMAC-SHA1" => OpenSSL::Digest::Digest.new("sha1")}
OAUTH_PARAMS = [:consumer_key, :token, :signature_method, :version, :nonce, :timestamp, :body_hash, :callback]
# Return an {OAuth "Authorization" HTTP header}[http://oauth.net/core/1.0/#auth_header] from request data
def header(oauth, uri, params = {}, http_method = :get)
oauth = oauth.dup
oauth[:signature_method] ||= "HMAC-SHA1"
oauth[:version] ||= "1.0" # Assumed version, according to the spec
oauth[:nonce] ||= Base64.encode64(OpenSSL::Random.random_bytes(32)).gsub(/\W/, '')
oauth[:timestamp] ||= Time.now.to_i
oauth[:token] ||= oauth.delete(:access_key)
oauth[:token_secret] ||= oauth.delete(:access_secret)
sig_params = oauth_params(oauth)
sig_params[:oauth_signature] = signature(oauth, uri, sig_params.merge(params), http_method)
sorted_sig_params = sig_params.sort_by{|k,v| [k.to_s, v.to_s] }
authorization_params = sorted_sig_params.map {|key, value|
[escape(key), "\"#{escape(value)}\""].join("=")
}.join(", ")
%{OAuth } + authorization_params
end
def parse(header)
header = header.dup
header = header.gsub!(/^OAuth\s/, "")
header = header.split(", ")
header = header.inject({}) {|hash, item|
key, value = item.split("=")
key.gsub!(/^oauth_/, "")
value.gsub!(/(^"|"$)/, "")
hash[key.to_sym] = unescape(value)
hash
}
header[:access_key] = header[:token]
header
end
def verify(oauth, header, uri, params = {}, http_method = :get)
header = header.is_a?(String) ? parse(header) : header.dup
client_signature = header.delete(:signature)
oauth[:consumer_key] ||= header[:consumer_key]
oauth[:token] ||= header[:token]
oauth[:token_secret] ||= oauth.delete(:access_secret)
oauth[:signature_method] ||= "HMAC-SHA1"
oauth[:version] ||= "1.0"
sig_params = params.dup
sig_params.merge!(oauth_params(header))
client_signature == signature(oauth, uri, sig_params, http_method)
end
protected
def oauth_params(oauth)
oauth = oauth.to_a.select {|key, value|
OAUTH_PARAMS.include?(key)
}
oauth.inject({}) {|hash, (key, value)|
hash["oauth_#{key}"] = value
hash
}
end
def signature(oauth, uri, params, http_method = :get)
uri = URI.parse(uri)
uri.query = nil
uri = uri.to_s
sig_base = http_method.to_s.upcase + "&" + escape(uri) + "&" + escape(normalize(params))
digest = SIGNATURE_METHODS[oauth[:signature_method]]
secret = "#{escape(oauth[:consumer_secret])}&#{escape(oauth[:token_secret])}"
Base64.encode64(OpenSSL::HMAC.digest(digest, secret, sig_base)).chomp.gsub(/\n/, "")
end
# Escape characters in a string according to the {OAuth spec}[http://oauth.net/core/1.0/]
def escape(value)
URI.escape(value.to_s, /[^a-zA-Z0-9\-\.\_\~]/) # Unreserved characters -- must not be encoded
end
def unescape(value)
URI.unescape(value)
end
# Normalize a string of parameters based on the {OAuth spec}[http://oauth.net/core/1.0/#rfc.section.9.1.1]
def normalize(params)
# Stringify keys - so we can compare them
params.keys.each {|key| params[key.to_s] = params.delete(key) }
params.sort_by {|key, values| key.to_s }.map do |key, values|
if values.is_a?(Array)
# Multiple values were provided for a single key
# in a hash
values.sort_by(&:to_s).collect do |v|
[escape(key), escape(v)] * "="
end
else
[escape(key), escape(values)] * "="
end
end * "&"
end
extend self
end