opro 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ ## 0.2.0
2
+
3
+ - Allow password exchange for access_token using `config.allow_password_exchange = true`
4
+
1
5
  ## 0.1.0
2
6
 
3
7
  - Refresh Token Support
data/README.md CHANGED
@@ -41,7 +41,7 @@ Now we're ready to migrate the database
41
41
  $ rake db:migrate
42
42
  ````
43
43
 
44
- This will add `Oauth::AuthGrant` and `Oauth::ClientApp` to your database. An iPhone app would need to register for a `client_id` and `client_secret` before using Oauth as a ClientApp. Once created they could get authorization from users by going through the oauth flow, thus creating AuthGrants. In other words a ClientApp has many users through AuthGrants.
44
+ This will add `Opro::Oauth::AuthGrant` and `Opro::Oauth::ClientApp` to your database. An iPhone app would need to register for a `client_id` and `client_secret` before using Oauth as a ClientApp. Once created they could get authorization from users by going through the oauth flow, thus creating AuthGrants. In other words a ClientApp has many users through AuthGrants.
45
45
 
46
46
  ## Setup
47
47
 
@@ -79,7 +79,7 @@ That should be all you need to do to get setup, congrats you're now able to auth
79
79
 
80
80
  ## Use it
81
81
 
82
- Opro comes with built in documentation, so if you start your server you can view them at http://localhost:3000/oauth_docs. If you're reading this on Github you can jump right to the [Quick Start](https://github.com/schneems/opro/blob/master/app/views/oauth/docs/markdown/quick_start.md.erb) guide. This guide will walk you through creating your first OAuth client application, giving access to that app as a logged in user, getting an access token for that user, and using that token to access the server as an authenticated user!
82
+ Opro comes with built in documentation, so if you start your server you can view them at [http://localhost:3000/oauth_docs](http://localhost:3000/oauth_docs). Or you can [view the guide](http://opro-demo.herokuapp.com/oauth_docs) on the example app. This guide will walk you through creating your first OAuth client application, giving access to that app as a logged in user, getting an access token for that user, and using that token to access the server as an authenticated user!
83
83
 
84
84
  ## Custom Auth
85
85
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -16,17 +16,20 @@ class Opro::Oauth::TokenController < OproController
16
16
  return
17
17
  end
18
18
 
19
-
20
19
  if params[:code]
21
- auth_grant = Opro::Oauth::AuthGrant.authenticate(params[:code], application.id)
22
- else
20
+ auth_grant = Opro::Oauth::AuthGrant.auth_with_code!(params[:code], application.id)
21
+ elsif params[:refresh_token]
23
22
  auth_grant = Opro::Oauth::AuthGrant.refresh_tokens!(params[:refresh_token], application.id)
23
+ elsif params[:password] || passwords[:auth_grant] == "password"
24
+ user = ::Opro.find_user_for_auth.call(self, params) if Opro.password_exchange_enabled? && oauth_valid_password_auth?(params[:client_id], params[:client_secret])
25
+ auth_grant = Opro::Oauth::AuthGrant.auth_with_user!(user, application.id) if user.present?
24
26
  end
25
27
 
26
- if auth_grant.nil?
27
- msg = "Could not find a user that belongs to this application & "
28
- msg << " has a refresh_token=#{params[:refresh_token]}" if params[:refresh_token]
29
- msg << " has been granted a code=#{params[:code]}" if params[:code]
28
+ if auth_grant.blank?
29
+ msg = "Could not find a user that belongs to this application"
30
+ msg << " & has a refresh_token=#{params[:refresh_token]}" if params[:refresh_token]
31
+ msg << " & has been granted a code=#{params[:code]}" if params[:code]
32
+ msg << " using username and password" if params[:password]
30
33
  render :json => {:error => msg }, :status => :unauthorized
31
34
  return
32
35
  end
@@ -50,10 +50,19 @@ class Opro::Oauth::AuthGrant < ActiveRecord::Base
50
50
  find_app_for_token.try(:user)
51
51
  end
52
52
 
53
- def self.authenticate(code, application_id)
53
+ def self.auth_with_code!(code, application_id)
54
54
  auth_grant = self.where("code = ? AND application_id = ?", code, application_id).first
55
55
  end
56
56
 
57
+ def self.auth_with_user!(user, applicaiton_id, permissions = ::Opro.request_permissions)
58
+ return false unless user
59
+ permissions_hash = permissions.each_with_object({}) {|element, hash| hash[element] = true }
60
+ auth_grant = self.where(:user_id => user.id, :application_id => applicaiton_id).first
61
+ auth_grant ||= self.create(:user_id => user.id, :application_id => applicaiton_id)
62
+ auth_grant.update_attributes(:permissions => permissions_hash)
63
+ auth_grant
64
+ end
65
+
57
66
  def self.refresh_tokens!(refresh_token, application_id)
58
67
  auth_grant = self.where("refresh_token = ? AND application_id = ?", refresh_token, application_id).first
59
68
  if auth_grant.present?
@@ -2,7 +2,7 @@ Opro.setup do |config|
2
2
  ## Configure the auth_strategy or use set :login_method, :logout_method, & :authenticate_user_method
3
3
  config.auth_strategy = :devise
4
4
 
5
- ## Add or remove application permissions
5
+ ## Add or Remove Application Permissions
6
6
  # Read permission (any request with [GET]) is turned on by default
7
7
  # Write permission (any request other than [GET]) is requestable by default
8
8
  # Custom permissions can be configured by adding them to `config.request_permissions`
@@ -10,8 +10,17 @@ Opro.setup do |config|
10
10
  # `require_oauth_permissions` in the controller
11
11
  config.request_permissions = [:write]
12
12
 
13
- ## Refresh Token config
13
+ ## Refresh Token Config
14
14
  # uncomment `config.require_refresh_within` to require refresh tokens
15
- # this will expire tokens within the given time duration
15
+ # this will expire tokens within the given time duration, having it enabled
16
+ # is more secure, but harder to use.
16
17
  # config.require_refresh_within = 1.month
18
+
19
+ ## Allow Password Exchange
20
+ # You can allow client applications to exchange a user's credentials
21
+ # password, etc. for an access token.
22
+ # Caution: This bypasses the traditional OAuth flow
23
+ # as a result users cannot opt out of client permissions, all permissions are granted
24
+ config.allow_password_exchange = false
25
+
17
26
  end
@@ -25,6 +25,17 @@ module Opro
25
25
  login_method { |controller, current_user| controller.sign_in(current_user, :bypass => true) }
26
26
  logout_method { |controller, current_user| controller.sign_out(current_user) }
27
27
  authenticate_user_method { |controller| controller.authenticate_user! }
28
+
29
+ find_user_for_auth do |controller, params|
30
+ find_params = params.each_with_object({}) {|(key,value), hash| hash[key] = value if Devise.authentication_keys.include?(key.to_sym) }
31
+ user = User.where(find_params).first
32
+ if user && user.valid_password?(params[:password])
33
+ return_user = user
34
+ else
35
+ return_user = false
36
+ end
37
+ return_user
38
+ end
28
39
  else
29
40
  # nothing
30
41
  end
@@ -92,9 +103,25 @@ module Opro
92
103
  if block.present?
93
104
  @authenticate_user_method = block
94
105
  else
95
- @authenticate_user_method or raise 'authenticate user method not set, please specify Opro auth_strategy'
106
+ @authenticate_user_method or raise 'authenticate_user_method not set, please specify Opro auth_strategy'
107
+ end
108
+ end
109
+
110
+ def self.find_user_for_auth(&block)
111
+ if block.present?
112
+ @find_for_authentication = block
113
+ else
114
+ @find_for_authentication or raise 'find_for_authentication not set, please specify Opro auth_strategy'
96
115
  end
97
116
  end
117
+
118
+ def self.password_exchange_enabled=(password_exchange_enabled)
119
+ @password_exchange_enabled = password_exchange_enabled
120
+ end
121
+
122
+ def self.password_exchange_enabled?
123
+ @password_exchange_enabled
124
+ end
98
125
  end
99
126
 
100
127
  require 'opro/controllers/concerns/error_messages'
@@ -57,6 +57,12 @@ module Opro
57
57
  allow_oauth? && params[:access_token].present?
58
58
  end
59
59
 
60
+ # Override with custom logic to exclude or allow applications from exchanging
61
+ # passwords for access_tokens
62
+ def oauth_valid_password_auth?(client_id, client_secret)
63
+ true
64
+ end
65
+
60
66
  def oauth_access_grant
61
67
  @oauth_access_grant ||= Opro::Oauth::AuthGrant.find_for_token(params[:access_token])
62
68
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "opro"
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["schneems"]
12
- s.date = "2012-06-19"
12
+ s.date = "2012-06-20"
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 = [
@@ -104,6 +104,7 @@ Gem::Specification.new do |s|
104
104
  "test/dummy/script/rails",
105
105
  "test/integration/action_dispatch/auth_controller_test.rb",
106
106
  "test/integration/action_dispatch/oauth_flow_test.rb",
107
+ "test/integration/action_dispatch/password_token_test.rb",
107
108
  "test/integration/action_dispatch/refresh_token_test.rb",
108
109
  "test/integration/auth_controller_test.rb",
109
110
  "test/integration/client_app_controller_test.rb",
@@ -0,0 +1,61 @@
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 PasswordTokenTest < ActionDispatch::IntegrationTest
9
+
10
+ setup do
11
+ @user = create_user
12
+ @client_app = create_client_app
13
+ @password = "password"
14
+ Opro.setup {|config| config.password_exchange_enabled = true}
15
+ end
16
+
17
+
18
+ test "exchange a password and email for an access_token" do
19
+ params = {:client_id => @client_app.client_id ,
20
+ :client_secret => @client_app.client_secret,
21
+ :password => @password,
22
+ :email => @user.email }
23
+
24
+ post oauth_token_path(params)
25
+
26
+ json_hash = JSON.parse(response.body)
27
+ assert json_hash['access_token'].present?
28
+ end
29
+
30
+ test "do not provide access_token for invalid password or email" do
31
+ params = {:client_id => @client_app.client_id ,
32
+ :client_secret => @client_app.client_secret,
33
+ :password => @password,
34
+ :email => @user.email }
35
+
36
+ post oauth_token_path(params.merge(:password => @password + "invalid"))
37
+ json_hash = JSON.parse(response.body)
38
+ assert json_hash['access_token'].blank?
39
+
40
+ post oauth_token_path(params.merge(:email => "invalid" + @user.email ))
41
+ json_hash = JSON.parse(response.body)
42
+ assert json_hash['access_token'].blank?
43
+ end
44
+
45
+ test "Only allow authenticated apps" do
46
+ Opro::Oauth::TokenController.any_instance.stubs(:oauth_valid_password_auth?).
47
+ with(@client_app.client_id, @client_app.client_secret).
48
+ returns(false)
49
+
50
+ params = {:client_id => @client_app.client_id ,
51
+ :client_secret => @client_app.client_secret,
52
+ :password => @password,
53
+ :email => @user.email }
54
+
55
+ post oauth_token_path(params)
56
+ json_hash = JSON.parse(response.body)
57
+ assert json_hash['access_token'].blank?
58
+ end
59
+
60
+ end
61
+
@@ -20,7 +20,6 @@ class CapybaraOauthTest < ActiveSupport::IntegrationCase
20
20
  auth_grant = create_auth_grant_for_user(user)
21
21
  access_token = auth_grant.access_token + "foo"
22
22
  visit "/?access_token=#{access_token}"
23
- save_and_open_page
24
23
  assert has_content?('NO logged in users')
25
24
  end
26
25
 
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.1.0
4
+ version: 0.2.0
5
5
  prerelease:
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-06-19 00:00:00.000000000Z
12
+ date: 2012-06-20 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70273794750300 !ruby/object:Gem::Requirement
16
+ requirement: &70339795418460 !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: *70273794750300
24
+ version_requirements: *70339795418460
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rails
27
- requirement: &70273794749360 !ruby/object:Gem::Requirement
27
+ requirement: &70339795417860 !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: *70273794749360
35
+ version_requirements: *70339795417860
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bluecloth
38
- requirement: &70273794748420 !ruby/object:Gem::Requirement
38
+ requirement: &70339795417120 !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: *70273794748420
46
+ version_requirements: *70339795417120
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: mocha
49
- requirement: &70273794747460 !ruby/object:Gem::Requirement
49
+ requirement: &70339795416500 !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: *70273794747460
57
+ version_requirements: *70339795416500
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: timecop
60
- requirement: &70273794746540 !ruby/object:Gem::Requirement
60
+ requirement: &70339795415860 !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: *70273794746540
68
+ version_requirements: *70339795415860
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: jeweler
71
- requirement: &70273794745720 !ruby/object:Gem::Requirement
71
+ requirement: &70339795415100 !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: *70273794745720
79
+ version_requirements: *70339795415100
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: bundler
82
- requirement: &70273794745020 !ruby/object:Gem::Requirement
82
+ requirement: &70339795414500 !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: *70273794745020
90
+ version_requirements: *70339795414500
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: capybara
93
- requirement: &70273794744280 !ruby/object:Gem::Requirement
93
+ requirement: &70339780154620 !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: *70273794744280
101
+ version_requirements: *70339780154620
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: sqlite3
104
- requirement: &70273794743560 !ruby/object:Gem::Requirement
104
+ requirement: &70339780153680 !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: *70273794743560
112
+ version_requirements: *70339780153680
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: launchy
115
- requirement: &70273794742780 !ruby/object:Gem::Requirement
115
+ requirement: &70339780152320 !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: *70273794742780
123
+ version_requirements: *70339780152320
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: devise
126
- requirement: &70273794742200 !ruby/object:Gem::Requirement
126
+ requirement: &70339780151320 !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: *70273794742200
134
+ version_requirements: *70339780151320
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: rcov
137
- requirement: &70273794741600 !ruby/object:Gem::Requirement
137
+ requirement: &70339780150240 !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: *70273794741600
145
+ version_requirements: *70339780150240
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: simplecov
148
- requirement: &70273794740720 !ruby/object:Gem::Requirement
148
+ requirement: &70339780149400 !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: *70273794740720
156
+ version_requirements: *70339780149400
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
@@ -250,6 +250,7 @@ files:
250
250
  - test/dummy/script/rails
251
251
  - test/integration/action_dispatch/auth_controller_test.rb
252
252
  - test/integration/action_dispatch/oauth_flow_test.rb
253
+ - test/integration/action_dispatch/password_token_test.rb
253
254
  - test/integration/action_dispatch/refresh_token_test.rb
254
255
  - test/integration/auth_controller_test.rb
255
256
  - test/integration/client_app_controller_test.rb
@@ -274,7 +275,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
274
275
  version: '0'
275
276
  segments:
276
277
  - 0
277
- hash: -4287808992153183066
278
+ hash: -921611140833646542
278
279
  required_rubygems_version: !ruby/object:Gem::Requirement
279
280
  none: false
280
281
  requirements: