opro 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Gemfile +6 -2
  2. data/Gemfile.lock +37 -33
  3. data/README.md +60 -31
  4. data/Rakefile +8 -0
  5. data/VERSION +1 -1
  6. data/app/controllers/oauth/auth_controller.rb +35 -36
  7. data/app/controllers/oauth/client_app_controller.rb +24 -0
  8. data/app/controllers/oauth/docs_controller.rb +1 -1
  9. data/app/controllers/oauth/tests_controller.rb +4 -26
  10. data/app/controllers/oauth/token_controller.rb +40 -0
  11. data/app/controllers/opro_controller.rb +4 -0
  12. data/app/models/oauth/access_grant.rb +47 -14
  13. data/app/models/oauth/{client_application.rb → client_appl.rb} +1 -1
  14. data/app/views/oauth/{client_application → client_app}/create.html.erb +1 -1
  15. data/app/views/oauth/{client_application → client_app}/index.html.erb +1 -1
  16. data/app/views/oauth/{client_application → client_app}/new.html.erb +1 -1
  17. data/app/views/oauth/docs/index.html.erb +8 -0
  18. data/app/views/oauth/docs/markdown/oauth.md.erb +2 -2
  19. data/app/views/oauth/docs/markdown/permissions.md.erb +30 -0
  20. data/app/views/oauth/docs/markdown/quick_start.md.erb +1 -1
  21. data/app/views/oauth/docs/markdown/refresh_tokens.md.erb +18 -0
  22. data/config/routes.rb +5 -10
  23. data/lib/generators/active_record/opro_generator.rb +1 -1
  24. data/lib/generators/active_record/templates/access_grants.rb +1 -1
  25. data/lib/generators/active_record/templates/client_applications.rb +1 -1
  26. data/lib/generators/opro/install_generator.rb +5 -0
  27. data/lib/generators/templates/opro.rb +10 -3
  28. data/lib/opro.rb +7 -1
  29. data/lib/opro/controllers/application_controller_helper.rb +7 -2
  30. data/lib/opro/engine.rb +4 -0
  31. data/lib/opro/rails/routes.rb +17 -0
  32. data/opro.gemspec +27 -15
  33. data/test/controllers/refresh_token_test.rb +0 -0
  34. data/test/dummy/config/environments/test.rb +4 -0
  35. data/test/dummy/config/initializers/opro.rb +10 -3
  36. data/test/dummy/config/routes.rb +2 -0
  37. data/test/dummy/db/migrate/20120514060322_create_opro_access_grants.rb +1 -1
  38. data/test/dummy/db/migrate/20120514060323_create_opro_client_applications.rb +1 -1
  39. data/test/integration/action_dispatch/auth_controller_test.rb +64 -0
  40. data/test/integration/action_dispatch/oauth_flow_test.rb +34 -0
  41. data/test/integration/action_dispatch/refresh_token_test.rb +54 -0
  42. data/test/integration/auth_controller_test.rb +8 -7
  43. data/test/integration/client_app_controller_test.rb +24 -0
  44. data/test/integration/docs_controller_test.rb +9 -1
  45. data/test/integration/oauth_test.rb +1 -4
  46. data/test/integration/refresh_token_test.rb +32 -0
  47. data/test/support/integration_case.rb +10 -1
  48. data/test/test_helper.rb +7 -3
  49. metadata +56 -36
  50. data/app/controllers/oauth/client_application_controller.rb +0 -21
  51. data/app/controllers/opro_application_controller.rb +0 -8
  52. data/test/integration/client_application_controller_test.rb +0 -24
data/Gemfile CHANGED
@@ -1,13 +1,14 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "activesupport" , ">= 3.0.7"
4
- gem "rails" , ">= 3.0.7"
3
+ gem "activesupport" , ">= 3.1.0"
4
+ gem "rails" , ">= 3.1.0"
5
5
 
6
6
 
7
7
  gem 'bluecloth'
8
8
 
9
9
  group :development, :test do
10
10
  gem 'mocha'
11
+ gem 'timecop'
11
12
  gem 'jeweler', "~> 1.6.4"
12
13
  gem "bundler", ">= 1.1.3"
13
14
 
@@ -17,6 +18,9 @@ group :development, :test do
17
18
  gem "launchy"
18
19
  end
19
20
 
21
+ group :test do
22
+ gem 'database_cleaner'
23
+ end
20
24
 
21
25
  group :test, :development do
22
26
  gem 'devise'
data/Gemfile.lock CHANGED
@@ -1,31 +1,31 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- actionmailer (3.2.3)
5
- actionpack (= 3.2.3)
4
+ actionmailer (3.2.6)
5
+ actionpack (= 3.2.6)
6
6
  mail (~> 2.4.4)
7
- actionpack (3.2.3)
8
- activemodel (= 3.2.3)
9
- activesupport (= 3.2.3)
7
+ actionpack (3.2.6)
8
+ activemodel (= 3.2.6)
9
+ activesupport (= 3.2.6)
10
10
  builder (~> 3.0.0)
11
11
  erubis (~> 2.7.0)
12
12
  journey (~> 1.0.1)
13
13
  rack (~> 1.4.0)
14
14
  rack-cache (~> 1.2)
15
15
  rack-test (~> 0.6.1)
16
- sprockets (~> 2.1.2)
17
- activemodel (3.2.3)
18
- activesupport (= 3.2.3)
16
+ sprockets (~> 2.1.3)
17
+ activemodel (3.2.6)
18
+ activesupport (= 3.2.6)
19
19
  builder (~> 3.0.0)
20
- activerecord (3.2.3)
21
- activemodel (= 3.2.3)
22
- activesupport (= 3.2.3)
20
+ activerecord (3.2.6)
21
+ activemodel (= 3.2.6)
22
+ activesupport (= 3.2.6)
23
23
  arel (~> 3.0.2)
24
24
  tzinfo (~> 0.3.29)
25
- activeresource (3.2.3)
26
- activemodel (= 3.2.3)
27
- activesupport (= 3.2.3)
28
- activesupport (3.2.3)
25
+ activeresource (3.2.6)
26
+ activemodel (= 3.2.6)
27
+ activesupport (= 3.2.6)
28
+ activesupport (3.2.6)
29
29
  i18n (~> 0.6)
30
30
  multi_json (~> 1.0)
31
31
  addressable (2.2.7)
@@ -42,6 +42,7 @@ GEM
42
42
  xpath (~> 0.1.4)
43
43
  childprocess (0.3.1)
44
44
  ffi (~> 1.0.6)
45
+ database_cleaner (0.8.0)
45
46
  devise (2.0.4)
46
47
  bcrypt-ruby (~> 3.0)
47
48
  orm_adapter (~> 0.0.3)
@@ -56,8 +57,8 @@ GEM
56
57
  bundler (~> 1.0)
57
58
  git (>= 1.2.5)
58
59
  rake
59
- journey (1.0.3)
60
- json (1.6.6)
60
+ journey (1.0.4)
61
+ json (1.7.3)
61
62
  launchy (2.1.0)
62
63
  addressable (~> 2.2.6)
63
64
  mail (2.4.4)
@@ -68,7 +69,7 @@ GEM
68
69
  mime-types (1.18)
69
70
  mocha (0.11.4)
70
71
  metaclass (~> 0.0.1)
71
- multi_json (1.2.0)
72
+ multi_json (1.3.6)
72
73
  nokogiri (1.5.2)
73
74
  orm_adapter (0.0.7)
74
75
  polyglot (0.3.3)
@@ -79,21 +80,21 @@ GEM
79
80
  rack
80
81
  rack-test (0.6.1)
81
82
  rack (>= 1.0)
82
- rails (3.2.3)
83
- actionmailer (= 3.2.3)
84
- actionpack (= 3.2.3)
85
- activerecord (= 3.2.3)
86
- activeresource (= 3.2.3)
87
- activesupport (= 3.2.3)
83
+ rails (3.2.6)
84
+ actionmailer (= 3.2.6)
85
+ actionpack (= 3.2.6)
86
+ activerecord (= 3.2.6)
87
+ activeresource (= 3.2.6)
88
+ activesupport (= 3.2.6)
88
89
  bundler (~> 1.0)
89
- railties (= 3.2.3)
90
- railties (3.2.3)
91
- actionpack (= 3.2.3)
92
- activesupport (= 3.2.3)
90
+ railties (= 3.2.6)
91
+ railties (3.2.6)
92
+ actionpack (= 3.2.6)
93
+ activesupport (= 3.2.6)
93
94
  rack-ssl (~> 1.3.2)
94
95
  rake (>= 0.8.7)
95
96
  rdoc (~> 3.4)
96
- thor (~> 0.14.6)
97
+ thor (>= 0.14.6, < 2.0)
97
98
  rake (0.9.2.2)
98
99
  rcov (1.0.0)
99
100
  rdoc (3.12)
@@ -108,13 +109,14 @@ GEM
108
109
  multi_json (~> 1.0)
109
110
  simplecov-html (~> 0.5.3)
110
111
  simplecov-html (0.5.3)
111
- sprockets (2.1.2)
112
+ sprockets (2.1.3)
112
113
  hike (~> 1.2)
113
114
  rack (~> 1.0)
114
115
  tilt (~> 1.1, != 1.3.0)
115
116
  sqlite3 (1.3.5)
116
- thor (0.14.6)
117
+ thor (0.15.2)
117
118
  tilt (1.3.3)
119
+ timecop (0.3.5)
118
120
  treetop (1.4.10)
119
121
  polyglot
120
122
  polyglot (>= 0.3.1)
@@ -128,15 +130,17 @@ PLATFORMS
128
130
  ruby
129
131
 
130
132
  DEPENDENCIES
131
- activesupport (>= 3.0.7)
133
+ activesupport (>= 3.1.0)
132
134
  bluecloth
133
135
  bundler (>= 1.1.3)
134
136
  capybara (>= 0.4.0)
137
+ database_cleaner
135
138
  devise
136
139
  jeweler (~> 1.6.4)
137
140
  launchy
138
141
  mocha
139
- rails (>= 3.0.7)
142
+ rails (>= 3.1.0)
140
143
  rcov
141
144
  simplecov
142
145
  sqlite3
146
+ timecop
data/README.md CHANGED
@@ -4,7 +4,10 @@ If you want to use this, do so at your own risk. I'm vetting it on some developm
4
4
 
5
5
  ## Opro
6
6
 
7
- A Rails Engine that turns your app into an [Oauth](http://oauth.net/2/) Provider.
7
+ A Rails Engine that turns your app into an [Oauth2](http://oauth.net/2/) Provider.
8
+
9
+ * [Demo OAuth Provider app with Opro](http://opro-demo.herokuapp.com/) on Heroku
10
+ * [Built in Opro docs](http://opro-demo.herokuapp.com/oauth_docs)
8
11
 
9
12
  ## Why would I use this?
10
13
 
@@ -18,58 +21,49 @@ Wouldn't it be great if we could have a token exchange where the user goes to a
18
21
  Gemfile
19
22
 
20
23
  ```ruby
21
- gem 'opro'
24
+ gem 'opro'
22
25
  ```
23
26
 
24
27
  Then run
25
28
 
26
29
  ```shell
27
- $ bundle install
30
+ $ bundle install
28
31
  ```
29
32
 
30
33
  and don't forget
31
34
 
32
35
  ```shell
33
- $ rails g opro:install
36
+ $ rails g opro:install
34
37
  ```
35
38
 
36
- This will put a file in `initializers/opro.rb` and generate some migrations.
39
+ This will put a file in `initializers/opro.rb` and generate some migrations, and add `mount_opro_oauth` to your routes.
37
40
 
38
41
 
39
42
  Now we're ready to migrate the database
40
43
 
41
44
  ```shell
42
- $ rake db:migrate
45
+ $ rake db:migrate
43
46
  ````
44
47
 
45
- This will add `Oauth::AccessGrant` and `Oauth::ClientApplication` to your database
48
+ 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.
46
49
 
47
50
  ## Setup
48
51
 
49
- Go to `initializers/opro.rb` and configure your app for your authentication scheme.
52
+ Go to `initializers/opro.rb` and configure your app for your authentication scheme, if you're not using devise see "Custom Auth" below.
50
53
 
51
54
  ```ruby
52
- Opro.setup do |config|
53
- config.auth_strategy = :devise
54
- end
55
+ Opro.setup do |config|
56
+ config.auth_strategy = :devise
57
+ end
55
58
  ```
56
59
 
57
- If you're not using devise you can manually configure your own auth strategy. In the future I plan on adding more auth strategies, ping me or submit a pull request for your desired authentication scheme.
58
-
59
- ```ruby
60
- Opro.setup do |config|
61
- config.login_method { |controller, current_user| controller.sign_in(current_user, :bypass => true) }
62
- config.logout_method { |controller, current_user| controller.sign_out(current_user) }
63
- config.authenticate_user_method { |controller| controller.authenticate_user! }
64
- end
65
- ```
66
60
 
67
61
  Now in your controllers you can allow OAuth access using the same syntax of the rails `before_filter`
68
62
 
69
63
  ```ruby
70
- class UsersController < ApplicationController
71
- allow_oauth! :only => [:show]
72
- end
64
+ class UsersController < ApplicationController
65
+ allow_oauth! :only => [:show]
66
+ end
73
67
  ```
74
68
 
75
69
 
@@ -77,12 +71,12 @@ You can also disallow OAuth on specific actions. Disallowing will always over-ri
77
71
 
78
72
 
79
73
  ```ruby
80
- class ProductsController < ApplicationController
81
- disallow_oauth! :only => [:create]
82
- end
74
+ class ProductsController < ApplicationController
75
+ disallow_oauth! :only => [:create]
76
+ end
83
77
  ```
84
78
 
85
- By default all OAuth access is blacklisted. To whitelist all access, add `allow_oauth!` to your `ApplicationController` (this is not recommended). The best practice is to add allow or disallow code to each controller.
79
+ By default all OAuth access is blacklisted. To whitelist all access, add `allow_oauth!` to your `ApplicationController` (this is not recommended). The best practice is to add `allow_oauth!` or `disallow_oauth` to each and every controller.
86
80
 
87
81
  That should be all you need to do to get setup, congrats you're now able to authenticate users using OAuth!!
88
82
 
@@ -91,17 +85,52 @@ That should be all you need to do to get setup, congrats you're now able to auth
91
85
 
92
86
  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!
93
87
 
88
+ ## Custom Auth
89
+
90
+ If you're not using devise you can manually configure your own auth strategy. In the future I plan on adding more auth strategies, ping me or submit a pull request for your desired authentication scheme.
91
+
92
+ ```ruby
93
+ Opro.setup do |config|
94
+ config.login_method { |controller, current_user| controller.sign_in(current_user, :bypass => true) }
95
+ config.logout_method { |controller, current_user| controller.sign_out(current_user) }
96
+ config.authenticate_user_method { |controller| controller.authenticate_user! }
97
+ end
98
+ ```
99
+
100
+ ## Permissions
101
+
102
+ When a user auth's with a client they automatically are granting read permission to any action that you `allow_oauth!`. Read only clients are restricted to using GET requests. By default Opro will ask users for write permission on a client by client application. Client apps with `:write` permission can use all HTTP verbs including POST, PATCH, PUT, DESTROY on any url you whitelist using `allow_oauth!`.
103
+
104
+
105
+ ### Custom Permissions
106
+
107
+ To remove write permissions comment out this line in the Opro initializer:
108
+
109
+ config.request_permissions = [:write]
110
+
111
+ You can add custom permissions by adding to the array:
112
+
113
+ config.request_permissions = [:write, :email, :picture, :whatever]
114
+
115
+ You can then restrict access using the custom permissions by calling `require_oauth_permissions` which takes the same arguments as `before_filter`
116
+
117
+ require_oauth_permissions :email, :only => :index
118
+
119
+ You can also skip permissions using `skip_oauth_permissions`. By default permissions will just check to see if a client has the permission, and will allow the action if it is present. If you want to implement custom permission checks you can write custom methods using the pattern `oauth_client_can_#{permission}?` for example if you were restricting the `:email` permission, you would create a method.
120
+
121
+ def oauth_client_can_email?
122
+ # ...
123
+ end
124
+
125
+ The result is expected to be true or false.
94
126
 
95
127
  ## Assumptions
96
128
 
97
129
  * You have a user model and that is what your authenticating
98
130
  * You're using Active::Record
99
131
 
100
- If you submit a _good_ pull request for other adapters, or for generalizing the resource we're authenticating, you'll make me pretty happy.
101
-
102
-
103
132
  ## About
104
133
 
105
- If you have a question file an issue or, find me on the Twitters [@schneems](http://twitter.com/schneems).
134
+ If you have a question file an issue or, find me on the Twitters [@schneems](http://twitter.com/schneems). Another good library for turning your app into an OAuth provider is [Doorkeeper](https://github.com/applicake/doorkeeper), if this project doesn't meet your needs let me know why and use them :)
106
135
 
107
136
  This project rocks and uses MIT-LICENSE.
data/Rakefile CHANGED
@@ -32,6 +32,14 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
32
32
  rdoc.rdoc_files.include('lib/**/*.rb')
33
33
  end
34
34
 
35
+ namespace :opro do
36
+ desc "Install opro in dummy app"
37
+ task :install do
38
+ cd 'test/dummy'
39
+ system 'bundle exec rails g opro:install --force'
40
+ end
41
+ end
42
+
35
43
 
36
44
  require 'jeweler'
37
45
  Jeweler::Tasks.new do |gem|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1.pre1.0.2
1
+ 0.0.2
@@ -1,79 +1,78 @@
1
- class Oauth::AuthController < ApplicationController
2
- before_filter :opro_authenticate_user!, :except => [:access_token]
3
- skip_before_filter :verify_authenticity_token, :only => [:access_token, :user]
4
- before_filter :ask_user!, :only => :authorize
1
+ class Oauth::AuthController < OproController
2
+ before_filter :opro_authenticate_user!
3
+ before_filter :ask_user!, :only => [:create]
4
+
5
5
 
6
6
  def new
7
7
  @redirect_uri = params[:redirect_uri]
8
- @client_app = Oauth::ClientApplication.find_by_app_id(params[:client_id])
8
+ @client_app = Oauth::ClientApp.find_by_app_id(params[:client_id])
9
9
  @scopes = scope_from_params(params)
10
10
  end
11
11
 
12
- def authorize
13
- application = Oauth::ClientApplication.find_by_app_id(params[:client_id])
14
- permissions = params[:permissions]
15
- access_grant = Oauth::AccessGrant.where( :user_id => current_user.id, :application_id => application.id).first
16
- access_grant ||= Oauth::AccessGrant.create(:user => current_user, :application => application)
17
- access_grant.update_attributes(:permissions => permissions) if access_grant.permissions != permissions
18
- redirect_to access_grant.redirect_uri_for(params[:redirect_uri])
19
- end
20
12
 
21
- def access_token
22
- application = Oauth::ClientApplication.authenticate(params[:client_id], params[:client_secret])
23
13
 
24
- if application.nil?
25
- render :json => {:error => "Could not find application"}
26
- return
27
- end
14
+ # :ask_user! is called before creating a new authorization, this allows us to redirect
15
+ def create
16
+ # find or create an auth_grant for a given user
17
+ application = Oauth::ClientApp.find_by_app_id(params[:client_id])
18
+ access_grant = Oauth::AuthGrant.where( :user_id => current_user.id, :application_id => application.id).first
19
+ access_grant ||= Oauth::AuthGrant.create(:user => current_user, :application => application)
28
20
 
29
- access_grant = Oauth::AccessGrant.authenticate(params[:code], application.id)
30
21
 
31
- if access_grant.nil?
32
- render :json => {:error => "Could not authenticate access code"}
33
- return
34
- end
22
+ # add permission changes if there are any
23
+ access_grant.update_attributes(:permissions => params[:permissions]) if access_grant.permissions != params[:permissions]
35
24
 
36
- access_grant.start_expiry_period!
37
- render :json => {:access_token => access_grant.access_token, :refresh_token => access_grant.refresh_token, :expires_in => access_grant.access_token_expires_at}
25
+ redirect_to access_grant.redirect_uri_for(params[:redirect_uri])
38
26
  end
39
27
 
28
+
29
+ private
40
30
  # When a user is sent to authorize an application they must first accept the authorization
41
31
  # if they've already authed the app, they skip this section
42
32
  def ask_user!
43
33
  if user_granted_access_before?(current_user, params)
44
34
  # Re-Authorize the application, do not ask the user
35
+ params.delete(:permissions) ## Delete permissions supplied by client app, this was a security hole
45
36
  return true
46
37
  elsif user_authorizes_the_request?(request)
47
- # Authorize the application, do not ask the user
38
+ # The user just authorized the application from the form
48
39
  return true
49
40
  else
41
+
50
42
  # if the request did not come from a form within the application, render the user form
51
43
  @redirect_uri ||= params[:redirect_uri]
52
- @client_app ||= Oauth::ClientApplication.find_by_app_id(params[:client_id])
44
+ @client_app ||= Oauth::ClientApp.find_by_app_id(params[:client_id])
53
45
  redirect_to oauth_new_path(params)
54
46
  end
55
47
  end
56
48
 
57
- private
58
49
  def user_granted_access_before?(user, params)
59
- @client_app ||= Oauth::ClientApplication.find_by_app_id(params[:client_id])
60
- Oauth::AccessGrant.where(:application_id => @client_app.id, :user_id => user.id).present?
50
+ @client_app ||= Oauth::ClientApp.find_by_app_id(params[:client_id])
51
+ Oauth::AuthGrant.where(:application_id => @client_app.id, :user_id => user.id).present?
61
52
  end
62
53
 
54
+
55
+ # take params[:scope] = [:write, :read, :etc] or
56
+ # take params[:scope] = "write, read, etc"
57
+ # compare against available scopes ::Opro.request_permissions
58
+ # return the intersecting set. or the default scope if n
63
59
  def scope_from_params(params)
64
- requested_scope = (params[:scope]||[]).map(&:downcase)
65
60
  default_scope = ::Opro.request_permissions.map(&:to_s).map(&:downcase)
66
- return default_scope if requested_scope.blank?
67
- requested_scope & default_scope
61
+ return default_scope if params[:scope].blank?
62
+
63
+ scope = params[:scope].is_a?(Array) ? params[:scope] : params[:scope].split(',')
64
+ raise "Params #{params[:scope]} improperly formatted " unless scope.is_a?(Array)
65
+ requested_scope = scope.map(&:downcase).map(&:strip)
66
+ return requested_scope & default_scope
68
67
  end
69
68
 
70
69
 
71
- # We're verifying that a post was made from our own site, indicating a user confirmed via form
70
+ # Verifying that a post was made from our own site, indicating a user confirmed via form
72
71
  def user_authorizes_the_request?(request)
73
72
  request.post? && referrer_is_self?(request)
74
73
  end
75
74
 
76
- # Ensures that the referrer is also the current host, to prevent spoofing
75
+ # Ensures that the referrer is the current host, to prevent spoofing
77
76
  def referrer_is_self?(request)
78
77
  return false if request.referrer.blank?
79
78
  referrer_host = URI.parse(request.referrer).host