opro 0.3.0.pre1 → 0.3.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@
4
4
  - Allow access_token to be passed in header `curl -H "Authorization: token iAmAOaUthToken" http://localhost:3000`
5
5
  - Default `config.password_exchange_enabled' to true
6
6
  - Allow multiple `find_user_for_auth` calls in setup to allow custom finders for facebook, etc.
7
+ - You can now rate limit incoming client applications.
8
+ - Allow clients to mitigate security threat (http://homakov.blogspot.com/2012/07/saferweb-most-common-oauth2.html)
7
9
 
8
10
  ## 0.2.0
9
11
 
data/README.md CHANGED
@@ -157,7 +157,7 @@ If you have this feature enabled you can further control what applications can u
157
157
  # return user.valid_password?(params[:password]) ? user : false
158
158
  end
159
159
 
160
- If you're authenticating exchanging something other than a password (such as a facebook auth token) client's can still enable this functionality by setting `params[:auth_grant] == 'password'` in their initial request. You can then use `find_user_for_auth` method from above and implement your custom behavior. You can call `find_user_for_auth` multiple times and the application will try calling each auth method in order. It is suggested that you return from this block early if the params are missing a vital key like this:
160
+ If you're authenticating exchanging something other than a password (such as a facebook auth token) client's can still enable this functionality by setting `params[:grant_type] == 'password'` in their initial request. You can then use `find_user_for_auth` method from above and implement your custom behavior. You can call `find_user_for_auth` multiple times and the application will try calling each auth method in order. It is suggested that you return from this block early if the params are missing a vital key like this:
161
161
 
162
162
 
163
163
  config.find_user_for_auth do |controller, params|
@@ -166,6 +166,24 @@ If you're authenticating exchanging something other than a password (such as a f
166
166
  end
167
167
 
168
168
 
169
+ ## Rate Limiting
170
+
171
+ If your API becomes a runaway success and people starte abusing your api, you might chose to limit the rate that client applications can access your API. It is common for popular read only API's to have an hourly, or daily rate limit to help prevent abuse. If you want this type of functionality you can use Opro's built in hooks, one to record the number of times a client application has accessed your api. And another to let the application know if the Client app has gone over it's alloted rate.
172
+
173
+ To record the number of times an application has accessed your site add this method to your ApplicationController:
174
+
175
+ def oauth_client_record_access!(client_id, params)
176
+ # implement your rate counting mechanism here
177
+ end
178
+
179
+ Then to let our server know if a given client has reached add this method, the output is expected to be true if the client has gone over their limit, and false if they have not:
180
+
181
+ def oauth_client_rate_limited?(client_id, params)
182
+ # implement your own custom rate limiting logic here
183
+ end
184
+
185
+ Rate limited clients will receive an "unsuccessful" response to any query with a message letting them know they've been rate limited. Using redis with a rotating key generator based on (hour, daty, etc.) is one very common way to count rate, and implement the rate limits. Since there are so many different ways to implement this, we decided to give you a blank slate and implement it however you like. The default is that apps are not rate limited, and in general unlimited API access is the way to go, but if you do find abusive behavior you can always easily add in a rate limit.
186
+
169
187
 
170
188
  ## Assumptions
171
189
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0.pre1
1
+ 0.3.0.pre2
@@ -15,14 +15,14 @@ class Opro::Oauth::AuthController < OproController
15
15
  def create
16
16
  # find or create an auth_grant for a given user
17
17
  application = Opro::Oauth::ClientApp.find_by_app_id(params[:client_id])
18
- access_grant = Opro::Oauth::AuthGrant.where( :user_id => current_user.id, :application_id => application.id).first
19
- access_grant ||= Opro::Oauth::AuthGrant.create(:user => current_user, :application => application)
18
+ auth_grant = Opro::Oauth::AuthGrant.where( :user_id => current_user.id, :application_id => application.id).first
19
+ auth_grant ||= Opro::Oauth::AuthGrant.create(:user => current_user, :application => application)
20
20
 
21
21
 
22
22
  # add permission changes if there are any
23
- access_grant.update_attributes(:permissions => params[:permissions]) if access_grant.permissions != params[:permissions]
23
+ auth_grant.update_attributes(:permissions => params[:permissions]) if auth_grant.permissions != params[:permissions]
24
24
 
25
- redirect_to access_grant.redirect_uri_for(params[:redirect_uri])
25
+ redirect_to auth_grant.redirect_uri_for(params[:redirect_uri], params[:state])
26
26
  end
27
27
 
28
28
 
@@ -48,6 +48,7 @@ class Opro::Oauth::AuthController < OproController
48
48
 
49
49
  def user_granted_access_before?(user, params)
50
50
  @client_app ||= Opro::Oauth::ClientApp.find_by_app_id(params[:client_id])
51
+ return false if user.blank? || @client_app.blank?
51
52
  Opro::Oauth::AuthGrant.where(:application_id => @client_app.id, :user_id => user.id).present?
52
53
  end
53
54
 
@@ -80,4 +81,4 @@ class Opro::Oauth::AuthController < OproController
80
81
  referrer_host == self_host
81
82
  end
82
83
 
83
- end
84
+ end
@@ -20,7 +20,7 @@ class Opro::Oauth::TokenController < OproController
20
20
  auth_grant = Opro::Oauth::AuthGrant.auth_with_code!(params[:code], application.id)
21
21
  elsif params[:refresh_token]
22
22
  auth_grant = Opro::Oauth::AuthGrant.refresh_tokens!(params[:refresh_token], application.id)
23
- elsif params[:password] || params[:auth_grant] == "password"
23
+ elsif params[:password].present? || params[:grant_type] == "password"|| params[:grant_type] == "bearer"
24
24
  user = ::Opro.find_user_for_all_auths!(self, params) if Opro.password_exchange_enabled? && oauth_valid_password_auth?(params[:client_id], params[:client_secret])
25
25
  auth_grant = Opro::Oauth::AuthGrant.auth_with_user!(user, application.id) if user.present?
26
26
  end
@@ -29,7 +29,7 @@ class Opro::Oauth::TokenController < OproController
29
29
  msg = "Could not find a user that belongs to this application"
30
30
  msg << " & has a refresh_token=#{params[:refresh_token]}" if params[:refresh_token]
31
31
  msg << " & has been granted a code=#{params[:code]}" if params[:code]
32
- msg << " using username and password" if params[:password]
32
+ msg << " using username and password" if params[:password]
33
33
  render :json => {:error => msg }, :status => :unauthorized
34
34
  return
35
35
  end
@@ -3,8 +3,10 @@ class Opro::Oauth::AuthGrant < ActiveRecord::Base
3
3
  self.table_name = :opro_auth_grants
4
4
 
5
5
  belongs_to :user
6
- belongs_to :client_application, :class_name => "Oauth::ClientApp"
7
- belongs_to :application, :class_name => "Oauth::ClientApp"
6
+ belongs_to :client_application, :class_name => "Opro::Oauth::ClientApp", :foreign_key => "application_id"
7
+ belongs_to :application, :class_name => "Opro::Oauth::ClientApp", :foreign_key => "application_id"
8
+ belongs_to :client_app, :class_name => "Opro::Oauth::ClientApp", :foreign_key => "application_id"
9
+
8
10
 
9
11
  validates :application_id, :uniqueness => {:scope => :user_id, :message => "Application is already authed for this user"}, :presence => true
10
12
 
@@ -79,11 +81,13 @@ class Opro::Oauth::AuthGrant < ActiveRecord::Base
79
81
  self.code, self.access_token, self.refresh_token = SecureRandom.hex(16), SecureRandom.hex(16), SecureRandom.hex(16)
80
82
  end
81
83
 
82
- def redirect_uri_for(redirect_uri)
84
+ def redirect_uri_for(redirect_uri, state = nil)
83
85
  if redirect_uri =~ /\?/
84
- redirect_uri + "&code=#{code}&response_type=code"
86
+ redirect_uri << "&code=#{code}&response_type=code"
85
87
  else
86
- redirect_uri + "?code=#{code}&response_type=code"
88
+ redirect_uri << "?code=#{code}&response_type=code"
87
89
  end
90
+ redirect_uri << "&state=#{state}" if state.present?
91
+ redirect_uri
88
92
  end
89
93
  end
@@ -9,13 +9,18 @@
9
9
  <li><%= link_to 'Quick Start', oauth_doc_path(:quick_start) %></li>
10
10
  <li><%= link_to 'Curl', oauth_doc_path(:curl) %></li>
11
11
  <li><%= link_to 'Oauth', oauth_doc_path(:oauth) %></li>
12
+
12
13
  <% if ::Opro.request_permissions.present? %>
13
14
  <li><%= link_to 'Permisions', oauth_doc_path(:permissions) %></li>
14
15
  <% end %>
16
+
15
17
  <% if ::Opro.require_refresh_within.present? %>
16
18
  <li><%= link_to 'Refresh Tokens', oauth_doc_path(:refresh_tokens) %></li>
17
19
  <% end %>
18
20
 
21
+ <% if ::Opro.password_exchange_enabled? %>
22
+ <li><%= link_to 'Password Exchange', oauth_doc_path(:password_exchange) %></li>
23
+ <% end %>
19
24
 
20
25
  </ul>
21
26
 
@@ -20,7 +20,6 @@ You can get the entire contents of a web document by simply issuing curl to that
20
20
 
21
21
  $ curl https://www.google.com
22
22
 
23
-
24
23
  ### Get Headers
25
24
 
26
25
 
@@ -1,6 +1,6 @@
1
1
  ## Opro Oauth
2
2
 
3
- OAuth comes in a few different flavors, the implementation of OAuth comes from [Version 22 of the OAuth 2.0 protocol](http://tools.ietf.org/html/draft-ietf-oauth-v2-22) and is heavily influenced by [Facebook's Server Side OAuth Authentication](http://developers.facebook.com/docs/authentication/server-side/).
3
+ OAuth comes in a few different flavors, the implementation of OAuth comes from [Version 22 of the OAuth 2.0 protocol](http://tools.ietf.org/html/draft-ietf-oauth-v2-22) and is heavily influenced by [Facebook's Server Side OAuth Authentication](http://developers.facebook.com/docs/authentication/server-side/) and [Github's API](http://developer.github.com/v3/oauth/).
4
4
 
5
5
 
6
6
  ## What is It?
@@ -0,0 +1,38 @@
1
+ <%= "# Password Exchange is NOT enabled in this App" unless ::Opro.password_exchange_enabled? %>
2
+
3
+
4
+ ## Password Exchange
5
+
6
+ If you're building a mobile (iPhone, Android, etc.) app it can be easier to exchange a user's password and email/username for an access token then to send your user throught the traditional OAuth flow.
7
+
8
+ ## Get Started
9
+
10
+ First obtain a client app id and secret, if you don't have them see the [Quick Start Guide](/oauth_docs/quick_start) and return here. For this example we will use this data:
11
+
12
+ Name: foo
13
+ client id: 3234myClientId5678
14
+ Secret: 14321myClientSecret8765
15
+
16
+ Note you will need to replace the client id and secret with your actual client id and secret.
17
+
18
+ Once you have your id and secret, you can ask the user of your application to provide you with their username and password. For the purposes of this we will be using the example email address of `foo@example.com` and password of `p4ssw0rd`.
19
+
20
+ Once you have the email/username and password of your user on the mobile device, you can then send all of this information to the server with your client id & secret. Don't forget to url encode any special characters like `@`, and always transmit this sensitive data using secure https:
21
+
22
+
23
+ <%= "#{request.base_url.gsub('http://', 'https://')}/oauth/token?" %>email=foo%40example.com&password=p4ssw0rd&client_id=3234myClientId5678&client_secret=14321myClientSecret8765
24
+
25
+ The response should be json with an access_token:
26
+
27
+
28
+ {"access_token":"9693accessTokena7ca570bbaf","refresh_token":"3a3c129ad02b573de78e65af06c293f1","expires_in":null}
29
+
30
+ You can now use the `access_token` in the parameters of future requests or in the header, see the end of the [Quick Start Guide](/oauth_docs/quick_start) for examples.
31
+
32
+
33
+ ## Additional Exchange
34
+
35
+
36
+ In addition to username/password the owner of this web service may choose to implement other custom exchanges such as Facebook or Twitter token exchange for an access token. You must contact the owner of this web service to see if they support other data when exchanging tokens.
37
+
38
+ When you do this make sure to pass `grant_type=password` or `grant_type=bearer` in the request to `/oauth/token`, in addition to any required parameters.
data/lib/opro.rb CHANGED
@@ -143,6 +143,7 @@ module Opro
143
143
  end
144
144
  end
145
145
 
146
+ require 'opro/controllers/concerns/rate_limits'
146
147
  require 'opro/controllers/concerns/error_messages'
147
148
  require 'opro/controllers/concerns/permissions'
148
149
  require 'opro/controllers/application_controller_helper'
@@ -7,6 +7,7 @@ module Opro
7
7
 
8
8
  include Opro::Controllers::Concerns::Permissions
9
9
  include Opro::Controllers::Concerns::ErrorMessages
10
+ include Opro::Controllers::Concerns::RateLimits
10
11
 
11
12
  included do
12
13
  around_filter :oauth_auth!
@@ -32,13 +33,18 @@ module Opro
32
33
 
33
34
  protected
34
35
 
36
+ def oauth_fail_request!
37
+ render :json => {:errors => generate_oauth_error_message! }, :status => :unauthorized
38
+ false
39
+ end
40
+
35
41
  def allow_oauth?
36
42
  @use_oauth ||= false
37
43
  end
38
44
 
39
- # returns boolean if oauth request
45
+
40
46
  def valid_oauth?
41
- oauth? && oauth_user.present? && oauth_client_not_expired? && oauth_client_has_permissions?
47
+ oauth? && oauth_user.present? && oauth_client_not_expired? && oauth_client_has_permissions? && oauth_client_under_rate_limit?
42
48
  end
43
49
 
44
50
  def oauth_client_not_expired?
@@ -57,6 +63,7 @@ module Opro
57
63
  params[:access_token] || oauth_access_token_from_header
58
64
  end
59
65
 
66
+ # grabs access_token from header if one is present
60
67
  def oauth_access_token_from_header
61
68
  auth_header = request.env["HTTP_AUTHORIZATION"]||""
62
69
  match = auth_header.match(/^token\s(.*)/)
@@ -6,6 +6,7 @@ module Opro::Controllers::Concerns::ErrorMessages
6
6
  msg << ' - No OAuth Token Provided!' if oauth_access_token.blank?
7
7
  msg << ' - Allow OAuth set to false!' if allow_oauth? == false
8
8
  msg << ' - OAuth user not found!' if oauth_user.blank?
9
+ msg << ' - OAuth client has been rate limited' if oauth_client_over_rate_limit?
9
10
  msg = generate_oauth_permissions_error_message!(msg)
10
11
  msg
11
12
  end
@@ -0,0 +1,34 @@
1
+ module Opro::Controllers::Concerns::RateLimits
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_filter :oauth_record_rate_limit!, :if => :valid_oauth?
6
+ before_filter :oauth_fail_request!, :if => :oauth_client_over_rate_limit?
7
+ end
8
+
9
+ def oauth_client_record_access!(client_id, params)
10
+ # implement your access counting mechanism here
11
+ end
12
+
13
+ def oauth_client_rate_limited?(client_id, params)
14
+
15
+
16
+ end
17
+
18
+
19
+ # override to implement custom rate limits
20
+ def oauth_client_over_rate_limit?
21
+ return oauth_client_rate_limited?(oauth_client_app.id, params) unless oauth_client_app.blank?
22
+ false
23
+ end
24
+
25
+ def oauth_record_rate_limit!
26
+ return false if oauth_client_app.blank?
27
+ oauth_client_record_access!(oauth_client_app.id, params)
28
+ end
29
+
30
+ def oauth_client_under_rate_limit?
31
+ !oauth_client_over_rate_limit?
32
+ end
33
+
34
+ end
data/opro.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "opro"
8
- s.version = "0.3.0.pre1"
8
+ s.version = "0.3.0.pre2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["schneems"]
12
- s.date = "2012-07-01"
12
+ s.date = "2012-07-05"
13
13
  s.description = " Enable OAuth clients (iphone, android, web sites, etc.) to access and use your Rails application, what you do with it is up to you"
14
14
  s.email = "richard.schneeman@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -39,6 +39,7 @@ Gem::Specification.new do |s|
39
39
  "app/views/opro/oauth/docs/index.html.erb",
40
40
  "app/views/opro/oauth/docs/markdown/curl.md.erb",
41
41
  "app/views/opro/oauth/docs/markdown/oauth.md.erb",
42
+ "app/views/opro/oauth/docs/markdown/password_exchange.md.erb",
42
43
  "app/views/opro/oauth/docs/markdown/permissions.md.erb",
43
44
  "app/views/opro/oauth/docs/markdown/quick_start.md.erb",
44
45
  "app/views/opro/oauth/docs/markdown/refresh_tokens.md.erb",
@@ -54,6 +55,7 @@ Gem::Specification.new do |s|
54
55
  "lib/opro/controllers/application_controller_helper.rb",
55
56
  "lib/opro/controllers/concerns/error_messages.rb",
56
57
  "lib/opro/controllers/concerns/permissions.rb",
58
+ "lib/opro/controllers/concerns/rate_limits.rb",
57
59
  "lib/opro/engine.rb",
58
60
  "lib/opro/rails/routes.rb",
59
61
  "opro.gemspec",
@@ -105,6 +107,7 @@ Gem::Specification.new do |s|
105
107
  "test/integration/action_dispatch/auth_controller_test.rb",
106
108
  "test/integration/action_dispatch/oauth_flow_test.rb",
107
109
  "test/integration/action_dispatch/password_token_test.rb",
110
+ "test/integration/action_dispatch/rate_limits_test.rb",
108
111
  "test/integration/action_dispatch/refresh_token_test.rb",
109
112
  "test/integration/auth_controller_test.rb",
110
113
  "test/integration/client_app_controller_test.rb",
@@ -14,4 +14,11 @@ Opro.setup do |config|
14
14
  # uncomment `config.require_refresh_within` to require refresh tokens
15
15
  # this will expire tokens within the given time duration
16
16
  # config.require_refresh_within = 1.month
17
+
18
+ ## Allow Password Exchange
19
+ # You can allow client applications to exchange a user's credentials
20
+ # password, etc. for an access token.
21
+ # Caution: This bypasses the traditional OAuth flow
22
+ # as a result users cannot opt out of client permissions, all permissions are granted
23
+ config.password_exchange_enabled = true
17
24
  end
@@ -27,6 +27,28 @@ class AuthControllerTest < ActionDispatch::IntegrationTest
27
27
  assert_equal @redirect_uri, path
28
28
  end
29
29
 
30
+ # Tests against common OAuth Security issue
31
+ # http://homakov.blogspot.com/2012/07/saferweb-most-common-oauth2.html
32
+ # still relies on client to submit :status param
33
+ test "oauth auth jacking is can be avoided by clients" do
34
+ auth_grant = create_auth_grant_for_user(@user, @client_app)
35
+ state = SecureRandom.hex(16)
36
+ params = { :client_id => @client_app.client_id ,
37
+ :client_secret => @client_app.client_secret,
38
+ :redirect_uri => @redirect_uri,
39
+ :state => state }
40
+
41
+ as_user(@user).post oauth_authorize_path(params)
42
+
43
+ assert response["Location"].include?("state=#{state}")
44
+
45
+ assert_equal 302, status
46
+ follow_redirect!
47
+
48
+ assert_equal @redirect_uri, path
49
+ assert_equal 200, status
50
+ end
51
+
30
52
 
31
53
  test "AUTHORIZE: app cannot force permissions change for previously authed user" do
32
54
  auth_grant = create_auth_grant_for_user(@user, @client_app)
@@ -12,8 +12,7 @@ class OauthTokenTest < ActionDispatch::IntegrationTest
12
12
 
13
13
 
14
14
  test "exchange a code for a token" do
15
- user = create_user
16
- auth_grant = create_auth_grant_for_user(user)
15
+ auth_grant = create_auth_grant_for_user(@user)
17
16
  client = auth_grant.application
18
17
  params = {:code => auth_grant.code,
19
18
  :client_id => client.client_id,
@@ -24,10 +23,10 @@ class OauthTokenTest < ActionDispatch::IntegrationTest
24
23
 
25
24
  json_hash = JSON.parse(response.body)
26
25
  assert json_hash["access_token"]
27
- assert json_hash["access_token"], auth_grant.access_token
26
+ assert_equal json_hash["access_token"], auth_grant.access_token
28
27
 
29
28
  assert json_hash["refresh_token"]
30
- assert json_hash["refresh_token"], auth_grant.refresh_token
29
+ assert_equal json_hash["refresh_token"], auth_grant.refresh_token
31
30
  end
32
31
 
33
32
 
@@ -70,7 +70,7 @@ class PasswordTokenTest < ActionDispatch::IntegrationTest
70
70
  params = {:client_id => @client_app.client_id ,
71
71
  :client_secret => @client_app.client_secret,
72
72
  :special_key => "fooBarzyrhaz",
73
- :auth_grant => 'password' }
73
+ :grant_type => 'password' }
74
74
 
75
75
  post oauth_token_path(params)
76
76
  json_hash = JSON.parse(response.body)
@@ -0,0 +1,37 @@
1
+ ## NOT CAPYBARA
2
+ # ActionDispatch::IntegrationTest
3
+ # http://guides.rubyonrails.org/testing.html#integration-testing
4
+ # used so we can test POST actions ^_^
5
+
6
+ require 'test_helper'
7
+
8
+ class RateLimitTest < ActionDispatch::IntegrationTest
9
+
10
+ setup do
11
+ @user = create_user
12
+ @auth_grant = create_auth_grant_for_user(@user)
13
+ @client_app = @auth_grant.application
14
+ @params = {:client_id => @client_app.client_id ,
15
+ :client_secret => @client_app.client_secret,
16
+ :access_token => @auth_grant.access_token}
17
+ @auth_grant.update_attributes(:permissions => {:write => true})
18
+ end
19
+
20
+ test "A rate limited app does not get a valid user" do
21
+ Opro::Oauth::TestsController.any_instance.stubs(:oauth_client_over_rate_limit?).returns(true)
22
+
23
+ post oauth_tests_path(@params)
24
+
25
+ assert_equal 401, status
26
+ end
27
+
28
+ test "A NON rate limited app does get a valid user" do
29
+ Opro::Oauth::TestsController.any_instance.expects(:oauth_client_record_access!).at_least_once
30
+ Opro::Oauth::TestsController.any_instance.stubs(:oauth_client_over_rate_limit?).returns(false)
31
+
32
+ post oauth_tests_path(@params)
33
+
34
+ assert_equal 200, status
35
+ end
36
+
37
+ end
@@ -1,13 +1,16 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class DocsControllerTest < ActiveSupport::IntegrationCase
4
+ DOCS_PATH = File.join(File.dirname(__FILE__), '../../app/views/opro/oauth/docs/markdown/*.md.erb')
5
+
4
6
  test 'renders index' do
5
7
  visit oauth_docs_path
6
8
  assert_equal '/oauth_docs', current_path
7
9
  end
8
10
 
9
11
  test 'renders show' do
10
- [:curl, :oauth, :quick_start].each do |doc|
12
+ Dir[DOCS_PATH].each do |file|
13
+ doc = file.split('/').last.gsub('.md.erb', '')
11
14
  doc_path = oauth_doc_path(:id => doc)
12
15
  visit doc_path
13
16
  assert_equal doc_path, current_path
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.pre1
4
+ version: 0.3.0.pre2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-01 00:00:00.000000000Z
12
+ date: 2012-07-05 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70236297859480 !ruby/object:Gem::Requirement
16
+ requirement: &70279061895320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.1.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70236297859480
24
+ version_requirements: *70279061895320
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rails
27
- requirement: &70236297858660 !ruby/object:Gem::Requirement
27
+ requirement: &70279061894660 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70236297858660
35
+ version_requirements: *70279061894660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bluecloth
38
- requirement: &70236297857420 !ruby/object:Gem::Requirement
38
+ requirement: &70279061893760 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70236297857420
46
+ version_requirements: *70279061893760
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: mocha
49
- requirement: &70236297856460 !ruby/object:Gem::Requirement
49
+ requirement: &70279061893060 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70236297856460
57
+ version_requirements: *70279061893060
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: timecop
60
- requirement: &70236297855480 !ruby/object:Gem::Requirement
60
+ requirement: &70279061892280 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70236297855480
68
+ version_requirements: *70279061892280
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: jeweler
71
- requirement: &70236297854520 !ruby/object:Gem::Requirement
71
+ requirement: &70279061891440 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 1.6.4
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70236297854520
79
+ version_requirements: *70279061891440
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: bundler
82
- requirement: &70236297849720 !ruby/object:Gem::Requirement
82
+ requirement: &70279061886220 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: 1.1.3
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70236297849720
90
+ version_requirements: *70279061886220
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: capybara
93
- requirement: &70236297849140 !ruby/object:Gem::Requirement
93
+ requirement: &70279061885480 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: 0.4.0
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *70236297849140
101
+ version_requirements: *70279061885480
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: sqlite3
104
- requirement: &70236297848460 !ruby/object:Gem::Requirement
104
+ requirement: &70279061884480 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *70236297848460
112
+ version_requirements: *70279061884480
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: launchy
115
- requirement: &70236297847820 !ruby/object:Gem::Requirement
115
+ requirement: &70279061883420 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ! '>='
@@ -120,10 +120,10 @@ dependencies:
120
120
  version: '0'
121
121
  type: :development
122
122
  prerelease: false
123
- version_requirements: *70236297847820
123
+ version_requirements: *70279061883420
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: devise
126
- requirement: &70236297847060 !ruby/object:Gem::Requirement
126
+ requirement: &70279061882240 !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
129
129
  - - ! '>='
@@ -131,10 +131,10 @@ dependencies:
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
- version_requirements: *70236297847060
134
+ version_requirements: *70279061882240
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: rcov
137
- requirement: &70236297846500 !ruby/object:Gem::Requirement
137
+ requirement: &70279061880620 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ! '>='
@@ -142,10 +142,10 @@ dependencies:
142
142
  version: '0'
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *70236297846500
145
+ version_requirements: *70279061880620
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: simplecov
148
- requirement: &70236297845900 !ruby/object:Gem::Requirement
148
+ requirement: &70279061878840 !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
151
151
  - - ! '>='
@@ -153,7 +153,7 @@ dependencies:
153
153
  version: '0'
154
154
  type: :development
155
155
  prerelease: false
156
- version_requirements: *70236297845900
156
+ version_requirements: *70279061878840
157
157
  description: ! ' Enable OAuth clients (iphone, android, web sites, etc.) to access
158
158
  and use your Rails application, what you do with it is up to you'
159
159
  email: richard.schneeman@gmail.com
@@ -185,6 +185,7 @@ files:
185
185
  - app/views/opro/oauth/docs/index.html.erb
186
186
  - app/views/opro/oauth/docs/markdown/curl.md.erb
187
187
  - app/views/opro/oauth/docs/markdown/oauth.md.erb
188
+ - app/views/opro/oauth/docs/markdown/password_exchange.md.erb
188
189
  - app/views/opro/oauth/docs/markdown/permissions.md.erb
189
190
  - app/views/opro/oauth/docs/markdown/quick_start.md.erb
190
191
  - app/views/opro/oauth/docs/markdown/refresh_tokens.md.erb
@@ -200,6 +201,7 @@ files:
200
201
  - lib/opro/controllers/application_controller_helper.rb
201
202
  - lib/opro/controllers/concerns/error_messages.rb
202
203
  - lib/opro/controllers/concerns/permissions.rb
204
+ - lib/opro/controllers/concerns/rate_limits.rb
203
205
  - lib/opro/engine.rb
204
206
  - lib/opro/rails/routes.rb
205
207
  - opro.gemspec
@@ -251,6 +253,7 @@ files:
251
253
  - test/integration/action_dispatch/auth_controller_test.rb
252
254
  - test/integration/action_dispatch/oauth_flow_test.rb
253
255
  - test/integration/action_dispatch/password_token_test.rb
256
+ - test/integration/action_dispatch/rate_limits_test.rb
254
257
  - test/integration/action_dispatch/refresh_token_test.rb
255
258
  - test/integration/auth_controller_test.rb
256
259
  - test/integration/client_app_controller_test.rb
@@ -275,7 +278,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
275
278
  version: '0'
276
279
  segments:
277
280
  - 0
278
- hash: -3624448401310655652
281
+ hash: 68018896701439782
279
282
  required_rubygems_version: !ruby/object:Gem::Requirement
280
283
  none: false
281
284
  requirements: