opro 0.0.1 → 0.0.2

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.
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
@@ -0,0 +1,24 @@
1
+ class Oauth::ClientAppController < OproController
2
+ before_filter :opro_authenticate_user!
3
+
4
+ def new
5
+ @client_app = Oauth::ClientApp.new
6
+ end
7
+
8
+ # Show all client applications belonging to the current user
9
+ def index
10
+ @client_apps = Oauth::ClientApp.where(:user_id => current_user.id)
11
+ end
12
+
13
+
14
+ def create
15
+ @client_app = Oauth::ClientApp.find_by_user_id_and_name(current_user.id, params[:oauth_client_app][:name])
16
+ @client_app ||= Oauth::ClientApp.create_with_user_and_name(current_user, params[:oauth_client_app][:name])
17
+ if @client_app.save
18
+ # do nothing
19
+ else
20
+ render :new
21
+ end
22
+ end
23
+
24
+ end
@@ -4,7 +4,7 @@ require 'bluecloth'
4
4
  OPRO_MD_ROOT=File.join(File.dirname(__FILE__), '../../views/oauth/docs/markdown/')
5
5
 
6
6
 
7
- class Oauth::DocsController < ApplicationController
7
+ class Oauth::DocsController < OproController
8
8
  helper_method :render_doc
9
9
 
10
10
  def index
@@ -1,4 +1,4 @@
1
- class Oauth::TestsController < ApplicationController
1
+ class Oauth::TestsController < OproController
2
2
  allow_oauth!
3
3
  disallow_oauth! :only => [:destroy]
4
4
 
@@ -10,7 +10,7 @@ class Oauth::TestsController < ApplicationController
10
10
  result = if valid_oauth?
11
11
  {:status => 200, :message => 'OAuth Worked!!', :params => params, :user_id => oauth_user.id }
12
12
  else
13
- {:status => 401, :message => "OAuth Did not Work :( #{generate_oauth_error_message!}", :params => params}
13
+ {:status => :unauthorized, :message => "OAuth Did not Work :( #{generate_oauth_error_message!}", :params => params}
14
14
  end
15
15
 
16
16
  respond_to do |format|
@@ -27,7 +27,7 @@ class Oauth::TestsController < ApplicationController
27
27
  result = if valid_oauth?
28
28
  {:status => 200, :message => 'OAuth Worked!!', :params => params, :user_id => oauth_user.id }
29
29
  else
30
- {:status => 401, :message => "OAuth Did not Work D: #{generate_oauth_error_message!}", :params => params}
30
+ {:status => :unauthorized, :message => "OAuth Did not Work D: #{generate_oauth_error_message!}", :params => params}
31
31
  end
32
32
 
33
33
  respond_to do |format|
@@ -44,7 +44,7 @@ class Oauth::TestsController < ApplicationController
44
44
  result = if valid_oauth?
45
45
  {:status => 200, :message => 'OHNO!!! OAuth is Disabled on this Action, this is bad', :params => params}
46
46
  else
47
- {:status => 401, :message => "Oauth is Disabled on this Action, this is the correct result!", :params => params}
47
+ {:status => :unauthorized, :message => "Oauth is Disabled on this Action, this is the correct result!", :params => params}
48
48
  end
49
49
 
50
50
  respond_to do |format|
@@ -56,26 +56,4 @@ class Oauth::TestsController < ApplicationController
56
56
  end
57
57
  end
58
58
  end
59
-
60
- private
61
-
62
- def generate_oauth_error_message!
63
- msg = ""
64
- msg << ' - No OAuth Token Provided!' if params[:access_token].blank?
65
- msg << ' - Allow OAuth set to false!' if allow_oauth? == false
66
- msg << ' - OAuth user not found!' if oauth_user.blank?
67
- generate_oauth_permissions_error_message!(msg)
68
- msg
69
- end
70
-
71
- def generate_oauth_permissions_error_message!(msg = '')
72
- if !oauth_client_has_permissions?
73
- msg << ' - OAuth client not permitted'
74
- oauth_required_permissions.each do |permission|
75
- msg << "- #{permission} permission required;" unless oauth_client_has_permission?(permission)
76
- end
77
- end
78
- msg
79
- end
80
-
81
59
  end
@@ -0,0 +1,40 @@
1
+ # This controller is where clients can exchange
2
+ # codes and refresh_tokens for access_tokens
3
+
4
+ class Oauth::TokenController < OproController
5
+ before_filter :opro_authenticate_user!, :except => [:create]
6
+ skip_before_filter :verify_authenticity_token, :only => [:create]
7
+
8
+
9
+ def create
10
+ # Find the client application
11
+ application = Oauth::ClientApp.authenticate(params[:client_id], params[:client_secret])
12
+
13
+ if application.nil?
14
+ render :json => {:error => "Could not find application based on client_id=#{params[:client_id]}
15
+ and client_secret=#{params[:client_secret]}"}, :status => :unauthorized
16
+ return
17
+ end
18
+
19
+
20
+ if params[:code]
21
+ auth_grant = Oauth::AuthGrant.authenticate(params[:code], application.id)
22
+ else
23
+ auth_grant = Oauth::AuthGrant.refresh_tokens!(params[:refresh_token], application.id)
24
+ end
25
+
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]
30
+ render :json => {:error => msg }, :status => :unauthorized
31
+ return
32
+ end
33
+
34
+ auth_grant.generate_expires_at!
35
+ render :json => { :access_token => auth_grant.access_token,
36
+ :refresh_token => auth_grant.refresh_token,
37
+ :expires_in => auth_grant.expires_in }
38
+ end
39
+
40
+ end
@@ -0,0 +1,4 @@
1
+ class OproController < ApplicationController
2
+
3
+
4
+ end
@@ -1,14 +1,14 @@
1
- class Oauth::AccessGrant < ActiveRecord::Base
1
+ class Oauth::AuthGrant < ActiveRecord::Base
2
2
 
3
3
  self.table_name = :opro_access_grants
4
4
 
5
5
  belongs_to :user
6
- belongs_to :client_application, :class_name => "Oauth::ClientApplication"
7
- belongs_to :application, :class_name => "Oauth::ClientApplication"
6
+ belongs_to :client_application, :class_name => "Oauth::ClientApp"
7
+ belongs_to :application, :class_name => "Oauth::ClientApp"
8
8
 
9
- validates :application_id, :uniqueness => {:scope => :user_id, :message => "Applicaiton is already authed for this user"}, :presence => true
9
+ validates :application_id, :uniqueness => {:scope => :user_id, :message => "Application is already authed for this user"}, :presence => true
10
10
 
11
- before_create :generate_tokens
11
+ before_create :generate_tokens!, :generate_expires_at!
12
12
 
13
13
  alias_attribute :token, :access_token
14
14
 
@@ -19,8 +19,27 @@ class Oauth::AccessGrant < ActiveRecord::Base
19
19
  end
20
20
 
21
21
  def self.prune!
22
- # UPDATEME
23
- # delete_all(["created_at < ?", 3.days.ago])
22
+ return false unless ::Opro.require_refresh_within.present?
23
+
24
+ time_to_expire = ::Opro.require_refresh_within.ago.in_time_zone
25
+ find_each(:conditions => ["created_at < ?", time_to_expire]) do |grant|
26
+ # grant.
27
+ end
28
+ end
29
+
30
+ def expired?
31
+ return false unless ::Opro.require_refresh_within.present?
32
+ return expires_in < 0
33
+ end
34
+
35
+ def not_expired?
36
+ !expired?
37
+ end
38
+
39
+ def expires_in
40
+ return false unless access_token_expires_at.present?
41
+ time = access_token_expires_at - Time.now
42
+ time.to_i
24
43
  end
25
44
 
26
45
  def self.find_for_token(token)
@@ -32,10 +51,29 @@ class Oauth::AccessGrant < ActiveRecord::Base
32
51
  end
33
52
 
34
53
  def self.authenticate(code, application_id)
35
- self.where("code = ? AND application_id = ?", code, application_id).first
54
+ auth_grant = self.where("code = ? AND application_id = ?", code, application_id).first
36
55
  end
37
56
 
38
- def generate_tokens
57
+ def self.refresh_tokens!(refresh_token, application_id)
58
+ auth_grant = self.where("refresh_token = ? AND application_id = ?", refresh_token, application_id).first
59
+ if auth_grant.present?
60
+ auth_grant.generate_tokens!
61
+ auth_grant.generate_expires_at!
62
+ auth_grant.save!
63
+ end
64
+ auth_grant
65
+ end
66
+
67
+ def generate_expires_at!
68
+ if ::Opro.require_refresh_within.present?
69
+ self.access_token_expires_at = Time.now + ::Opro.require_refresh_within
70
+ else
71
+ self.access_token_expires_at = nil
72
+ end
73
+ true
74
+ end
75
+
76
+ def generate_tokens!
39
77
  self.code, self.access_token, self.refresh_token = SecureRandom.hex(16), SecureRandom.hex(16), SecureRandom.hex(16)
40
78
  end
41
79
 
@@ -46,9 +84,4 @@ class Oauth::AccessGrant < ActiveRecord::Base
46
84
  redirect_uri + "?code=#{code}&response_type=code"
47
85
  end
48
86
  end
49
-
50
- def start_expiry_period!
51
- # UPDATEME
52
- # self.update_attribute(:access_token_expires_at, 2.days.from_now)
53
- end
54
87
  end
@@ -1,4 +1,4 @@
1
- class Oauth::ClientApplication < ActiveRecord::Base
1
+ class Oauth::ClientApp < ActiveRecord::Base
2
2
  self.table_name = :opro_client_applications
3
3
 
4
4
  belongs_to :user
@@ -10,6 +10,6 @@
10
10
  <p>
11
11
  Read the
12
12
  <%= link_to 'Quick Start Documentation', oauth_doc_path(:quick_start) %> or
13
- <%= link_to 'Register Another Oauth Client App', new_oauth_client_application_path %>
13
+ <%= link_to 'Register Another Oauth Client App', new_oauth_client_app_path %>
14
14
  </p>
15
15
 
@@ -14,5 +14,5 @@
14
14
 
15
15
 
16
16
  <div>
17
- <%= link_to 'Register an New App', new_oauth_client_application_path %>
17
+ <%= link_to 'Register an New App', new_oauth_client_app_path %>
18
18
  </div>
@@ -10,5 +10,5 @@
10
10
 
11
11
 
12
12
  <div>
13
- <%= link_to 'My Aplications', oauth_client_applications_path %>
13
+ <%= link_to 'My Aplications', oauth_client_apps_path %>
14
14
  </div>
@@ -9,6 +9,14 @@
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
+ <% if ::Opro.request_permissions.present? %>
13
+ <li><%= link_to 'Permisions', oauth_doc_path(:permissions) %></li>
14
+ <% end %>
15
+ <% if ::Opro.require_refresh_within.blank? %>
16
+ <li><%= link_to 'Refresh Tokens', oauth_doc_path(:refresh_tokens) %></li>
17
+ <% end %>
18
+
19
+
12
20
  </ul>
13
21
 
14
22
  <%= render_doc(:quick_start) %>
@@ -1,6 +1,6 @@
1
1
  ## Opro Oauth
2
2
 
3
- OAuth comes in a few different flavors, the implementation of OAuth comes from <%= link_to "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 [Facebook's Server Side OAuth Authentication](http://developers.facebook.com/docs/authentication/server-side/).
4
4
 
5
5
 
6
6
  ## What is It?
@@ -23,6 +23,6 @@ OAuth is simple in concept, but can be tricky to implement right. Many services
23
23
 
24
24
  This website is an OAuth Provider, and you can create an OAuth client to access this website as a logged in user for select url's.
25
25
 
26
- To get started getting your first OAuth token follow the <%= view_context.link_to 'quick start guide', oauth_doc_path(:quick_start) %>.
26
+ To get started getting your first OAuth token follow the <%= view_context.link_to 'quick start guide', oauth_doc_path(:quick_start) %>.
27
27
 
28
28
 
@@ -0,0 +1,30 @@
1
+ <%= "# Individual Permissions are not implemented in this application" if ::Opro.request_permissions.blank? %>
2
+
3
+
4
+ # Permissions
5
+
6
+ Permissions give the user a way to control the scope of access that a client application has to their data. This is similar to how Facebook will ask users what actions an app can take on their part.
7
+
8
+
9
+ <% if ::Opro.request_permissions.present? && ::Opro.request_permissions.map(&:to_s).include?("write") %>
10
+
11
+ ## Write Access
12
+
13
+ To perform any type of request other than a GET ([HTTP Request methods](http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods)), your application must have `write` access to the user. You can specify this scope (see Request Scope below) or by default all permissions will be requested.
14
+
15
+ <% end %>
16
+
17
+ ## Request Scope
18
+
19
+ As a client app you can request specific scopes while you are authing a user, if no scope is specified all permissions will be requested.
20
+
21
+ <%= "#{request.base_url}/oauth/authorize?" %>scope[]=write&#... %>
22
+
23
+ While authorizing your app a user can choose to accept or deny individual permissions.
24
+
25
+
26
+ ## Available Permissions/Scopes
27
+
28
+ These are the available permissions you can request as a client application. If no scope is provided, all permissions will be requested.
29
+
30
+ <%= ::Opro.request_permissions.inspect %>
@@ -40,7 +40,7 @@ We'll be using [Curl](<%= oauth_doc_path(:curl) %>) to go through the process of
40
40
  (Note in all code examples the $ character indicates we are on the command line, it does not need to be coppied)
41
41
 
42
42
 
43
- $ curl '<%= "#{request.base_url}/oauth/access_token?" %>?client_id=3234myClientId5678&client_secret=14321myClientSecret8765&code=4857goldfish827423'
43
+ $ curl '<%= "#{request.base_url}/oauth/token?" %>?client_id=3234myClientId5678&client_secret=14321myClientSecret8765&code=4857goldfish827423'
44
44
 
45
45
  You'll want to make sure to replace `client_id`, `client_secret`, and `code` with your values.
46
46
 
@@ -0,0 +1,18 @@
1
+ <%= "# Refresh Tokens are not required in this application" if ::Opro.require_refresh_within.blank? %>
2
+
3
+ # Refresh Tokens
4
+
5
+ Refresh tokens can be used to refresh an expiring `access_token`. When you receive your initial `access_token` you will receive a `request_token` in the json as well as an `expires_in` value. The `expires_in` value marks the seconds that the current `access_token` is still valid. After this value reaches zero a client application will not be able to use the existing `access_token`.
6
+
7
+ ## Refreshing an Access Token
8
+
9
+ If a token has expired or you simply wish to receive a new `access_token` you can send your `refresh_token` along with your `app_id` and `app_secret` to `/oauth/token` and your access_token will be refreshed.
10
+
11
+
12
+ $ curl '<%= "#{request.base_url}/oauth/token?" %>?client_id=3234myClientId5678&client_secret=14321myClientSecret8765&refresh_token=4857goldfish827423'
13
+
14
+ You'll want to make sure to replace `client_id`, `client_secret`, and `refresh_token` with your values.
15
+
16
+ You should get back a response that looks like this
17
+
18
+ $ {"access_token":"9693accessTokena7ca570bbaf","refresh_token":"3a3c129ad02b573de78e65af06c293f1","expires_in":7300 }
data/config/routes.rb CHANGED
@@ -1,10 +1,5 @@
1
- Rails.application.routes.draw do
2
-
3
- match 'oauth/new' => 'oauth/auth#new', :as => 'oauth_new'
4
- match '/oauth/authorize' => 'oauth/auth#authorize', :as => 'oauth_authorize'
5
- match '/oauth/access_token' => 'oauth/auth#access_token', :as => 'oauth_token'
6
-
7
- resources :oauth_docs, :controller => 'oauth/docs'
8
- resources :oauth_tests, :controller => 'oauth/tests'
9
- resources :oauth_client_applications, :controller => 'oauth/client_application'
10
- end
1
+ # look in lib/opro/rails/routes.rb
2
+ # they get added to a users config/routes.rb when the user runs
3
+ # rails g opro:install
4
+ # this functionality is added in `add_opro_routes` of
5
+ # lib/generators/opro/install_generator.rb
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  include Rails::Generators::Migration
8
8
  source_root File.expand_path('../templates', __FILE__)
9
9
 
10
- desc "add the migrations"
10
+ desc "add the migrations needed for opro oauth"
11
11
 
12
12
  def self.next_migration_number(path)
13
13
  unless @prev_migration_nr
@@ -1,4 +1,4 @@
1
- class CreateOproAccessGrants < ActiveRecord::Migration
1
+ class CreateOproAuthGrants < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :opro_access_grants do |t|
4
4
  t.string :code
@@ -1,4 +1,4 @@
1
- class CreateOproClientApplications < ActiveRecord::Migration
1
+ class CreateOproClientApps < ActiveRecord::Migration
2
2
  def change
3
3
  create_table :opro_client_applications do |t|
4
4
  t.string :name
@@ -15,6 +15,11 @@ module Opro
15
15
  def run_other_generators
16
16
  generate "active_record:opro"
17
17
  end
18
+
19
+ def add_opro_routes
20
+ opro_routes = "mount_opro_oauth"
21
+ route opro_routes
22
+ end
18
23
  end
19
24
  end
20
25
  end
@@ -3,8 +3,15 @@ Opro.setup do |config|
3
3
  config.auth_strategy = :devise
4
4
 
5
5
  ## Add or remove application permissions
6
- # Read permission is turned on by default (any request with [GET])
7
- # Write permission is requestable by default (any request other than [GET])
8
- # Custom permissions can be configured by adding them to the request_permissions Array and configuring require_oauth_permissions in the controller
6
+ # Read permission (any request with [GET]) is turned on by default
7
+ # Write permission (any request other than [GET]) is requestable by default
8
+ # Custom permissions can be configured by adding them to `config.request_permissions`
9
+ # You can then require that permission on individual actions by calling
10
+ # `require_oauth_permissions` in the controller
9
11
  config.request_permissions = [:write]
12
+
13
+ ## Refresh Token config
14
+ # uncomment `config.require_refresh_within` to require refresh tokens
15
+ # this will expire tokens within the given time duration
16
+ # config.require_refresh_within = 1.month
10
17
  end
data/lib/opro.rb CHANGED
@@ -25,7 +25,6 @@ module Opro
25
25
  authenticate_user_method { |controller| controller.authenticate_user! }
26
26
  else
27
27
  # nothing
28
- # TODO, be smart here, if they have devise gem in Gemfile and haven't specified auth_strategy use devise
29
28
  end
30
29
  end
31
30
 
@@ -71,6 +70,13 @@ module Opro
71
70
  @request_permissions || []
72
71
  end
73
72
 
73
+ def self.require_refresh_within=(require_refresh_within)
74
+ @require_refresh_within = require_refresh_within
75
+ end
76
+
77
+ def self.require_refresh_within
78
+ @require_refresh_within
79
+ end
74
80
 
75
81
  def self.logout_method(&block)
76
82
  if block.present?