oauthenticator 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/oauthenticator/config_methods.rb +100 -0
- data/lib/oauthenticator/middleware.rb +61 -0
- data/lib/oauthenticator/signed_request.rb +215 -0
- data/lib/oauthenticator/version.rb +3 -0
- data/lib/oauthenticator.rb +6 -0
- data/test/oauthenticator_test.rb +430 -0
- metadata +263 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module OAuthenticator
|
2
|
+
# This module contains stubs, or in some cases default values, for implementations of particulars of the OAuth protocol. Applications must implement some of these, and are likely to want to override the default values of others. certain methods will need to use methods of the {OAuthenticator::SignedRequest} class.
|
3
|
+
#
|
4
|
+
# the methods your implementation will need to be used are primarily those from the parsed OAuth Authorization header. these are methods your implementation WILL need to use to implement the required functionality:
|
5
|
+
#
|
6
|
+
# - `#consumer_key`
|
7
|
+
# - `#token`
|
8
|
+
# - `#nonce`
|
9
|
+
# - `#timestamp`
|
10
|
+
#
|
11
|
+
# the following are the other parts of the Authorization, but your implementation will probably NOT need to use these (OAuthenticator does everything that is needed to validate these parts):
|
12
|
+
#
|
13
|
+
# - `#version`
|
14
|
+
# - `#signature_method`
|
15
|
+
# - `#signature`
|
16
|
+
module ConfigMethods
|
17
|
+
# the number of seconds (integer) in both the past and future for which the request is considered valid.
|
18
|
+
#
|
19
|
+
# if it is desired to have a different period considered valid in the past than in the future, then the methods {#timestamp_valid_past} and {#timestamp_valid_future} may be implemented instead, and this method may remain unimplemented.
|
20
|
+
#
|
21
|
+
# see the documentation for {#timestamp_valid_past} and {#timestamp_valid_future} for other considerations of the valid period.
|
22
|
+
#
|
23
|
+
# @return [Integer] period in seconds
|
24
|
+
def timestamp_valid_period
|
25
|
+
config_method_not_implemented
|
26
|
+
end
|
27
|
+
|
28
|
+
# the number of seconds (integer) in the past for which the request is considered valid.
|
29
|
+
#
|
30
|
+
# if the timestamp is more than this number of seconds less than the current clock time, then the request is considered invalid and the response is an error.
|
31
|
+
#
|
32
|
+
# this should be large enough to allow for clock skew between your application's server and the requester's clock.
|
33
|
+
#
|
34
|
+
# nonces older than Time.now - timestamp_valid_past may be discarded.
|
35
|
+
#
|
36
|
+
# this method may remain unimplemented if {#timestamp_valid_period} is implemented.
|
37
|
+
#
|
38
|
+
# @return [Integer] period in seconds
|
39
|
+
def timestamp_valid_past
|
40
|
+
timestamp_valid_period
|
41
|
+
end
|
42
|
+
|
43
|
+
# the number of seconds (integer) in the future for which the request is considered valid.
|
44
|
+
#
|
45
|
+
# if the timestamp is more than this number of seconds greater than the current clock time, then the request is considered invalid and the response is an error.
|
46
|
+
#
|
47
|
+
# this should be large enough to allow for clock skew between your application's server and the requester's clock.
|
48
|
+
#
|
49
|
+
# this method may remain unimplemented if {#timestamp_valid_period} is implemented.
|
50
|
+
#
|
51
|
+
# @return [Integer] period in seconds
|
52
|
+
def timestamp_valid_future
|
53
|
+
timestamp_valid_period
|
54
|
+
end
|
55
|
+
|
56
|
+
# the signature methods which the application will accept. this MUST be a subset of the signature methods defined in the OAuth 1.0 protocol: `%w(HMAC-SHA1 RSA-SHA1 PLAINTEXT)`. the default value for this is all allowed signature methods, and may remain unimplemented if you wish to allow all defined signature methods.
|
57
|
+
#
|
58
|
+
# @return [Array<String>]
|
59
|
+
def allowed_signature_methods
|
60
|
+
OAuthenticator::SignedRequest::VALID_SIGNATURE_METHODS
|
61
|
+
end
|
62
|
+
|
63
|
+
# this should look up the consumer secret in your application's storage corresponding to the request's consumer key, which is available via the `#consumer_key` method. see the README for an example implementation.
|
64
|
+
#
|
65
|
+
# @return [String] the consumer secret for the request's consumer key
|
66
|
+
def consumer_secret
|
67
|
+
config_method_not_implemented
|
68
|
+
end
|
69
|
+
|
70
|
+
# this should look up the access token secret in your application's storage corresponding to the request's access token, which is available via the `#token` method. see the README for an example implementation.
|
71
|
+
#
|
72
|
+
# @return [String] the access token secret for the request's access token
|
73
|
+
def access_token_secret
|
74
|
+
config_method_not_implemented
|
75
|
+
end
|
76
|
+
|
77
|
+
# whether the nonce, available via the `#nonce` method, has already been used. you may wish to use this in conjunction with the timestamp (`#timestamp`), per the OAuth 1.0 spec.
|
78
|
+
#
|
79
|
+
# it's worth noting that if this ever returns true, it may indicate a replay attack under way against your application. the replay attack will fail due to OAuth, but you may wish to log the event.
|
80
|
+
#
|
81
|
+
# @return [Boolean] whether the request's nonce has already been used.
|
82
|
+
def nonce_used?
|
83
|
+
config_method_not_implemented
|
84
|
+
end
|
85
|
+
|
86
|
+
# cause the nonce, available via the `#nonce` method, to be marked as used. you may wish to use this in conjunction with the timestamp (`#timestamp`).
|
87
|
+
#
|
88
|
+
# @return [Void] (return value is ignored / unused)
|
89
|
+
def use_nonce!
|
90
|
+
config_method_not_implemented
|
91
|
+
end
|
92
|
+
|
93
|
+
# whether the access token indicated by the request (via `#token`) belongs to the consumer indicated by the request (via `#consumer_key`).
|
94
|
+
#
|
95
|
+
# @return [Boolean] whether the request's access token belongs to the request's consumer
|
96
|
+
def access_token_belongs_to_consumer?
|
97
|
+
config_method_not_implemented
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'simple_oauth'
|
3
|
+
require 'json'
|
4
|
+
require 'oauthenticator/signed_request'
|
5
|
+
|
6
|
+
module OAuthenticator
|
7
|
+
# Rack middleware to determine if the incoming request is signed authentically with OAuth 1.0.
|
8
|
+
#
|
9
|
+
# If the request is not authentically signed, then the middleware responds with 401 Unauthorized, with the
|
10
|
+
# body a JSON object indicating errors encountered authenticating the request. The error object is
|
11
|
+
# structured like rails / ActiveResource:
|
12
|
+
#
|
13
|
+
# {'errors': {'attribute1': ['messageA', 'messageB'], 'attribute2': ['messageC']}}
|
14
|
+
class Middleware
|
15
|
+
# options:
|
16
|
+
#
|
17
|
+
# - `:bypass` - a proc which will be called with a Rack::Request, which must have a boolean result.
|
18
|
+
# if the result is true, authorization checking is bypassed. if false, the request is authenticated
|
19
|
+
# and responds 401 if not authenticated.
|
20
|
+
#
|
21
|
+
# - `:config_methods` - a Module which defines necessary methods for an {OAuthenticator::SignedRequest} to
|
22
|
+
# determine if it is validly signed. See documentation for {OAuthenticator::ConfigMethods}
|
23
|
+
# for details of what this module must implement.
|
24
|
+
def initialize(app, options={})
|
25
|
+
@app=app
|
26
|
+
@options = options
|
27
|
+
unless @options[:config_methods].is_a?(Module)
|
28
|
+
raise ArgumentError, "options[:config_methods] must be a Module"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# call the middleware!
|
33
|
+
def call(env)
|
34
|
+
request = Rack::Request.new(env)
|
35
|
+
|
36
|
+
if @options[:bypass] && @options[:bypass].call(request)
|
37
|
+
env["oauth.authenticated"] = false
|
38
|
+
@app.call(env, request)
|
39
|
+
else
|
40
|
+
oauth_signed_request_class = OAuthenticator::SignedRequest.including_config(@options[:config_methods])
|
41
|
+
oauth_request = oauth_signed_request_class.from_rack_request(request)
|
42
|
+
if oauth_request.errors
|
43
|
+
unauthorized_response({'errors' => oauth_request.errors})
|
44
|
+
else
|
45
|
+
env["oauth.consumer_key"] = oauth_request.consumer_key
|
46
|
+
env["oauth.access_token"] = oauth_request.token
|
47
|
+
env["oauth.authenticated"] = true
|
48
|
+
@app.call(env)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# the response for an unauthorized request. the argument will be a hash with the key 'errors', whose value
|
54
|
+
# is a hash with string keys indicating attributes with errors, and values being arrays of strings
|
55
|
+
# indicating error messages on the attribute key..
|
56
|
+
def unauthorized_response(error_object)
|
57
|
+
response_headers = {"WWW-Authenticate" => %q(OAuth realm="/"), 'Content-Type' => 'application/json'}
|
58
|
+
[401, response_headers, [JSON.pretty_generate(error_object)]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'simple_oauth'
|
2
|
+
|
3
|
+
module OAuthenticator
|
4
|
+
# this class represents an OAuth signed request. its primary user-facing method is {#errors}, which returns
|
5
|
+
# nil if the request is valid and authentic, or a helpful object of error messages describing what was
|
6
|
+
# invalid if not.
|
7
|
+
#
|
8
|
+
# this class is not useful on its own, as various methods must be implemented on a module to be included
|
9
|
+
# before the implementation is complete enough to use. see the README and the documentation for the module
|
10
|
+
# {OAuthenticator::ConfigMethods} for details. to pass such a module to
|
11
|
+
# {OAuthenticator::SignedRequest}, use {.including_config}, like
|
12
|
+
# `OAuthenticator::SignedRequest.including_config(config_module)`.
|
13
|
+
class SignedRequest
|
14
|
+
class << self
|
15
|
+
# returns a subclass of OAuthenticator::SignedRequest which includes the given config module
|
16
|
+
#
|
17
|
+
# @param config_methods_module [Module] a module which implements the methods described in the
|
18
|
+
# documentation for {OAuthenticator::ConfigMethods} and the README
|
19
|
+
#
|
20
|
+
# @return [Class] subclass of SignedRequest with the given module included
|
21
|
+
def including_config(config_methods_module)
|
22
|
+
@extended_classes ||= Hash.new do |h, confmodule|
|
23
|
+
h[confmodule] = Class.new(::OAuthenticator::SignedRequest).send(:include, confmodule)
|
24
|
+
end
|
25
|
+
@extended_classes[config_methods_module]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ATTRIBUTE_KEYS = %w(request_method url body media_type authorization).map(&:freeze).freeze
|
30
|
+
OAUTH_ATTRIBUTE_KEYS = %w(consumer_key token timestamp nonce version signature_method signature).map(&:to_sym).freeze
|
31
|
+
|
32
|
+
# readers
|
33
|
+
ATTRIBUTE_KEYS.each { |attribute_key| define_method(attribute_key) { @attributes[attribute_key] } }
|
34
|
+
|
35
|
+
# readers for oauth header parameters
|
36
|
+
OAUTH_ATTRIBUTE_KEYS.each { |key| define_method(key) { oauth_header_params[key] } }
|
37
|
+
|
38
|
+
# question methods to indicate whether oauth header parameters were included in the Authorization
|
39
|
+
OAUTH_ATTRIBUTE_KEYS.each { |key| define_method("#{key}?") { oauth_header_params.key?(key) } }
|
40
|
+
|
41
|
+
VALID_SIGNATURE_METHODS = %w(HMAC-SHA1 RSA-SHA1 PLAINTEXT).map(&:freeze).freeze
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# instantiates a `OAuthenticator::SignedRequest` (subclass thereof, more precisely) representing a
|
45
|
+
# request given as a Rack::Request.
|
46
|
+
#
|
47
|
+
# like {#initialize}, this should be called on a subclass of SignedRequest created with {.including_config}
|
48
|
+
#
|
49
|
+
# @param request [Rack::Request]
|
50
|
+
# @return [subclass of OAuthenticator::SignedRequest]
|
51
|
+
def from_rack_request(request)
|
52
|
+
new({
|
53
|
+
:request_method => request.request_method,
|
54
|
+
:url => request.url,
|
55
|
+
:body => request.body,
|
56
|
+
:media_type => request.media_type,
|
57
|
+
:authorization => request.env['HTTP_AUTHORIZATION'],
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# initialize a {SignedRequest}. this should not be called on OAuthenticator::SignedRequest directly, but
|
63
|
+
# a subclass made with {.including_config} - see {SignedRequest}'s documentation.
|
64
|
+
def initialize(attributes)
|
65
|
+
@attributes = attributes.inject({}){|acc, (k,v)| acc.update((k.is_a?(Symbol) ? k.to_s : k) => v) }
|
66
|
+
extra_attributes = @attributes.keys - ATTRIBUTE_KEYS
|
67
|
+
if extra_attributes.any?
|
68
|
+
raise ArgumentError, "received unrecognized attribute keys: #{extra_attributes.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# inspects the request represented by this instance of SignedRequest. if the request is authentically
|
73
|
+
# signed with OAuth, returns nil to indicate that there are no errors. if the request is inauthentic or
|
74
|
+
# invalid for any reason, this returns a hash containing the reason(s) why the request is invalid.
|
75
|
+
#
|
76
|
+
# The error object's structure is a hash with string keys indicating attributes with errors, and values
|
77
|
+
# being arrays of strings indicating error messages on the attribute key. this structure takes after
|
78
|
+
# structured rails / ActiveResource, and looks like:
|
79
|
+
#
|
80
|
+
# {'attribute1': ['messageA', 'messageB'], 'attribute2': ['messageC']}
|
81
|
+
#
|
82
|
+
# @return [nil, Hash<String, Array<String>>] either nil or a hash of errors
|
83
|
+
def errors
|
84
|
+
@errors ||= begin
|
85
|
+
if authorization.nil?
|
86
|
+
{'Authorization' => ["Authorization header is missing"]}
|
87
|
+
elsif authorization !~ /\S/
|
88
|
+
{'Authorization' => ["Authorization header is blank"]}
|
89
|
+
elsif authorization !~ /\Aoauth\s/i
|
90
|
+
{'Authorization' => ["Authorization scheme is not OAuth; received Authorization: #{authorization}"]}
|
91
|
+
else
|
92
|
+
errors = Hash.new { |h,k| h[k] = [] }
|
93
|
+
|
94
|
+
# timestamp
|
95
|
+
if !timestamp?
|
96
|
+
errors['Authorization oauth_timestamp'] << "is missing"
|
97
|
+
elsif timestamp !~ /\A\s*\d+\s*\z/
|
98
|
+
errors['Authorization oauth_timestamp'] << "is not an integer - got: #{timestamp}"
|
99
|
+
else
|
100
|
+
timestamp_i = timestamp.to_i
|
101
|
+
if timestamp_i < Time.now.to_i - timestamp_valid_past
|
102
|
+
errors['Authorization oauth_timestamp'] << "is too old: #{timestamp}"
|
103
|
+
elsif timestamp_i > Time.now.to_i + timestamp_valid_future
|
104
|
+
errors['Authorization oauth_timestamp'] << "is too far in the future: #{timestamp}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# oauth version
|
109
|
+
if version? && version != '1.0'
|
110
|
+
errors['Authorization oauth_version'] << "must be 1.0; got: #{version}"
|
111
|
+
end
|
112
|
+
|
113
|
+
# she's filled with secrets
|
114
|
+
secrets = {}
|
115
|
+
|
116
|
+
# consumer / client application
|
117
|
+
if !consumer_key?
|
118
|
+
errors['Authorization oauth_consumer_key'] << "is missing"
|
119
|
+
else
|
120
|
+
secrets[:consumer_secret] = consumer_secret
|
121
|
+
if !secrets[:consumer_secret]
|
122
|
+
errors['Authorization oauth_consumer_key'] << 'is invalid'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# access token
|
127
|
+
if token?
|
128
|
+
secrets[:token_secret] = access_token_secret
|
129
|
+
if !secrets[:token_secret]
|
130
|
+
errors['Authorization oauth_token'] << 'is invalid'
|
131
|
+
elsif !access_token_belongs_to_consumer?
|
132
|
+
errors['Authorization oauth_token'] << 'does not belong to the specified consumer'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# nonce
|
137
|
+
if !nonce?
|
138
|
+
errors['Authorization oauth_nonce'] << "is missing"
|
139
|
+
elsif nonce_used?
|
140
|
+
errors['Authorization oauth_nonce'] << "has already been used"
|
141
|
+
end
|
142
|
+
|
143
|
+
# signature method
|
144
|
+
if !signature_method?
|
145
|
+
errors['Authorization oauth_signature_method'] << "is missing"
|
146
|
+
elsif !allowed_signature_methods.any? { |sm| signature_method.downcase == sm.downcase }
|
147
|
+
errors['Authorization oauth_signature_method'] << "must be one of " +
|
148
|
+
"#{allowed_signature_methods.join(', ')}; got: #{signature_method}"
|
149
|
+
end
|
150
|
+
|
151
|
+
# signature
|
152
|
+
if !signature?
|
153
|
+
errors['Authorization oauth_signature'] << "is missing"
|
154
|
+
end
|
155
|
+
|
156
|
+
if errors.any?
|
157
|
+
errors
|
158
|
+
else
|
159
|
+
# proceed to check signature
|
160
|
+
if !simple_oauth_header.valid?(secrets)
|
161
|
+
{'Authorization oauth_signature' => ['is invalid']}
|
162
|
+
else
|
163
|
+
use_nonce!
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
require 'oauthenticator/config_methods'
|
172
|
+
include ConfigMethods
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
# hash of header params. keys should be a subset of OAUTH_ATTRIBUTE_KEYS.
|
177
|
+
def oauth_header_params
|
178
|
+
@oauth_header_params ||= SimpleOAuth::Header.parse(authorization)
|
179
|
+
end
|
180
|
+
|
181
|
+
# reads the request body, be it String or IO
|
182
|
+
def read_body
|
183
|
+
if body.is_a?(String)
|
184
|
+
body
|
185
|
+
elsif body.respond_to?(:read) && body.respond_to?(:rewind)
|
186
|
+
body.rewind
|
187
|
+
body.read.tap do
|
188
|
+
body.rewind
|
189
|
+
end
|
190
|
+
else
|
191
|
+
raise NotImplementedError, "body = #{body.inspect}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# SimpleOAuth::Header for this request
|
196
|
+
def simple_oauth_header
|
197
|
+
params = media_type == "application/x-www-form-urlencoded" ? CGI.parse(read_body).map{|k,vs| vs.map{|v| [k,v] } }.inject([], &:+) : nil
|
198
|
+
simple_oauth_header = SimpleOAuth::Header.new(request_method, url, params, authorization)
|
199
|
+
end
|
200
|
+
|
201
|
+
# raise a nice error message for a method that needs to be implemented on a module of config methods
|
202
|
+
def config_method_not_implemented
|
203
|
+
caller_name = caller[0].match(%r(in `(.*?)'))[1]
|
204
|
+
using_middleware = caller.any? { |l| l =~ %r(oauthenticator/middleware.rb:.*`call') }
|
205
|
+
message = "method \##{caller_name} must be implemented on a module of oauth config methods, which is " + begin
|
206
|
+
if using_middleware
|
207
|
+
"passed to OAuthenticator::Middleware using the option :config_methods."
|
208
|
+
else
|
209
|
+
"included in a subclass of OAuthenticator::SignedRequest, typically by passing it to OAuthenticator::SignedRequest.including_config(your_module)."
|
210
|
+
end
|
211
|
+
end + " Please consult the documentation."
|
212
|
+
raise NotImplementedError, message
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,430 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
|
5
|
+
require 'debugger'
|
6
|
+
Debugger.start
|
7
|
+
|
8
|
+
# NO EXPECTATIONS
|
9
|
+
ENV["MT_NO_EXPECTATIONS"]
|
10
|
+
|
11
|
+
require 'minitest/autorun'
|
12
|
+
require 'minitest/reporters'
|
13
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
14
|
+
|
15
|
+
require 'rack/test'
|
16
|
+
require 'timecop'
|
17
|
+
|
18
|
+
require 'oauthenticator'
|
19
|
+
|
20
|
+
# config methods for testing OAuthenticator. simple
|
21
|
+
module OAuthenticatorTestConfigMethods
|
22
|
+
class << self
|
23
|
+
# a set of nonces
|
24
|
+
define_method(:nonces) { @nonces ||= Set.new }
|
25
|
+
# a Hash keyed by consumer keys with values of consumer secrets
|
26
|
+
define_method(:consumer_secrets) { @consumer_secrets ||= {} }
|
27
|
+
# a Hash keyed by access tokens with values of access token secrets
|
28
|
+
define_method(:access_token_secrets) { @access_token_secrets ||= {} }
|
29
|
+
# a Hash keyed by access tokens with values of consumer keys
|
30
|
+
define_method(:access_token_consumers) { @access_token_consumers ||= {} }
|
31
|
+
end
|
32
|
+
|
33
|
+
def nonce_used?
|
34
|
+
OAuthenticatorTestConfigMethods.nonces.include?(oauth_header_params[:nonce])
|
35
|
+
end
|
36
|
+
|
37
|
+
def use_nonce!
|
38
|
+
OAuthenticatorTestConfigMethods.nonces << oauth_header_params[:nonce]
|
39
|
+
end
|
40
|
+
|
41
|
+
def timestamp_valid_period
|
42
|
+
10
|
43
|
+
end
|
44
|
+
|
45
|
+
def allowed_signature_methods
|
46
|
+
%w(HMAC-SHA1 RSA-SHA1 PLAINTEXT)
|
47
|
+
end
|
48
|
+
|
49
|
+
def consumer_secret
|
50
|
+
OAuthenticatorTestConfigMethods.consumer_secrets[oauth_header_params[:consumer_key]]
|
51
|
+
end
|
52
|
+
|
53
|
+
def access_token_secret
|
54
|
+
OAuthenticatorTestConfigMethods.access_token_secrets[oauth_header_params[:token]]
|
55
|
+
end
|
56
|
+
|
57
|
+
def access_token_belongs_to_consumer?
|
58
|
+
OAuthenticatorTestConfigMethods.access_token_consumers[oauth_header_params[:token]] == oauth_header_params[:consumer_key]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe OAuthenticator::Middleware do
|
63
|
+
# act like a database cleaner
|
64
|
+
after do
|
65
|
+
[:nonces, :consumer_secrets, :access_token_secrets, :access_token_consumers].each do |db|
|
66
|
+
OAuthenticatorTestConfigMethods.send(db).clear
|
67
|
+
end
|
68
|
+
|
69
|
+
Timecop.return
|
70
|
+
end
|
71
|
+
|
72
|
+
let(:simpleapp) { proc { |env| [200, {}, ['☺']] } }
|
73
|
+
let(:oapp) { OAuthenticator::Middleware.new(simpleapp, :config_methods => OAuthenticatorTestConfigMethods) }
|
74
|
+
|
75
|
+
let(:consumer) do
|
76
|
+
{:key => "test_client_app_key", :secret => "test_client_app_secret"}.tap do |consumer|
|
77
|
+
OAuthenticatorTestConfigMethods.consumer_secrets[consumer[:key]] = consumer[:secret]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
let(:consumer_key) { consumer[:key] }
|
81
|
+
let(:consumer_secret) { consumer[:secret] }
|
82
|
+
|
83
|
+
let(:access_token_hash) do
|
84
|
+
{:token => 'test_access_token', :secret => 'test_access_token_secret', :consumer_key => consumer_key}.tap do |hash|
|
85
|
+
OAuthenticatorTestConfigMethods.access_token_secrets[hash[:token]] = hash[:secret]
|
86
|
+
OAuthenticatorTestConfigMethods.access_token_consumers[hash[:token]] = hash[:consumer_key]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
let(:access_token) { access_token_hash[:token] }
|
90
|
+
let(:access_token_secret) { access_token_hash[:secret] }
|
91
|
+
|
92
|
+
def assert_response(expected_status, expected_body, actual_status, actual_headers, actual_body)
|
93
|
+
actual_body_s = actual_body.to_enum.to_a.join
|
94
|
+
assert_equal expected_status.to_i, actual_status.to_i, "Expected status to be #{expected_status.inspect}" +
|
95
|
+
"; got #{actual_status.inspect}. body was: #{actual_body_s}"
|
96
|
+
assert expected_body === actual_body_s, "Expected match for #{expected_body}; got #{actual_body_s}"
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'makes a valid two-legged signed request (generated)' do
|
100
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
101
|
+
request.env['HTTP_AUTHORIZATION'] = SimpleOAuth::Header.new(
|
102
|
+
request.request_method,
|
103
|
+
request.url,
|
104
|
+
nil,
|
105
|
+
{:consumer_key => consumer_key, :consumer_secret => consumer_secret}
|
106
|
+
).to_s
|
107
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'makes a valid two-legged signed request with a form encoded body (generated)' do
|
111
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/',
|
112
|
+
:method => 'GET',
|
113
|
+
:input => 'a=b&a=c',
|
114
|
+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=UTF8',
|
115
|
+
))
|
116
|
+
request.env['HTTP_AUTHORIZATION'] = SimpleOAuth::Header.new(
|
117
|
+
request.request_method,
|
118
|
+
request.url,
|
119
|
+
[['a', 'b'], ['a', 'c']],
|
120
|
+
{:consumer_key => consumer_key, :consumer_secret => consumer_secret}
|
121
|
+
).to_s
|
122
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'makes a valid three-legged signed request (generated)' do
|
126
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
127
|
+
request.env['HTTP_AUTHORIZATION'] = SimpleOAuth::Header.new(
|
128
|
+
request.request_method,
|
129
|
+
request.url,
|
130
|
+
nil,
|
131
|
+
{ :consumer_key => consumer_key,
|
132
|
+
:consumer_secret => consumer_secret,
|
133
|
+
:token => access_token,
|
134
|
+
:token_secret => access_token_secret,
|
135
|
+
}
|
136
|
+
).to_s
|
137
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
138
|
+
end
|
139
|
+
|
140
|
+
2.times do |i|
|
141
|
+
# run these twice to make sure that the databas cleaner clears out the nonce since we use the same
|
142
|
+
# nonce across tests
|
143
|
+
it "makes a valid signed two-legged request (static #{i})" do
|
144
|
+
Timecop.travel Time.at 1391021695
|
145
|
+
consumer # cause this to be created
|
146
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
147
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
148
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
149
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
150
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
151
|
+
%q(oauth_timestamp="1391021695", ) +
|
152
|
+
%q(oauth_version="1.0")
|
153
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
154
|
+
end
|
155
|
+
|
156
|
+
it "makes a valid signed three-legged request (static #{i})" do
|
157
|
+
Timecop.travel Time.at 1391021695
|
158
|
+
consumer # cause this to be created
|
159
|
+
access_token_hash # cause this to be created
|
160
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
161
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth ) +
|
162
|
+
%q(oauth_consumer_key="test_client_app_key", ) +
|
163
|
+
%q(oauth_nonce="6320851a8f4e18b2ac223497b0477f2e", ) +
|
164
|
+
%q(oauth_signature="B0sJjhfiXajEveqgjaRL3L60sCM%3D", ) +
|
165
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
166
|
+
%q(oauth_timestamp="1391021695", ) +
|
167
|
+
%q(oauth_token="test_access_token", ) +
|
168
|
+
%q(oauth_version="1.0")
|
169
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'complains about a missing Authorization header' do
|
174
|
+
assert_response(401, /Authorization header is missing/, *oapp.call({}))
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'complains about a blank Authorization header' do
|
178
|
+
assert_response(401, /Authorization header is blank/, *oapp.call({'HTTP_AUTHORIZATION' => ' '}))
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'complains about a non-OAuth Authentication header' do
|
182
|
+
assert_response(401, /Authorization scheme is not OAuth/, *oapp.call({'HTTP_AUTHORIZATION' => 'Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='}))
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'omits timestamp' do
|
186
|
+
Timecop.travel Time.at 1391021695
|
187
|
+
consumer # cause this to be created
|
188
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
189
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
190
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
191
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
192
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
193
|
+
#%q(oauth_timestamp="1391021695", ) +
|
194
|
+
%q(oauth_version="1.0")
|
195
|
+
assert_response(401, /Authorization oauth_timestamp.*is missing/m, *oapp.call(request.env))
|
196
|
+
end
|
197
|
+
it 'has a non-integer timestamp' do
|
198
|
+
Timecop.travel Time.at 1391021695
|
199
|
+
consumer # cause this to be created
|
200
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
201
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
202
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
203
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
204
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
205
|
+
%q(oauth_timestamp="now", ) +
|
206
|
+
%q(oauth_version="1.0")
|
207
|
+
assert_response(401, /Authorization oauth_timestamp.*is not an integer - got: now/m, *oapp.call(request.env))
|
208
|
+
end
|
209
|
+
it 'has a too-old timestamp' do
|
210
|
+
Timecop.travel Time.at 1391021695
|
211
|
+
consumer # cause this to be created
|
212
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
213
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
214
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
215
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
216
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
217
|
+
%q(oauth_timestamp="1391010893", ) +
|
218
|
+
%q(oauth_version="1.0")
|
219
|
+
assert_response(401, /Authorization oauth_timestamp.*is too old: 1391010893/m, *oapp.call(request.env))
|
220
|
+
end
|
221
|
+
it 'has a timestamp too far in the future' do
|
222
|
+
Timecop.travel Time.at 1391021695
|
223
|
+
consumer # cause this to be created
|
224
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
225
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
226
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
227
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
228
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
229
|
+
%q(oauth_timestamp="1391032497", ) +
|
230
|
+
%q(oauth_version="1.0")
|
231
|
+
assert_response(401, /Authorization oauth_timestamp.*is too far in the future: 1391032497/m, *oapp.call(request.env))
|
232
|
+
end
|
233
|
+
it 'omits version' do
|
234
|
+
Timecop.travel Time.at 1391021695
|
235
|
+
consumer # cause this to be created
|
236
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
237
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
238
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
239
|
+
%q(oauth_signature="lCVypLHYc6oKz+vOa6DKEivoyys%3D", ) +
|
240
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
241
|
+
%q(oauth_timestamp="1391021695")
|
242
|
+
#%q(oauth_version="1.0")
|
243
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
244
|
+
end
|
245
|
+
it 'has a wrong version' do
|
246
|
+
Timecop.travel Time.at 1391021695
|
247
|
+
consumer # cause this to be created
|
248
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
249
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
250
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
251
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
252
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
253
|
+
%q(oauth_timestamp="1391021695", ) +
|
254
|
+
%q(oauth_version="3.14")
|
255
|
+
assert_response(401, /Authorization oauth_version.*must be 1\.0; got: 3\.14/m, *oapp.call(request.env))
|
256
|
+
end
|
257
|
+
it 'omits consumer key' do
|
258
|
+
Timecop.travel Time.at 1391021695
|
259
|
+
consumer # cause this to be created
|
260
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
261
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth ) + #%q(oauth_consumer_key="test_client_app_key", ) +
|
262
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
263
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
264
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
265
|
+
%q(oauth_timestamp="1391021695", ) +
|
266
|
+
%q(oauth_version="1.0")
|
267
|
+
assert_response(401, /Authorization oauth_consumer_key.*is missing/m, *oapp.call(request.env))
|
268
|
+
end
|
269
|
+
it 'has an invalid consumer key' do
|
270
|
+
Timecop.travel Time.at 1391021695
|
271
|
+
consumer # cause this to be created
|
272
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
273
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="nonexistent_app_key", ) +
|
274
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
275
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
276
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
277
|
+
%q(oauth_timestamp="1391021695", ) +
|
278
|
+
%q(oauth_version="1.0")
|
279
|
+
assert_response(401, /Authorization oauth_consumer_key.*is invalid/m, *oapp.call(request.env))
|
280
|
+
end
|
281
|
+
it 'has an invalid access token' do
|
282
|
+
Timecop.travel Time.at 1391021695
|
283
|
+
consumer # cause this to be created
|
284
|
+
access_token_hash # cause this to be created
|
285
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
286
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth ) +
|
287
|
+
%q(oauth_consumer_key="test_client_app_key", ) +
|
288
|
+
%q(oauth_nonce="6320851a8f4e18b2ac223497b0477f2e", ) +
|
289
|
+
%q(oauth_signature="B0sJjhfiXajEveqgjaRL3L60sCM%3D", ) +
|
290
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
291
|
+
%q(oauth_timestamp="1391021695", ) +
|
292
|
+
%q(oauth_token="nonexistent_access_token", ) +
|
293
|
+
%q(oauth_version="1.0")
|
294
|
+
assert_response(401, /Authorization oauth_token.*is invalid/m, *oapp.call(request.env))
|
295
|
+
end
|
296
|
+
it 'has an access token belonging to a different consumer key' do
|
297
|
+
Timecop.travel Time.at 1391021695
|
298
|
+
consumer # cause this to be created
|
299
|
+
access_token_hash # cause this to be created
|
300
|
+
|
301
|
+
OAuthenticatorTestConfigMethods.consumer_secrets["different_client_app_key"] = "different_client_app_secret"
|
302
|
+
|
303
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
304
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth ) +
|
305
|
+
%q(oauth_consumer_key="different_client_app_key", ) +
|
306
|
+
%q(oauth_nonce="6320851a8f4e18b2ac223497b0477f2e", ) +
|
307
|
+
%q(oauth_signature="PVscPDg%2B%2FjAXRiahIggkeBpN5zI%3D", ) +
|
308
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
309
|
+
%q(oauth_timestamp="1391021695", ) +
|
310
|
+
%q(oauth_token="test_access_token", ) +
|
311
|
+
%q(oauth_version="1.0")
|
312
|
+
assert_response(401, /Authorization oauth_token.*does not belong to the specified consumer/m, *oapp.call(request.env))
|
313
|
+
end
|
314
|
+
it 'omits nonce' do
|
315
|
+
Timecop.travel Time.at 1391021695
|
316
|
+
consumer # cause this to be created
|
317
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
318
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
319
|
+
#%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
320
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
321
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
322
|
+
%q(oauth_timestamp="1391021695", ) +
|
323
|
+
%q(oauth_version="1.0")
|
324
|
+
assert_response(401, /Authorization oauth_nonce.*is missing/m, *oapp.call(request.env))
|
325
|
+
end
|
326
|
+
it 'has an already-used nonce' do
|
327
|
+
Timecop.travel Time.at 1391021695
|
328
|
+
consumer # cause this to be created
|
329
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
330
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
331
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
332
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
333
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
334
|
+
%q(oauth_timestamp="1391021695", ) +
|
335
|
+
%q(oauth_version="1.0")
|
336
|
+
assert_response(200, '☺', *oapp.call(request.env))
|
337
|
+
assert_response(401, /Authorization oauth_nonce.*has already been used/m, *oapp.call(request.env))
|
338
|
+
end
|
339
|
+
it 'omits signature' do
|
340
|
+
Timecop.travel Time.at 1391021695
|
341
|
+
consumer # cause this to be created
|
342
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
343
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
344
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
345
|
+
#%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
346
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
347
|
+
%q(oauth_timestamp="1391021695", ) +
|
348
|
+
%q(oauth_version="1.0")
|
349
|
+
assert_response(401, /Authorization oauth_signature.*is missing/m, *oapp.call(request.env))
|
350
|
+
end
|
351
|
+
it 'omits signature method' do
|
352
|
+
Timecop.travel Time.at 1391021695
|
353
|
+
consumer # cause this to be created
|
354
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
355
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
356
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
357
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
358
|
+
#%q(oauth_signature_method="HMAC-SHA1", ) +
|
359
|
+
%q(oauth_timestamp="1391021695", ) +
|
360
|
+
%q(oauth_version="1.0")
|
361
|
+
assert_response(401, /Authorization oauth_signature_method.*is missing/m, *oapp.call(request.env))
|
362
|
+
end
|
363
|
+
it 'specifies an invalid signature method' do
|
364
|
+
Timecop.travel Time.at 1391021695
|
365
|
+
consumer # cause this to be created
|
366
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
367
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
368
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
369
|
+
%q(oauth_signature="Xy1s5IUn8x0U2KPyHBw4B2cHZMo%3D", ) +
|
370
|
+
%q(oauth_signature_method="ROT13", ) +
|
371
|
+
%q(oauth_timestamp="1391021695", ) +
|
372
|
+
%q(oauth_version="1.0")
|
373
|
+
assert_response(401, /Authorization oauth_signature_method.*must be one of HMAC-SHA1, RSA-SHA1, PLAINTEXT; got: ROT13/m, *oapp.call(request.env))
|
374
|
+
end
|
375
|
+
it 'has an invalid signature' do
|
376
|
+
Timecop.travel Time.at 1391021695
|
377
|
+
consumer # cause this to be created
|
378
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
379
|
+
request.env['HTTP_AUTHORIZATION'] = %q(OAuth oauth_consumer_key="test_client_app_key", ) +
|
380
|
+
%q(oauth_nonce="c1c2bd8676d44e48691c8dceffa66a96", ) +
|
381
|
+
%q(oauth_signature="totallylegit", ) +
|
382
|
+
%q(oauth_signature_method="HMAC-SHA1", ) +
|
383
|
+
%q(oauth_timestamp="1391021695", ) +
|
384
|
+
%q(oauth_version="1.0")
|
385
|
+
assert_response(401, /Authorization oauth_signature.*is invalid/m, *oapp.call(request.env))
|
386
|
+
end
|
387
|
+
|
388
|
+
describe :bypass do
|
389
|
+
it 'bypasses with invalid request' do
|
390
|
+
oapp = OAuthenticator::Middleware.new(simpleapp, :bypass => proc { true }, :config_methods => OAuthenticatorTestConfigMethods)
|
391
|
+
env = Rack::MockRequest.env_for('/', :method => 'GET').merge({'HTTP_AUTHORIZATION' => 'oauth ?'})
|
392
|
+
assert_response(200, '☺', *oapp.call(env))
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'does not bypass with invalid request' do
|
396
|
+
oapp = OAuthenticator::Middleware.new(simpleapp, :bypass => proc { false }, :config_methods => OAuthenticatorTestConfigMethods)
|
397
|
+
assert_equal(401, oapp.call({}).first)
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'bypasses with valid request' do
|
401
|
+
was_authenticated = nil
|
402
|
+
bapp = proc { |env| was_authenticated = env['oauth.authenticated']; [200, {}, ['☺']] }
|
403
|
+
boapp = OAuthenticator::Middleware.new(bapp, :bypass => proc { true }, :config_methods => OAuthenticatorTestConfigMethods)
|
404
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
405
|
+
request.env['HTTP_AUTHORIZATION'] = SimpleOAuth::Header.new(
|
406
|
+
request.request_method,
|
407
|
+
request.url,
|
408
|
+
nil,
|
409
|
+
{:consumer_key => consumer_key, :consumer_secret => consumer_secret}
|
410
|
+
).to_s
|
411
|
+
assert_response(200, '☺', *boapp.call(request.env))
|
412
|
+
assert(was_authenticated == false)
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'does not bypass with valid request' do
|
416
|
+
was_authenticated = nil
|
417
|
+
bapp = proc { |env| was_authenticated = env['oauth.authenticated']; [200, {}, ['☺']] }
|
418
|
+
boapp = OAuthenticator::Middleware.new(bapp, :bypass => proc { false }, :config_methods => OAuthenticatorTestConfigMethods)
|
419
|
+
request = Rack::Request.new(Rack::MockRequest.env_for('/', :method => 'GET'))
|
420
|
+
request.env['HTTP_AUTHORIZATION'] = SimpleOAuth::Header.new(
|
421
|
+
request.request_method,
|
422
|
+
request.url,
|
423
|
+
nil,
|
424
|
+
{:consumer_key => consumer_key, :consumer_secret => consumer_secret}
|
425
|
+
).to_s
|
426
|
+
assert_response(200, '☺', *boapp.call(request.env))
|
427
|
+
assert(was_authenticated == true)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
metadata
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oauthenticator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ethan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: simple_oauth
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: minitest
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: minitest-reporters
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rack-test
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: timecop
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: simplecov
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: yard
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: rdiscount
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: redcarpet
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: rdoc
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ~>
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: 3.9.0
|
214
|
+
type: :development
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ~>
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 3.9.0
|
222
|
+
description: OAuthenticator authenticates OAuth 1.0 signed requests, primarily as
|
223
|
+
a middleware, and forms useful error messages when authentication fails.
|
224
|
+
email:
|
225
|
+
- ethan@dishdigital
|
226
|
+
executables: []
|
227
|
+
extensions: []
|
228
|
+
extra_rdoc_files: []
|
229
|
+
files:
|
230
|
+
- lib/oauthenticator.rb
|
231
|
+
- lib/oauthenticator/middleware.rb
|
232
|
+
- lib/oauthenticator/config_methods.rb
|
233
|
+
- lib/oauthenticator/signed_request.rb
|
234
|
+
- lib/oauthenticator/version.rb
|
235
|
+
- test/oauthenticator_test.rb
|
236
|
+
homepage: https://github.com/notEthan/oauthenticator
|
237
|
+
licenses:
|
238
|
+
- MIT
|
239
|
+
post_install_message:
|
240
|
+
rdoc_options: []
|
241
|
+
require_paths:
|
242
|
+
- lib
|
243
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
244
|
+
none: false
|
245
|
+
requirements:
|
246
|
+
- - ! '>='
|
247
|
+
- !ruby/object:Gem::Version
|
248
|
+
version: '0'
|
249
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
250
|
+
none: false
|
251
|
+
requirements:
|
252
|
+
- - ! '>='
|
253
|
+
- !ruby/object:Gem::Version
|
254
|
+
version: '0'
|
255
|
+
requirements: []
|
256
|
+
rubyforge_project:
|
257
|
+
rubygems_version: 1.8.25
|
258
|
+
signing_key:
|
259
|
+
specification_version: 3
|
260
|
+
summary: OAuth 1.0 request authentication middleware
|
261
|
+
test_files:
|
262
|
+
- test/oauthenticator_test.rb
|
263
|
+
has_rdoc:
|