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 +2 -0
- data/README.md +19 -1
- data/VERSION +1 -1
- data/app/controllers/opro/oauth/auth_controller.rb +6 -5
- data/app/controllers/opro/oauth/token_controller.rb +2 -2
- data/app/models/opro/oauth/auth_grant.rb +9 -5
- data/app/views/opro/oauth/docs/index.html.erb +5 -0
- data/app/views/opro/oauth/docs/markdown/curl.md.erb +0 -1
- data/app/views/opro/oauth/docs/markdown/oauth.md.erb +1 -1
- data/app/views/opro/oauth/docs/markdown/password_exchange.md.erb +38 -0
- data/lib/opro.rb +1 -0
- data/lib/opro/controllers/application_controller_helper.rb +9 -2
- data/lib/opro/controllers/concerns/error_messages.rb +1 -0
- data/lib/opro/controllers/concerns/rate_limits.rb +34 -0
- data/opro.gemspec +5 -2
- data/test/dummy/config/initializers/opro.rb +7 -0
- data/test/integration/action_dispatch/auth_controller_test.rb +22 -0
- data/test/integration/action_dispatch/oauth_flow_test.rb +3 -4
- data/test/integration/action_dispatch/password_token_test.rb +1 -1
- data/test/integration/action_dispatch/rate_limits_test.rb +37 -0
- data/test/integration/docs_controller_test.rb +4 -1
- metadata +32 -29
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[:
|
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.
|
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
|
-
|
19
|
-
|
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
|
-
|
23
|
+
auth_grant.update_attributes(:permissions => params[:permissions]) if auth_grant.permissions != params[:permissions]
|
24
24
|
|
25
|
-
redirect_to
|
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[:
|
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"
|
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
|
86
|
+
redirect_uri << "&code=#{code}&response_type=code"
|
85
87
|
else
|
86
|
-
redirect_uri
|
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
|
|
@@ -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
@@ -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
|
-
|
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.
|
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-
|
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
|
-
|
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
|
-
|
26
|
+
assert_equal json_hash["access_token"], auth_grant.access_token
|
28
27
|
|
29
28
|
assert json_hash["refresh_token"]
|
30
|
-
|
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
|
-
:
|
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
|
-
[
|
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.
|
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-
|
12
|
+
date: 2012-07-05 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
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: *
|
24
|
+
version_requirements: *70279061895320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rails
|
27
|
-
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: *
|
35
|
+
version_requirements: *70279061894660
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bluecloth
|
38
|
-
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: *
|
46
|
+
version_requirements: *70279061893760
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mocha
|
49
|
-
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: *
|
57
|
+
version_requirements: *70279061893060
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: timecop
|
60
|
-
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: *
|
68
|
+
version_requirements: *70279061892280
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: jeweler
|
71
|
-
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: *
|
79
|
+
version_requirements: *70279061891440
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: bundler
|
82
|
-
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: *
|
90
|
+
version_requirements: *70279061886220
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: capybara
|
93
|
-
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: *
|
101
|
+
version_requirements: *70279061885480
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: sqlite3
|
104
|
-
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: *
|
112
|
+
version_requirements: *70279061884480
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: launchy
|
115
|
-
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: *
|
123
|
+
version_requirements: *70279061883420
|
124
124
|
- !ruby/object:Gem::Dependency
|
125
125
|
name: devise
|
126
|
-
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: *
|
134
|
+
version_requirements: *70279061882240
|
135
135
|
- !ruby/object:Gem::Dependency
|
136
136
|
name: rcov
|
137
|
-
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: *
|
145
|
+
version_requirements: *70279061880620
|
146
146
|
- !ruby/object:Gem::Dependency
|
147
147
|
name: simplecov
|
148
|
-
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: *
|
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:
|
281
|
+
hash: 68018896701439782
|
279
282
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
280
283
|
none: false
|
281
284
|
requirements:
|