pelle-oauth 0.3.1 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/History.txt +66 -17
  2. data/Manifest.txt +14 -1
  3. data/README.rdoc +7 -9
  4. data/Rakefile +7 -5
  5. data/TODO +17 -0
  6. data/bin/oauth +2 -2
  7. data/examples/yql.rb +44 -0
  8. data/lib/oauth.rb +1 -0
  9. data/lib/oauth/cli.rb +279 -31
  10. data/lib/oauth/client/action_controller_request.rb +14 -12
  11. data/lib/oauth/client/helper.rb +22 -14
  12. data/lib/oauth/client/net_http.rb +53 -22
  13. data/lib/oauth/consumer.rb +217 -111
  14. data/lib/oauth/errors.rb +3 -0
  15. data/lib/oauth/errors/error.rb +4 -0
  16. data/lib/oauth/errors/problem.rb +14 -0
  17. data/lib/oauth/errors/unauthorized.rb +12 -0
  18. data/lib/oauth/helper.rb +67 -6
  19. data/lib/oauth/oauth.rb +11 -0
  20. data/lib/oauth/oauth_test_helper.rb +12 -13
  21. data/lib/oauth/request_proxy/action_controller_request.rb +8 -8
  22. data/lib/oauth/request_proxy/base.rb +102 -44
  23. data/lib/oauth/request_proxy/jabber_request.rb +1 -2
  24. data/lib/oauth/request_proxy/mock_request.rb +8 -0
  25. data/lib/oauth/request_proxy/net_http.rb +2 -2
  26. data/lib/oauth/request_proxy/rack_request.rb +7 -7
  27. data/lib/oauth/server.rb +31 -33
  28. data/lib/oauth/signature.rb +9 -0
  29. data/lib/oauth/signature/base.rb +23 -21
  30. data/lib/oauth/signature/hmac/base.rb +1 -1
  31. data/lib/oauth/signature/hmac/sha1.rb +0 -1
  32. data/lib/oauth/signature/plaintext.rb +2 -2
  33. data/lib/oauth/signature/rsa/sha1.rb +5 -4
  34. data/lib/oauth/token.rb +6 -136
  35. data/lib/oauth/tokens/access_token.rb +68 -0
  36. data/lib/oauth/tokens/consumer_token.rb +33 -0
  37. data/lib/oauth/tokens/request_token.rb +32 -0
  38. data/lib/oauth/tokens/server_token.rb +9 -0
  39. data/lib/oauth/tokens/token.rb +17 -0
  40. data/lib/oauth/version.rb +1 -1
  41. data/oauth.gemspec +13 -7
  42. data/test/cases/spec/1_0-final/test_construct_request_url.rb +1 -1
  43. data/test/test_access_token.rb +28 -0
  44. data/test/test_action_controller_request_proxy.rb +105 -6
  45. data/test/test_consumer.rb +41 -5
  46. data/test/test_helper.rb +0 -5
  47. data/test/test_net_http_client.rb +38 -20
  48. data/test/test_net_http_request_proxy.rb +43 -8
  49. data/test/test_oauth_helper.rb +50 -0
  50. data/test/test_request_token.rb +53 -0
  51. data/test/test_server.rb +1 -1
  52. data/test/test_signature.rb +19 -11
  53. data/website/index.html +2 -2
  54. metadata +46 -6
@@ -0,0 +1,3 @@
1
+ require 'oauth/errors/error'
2
+ require 'oauth/errors/unauthorized'
3
+ require 'oauth/errors/problem'
@@ -0,0 +1,4 @@
1
+ module OAuth
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ module OAuth
2
+ class Problem < OAuth::Unauthorized
3
+ attr_reader :problem, :params
4
+ def initialize(problem, request = nil, params = {})
5
+ super(request)
6
+ @problem = problem
7
+ @params = params
8
+ end
9
+
10
+ def to_s
11
+ problem
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module OAuth
2
+ class Unauthorized < OAuth::Error
3
+ attr_reader :request
4
+ def initialize(request = nil)
5
+ @request = request
6
+ end
7
+
8
+ def to_s
9
+ [request.code, request.message] * " "
10
+ end
11
+ end
12
+ end
data/lib/oauth/helper.rb CHANGED
@@ -1,17 +1,78 @@
1
1
  require 'openssl'
2
2
  require 'base64'
3
- require 'cgi'
3
+
4
4
  module OAuth
5
5
  module Helper
6
6
  extend self
7
7
 
8
+ # Escape +value+ by URL encoding all non-reserved character.
9
+ #
10
+ # See Also: {OAuth core spec version 1.0, section 5.1}[http://oauth.net/core/1.0#rfc.section.5.1]
8
11
  def escape(value)
9
- CGI.escape(value.to_s).gsub("%7E", '~').gsub("+", "%20")
12
+ URI::escape(value.to_s, OAuth::RESERVED_CHARACTERS)
10
13
  end
11
-
14
+
15
+ # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
16
+ # characters removed.
12
17
  def generate_key(size=32)
13
- Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/,'')
14
- end
15
-
18
+ Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
19
+ end
20
+
21
+ alias_method :generate_nonce, :generate_key
22
+
23
+ def generate_timestamp #:nodoc:
24
+ Time.now.to_i.to_s
25
+ end
26
+
27
+ # Normalize a +Hash+ of parameter values. Parameters are sorted by name, using lexicographical
28
+ # byte value ordering. If two or more parameters share the same name, they are sorted by their value.
29
+ # Parameters are concatenated in their sorted order into a single string. For each parameter, the name
30
+ # is separated from the corresponding value by an "=" character, even if the value is empty. Each
31
+ # name-value pair is separated by an "&" character.
32
+ #
33
+ # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
34
+ def normalize(params)
35
+ params.sort.map do |k, values|
36
+
37
+ if values.is_a?(Array)
38
+ # multiple values were provided for a single key
39
+ values.sort.collect do |v|
40
+ [escape(k),escape(v)] * "="
41
+ end
42
+ else
43
+ [escape(k),escape(values)] * "="
44
+ end
45
+ end * "&"
46
+ end
47
+
48
+ # Parse an Authorization / WWW-Authenticate header into a hash. Takes care of unescaping and
49
+ # removing surrounding quotes. Raises a OAuth::Problem if the header is not parsable into a
50
+ # valid hash. Does not validate the keys or values.
51
+ #
52
+ # hash = parse_header(headers['Authorization'] || headers['WWW-Authenticate'])
53
+ # hash['oauth_timestamp']
54
+ # #=>"1234567890"
55
+ #
56
+ def parse_header(header)
57
+ # decompose
58
+ params = header[6,header.length].split(/[,=]/)
59
+
60
+ # odd number of arguments - must be a malformed header.
61
+ raise OAuth::Problem.new("Invalid authorization header") if params.size % 2 != 0
62
+
63
+ params.map! do |v|
64
+ # strip and unescape
65
+ val = unescape(v.strip)
66
+ # strip quotes
67
+ val.sub(/^\"(.*)\"$/, '\1')
68
+ end
69
+
70
+ # convert into a Hash
71
+ Hash[*params.flatten]
72
+ end
73
+
74
+ def unescape(value)
75
+ URI.unescape(value.gsub('+', '%2B'))
76
+ end
16
77
  end
17
78
  end
@@ -0,0 +1,11 @@
1
+ module OAuth
2
+ # request tokens are passed between the consumer and the provider out of
3
+ # band (i.e. callbacks cannot be used), per section 6.1.1
4
+ OUT_OF_BAND = "oob"
5
+
6
+ # required parameters, per sections 6.1.1, 6.3.1, and 7
7
+ PARAMETERS = %w(oauth_callback oauth_consumer_key oauth_token oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier oauth_version oauth_signature)
8
+
9
+ # reserved character regexp, per section 5.1
10
+ RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~]/
11
+ end
@@ -1,26 +1,25 @@
1
1
  require 'action_controller'
2
2
  require 'action_controller/test_process'
3
+
3
4
  module OAuth
4
5
  module OAuthTestHelper
5
-
6
6
  def mock_incoming_request_with_query(request)
7
- incoming=ActionController::TestRequest.new(request.to_hash)
8
- incoming.request_uri=request.path
9
- incoming.env["SERVER_PORT"]=request.uri.port
10
- incoming.host=request.uri.host
11
- incoming.env['REQUEST_METHOD']=request.http_method
7
+ incoming = ActionController::TestRequest.new(request.to_hash)
8
+ incoming.request_uri = request.path
9
+ incoming.host = request.uri.host
10
+ incoming.env["SERVER_PORT"] = request.uri.port
11
+ incoming.env['REQUEST_METHOD'] = request.http_method
12
12
  incoming
13
13
  end
14
14
 
15
15
  def mock_incoming_request_with_authorize_header(request)
16
- incoming=ActionController::TestRequest.new
17
- incoming.env["HTTP_AUTHORIZATION"]=request.to_auth_string
18
- incoming.request_uri=request.path
19
- incoming.env["SERVER_PORT"]=request.uri.port
20
- incoming.host=request.uri.host
21
- incoming.env['REQUEST_METHOD']=request.http_method
16
+ incoming = ActionController::TestRequest.new
17
+ incoming.request_uri = request.path
18
+ incoming.host = request.uri.host
19
+ incoming.env["HTTP_AUTHORIZATION"] = request.to_auth_string
20
+ incoming.env["SERVER_PORT"] = request.uri.port
21
+ incoming.env['REQUEST_METHOD'] = request.http_method
22
22
  incoming
23
23
  end
24
-
25
24
  end
26
25
  end
@@ -1,5 +1,5 @@
1
- require 'rubygems'
2
1
  require 'active_support'
2
+ require 'action_controller'
3
3
  require 'action_controller/request'
4
4
  require 'oauth/request_proxy/base'
5
5
  require 'uri'
@@ -25,7 +25,7 @@ module OAuth::RequestProxy
25
25
  params.merge(options[:parameters] || {})
26
26
  end
27
27
  end
28
-
28
+
29
29
  # Override from OAuth::RequestProxy::Base to avoid roundtrip
30
30
  # conversion to Hash or Array and thus preserve the original
31
31
  # parameter names
@@ -36,19 +36,19 @@ module OAuth::RequestProxy
36
36
  unless options[:clobber_request]
37
37
  params << header_params.to_query
38
38
  params << request.query_string unless request.query_string.blank?
39
- if request.content_type == Mime::Type.lookup("application/x-www-form-urlencoded")
40
- params << CGI.unescape(request.raw_post)
39
+ if request.post? && request.content_type == Mime::Type.lookup("application/x-www-form-urlencoded")
40
+ params << request.raw_post
41
41
  end
42
42
  end
43
-
43
+
44
44
  params.
45
45
  join('&').split('&').
46
- reject { |kv| kv =~ /^oauth_signature=.*/}.
47
46
  reject(&:blank?).
48
- map { |p| p.split('=') }
47
+ map { |p| p.split('=').map{|esc| CGI.unescape(esc)} }.
48
+ reject { |kv| kv[0] == 'oauth_signature'}
49
49
  end
50
50
 
51
- protected
51
+ protected
52
52
 
53
53
  def query_params
54
54
  request.query_parameters
@@ -16,70 +16,133 @@ module OAuth::RequestProxy
16
16
  @options = options
17
17
  end
18
18
 
19
- def token
20
- parameters['oauth_token']
21
- end
19
+ ## OAuth parameters
22
20
 
23
- def consumer_key
24
- parameters['oauth_consumer_key']
21
+ def oauth_callback
22
+ parameters['oauth_callback']
25
23
  end
26
24
 
27
- def parameters_for_signature
28
- p = parameters.dup
29
- p.delete("oauth_signature")
30
- p
25
+ def oauth_consumer_key
26
+ parameters['oauth_consumer_key']
31
27
  end
32
28
 
33
- def nonce
29
+ def oauth_nonce
34
30
  parameters['oauth_nonce']
35
31
  end
36
32
 
37
- def timestamp
38
- parameters['oauth_timestamp']
33
+ def oauth_signature
34
+ # TODO can this be nil?
35
+ parameters['oauth_signature'] || ""
39
36
  end
40
37
 
41
- def signature_method
38
+ def oauth_signature_method
42
39
  case parameters['oauth_signature_method']
43
- when Array: parameters['oauth_signature_method'].first
40
+ when Array
41
+ parameters['oauth_signature_method'].first
44
42
  else
45
43
  parameters['oauth_signature_method']
46
44
  end
47
45
  end
48
46
 
49
- def signature
50
- parameters['oauth_signature'] || ""
47
+ def oauth_timestamp
48
+ parameters['oauth_timestamp']
49
+ end
50
+
51
+ def oauth_token
52
+ parameters['oauth_token']
53
+ end
54
+
55
+ def oauth_verifier
56
+ parameters['oauth_verifier']
57
+ end
58
+
59
+ def oauth_version
60
+ parameters["oauth_version"]
61
+ end
62
+
63
+ # TODO deprecate these
64
+ alias_method :consumer_key, :oauth_consumer_key
65
+ alias_method :token, :oauth_token
66
+ alias_method :nonce, :oauth_nonce
67
+ alias_method :timestamp, :oauth_timestamp
68
+ alias_method :signature, :oauth_signature
69
+ alias_method :signature_method, :oauth_signature_method
70
+
71
+ ## Parameter accessors
72
+
73
+ def parameters
74
+ raise NotImplementedError, "Must be implemented by subclasses"
51
75
  end
52
-
76
+
77
+ def parameters_for_signature
78
+ parameters.reject { |k,v| k == "oauth_signature" }
79
+ end
80
+
81
+ def oauth_parameters
82
+ parameters.select { |k,v| OAuth::PARAMETERS.include?(k) }.reject { |k,v| v == "" }
83
+ end
84
+
85
+ def non_oauth_parameters
86
+ parameters.reject { |k,v| OAuth::PARAMETERS.include?(k) }
87
+ end
88
+
53
89
  # See 9.1.2 in specs
54
90
  def normalized_uri
55
- u=URI.parse(uri)
56
- "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.downcase=='http'&&u.port!=80)||(u.scheme.downcase=='https'&&u.port!=443) ? ":#{u.port}" : ""}#{(u.path&&u.path!='') ? u.path : '/'}"
91
+ u = URI.parse(uri)
92
+ "#{u.scheme.downcase}://#{u.host.downcase}#{(u.scheme.downcase == 'http' && u.port != 80) || (u.scheme.downcase == 'https' && u.port != 443) ? ":#{u.port}" : ""}#{(u.path && u.path != '') ? u.path : '/'}"
57
93
  end
58
-
94
+
59
95
  # See 9.1.1. in specs Normalize Request Parameters
60
96
  def normalized_parameters
61
- parameters_for_signature.sort.map do |k, values|
97
+ normalize(parameters_for_signature)
98
+ end
62
99
 
63
- if values.is_a?(Array)
64
- # multiple values were provided for a single key
65
- values.sort.collect do |v|
66
- [escape(k),escape(v)] * "="
67
- end
68
- else
69
- [escape(k),escape(values)] * "="
70
- end
71
- end * "&"
100
+ def sign(options = {})
101
+ OAuth::Signature.sign(self, options)
72
102
  end
73
-
103
+
104
+ def sign!(options = {})
105
+ parameters["oauth_signature"] = sign(options)
106
+ @signed = true
107
+ signature
108
+ end
109
+
74
110
  # See 9.1 in specs
75
111
  def signature_base_string
76
112
  base = [method, normalized_uri, normalized_parameters]
77
113
  base.map { |v| escape(v) }.join("&")
78
114
  end
79
-
80
-
81
- protected
82
-
115
+
116
+ # Has this request been signed yet?
117
+ def signed?
118
+ @signed
119
+ end
120
+
121
+ # URI, including OAuth parameters
122
+ def signed_uri(with_oauth = true)
123
+ if signed?
124
+ if with_oauth
125
+ params = parameters
126
+ else
127
+ params = non_oauth_parameters
128
+ end
129
+
130
+ [uri, normalize(params)] * "?"
131
+ else
132
+ STDERR.puts "This request has not yet been signed!"
133
+ end
134
+ end
135
+
136
+ # Authorization header for OAuth
137
+ def oauth_header(options = {})
138
+ header_params_str = oauth_parameters.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
139
+
140
+ realm = "realm=\"#{options[:realm]}\", " if options[:realm]
141
+ "OAuth #{realm}#{header_params_str}"
142
+ end
143
+
144
+ protected
145
+
83
146
  def header_params
84
147
  %w( X-HTTP_AUTHORIZATION Authorization HTTP_AUTHORIZATION ).each do |header|
85
148
  next unless request.env.include?(header)
@@ -87,10 +150,10 @@ module OAuth::RequestProxy
87
150
  header = request.env[header]
88
151
  next unless header[0,6] == 'OAuth '
89
152
 
90
- oauth_param_string = header[6,header.length].split(/[,=]/)
91
- oauth_param_string.map! { |v| unescape(v.strip) }
92
- oauth_param_string.map! { |v| v =~ /^\".*\"$/ ? v[1..-2] : v }
93
- oauth_params = Hash[*oauth_param_string.flatten]
153
+ # parse the header into a Hash
154
+ oauth_params = OAuth::Helper.parse_header(header)
155
+
156
+ # remove non-OAuth parameters
94
157
  oauth_params.reject! { |k,v| k !~ /^oauth_/ }
95
158
 
96
159
  return oauth_params
@@ -98,10 +161,5 @@ module OAuth::RequestProxy
98
161
 
99
162
  return {}
100
163
  end
101
-
102
- def unescape(value)
103
- URI.unescape(value.gsub('+', '%2B'))
104
- end
105
-
106
164
  end
107
165
  end
@@ -32,11 +32,10 @@ module OAuth
32
32
  def uri
33
33
  [@request.from.strip.to_s, @request.to.strip.to_s].join("&")
34
34
  end
35
-
35
+
36
36
  def normalized_uri
37
37
  uri
38
38
  end
39
-
40
39
  end
41
40
  end
42
41
  end
@@ -28,6 +28,14 @@ module OAuth
28
28
  @request["method"]
29
29
  end
30
30
 
31
+ def normalized_uri
32
+ super
33
+ rescue
34
+ # if this is a non-standard URI, it may not parse properly
35
+ # in that case, assume that it's already been normalized
36
+ uri
37
+ end
38
+
31
39
  def uri
32
40
  @request["uri"]
33
41
  end
@@ -25,7 +25,7 @@ module OAuth::RequestProxy::Net
25
25
  end
26
26
  end
27
27
 
28
- private
28
+ private
29
29
 
30
30
  def all_parameters
31
31
  request_params = CGI.parse(query_string)
@@ -47,7 +47,7 @@ module OAuth::RequestProxy::Net
47
47
  params << post_params if method.to_s.upcase == 'POST' && is_form_urlencoded
48
48
  params.compact.join('&')
49
49
  end
50
-
50
+
51
51
  def query_params
52
52
  URI.parse(request.path).query
53
53
  end