anjlab-devise-oauth2-providable 1.1.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.
Files changed (89) hide show
  1. data/.gitignore +40 -0
  2. data/.rvmrc +1 -0
  3. data/CONTRIBUTORS.txt +6 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +158 -0
  7. data/Rakefile +5 -0
  8. data/app/controllers/devise/oauth2_providable/authorizations_controller.rb +58 -0
  9. data/app/controllers/devise/oauth2_providable/tokens_controller.rb +19 -0
  10. data/app/models/devise/oauth2_providable/access_token.rb +24 -0
  11. data/app/models/devise/oauth2_providable/authorization_code.rb +4 -0
  12. data/app/models/devise/oauth2_providable/client.rb +24 -0
  13. data/app/models/devise/oauth2_providable/refresh_token.rb +7 -0
  14. data/app/views/devise/oauth2_providable/authorizations/_form.html.erb +7 -0
  15. data/app/views/devise/oauth2_providable/authorizations/error.html.erb +4 -0
  16. data/app/views/devise/oauth2_providable/authorizations/new.html.erb +4 -0
  17. data/config/routes.rb +9 -0
  18. data/db/migrate/20111014160714_create_devise_oauth2_providable_schema.rb +55 -0
  19. data/devise_oauth2_providable.gemspec +30 -0
  20. data/lib/anjlab-devise-oauth2-providable.rb +1 -0
  21. data/lib/devise/oauth2_providable/engine.rb +16 -0
  22. data/lib/devise/oauth2_providable/expirable_token.rb +57 -0
  23. data/lib/devise/oauth2_providable/models/oauth2_authorization_code_grantable.rb +6 -0
  24. data/lib/devise/oauth2_providable/models/oauth2_password_grantable.rb +6 -0
  25. data/lib/devise/oauth2_providable/models/oauth2_providable.rb +14 -0
  26. data/lib/devise/oauth2_providable/models/oauth2_refresh_token_grantable.rb +6 -0
  27. data/lib/devise/oauth2_providable/strategies/oauth2_authorization_code_grant_type_strategy.rb +21 -0
  28. data/lib/devise/oauth2_providable/strategies/oauth2_grant_type_strategy.rb +38 -0
  29. data/lib/devise/oauth2_providable/strategies/oauth2_password_grant_type_strategy.rb +22 -0
  30. data/lib/devise/oauth2_providable/strategies/oauth2_providable_strategy.rb +25 -0
  31. data/lib/devise/oauth2_providable/strategies/oauth2_refresh_token_grant_type_strategy.rb +22 -0
  32. data/lib/devise/oauth2_providable/version.rb +5 -0
  33. data/lib/devise_oauth2_providable.rb +41 -0
  34. data/script/rails +6 -0
  35. data/spec/controllers/authorizations_controller_spec.rb +32 -0
  36. data/spec/controllers/protected_controller_spec.rb +42 -0
  37. data/spec/dummy/Rakefile +7 -0
  38. data/spec/dummy/app/assets/javascripts/application.js +7 -0
  39. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy/app/controllers/protected_controller.rb +6 -0
  42. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  43. data/spec/dummy/app/mailers/.gitkeep +0 -0
  44. data/spec/dummy/app/models/.gitkeep +0 -0
  45. data/spec/dummy/app/models/user.rb +15 -0
  46. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  47. data/spec/dummy/config.ru +4 -0
  48. data/spec/dummy/config/application.rb +50 -0
  49. data/spec/dummy/config/boot.rb +10 -0
  50. data/spec/dummy/config/database.yml +25 -0
  51. data/spec/dummy/config/environment.rb +5 -0
  52. data/spec/dummy/config/environments/development.rb +30 -0
  53. data/spec/dummy/config/environments/production.rb +60 -0
  54. data/spec/dummy/config/environments/test.rb +39 -0
  55. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  56. data/spec/dummy/config/initializers/devise.rb +216 -0
  57. data/spec/dummy/config/initializers/inflections.rb +10 -0
  58. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  59. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  60. data/spec/dummy/config/initializers/session_store.rb +8 -0
  61. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/spec/dummy/config/locales/devise.en.yml +57 -0
  63. data/spec/dummy/config/locales/en.yml +5 -0
  64. data/spec/dummy/config/routes.rb +7 -0
  65. data/spec/dummy/db/migrate/20120521101005_create_users.rb +5 -0
  66. data/spec/dummy/db/migrate/20120521101006_add_devise_to_users.rb +53 -0
  67. data/spec/dummy/db/migrate/20120521101407_create_devise_oauth2_providable_schema.devise_oauth2_providable.rb +56 -0
  68. data/spec/dummy/db/schema.rb +88 -0
  69. data/spec/dummy/lib/assets/.gitkeep +0 -0
  70. data/spec/dummy/public/404.html +26 -0
  71. data/spec/dummy/public/422.html +26 -0
  72. data/spec/dummy/public/500.html +26 -0
  73. data/spec/dummy/public/favicon.ico +0 -0
  74. data/spec/dummy/script/rails +6 -0
  75. data/spec/factories.rb +19 -0
  76. data/spec/integration/oauth2_authorization_token_grant_type_strategy_spec.rb +136 -0
  77. data/spec/integration/oauth2_password_grant_type_strategy_spec.rb +198 -0
  78. data/spec/integration/oauth2_refresh_token_grant_type_strategy_spec.rb +138 -0
  79. data/spec/lib/devise_oauth2_providable_spec.rb +7 -0
  80. data/spec/models/access_token_spec.rb +51 -0
  81. data/spec/models/authorization_code_spec.rb +21 -0
  82. data/spec/models/client_spec.rb +22 -0
  83. data/spec/models/refresh_token_spec.rb +23 -0
  84. data/spec/models/user_spec.rb +6 -0
  85. data/spec/routing/authorizations_routing_spec.rb +17 -0
  86. data/spec/routing/tokens_routing_spec.rb +11 -0
  87. data/spec/spec_helper.rb +28 -0
  88. data/spec/support/match_json.rb +6 -0
  89. metadata +334 -0
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "devise/oauth2_providable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "anjlab-devise-oauth2-providable"
7
+ s.version = Devise::Oauth2Providable::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ryan Sonnek"]
10
+ s.email = ["ryan@socialcast.com"]
11
+ s.homepage = "https://github.com/anjlab/devise_oauth2_providable"
12
+ s.summary = %q{OAuth2 Provider for Rails3 applications with Devise 2.1.0}
13
+ s.description = %q{Rails3 engine that adds OAuth2 Provider support to any application built with Devise authentication}
14
+
15
+ s.add_runtime_dependency "rails", ">= 3.2.0"
16
+ s.add_runtime_dependency "devise", ">= 2.1.0"
17
+ s.add_runtime_dependency "rack-oauth2", "~> 0.14.4"
18
+
19
+ s.add_development_dependency "rspec-rails", '>= 2.6.2'
20
+ s.add_development_dependency "sqlite3"
21
+ s.add_development_dependency "shoulda-matchers", ">=1.0.0"
22
+ s.add_development_dependency "pry-rails"
23
+ s.add_development_dependency "factory_girl_rspec", '>= 0.0.1'
24
+ s.add_development_dependency "rake", '>= 0.9.2.2'
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+ end
@@ -0,0 +1 @@
1
+ require 'devise_oauth2_providable'
@@ -0,0 +1,16 @@
1
+ module Devise
2
+ module Oauth2Providable
3
+ class Engine < Rails::Engine
4
+ config.devise_oauth2_providable = ActiveSupport::OrderedOptions.new
5
+ config.devise_oauth2_providable.access_token_expires_in = 15.minutes
6
+ config.devise_oauth2_providable.refresh_token_expires_in = 1.month
7
+ config.devise_oauth2_providable.authorization_code_expires_in = 1.minute
8
+
9
+ engine_name 'oauth2'
10
+ isolate_namespace Devise::Oauth2Providable
11
+ initializer "devise_oauth2_providable.initialize_application", :before=> :load_config_initializers do |app|
12
+ app.config.filter_parameters << :client_secret
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ require 'active_support/concern'
2
+ require 'active_record'
3
+
4
+ module Devise
5
+ module Oauth2Providable
6
+ module ExpirableToken
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def expires_according_to(config_name)
11
+ cattr_accessor :default_lifetime
12
+ self.default_lifetime = Rails.application.config.devise_oauth2_providable[config_name]
13
+
14
+ belongs_to :user
15
+ belongs_to :client
16
+
17
+ after_initialize :init_token, :on => :create, :unless => :token?
18
+ after_initialize :init_expires_at, :on => :create, :unless => :expires_at?
19
+ validates :expires_at, :presence => true
20
+ validates :client, :presence => true
21
+ validates :token, :presence => true, :uniqueness => true
22
+
23
+ default_scope lambda {
24
+ where(self.arel_table[:expires_at].gteq(Time.now.utc))
25
+ }
26
+
27
+ include LocalInstanceMethods
28
+ end
29
+ end
30
+
31
+ module LocalInstanceMethods
32
+ # number of seconds until the token expires
33
+ def expires_in
34
+ (expires_at - Time.now.utc).to_i
35
+ end
36
+
37
+ # forcefully expire the token
38
+ def expired!
39
+ self.expires_at = Time.now.utc
40
+ self.save!
41
+ end
42
+
43
+ private
44
+
45
+ def init_token
46
+ self.token = Devise::Oauth2Providable.random_id
47
+ end
48
+ def init_expires_at
49
+ self.expires_at = self.default_lifetime.from_now
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ ActiveRecord::Base.send :include, Devise::Oauth2Providable::ExpirableToken
57
+
@@ -0,0 +1,6 @@
1
+ module Devise
2
+ module Models
3
+ module Oauth2AuthorizationCodeGrantable
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Devise
2
+ module Models
3
+ module Oauth2PasswordGrantable
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'devise/models'
2
+
3
+ module Devise
4
+ module Models
5
+ module Oauth2Providable
6
+ extend ActiveSupport::Concern
7
+ included do
8
+ has_many :access_tokens, class_name: Devise::Oauth2Providable::AccessToken.name
9
+ has_many :authorization_codes, class_name: Devise::Oauth2Providable::AuthorizationCode.name
10
+ has_many :clients, class_name: Devise::Oauth2Providable::Client.name
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ module Devise
2
+ module Models
3
+ module Oauth2RefreshTokenGrantable
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ require 'devise/oauth2_providable/strategies/oauth2_grant_type_strategy'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Oauth2AuthorizationCodeGrantTypeStrategy < Oauth2GrantTypeStrategy
6
+ def grant_type
7
+ 'authorization_code'
8
+ end
9
+
10
+ def authenticate_grant_type(client)
11
+ if code = client.authorization_codes.find_by_token(params[:code])
12
+ success! code.user
13
+ else
14
+ oauth_error! :invalid_grant, 'invalid authorization code request'
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Warden::Strategies.add(:oauth2_authorization_code_grantable, Devise::Strategies::Oauth2AuthorizationCodeGrantTypeStrategy)
@@ -0,0 +1,38 @@
1
+ require 'devise/strategies/base'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Oauth2GrantTypeStrategy < Authenticatable
6
+ def valid?
7
+ params[:controller] == 'devise/oauth2_providable/tokens' && request.post? && params[:grant_type] == grant_type
8
+ end
9
+
10
+ # defined by subclass
11
+ def grant_type
12
+ end
13
+
14
+ # defined by subclass
15
+ def authenticate_grant_type(client)
16
+ end
17
+
18
+ def authenticate!
19
+ client_id, client_secret = request.authorization ? decode_credentials : [params[:client_id], params[:client_secret]]
20
+ client = Devise::Oauth2Providable::Client.find_by_identifier client_id
21
+ if client && client.secret == client_secret
22
+ env[Devise::Oauth2Providable::CLIENT_ENV_REF] = client
23
+ authenticate_grant_type(client)
24
+ else
25
+ oauth_error! :invalid_client, 'invalid client credentials'
26
+ end
27
+ end
28
+
29
+ # return custom error response in accordance with the oauth spec
30
+ # see http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-4.3
31
+ def oauth_error!(error_code = :invalid_request, description = nil)
32
+ body = {:error => error_code}
33
+ body[:error_description] = description if description
34
+ custom! [400, {'Content-Type' => 'application/json'}, [body.to_json]]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ require 'devise/oauth2_providable/strategies/oauth2_grant_type_strategy'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Oauth2PasswordGrantTypeStrategy < Oauth2GrantTypeStrategy
6
+ def grant_type
7
+ 'password'
8
+ end
9
+
10
+ def authenticate_grant_type(client)
11
+ resource = mapping.to.find_for_authentication(mapping.to.authentication_keys.first => params[:username])
12
+ if resource && validate(resource) { resource.valid_password?(params[:password]) }
13
+ success! resource
14
+ else
15
+ oauth_error! :invalid_grant, 'invalid password authentication request'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Warden::Strategies.add(:oauth2_password_grantable, Devise::Strategies::Oauth2PasswordGrantTypeStrategy)
@@ -0,0 +1,25 @@
1
+ require 'devise/strategies/base'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Oauth2Providable < Authenticatable
6
+ def valid?
7
+ @req = Rack::OAuth2::Server::Resource::Bearer::Request.new(env)
8
+ @req.oauth2?
9
+ end
10
+ def authenticate!
11
+ @req.setup!
12
+ token = Devise::Oauth2Providable::AccessToken.find_by_token @req.access_token
13
+ env[Devise::Oauth2Providable::CLIENT_ENV_REF] = token.client if token
14
+ resource = token ? token.user : nil
15
+ if resource && validate(resource)
16
+ success! resource
17
+ else
18
+ fail(:invalid_token)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Warden::Strategies.add(:oauth2_providable, Devise::Strategies::Oauth2Providable)
@@ -0,0 +1,22 @@
1
+ require 'devise/oauth2_providable/strategies/oauth2_grant_type_strategy'
2
+
3
+ module Devise
4
+ module Strategies
5
+ class Oauth2RefreshTokenGrantTypeStrategy < Oauth2GrantTypeStrategy
6
+ def grant_type
7
+ 'refresh_token'
8
+ end
9
+
10
+ def authenticate_grant_type(client)
11
+ if refresh_token = client.refresh_tokens.find_by_token(params[:refresh_token])
12
+ env[Devise::Oauth2Providable::REFRESH_TOKEN_ENV_REF] = refresh_token
13
+ success! refresh_token.user
14
+ else
15
+ oauth_error! :invalid_grant, 'invalid refresh token'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Warden::Strategies.add(:oauth2_refresh_token_grantable, Devise::Strategies::Oauth2RefreshTokenGrantTypeStrategy)
@@ -0,0 +1,5 @@
1
+ module Devise
2
+ module Oauth2Providable
3
+ VERSION = "1.1.0"
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ require 'devise'
2
+ require 'rack/oauth2'
3
+ require 'devise/oauth2_providable/engine'
4
+ require 'devise/oauth2_providable/expirable_token'
5
+ require 'devise/oauth2_providable/strategies/oauth2_providable_strategy'
6
+ require 'devise/oauth2_providable/strategies/oauth2_password_grant_type_strategy'
7
+ require 'devise/oauth2_providable/strategies/oauth2_refresh_token_grant_type_strategy'
8
+ require 'devise/oauth2_providable/strategies/oauth2_authorization_code_grant_type_strategy'
9
+ require 'devise/oauth2_providable/models/oauth2_providable'
10
+ require 'devise/oauth2_providable/models/oauth2_password_grantable'
11
+ require 'devise/oauth2_providable/models/oauth2_refresh_token_grantable'
12
+ require 'devise/oauth2_providable/models/oauth2_authorization_code_grantable'
13
+
14
+ module Devise
15
+ module Oauth2Providable
16
+ CLIENT_ENV_REF = 'oauth2.client'
17
+ REFRESH_TOKEN_ENV_REF = "oauth2.refresh_token"
18
+
19
+ class << self
20
+ def random_id
21
+ SecureRandom.hex
22
+ end
23
+ def table_name_prefix
24
+ 'oauth2_'
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ Devise.add_module(:oauth2_providable,
31
+ :strategy => true,
32
+ :model => 'devise/oauth2_providable/models/oauth2_providable')
33
+ Devise.add_module(:oauth2_password_grantable,
34
+ :strategy => true,
35
+ :model => 'devise/oauth2_providable/models/oauth2_password_grantable')
36
+ Devise.add_module(:oauth2_refresh_token_grantable,
37
+ :strategy => true,
38
+ :model => 'devise/oauth2_providable/models/oauth2_refresh_token_grantable')
39
+ Devise.add_module(:oauth2_authorization_code_grantable,
40
+ :strategy => true,
41
+ :model => 'devise/oauth2_providable/models/oauth2_authorization_code_grantable')
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ #!/usr/bin/env ruby
3
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
4
+
5
+ ENGINE_PATH = File.expand_path('../..', __FILE__)
6
+ load File.expand_path('../../spec/dummy/script/rails', __FILE__)
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Devise::Oauth2Providable::AuthorizationsController do
4
+ describe 'GET #new' do
5
+ context 'with valid redirect_uri' do
6
+ let(:user) { create(:user) }
7
+ let(:client) { create(:client) }
8
+ let(:redirect_uri) { client.redirect_uri }
9
+ before do
10
+ sign_in user
11
+ get :new, :client_id => client.identifier, :redirect_uri => redirect_uri, :response_type => 'code', :use_route => 'devise_oauth2_providable'
12
+ end
13
+ it { should respond_with :ok }
14
+ it { should respond_with_content_type :html }
15
+ it { should assign_to(:redirect_uri).with(redirect_uri) }
16
+ it { should assign_to(:response_type) }
17
+ it { should render_template 'devise/oauth2_providable/authorizations/new' }
18
+ # it { should render_with_layout 'application' }
19
+ end
20
+ context 'with invalid redirect_uri' do
21
+ let(:user) { create(:user) }
22
+ let(:client) { create(:client) }
23
+ let(:redirect_uri) { 'http://example.com/foo/bar' }
24
+ before do
25
+ sign_in user
26
+ get :new, :client_id => client.identifier, :redirect_uri => redirect_uri, :response_type => 'code', :use_route => 'devise_oauth2_providable'
27
+ end
28
+ it { should respond_with :bad_request }
29
+ it { should respond_with_content_type :html }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProtectedController do
4
+
5
+ describe 'get :index' do
6
+ let(:user) { create(:user) }
7
+ let(:client) { create(:client) }
8
+ before do
9
+ @token = Devise::Oauth2Providable::AccessToken.create! :client => client, :user => user
10
+ end
11
+ context 'with valid bearer token in header' do
12
+ before do
13
+ @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token.token}"
14
+ get :index, :format => 'json'
15
+ end
16
+ it { should respond_with :ok }
17
+ end
18
+ context 'with valid bearer token in query string' do
19
+ before do
20
+ get :index, :access_token => @token.token, :format => 'json'
21
+ end
22
+ it { should respond_with :ok }
23
+ end
24
+
25
+ context 'with invalid bearer token in query param' do
26
+ before do
27
+ get :index, :access_token => 'invalid', :format => 'json'
28
+ end
29
+ it { should respond_with :unauthorized }
30
+ end
31
+ context 'with valid bearer token in header and query string' do
32
+ before do
33
+ end
34
+ it 'raises error' do
35
+ lambda {
36
+ @request.env['HTTP_AUTHORIZATION'] = "Bearer #{@token.token}"
37
+ get :index, :access_token => @token.token, :format => 'json'
38
+ }.should raise_error
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
3
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4
+
5
+ require File.expand_path('../config/application', __FILE__)
6
+
7
+ Dummy::Application.load_tasks
@@ -0,0 +1,7 @@
1
+ // This is a manifest file that'll be compiled into including all the files listed below.
2
+ // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ // be included in the compiled file accessible from http://example.com/assets/application.js
4
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ // the compiled file.
6
+ //
7
+ //= require_tree .
@@ -0,0 +1,7 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_self
6
+ *= require_tree .
7
+ */