opro 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: