devise-edge 1.2.rc
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.rdoc +500 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +335 -0
- data/app/controllers/devise/confirmations_controller.rb +33 -0
- data/app/controllers/devise/oauth_callbacks_controller.rb +4 -0
- data/app/controllers/devise/passwords_controller.rb +41 -0
- data/app/controllers/devise/registrations_controller.rb +75 -0
- data/app/controllers/devise/sessions_controller.rb +23 -0
- data/app/controllers/devise/unlocks_controller.rb +34 -0
- data/app/helpers/devise_helper.rb +17 -0
- data/app/mailers/devise/mailer.rb +88 -0
- data/app/views/devise/confirmations/new.html.erb +12 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +16 -0
- data/app/views/devise/passwords/new.html.erb +12 -0
- data/app/views/devise/registrations/edit.html.erb +25 -0
- data/app/views/devise/registrations/new.html.erb +18 -0
- data/app/views/devise/sessions/new.html.erb +17 -0
- data/app/views/devise/shared/_links.erb +25 -0
- data/app/views/devise/unlocks/new.html.erb +12 -0
- data/config/locales/en.yml +42 -0
- data/lib/devise.rb +371 -0
- data/lib/devise/controllers/helpers.rb +261 -0
- data/lib/devise/controllers/internal_helpers.rb +113 -0
- data/lib/devise/controllers/scoped_views.rb +33 -0
- data/lib/devise/controllers/url_helpers.rb +39 -0
- data/lib/devise/encryptors/authlogic_sha512.rb +19 -0
- data/lib/devise/encryptors/base.rb +20 -0
- data/lib/devise/encryptors/clearance_sha1.rb +17 -0
- data/lib/devise/encryptors/restful_authentication_sha1.rb +22 -0
- data/lib/devise/encryptors/sha1.rb +25 -0
- data/lib/devise/encryptors/sha512.rb +25 -0
- data/lib/devise/failure_app.rb +126 -0
- data/lib/devise/hooks/activatable.rb +11 -0
- data/lib/devise/hooks/forgetable.rb +12 -0
- data/lib/devise/hooks/rememberable.rb +45 -0
- data/lib/devise/hooks/timeoutable.rb +22 -0
- data/lib/devise/hooks/trackable.rb +9 -0
- data/lib/devise/mapping.rb +105 -0
- data/lib/devise/models.rb +66 -0
- data/lib/devise/models/authenticatable.rb +143 -0
- data/lib/devise/models/confirmable.rb +160 -0
- data/lib/devise/models/database_authenticatable.rb +94 -0
- data/lib/devise/models/encryptable.rb +65 -0
- data/lib/devise/models/lockable.rb +168 -0
- data/lib/devise/models/oauthable.rb +49 -0
- data/lib/devise/models/recoverable.rb +83 -0
- data/lib/devise/models/registerable.rb +21 -0
- data/lib/devise/models/rememberable.rb +122 -0
- data/lib/devise/models/timeoutable.rb +33 -0
- data/lib/devise/models/token_authenticatable.rb +72 -0
- data/lib/devise/models/trackable.rb +30 -0
- data/lib/devise/models/validatable.rb +60 -0
- data/lib/devise/modules.rb +30 -0
- data/lib/devise/oauth.rb +41 -0
- data/lib/devise/oauth/config.rb +33 -0
- data/lib/devise/oauth/helpers.rb +18 -0
- data/lib/devise/oauth/internal_helpers.rb +182 -0
- data/lib/devise/oauth/test_helpers.rb +29 -0
- data/lib/devise/oauth/url_helpers.rb +35 -0
- data/lib/devise/orm/active_record.rb +36 -0
- data/lib/devise/orm/mongo_mapper.rb +46 -0
- data/lib/devise/orm/mongoid.rb +29 -0
- data/lib/devise/path_checker.rb +18 -0
- data/lib/devise/rails.rb +67 -0
- data/lib/devise/rails/routes.rb +260 -0
- data/lib/devise/rails/warden_compat.rb +42 -0
- data/lib/devise/schema.rb +96 -0
- data/lib/devise/strategies/authenticatable.rb +150 -0
- data/lib/devise/strategies/base.rb +15 -0
- data/lib/devise/strategies/database_authenticatable.rb +21 -0
- data/lib/devise/strategies/rememberable.rb +51 -0
- data/lib/devise/strategies/token_authenticatable.rb +53 -0
- data/lib/devise/test_helpers.rb +100 -0
- data/lib/devise/version.rb +3 -0
- data/lib/generators/active_record/devise_generator.rb +28 -0
- data/lib/generators/active_record/templates/migration.rb +30 -0
- data/lib/generators/devise/devise_generator.rb +17 -0
- data/lib/generators/devise/install_generator.rb +24 -0
- data/lib/generators/devise/orm_helpers.rb +24 -0
- data/lib/generators/devise/views_generator.rb +63 -0
- data/lib/generators/mongoid/devise_generator.rb +17 -0
- data/lib/generators/templates/README +25 -0
- data/lib/generators/templates/devise.rb +168 -0
- data/test/controllers/helpers_test.rb +220 -0
- data/test/controllers/internal_helpers_test.rb +56 -0
- data/test/controllers/url_helpers_test.rb +59 -0
- data/test/devise_test.rb +65 -0
- data/test/encryptors_test.rb +30 -0
- data/test/failure_app_test.rb +148 -0
- data/test/integration/authenticatable_test.rb +424 -0
- data/test/integration/confirmable_test.rb +104 -0
- data/test/integration/database_authenticatable_test.rb +38 -0
- data/test/integration/http_authenticatable_test.rb +64 -0
- data/test/integration/lockable_test.rb +109 -0
- data/test/integration/oauthable_test.rb +258 -0
- data/test/integration/recoverable_test.rb +141 -0
- data/test/integration/registerable_test.rb +179 -0
- data/test/integration/rememberable_test.rb +179 -0
- data/test/integration/timeoutable_test.rb +80 -0
- data/test/integration/token_authenticatable_test.rb +99 -0
- data/test/integration/trackable_test.rb +64 -0
- data/test/mailers/confirmation_instructions_test.rb +84 -0
- data/test/mailers/reset_password_instructions_test.rb +72 -0
- data/test/mailers/unlock_instructions_test.rb +66 -0
- data/test/mapping_test.rb +95 -0
- data/test/models/confirmable_test.rb +221 -0
- data/test/models/database_authenticatable_test.rb +82 -0
- data/test/models/encryptable_test.rb +65 -0
- data/test/models/lockable_test.rb +204 -0
- data/test/models/oauthable_test.rb +21 -0
- data/test/models/recoverable_test.rb +155 -0
- data/test/models/rememberable_test.rb +271 -0
- data/test/models/timeoutable_test.rb +28 -0
- data/test/models/token_authenticatable_test.rb +37 -0
- data/test/models/trackable_test.rb +5 -0
- data/test/models/validatable_test.rb +99 -0
- data/test/models_test.rb +77 -0
- data/test/oauth/config_test.rb +44 -0
- data/test/oauth/url_helpers_test.rb +47 -0
- data/test/orm/active_record.rb +9 -0
- data/test/orm/mongoid.rb +10 -0
- data/test/rails_app/app/active_record/admin.rb +6 -0
- data/test/rails_app/app/active_record/shim.rb +2 -0
- data/test/rails_app/app/active_record/user.rb +8 -0
- data/test/rails_app/app/controllers/admins/sessions_controller.rb +6 -0
- data/test/rails_app/app/controllers/admins_controller.rb +6 -0
- data/test/rails_app/app/controllers/application_controller.rb +9 -0
- data/test/rails_app/app/controllers/home_controller.rb +12 -0
- data/test/rails_app/app/controllers/publisher/registrations_controller.rb +2 -0
- data/test/rails_app/app/controllers/publisher/sessions_controller.rb +2 -0
- data/test/rails_app/app/controllers/users_controller.rb +18 -0
- data/test/rails_app/app/helpers/application_helper.rb +3 -0
- data/test/rails_app/app/mongoid/admin.rb +9 -0
- data/test/rails_app/app/mongoid/shim.rb +24 -0
- data/test/rails_app/app/mongoid/user.rb +10 -0
- data/test/rails_app/config/application.rb +35 -0
- data/test/rails_app/config/boot.rb +13 -0
- data/test/rails_app/config/environment.rb +5 -0
- data/test/rails_app/config/environments/development.rb +19 -0
- data/test/rails_app/config/environments/production.rb +33 -0
- data/test/rails_app/config/environments/test.rb +33 -0
- data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails_app/config/initializers/devise.rb +172 -0
- data/test/rails_app/config/initializers/inflections.rb +2 -0
- data/test/rails_app/config/initializers/secret_token.rb +2 -0
- data/test/rails_app/config/routes.rb +54 -0
- data/test/rails_app/db/migrate/20100401102949_create_tables.rb +31 -0
- data/test/rails_app/db/schema.rb +52 -0
- data/test/rails_app/lib/shared_admin.rb +9 -0
- data/test/rails_app/lib/shared_user.rb +48 -0
- data/test/routes_test.rb +189 -0
- data/test/support/assertions.rb +24 -0
- data/test/support/helpers.rb +60 -0
- data/test/support/integration.rb +88 -0
- data/test/support/webrat/integrations/rails.rb +24 -0
- data/test/test_helper.rb +23 -0
- data/test/test_helpers_test.rb +101 -0
- metadata +335 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'devise/hooks/timeoutable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# Timeoutable takes care of veryfing whether a user session has already
|
|
6
|
+
# expired or not. When a session expires after the configured time, the user
|
|
7
|
+
# will be asked for credentials again, it means, he/she will be redirected
|
|
8
|
+
# to the sign in page.
|
|
9
|
+
#
|
|
10
|
+
# == Options
|
|
11
|
+
#
|
|
12
|
+
# Timeoutable adds the following options to devise_for:
|
|
13
|
+
#
|
|
14
|
+
# * +timeout_in+: the interval to timeout the user session without activity.
|
|
15
|
+
#
|
|
16
|
+
# == Examples
|
|
17
|
+
#
|
|
18
|
+
# user.timedout?(30.minutes.ago)
|
|
19
|
+
#
|
|
20
|
+
module Timeoutable
|
|
21
|
+
extend ActiveSupport::Concern
|
|
22
|
+
|
|
23
|
+
# Checks whether the user session has expired based on configured time.
|
|
24
|
+
def timedout?(last_access)
|
|
25
|
+
last_access && last_access <= self.class.timeout_in.ago
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
Devise::Models.config(self, :timeout_in)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'devise/strategies/token_authenticatable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# The TokenAuthenticatable module is responsible for generating an authentication token and
|
|
6
|
+
# validating the authenticity of the same while signing in.
|
|
7
|
+
#
|
|
8
|
+
# This module only provides a few helpers to help you manage the token, but it is up to you
|
|
9
|
+
# to choose how to use it. For example, if you want to have a new token every time the user
|
|
10
|
+
# saves his account, you can do the following:
|
|
11
|
+
#
|
|
12
|
+
# before_save :reset_authentication_token
|
|
13
|
+
#
|
|
14
|
+
# On the other hand, if you want to generate token unless one exists, you should use instead:
|
|
15
|
+
#
|
|
16
|
+
# before_save :ensure_authentication_token
|
|
17
|
+
#
|
|
18
|
+
# If you want to delete the token after it is used, you can do so in the
|
|
19
|
+
# after_token_authentication callback.
|
|
20
|
+
#
|
|
21
|
+
# == Options
|
|
22
|
+
#
|
|
23
|
+
# TokenAuthenticable adds the following options to devise_for:
|
|
24
|
+
#
|
|
25
|
+
# * +token_authentication_key+: Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
|
|
26
|
+
#
|
|
27
|
+
# * +stateless_token+: By default, when you sign up with a token, Devise will store the user in session
|
|
28
|
+
# as any other authentication strategy. You can set stateless_token to true to avoid this.
|
|
29
|
+
#
|
|
30
|
+
module TokenAuthenticatable
|
|
31
|
+
extend ActiveSupport::Concern
|
|
32
|
+
|
|
33
|
+
# Generate new authentication token (a.k.a. "single access token").
|
|
34
|
+
def reset_authentication_token
|
|
35
|
+
self.authentication_token = self.class.authentication_token
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Generate new authentication token and save the record.
|
|
39
|
+
def reset_authentication_token!
|
|
40
|
+
reset_authentication_token
|
|
41
|
+
self.save(:validate => false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Generate authentication token unless already exists.
|
|
45
|
+
def ensure_authentication_token
|
|
46
|
+
self.reset_authentication_token if self.authentication_token.blank?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Generate authentication token unless already exists and save the record.
|
|
50
|
+
def ensure_authentication_token!
|
|
51
|
+
self.reset_authentication_token! if self.authentication_token.blank?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Hook called after token authentication.
|
|
55
|
+
def after_token_authentication
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module ClassMethods
|
|
59
|
+
def find_for_token_authentication(conditions)
|
|
60
|
+
find_for_authentication(:authentication_token => conditions[token_authentication_key])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Generate a token checking if one does not already exist in the database.
|
|
64
|
+
def authentication_token
|
|
65
|
+
generate_token(:authentication_token)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
::Devise::Models.config(self, :token_authentication_key, :stateless_token)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'devise/hooks/trackable'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Models
|
|
5
|
+
# Track information about your user sign in. It tracks the following columns:
|
|
6
|
+
#
|
|
7
|
+
# * sign_in_count - Increased every time a sign in is made (by form, openid, oauth)
|
|
8
|
+
# * current_sign_in_at - A tiemstamp updated when the user signs in
|
|
9
|
+
# * last_sign_in_at - Holds the timestamp of the previous sign in
|
|
10
|
+
# * current_sign_in_ip - The remote ip updated when the user sign in
|
|
11
|
+
# * last_sign_in_at - Holds the remote ip of the previous sign in
|
|
12
|
+
#
|
|
13
|
+
module Trackable
|
|
14
|
+
def update_tracked_fields!(request)
|
|
15
|
+
old_current, new_current = self.current_sign_in_at, Time.now
|
|
16
|
+
self.last_sign_in_at = old_current || new_current
|
|
17
|
+
self.current_sign_in_at = new_current
|
|
18
|
+
|
|
19
|
+
old_current, new_current = self.current_sign_in_ip, request.remote_ip
|
|
20
|
+
self.last_sign_in_ip = old_current || new_current
|
|
21
|
+
self.current_sign_in_ip = new_current
|
|
22
|
+
|
|
23
|
+
self.sign_in_count ||= 0
|
|
24
|
+
self.sign_in_count += 1
|
|
25
|
+
|
|
26
|
+
save(:validate => false)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Devise
|
|
2
|
+
module Models
|
|
3
|
+
# Validatable creates all needed validations for a user email and password.
|
|
4
|
+
# It's optional, given you may want to create the validations by yourself.
|
|
5
|
+
# Automatically validate if the email is present, unique and it's format is
|
|
6
|
+
# valid. Also tests presence of password, confirmation and length.
|
|
7
|
+
#
|
|
8
|
+
# == Options
|
|
9
|
+
#
|
|
10
|
+
# Validatable adds the following options to devise_for:
|
|
11
|
+
#
|
|
12
|
+
# * +email_regexp+: the regular expression used to validate e-mails;
|
|
13
|
+
# * +password_length+: a range expressing password length. Defaults to 6..20.
|
|
14
|
+
#
|
|
15
|
+
module Validatable
|
|
16
|
+
# All validations used by this module.
|
|
17
|
+
VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
|
|
18
|
+
:validates_confirmation_of, :validates_length_of ].freeze
|
|
19
|
+
|
|
20
|
+
def self.included(base)
|
|
21
|
+
base.extend ClassMethods
|
|
22
|
+
assert_validations_api!(base)
|
|
23
|
+
|
|
24
|
+
base.class_eval do
|
|
25
|
+
validates_presence_of :email
|
|
26
|
+
validates_uniqueness_of :email, :scope => authentication_keys[1..-1], :case_sensitive => false, :allow_blank => true
|
|
27
|
+
validates_format_of :email, :with => email_regexp, :allow_blank => true
|
|
28
|
+
|
|
29
|
+
with_options :if => :password_required? do |v|
|
|
30
|
+
v.validates_presence_of :password
|
|
31
|
+
v.validates_confirmation_of :password
|
|
32
|
+
v.validates_length_of :password, :within => password_length, :allow_blank => true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.assert_validations_api!(base) #:nodoc:
|
|
38
|
+
unavailable_validations = VALIDATIONS.select { |v| !base.respond_to?(v) }
|
|
39
|
+
|
|
40
|
+
unless unavailable_validations.empty?
|
|
41
|
+
raise "Could not use :validatable module since #{base} does not respond " <<
|
|
42
|
+
"to the following methods: #{unavailable_validations.to_sentence}."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
protected
|
|
47
|
+
|
|
48
|
+
# Checks whether a password is needed or not. For validations only.
|
|
49
|
+
# Passwords are always required if it's a new record, or if the password
|
|
50
|
+
# or confirmation are being set somewhere.
|
|
51
|
+
def password_required?
|
|
52
|
+
!persisted? || !password.nil? || !password_confirmation.nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module ClassMethods
|
|
56
|
+
Devise::Models.config(self, :email_regexp, :password_length)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'active_support/core_ext/object/with_options'
|
|
2
|
+
|
|
3
|
+
Devise.with_options :model => true do |d|
|
|
4
|
+
# Strategies first
|
|
5
|
+
d.with_options :strategy => true do |s|
|
|
6
|
+
routes = [nil, :new, :destroy]
|
|
7
|
+
s.add_module :database_authenticatable, :controller => :sessions, :route => { :session => routes }
|
|
8
|
+
s.add_module :token_authenticatable, :controller => :sessions, :route => { :session => routes }
|
|
9
|
+
s.add_module :rememberable
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Other authentications
|
|
13
|
+
d.add_module :encryptable
|
|
14
|
+
d.add_module :oauthable, :controller => :oauth_callbacks, :route => :oauth_callback
|
|
15
|
+
|
|
16
|
+
# Misc after
|
|
17
|
+
routes = [nil, :new, :edit]
|
|
18
|
+
d.add_module :recoverable, :controller => :passwords, :route => { :password => routes }
|
|
19
|
+
d.add_module :registerable, :controller => :registrations, :route => { :registration => (routes << :cancel) }
|
|
20
|
+
d.add_module :validatable
|
|
21
|
+
|
|
22
|
+
# The ones which can sign out after
|
|
23
|
+
routes = [nil, :new]
|
|
24
|
+
d.add_module :confirmable, :controller => :confirmations, :route => { :confirmation => routes }
|
|
25
|
+
d.add_module :lockable, :controller => :unlocks, :route => { :unlock => routes }
|
|
26
|
+
d.add_module :timeoutable
|
|
27
|
+
|
|
28
|
+
# Stats for last, so we make sure the user is really signed in
|
|
29
|
+
d.add_module :trackable
|
|
30
|
+
end
|
data/lib/devise/oauth.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "oauth2"
|
|
3
|
+
rescue LoadError => e
|
|
4
|
+
warn "Could not load 'oauth2'. Please ensure you have the gem installed and listed in your Gemfile."
|
|
5
|
+
raise
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Devise
|
|
9
|
+
module Oauth
|
|
10
|
+
autoload :Config, "devise/oauth/config"
|
|
11
|
+
autoload :Helpers, "devise/oauth/helpers"
|
|
12
|
+
autoload :InternalHelpers, "devise/oauth/internal_helpers"
|
|
13
|
+
autoload :UrlHelpers, "devise/oauth/url_helpers"
|
|
14
|
+
autoload :TestHelpers, "devise/oauth/test_helpers"
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
delegate :short_circuit_authorizers!, :unshort_circuit_authorizers!, :to => "Devise::Oauth::TestHelpers"
|
|
18
|
+
|
|
19
|
+
def test_mode!
|
|
20
|
+
Faraday.default_adapter = :test
|
|
21
|
+
ActiveSupport.on_load(:action_controller) { include Devise::Oauth::TestHelpers }
|
|
22
|
+
ActiveSupport.on_load(:action_view) { include Devise::Oauth::TestHelpers }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stub!(provider, stubs=nil, &block)
|
|
26
|
+
raise "You either need to pass stubs as a block or as a parameter" unless block_given? || stubs
|
|
27
|
+
stubs ||= Faraday::Adapter::Test::Stubs.new(&block)
|
|
28
|
+
Devise.oauth_configs[provider].build_connection do |b|
|
|
29
|
+
b.adapter :test, stubs
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reset_stubs!(*providers)
|
|
34
|
+
target = providers.any? ? Devise.oauth_configs.slice(*providers) : Devise.oauth_configs
|
|
35
|
+
target.each_value do |v|
|
|
36
|
+
v.build_connection { |b| b.adapter Faraday.default_adapter }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
|
2
|
+
|
|
3
|
+
module Devise
|
|
4
|
+
module Oauth
|
|
5
|
+
# A configuration object that holds the OAuth2::Client object
|
|
6
|
+
# and all configuration values given config.oauth.
|
|
7
|
+
class Config
|
|
8
|
+
attr_reader :scope, :client
|
|
9
|
+
|
|
10
|
+
def initialize(app_id, app_secret, options)
|
|
11
|
+
@scope = Array.wrap(options.delete(:scope))
|
|
12
|
+
@client = OAuth2::Client.new(app_id, app_secret, options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def authorize_url(options)
|
|
16
|
+
options[:scope] ||= @scope.join(',')
|
|
17
|
+
client.web_server.authorize_url(options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def access_token_by_code(code, redirect_uri=nil)
|
|
21
|
+
client.web_server.get_access_token(code, :redirect_uri => redirect_uri)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def access_token_by_token(token)
|
|
25
|
+
OAuth2::AccessToken.new(client, token)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def build_connection(&block)
|
|
29
|
+
client.connection.build(&block)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Devise
|
|
2
|
+
module Oauth
|
|
3
|
+
# Provides a few helpers that are included in ActionController::Base
|
|
4
|
+
# for convenience.
|
|
5
|
+
module Helpers
|
|
6
|
+
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
# Overwrite expire_session_data_after_sign_in! so it removes all
|
|
10
|
+
# oauth tokens from session ensuring registrations done in a row
|
|
11
|
+
# do not try to store the same token in the database.
|
|
12
|
+
def expire_session_data_after_sign_in!
|
|
13
|
+
super
|
|
14
|
+
session.keys.grep(/_oauth_token$/).each { |k| session.delete(k) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
module Devise
|
|
2
|
+
module Oauth
|
|
3
|
+
module InternalHelpers
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def self.define_oauth_helpers(name) #:nodoc:
|
|
7
|
+
alias_method(name, :callback_action)
|
|
8
|
+
public name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
helpers = %w(oauth_callback oauth_provider oauth_config)
|
|
13
|
+
hide_action *helpers
|
|
14
|
+
helper_method *helpers
|
|
15
|
+
before_filter :valid_oauth_callback?, :oauth_error_happened?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the oauth_callback (also aliased as oauth_provider) as a symbol.
|
|
19
|
+
# For example: :github.
|
|
20
|
+
def oauth_callback
|
|
21
|
+
@oauth_callback ||= action_name.to_sym
|
|
22
|
+
end
|
|
23
|
+
alias :oauth_provider :oauth_callback
|
|
24
|
+
|
|
25
|
+
# Returns the configuration object for this oauth callback.
|
|
26
|
+
def oauth_config
|
|
27
|
+
@oauth_client ||= resource_class.oauth_configs[oauth_callback]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
protected
|
|
31
|
+
|
|
32
|
+
# This method checks three things:
|
|
33
|
+
#
|
|
34
|
+
# * If the URL being accessed is a valid provider for the given scope;
|
|
35
|
+
# * If code or error was streamed back from the server;
|
|
36
|
+
# * If the resource class implements the required hook;
|
|
37
|
+
#
|
|
38
|
+
def valid_oauth_callback?
|
|
39
|
+
unless oauth_config
|
|
40
|
+
unknown_action! "Skipping #{oauth_callback} OAuth because configuration " <<
|
|
41
|
+
"could not be found for model #{resource_name}."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
unless params[:code] || params[:error] || params[:error_reason]
|
|
45
|
+
unknown_action! "Skipping #{oauth_callback} OAuth because code nor error were sent."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
unless resource_class.respond_to?(oauth_model_callback)
|
|
49
|
+
raise "#{resource_class.name} does not respond to #{oauth_model_callback}. " <<
|
|
50
|
+
"Check the OAuth section in the README for more information."
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if an error was sent by the authorizer. If it happened, we redirect
|
|
55
|
+
# to url specified by after_oauth_failure_path_for, which defaults to new_session_path.
|
|
56
|
+
#
|
|
57
|
+
# By default, Devise shows a custom message from I18n saying the user could
|
|
58
|
+
# not be authenticated and the reason:
|
|
59
|
+
#
|
|
60
|
+
# en:
|
|
61
|
+
# devise:
|
|
62
|
+
# oauth_callbacks:
|
|
63
|
+
# failure: 'Could not authorize you from %{kind} because "%{reason}".'
|
|
64
|
+
#
|
|
65
|
+
# Let's suppose the reason returned by a Github was "access_denied". It will show:
|
|
66
|
+
#
|
|
67
|
+
# Could not authorize you from Github because "Access denied"
|
|
68
|
+
#
|
|
69
|
+
# And it will also be logged on console:
|
|
70
|
+
#
|
|
71
|
+
# github oauth failed: "access_denied".
|
|
72
|
+
#
|
|
73
|
+
# However, each specific error message can be customized using I18n:
|
|
74
|
+
#
|
|
75
|
+
# en:
|
|
76
|
+
# devise:
|
|
77
|
+
# oauth_callbacks:
|
|
78
|
+
# access_denied: 'You did not give access to our application on %{kind}.'
|
|
79
|
+
#
|
|
80
|
+
# Note "access_denied" follows the same lookup rule described in set_oauth_flash_message
|
|
81
|
+
# method. Besides, is important to remember most errors are specified by OAuth 2
|
|
82
|
+
# specification. But a few providers do not use them yet.
|
|
83
|
+
#
|
|
84
|
+
# TODO: Currently, Facebook is returning error_reason=user_denied when
|
|
85
|
+
# the user denies, but the specification defines error=access_denied instead.
|
|
86
|
+
def oauth_error_happened?
|
|
87
|
+
if error = params[:error] || params[:error_reason]
|
|
88
|
+
# Some providers returns access-denied instead of access_denied.
|
|
89
|
+
error = error.to_s.gsub("-", "_")
|
|
90
|
+
logger.warn "[Devise] #{oauth_callback} oauth failed: #{error.inspect}."
|
|
91
|
+
|
|
92
|
+
set_oauth_flash_message :alert, error[0,25], :default => :failure, :reason => error.humanize
|
|
93
|
+
redirect_to after_oauth_failure_path_for(resource_name)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The model method used as hook.
|
|
98
|
+
def oauth_model_callback #:nodoc:
|
|
99
|
+
"find_for_#{oauth_callback}_oauth"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The session key to store the token.
|
|
103
|
+
def oauth_session_key #:nodoc:
|
|
104
|
+
"#{resource_name}_#{oauth_callback}_oauth_token"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The callback redirect uri. Used to request the access token.
|
|
108
|
+
def oauth_redirect_uri #:nodoc:
|
|
109
|
+
oauth_callback_url(resource_name, oauth_callback)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# This is the implementation for all OAuth actions.
|
|
113
|
+
def callback_action
|
|
114
|
+
access_token = oauth_config.access_token_by_code(params[:code], oauth_redirect_uri)
|
|
115
|
+
self.resource = resource_class.send(oauth_model_callback, access_token, signed_in_resource)
|
|
116
|
+
|
|
117
|
+
if resource.persisted? && resource.errors.empty?
|
|
118
|
+
set_oauth_flash_message :notice, :success
|
|
119
|
+
sign_in_and_redirect resource_name, resource, :event => :authentication
|
|
120
|
+
else
|
|
121
|
+
session[oauth_session_key] = access_token.token
|
|
122
|
+
clean_up_passwords(resource)
|
|
123
|
+
render_for_oauth
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Handles oauth flash messages by adding a cascade. The default messages
|
|
128
|
+
# are always in the controller namespace:
|
|
129
|
+
#
|
|
130
|
+
# en:
|
|
131
|
+
# devise:
|
|
132
|
+
# oauth_callbacks:
|
|
133
|
+
# success: 'Successfully authorized from %{kind} account.'
|
|
134
|
+
# failure: 'Could not authorize you from %{kind} because "%{reason}".'
|
|
135
|
+
# skipped: 'Skipped Oauth authorization for %{kind}.'
|
|
136
|
+
#
|
|
137
|
+
# But they can also be nested according to the oauth provider:
|
|
138
|
+
#
|
|
139
|
+
# en:
|
|
140
|
+
# devise:
|
|
141
|
+
# oauth_callbacks:
|
|
142
|
+
# github:
|
|
143
|
+
# success: 'Hello coder! Welcome to our app!'
|
|
144
|
+
#
|
|
145
|
+
# And finally by Devise scope:
|
|
146
|
+
#
|
|
147
|
+
# en:
|
|
148
|
+
# devise:
|
|
149
|
+
# oauth_callbacks:
|
|
150
|
+
# admin:
|
|
151
|
+
# github:
|
|
152
|
+
# success: 'Hello coder with high permissions! Can I get a raise?'
|
|
153
|
+
#
|
|
154
|
+
def set_oauth_flash_message(key, type, options={})
|
|
155
|
+
options[:kind] = oauth_callback.to_s.titleize
|
|
156
|
+
options[:default] = Array(options[:default]).unshift(type.to_sym)
|
|
157
|
+
set_flash_message(key, "#{oauth_callback}.#{type}", options)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Choose which template to render when a not persisted resource is
|
|
161
|
+
# returned in the find_for_x_oauth. By default, it renders registrations/new.
|
|
162
|
+
def render_for_oauth
|
|
163
|
+
render_with_scope :new, devise_mapping.controllers[:registrations]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The default hook used by oauth to specify the redirect url for success.
|
|
167
|
+
def after_oauth_success_path_for(resource_or_scope)
|
|
168
|
+
after_sign_in_path_for(resource_or_scope)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The default hook used by oauth to specify the redirect url for failure.
|
|
172
|
+
def after_oauth_failure_path_for(scope)
|
|
173
|
+
new_session_path(scope)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Overwrite redirect_for_sign_in so it takes uses after_oauth_success_path_for.
|
|
177
|
+
def redirect_for_sign_in(scope, resource) #:nodoc:
|
|
178
|
+
redirect_to stored_location_for(scope) || after_oauth_success_path_for(resource)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|