oauthenticator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module OAuthenticator
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "oauthenticator/version"
2
+
3
+ module OAuthenticator
4
+ autoload :Middleware, 'oauthenticator/middleware'
5
+ autoload :SignedRequest, 'oauthenticator/signed_request'
6
+ 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: