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.
- data/CHANGELOG.md +4 -0
- data/README.md +2 -2
- data/VERSION +1 -1
- data/app/controllers/opro/oauth/token_controller.rb +10 -7
- data/app/models/opro/oauth/auth_grant.rb +10 -1
- data/lib/generators/templates/opro.rb +12 -3
- data/lib/opro.rb +28 -1
- data/lib/opro/controllers/application_controller_helper.rb +6 -0
- data/opro.gemspec +3 -2
- data/test/integration/action_dispatch/password_token_test.rb +61 -0
- data/test/integration/oauth_test.rb +0 -1
- metadata +30 -29
data/CHANGELOG.md
CHANGED
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.
|
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.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.
|
22
|
-
|
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.
|
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.
|
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
|
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
|
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
|
data/lib/opro.rb
CHANGED
@@ -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 '
|
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
|
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.
|
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-
|
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.
|
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-
|
12
|
+
date: 2012-06-20 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
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: *
|
24
|
+
version_requirements: *70339795418460
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rails
|
27
|
-
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: *
|
35
|
+
version_requirements: *70339795417860
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: bluecloth
|
38
|
-
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: *
|
46
|
+
version_requirements: *70339795417120
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: mocha
|
49
|
-
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: *
|
57
|
+
version_requirements: *70339795416500
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: timecop
|
60
|
-
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: *
|
68
|
+
version_requirements: *70339795415860
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: jeweler
|
71
|
-
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: *
|
79
|
+
version_requirements: *70339795415100
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: bundler
|
82
|
-
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: *
|
90
|
+
version_requirements: *70339795414500
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: capybara
|
93
|
-
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: *
|
101
|
+
version_requirements: *70339780154620
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: sqlite3
|
104
|
-
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: *
|
112
|
+
version_requirements: *70339780153680
|
113
113
|
- !ruby/object:Gem::Dependency
|
114
114
|
name: launchy
|
115
|
-
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: *
|
123
|
+
version_requirements: *70339780152320
|
124
124
|
- !ruby/object:Gem::Dependency
|
125
125
|
name: devise
|
126
|
-
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: *
|
134
|
+
version_requirements: *70339780151320
|
135
135
|
- !ruby/object:Gem::Dependency
|
136
136
|
name: rcov
|
137
|
-
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: *
|
145
|
+
version_requirements: *70339780150240
|
146
146
|
- !ruby/object:Gem::Dependency
|
147
147
|
name: simplecov
|
148
|
-
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: *
|
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: -
|
278
|
+
hash: -921611140833646542
|
278
279
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
279
280
|
none: false
|
280
281
|
requirements:
|