From e1c2e45f9756e34cc008b76e9be43b8491cde3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 20 Dec 2009 13:53:53 +0100 Subject: [PATCH] Extract Activatable from Confirmable, so if you need to active your account through other means, you can still do so and ensure cherry pick works as expected. --- CHANGELOG.rdoc | 4 + README.rdoc | 1 + TODO | 4 +- generators/devise_install/templates/devise.rb | 10 ++- lib/devise.rb | 10 ++- .../hooks/{confirmable.rb => activatable.rb} | 9 ++- lib/devise/locales/en.yml | 1 + lib/devise/models.rb | 5 +- lib/devise/models/activatable.rb | 16 ++++ lib/devise/models/confirmable.rb | 16 ++-- lib/devise/models/validatable.rb | 4 - test/models_test.rb | 16 ++-- test/rails_app/app/models/admin.rb | 2 +- test/rails_app/app/models/user.rb | 2 +- test/rails_app/config/initializers/devise.rb | 76 +++++++++++++++++++ test/rails_app/config/locales/en.yml | 5 -- 16 files changed, 143 insertions(+), 38 deletions(-) rename lib/devise/hooks/{confirmable.rb => activatable.rb} (67%) create mode 100644 lib/devise/models/activatable.rb create mode 100644 test/rails_app/config/initializers/devise.rb delete mode 100644 test/rails_app/config/locales/en.yml diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index f0892e03..46cdeed2 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,3 +1,7 @@ +* enhancements + * Extract Activatable from Confirmable + * Decouple Serializers from Devise modules + == 0.7.3 * bug fix diff --git a/README.rdoc b/README.rdoc index 4995de7f..5228f5da 100644 --- a/README.rdoc +++ b/README.rdoc @@ -13,6 +13,7 @@ Right now it's composed of seven mainly modules: * Confirmable: responsible for verifying whether an account is already confirmed to sign in, and to send emails with confirmation instructions. * Recoverable: takes care of reseting the user password and send reset instructions. * Rememberable: manages generating and clearing token for remember the user from a saved cookie. +* Activatable: if you need to activate accounts by other means, which are not through confirmation, use this module. * Timeoutable: expires sessions without activity in a certain period of time. * Trackable: tracks sign in count, timestamps and ip. * Validatable: creates all needed validations for email and password. It's totally optional, so you're able to to customize validations by yourself. diff --git a/TODO b/TODO index 7dba8d7b..cd930652 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ * Make test run with different ORMs * Add registerable support -* Add http authentication support \ No newline at end of file +* Add http authentication support +* Extract SessionSerializer tests from Authenticatable +* Extract Activatable tests from Confirmable \ No newline at end of file diff --git a/generators/devise_install/templates/devise.rb b/generators/devise_install/templates/devise.rb index a7422b40..af7a0140 100644 --- a/generators/devise_install/templates/devise.rb +++ b/generators/devise_install/templates/devise.rb @@ -1,10 +1,14 @@ # Use this hook to configure devise mailer, warden hooks and so forth. The first # four configuration values can also be set straight in your models. Devise.setup do |config| - # Configure the frameworks used by default. You should always set this value - # because if Devise add a new strategy, it won't be added to your application + # Configure Devise modules used by default. You should always set this value + # because if Devise adds a new strategy, it won't be added to your application # by default, unless you configure it here. - config.all = <%= Devise::ALL.inspect %> + # + # Remember that Devise includes other modules on its own (like :activatable + # and :timeoutable) which are not included here and also plugins. So be sure + # to check the docs for a complete set. + config.all = [:authenticatable, :confirmable, :recoverable, :rememberable, :trackable, :validatable] # Invoke `rake secret` and use the printed value to setup a pepper to generate # the encrypted password. By default no pepper is used. diff --git a/lib/devise.rb b/lib/devise.rb index e3b3c2af..1f40a74a 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -24,7 +24,7 @@ module Devise autoload :MongoMapper, 'devise/orm/mongo_mapper' end - ALL = [:authenticatable, :confirmable, :recoverable, :rememberable, + ALL = [:authenticatable, :activatable, :confirmable, :recoverable, :rememberable, :timeoutable, :trackable, :validatable] # Maps controller names to devise modules @@ -38,9 +38,8 @@ module Devise SERIALIZERS = [:session, :cookie] TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] - # Maps the messages types that are used in flash message. This array is not - # frozen, so you can add messages from your own strategies. - FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :timeout ] + # Maps the messages types that are used in flash message. + FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :timeout, :inactive ] # Declare encryptors length which are used in migrations. ENCRYPTORS_LENGTH = { @@ -51,6 +50,9 @@ module Devise :authlogic_sha512 => 128 } + # Email regex used to validate email formats. Retrieved from authlogic. + EMAIL_REGEX = /\A[\w\.%\+\-]+@(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,4}|museum|travel)\z/i + # Used to encrypt password. Please generate one with rake secret. mattr_accessor :pepper @@pepper = nil diff --git a/lib/devise/hooks/confirmable.rb b/lib/devise/hooks/activatable.rb similarity index 67% rename from lib/devise/hooks/confirmable.rb rename to lib/devise/hooks/activatable.rb index 71c897f1..39d3b9bb 100644 --- a/lib/devise/hooks/confirmable.rb +++ b/lib/devise/hooks/activatable.rb @@ -6,12 +6,13 @@ Warden::Manager.after_set_user do |record, warden, options| if record && record.respond_to?(:active?) && !record.active? scope = options[:scope] warden.logout(scope) + + # If winning strategy was set, this is being called after authenticate and + # there is no need to force a redirect. if warden.winning_strategy - # If winning strategy was set, this is being called after authenticate and - # there is no need to force a redirect. - warden.winning_strategy.fail!(:unconfirmed) + warden.winning_strategy.fail!(record.inactive_message) else - throw :warden, :scope => scope, :message => :unconfirmed + throw :warden, :scope => scope, :message => record.inactive_message end end end diff --git a/lib/devise/locales/en.yml b/lib/devise/locales/en.yml index 5fc6899e..82274a9d 100644 --- a/lib/devise/locales/en.yml +++ b/lib/devise/locales/en.yml @@ -7,6 +7,7 @@ en: unconfirmed: 'You have to confirm your account before continuing.' invalid: 'Invalid email or password.' timeout: 'Your session expired, please sign in again to continue.' + inactive: 'Your account was not activated yet.' passwords: send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' updated: 'Your password was changed successfully. You are now signed in.' diff --git a/lib/devise/models.rb b/lib/devise/models.rb index ebc8bb44..792b9326 100644 --- a/lib/devise/models.rb +++ b/lib/devise/models.rb @@ -1,5 +1,6 @@ module Devise module Models + autoload :Activatable, 'devise/models/activatable' autoload :Authenticatable, 'devise/models/authenticatable' autoload :Confirmable, 'devise/models/confirmable' autoload :Recoverable, 'devise/models/recoverable' @@ -81,9 +82,9 @@ module Devise raise "You need to give at least one Devise module" if modules.empty? options = modules.extract_options! - modules = Devise.all if modules.include?(:all) + modules += Devise.all if modules.delete(:all) modules -= Array(options.delete(:except)) - modules = Devise::ALL & modules + modules = Devise::ALL & modules.uniq Devise.orm_class.included_modules_hook(self, modules) do modules.each do |m| diff --git a/lib/devise/models/activatable.rb b/lib/devise/models/activatable.rb new file mode 100644 index 00000000..338da924 --- /dev/null +++ b/lib/devise/models/activatable.rb @@ -0,0 +1,16 @@ +require 'devise/hooks/activatable' + +module Devise + module Models + # This module implements the default API required in activatable hook. + module Activatable + def active? + raise NotImplementedError + end + + def inactive_message + :inactive + end + end + end +end \ No newline at end of file diff --git a/lib/devise/models/confirmable.rb b/lib/devise/models/confirmable.rb index 47a1ecec..c540b725 100644 --- a/lib/devise/models/confirmable.rb +++ b/lib/devise/models/confirmable.rb @@ -1,4 +1,4 @@ -require 'devise/hooks/confirmable' +require 'devise/models/activatable' module Devise module Models @@ -29,6 +29,7 @@ module Devise # User.find(1).send_confirmation_instructions # manually send instructions # User.find(1).resend_confirmation! # generates a new token and resent it module Confirmable + include Devise::Models::Activatable def self.included(base) base.class_eval do @@ -70,14 +71,19 @@ module Devise end end - # Verify whether a user is active to sign in or not. If the user is - # already confirmed, it should never be blocked. Otherwise we need to - # calculate if the confirm time has not expired for this user, in other - # words, if the confirmation is still valid. + # Overwrites active? from Devise::Models::Activatable for confirmation + # by verifying whether an user is active to sign in or not. If the user + # is already confirmed, it should never be blocked. Otherwise we need to + # calculate if the confirm time has not expired for this user. def active? confirmed? || confirmation_period_valid? end + # The message to be shown if the account is inactive. + def inactive_message + :unconfirmed + end + # If you don't want confirmation to be sent on create, neither a code # to be generated, call skip_confirmation! def skip_confirmation! diff --git a/lib/devise/models/validatable.rb b/lib/devise/models/validatable.rb index 2a6d931e..a31276c6 100644 --- a/lib/devise/models/validatable.rb +++ b/lib/devise/models/validatable.rb @@ -6,10 +6,6 @@ module Devise # Automatically validate if the email is present, unique and it's format is # valid. Also tests presence of password, confirmation and length module Validatable - - # Email regex used to validate email formats. Retrieved from authlogic. - EMAIL_REGEX = /\A[\w\.%\+\-]+@(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,4}|museum|travel)\z/i - # All validations used by this module. VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of, :validates_confirmation_of, :validates_length_of ].freeze diff --git a/test/models_test.rb b/test/models_test.rb index dbb369ea..3e3238aa 100644 --- a/test/models_test.rb +++ b/test/models_test.rb @@ -37,11 +37,11 @@ class Exceptable < User end class Configurable < User - devise :all, :stretches => 15, - :pepper => 'abcdef', - :confirm_within => 5.days, - :remember_for => 7.days, - :timeout_in => 15.minutes + devise :all, :timeoutable, :stretches => 15, + :pepper => 'abcdef', + :confirm_within => 5.days, + :remember_for => 7.days, + :timeout_in => 15.minutes end class ActiveRecordTest < ActiveSupport::TestCase @@ -60,7 +60,7 @@ class ActiveRecordTest < ActiveSupport::TestCase end end - test 'include by default authenticatable only' do + test 'add authenticatable module only' do assert_include_modules Authenticatable, :authenticatable end @@ -90,11 +90,11 @@ class ActiveRecordTest < ActiveSupport::TestCase test 'add all modules' do assert_include_modules Devisable, - :authenticatable, :confirmable, :recoverable, :rememberable, :trackable, :timeoutable, :validatable + :authenticatable, :confirmable, :recoverable, :rememberable, :trackable, :validatable end test 'configure modules with except option' do - assert_include_modules Exceptable, :authenticatable, :confirmable, :trackable, :timeoutable + assert_include_modules Exceptable, :authenticatable, :confirmable, :trackable end test 'set a default value for stretches' do diff --git a/test/rails_app/app/models/admin.rb b/test/rails_app/app/models/admin.rb index 3d7b9d3a..df67d149 100644 --- a/test/rails_app/app/models/admin.rb +++ b/test/rails_app/app/models/admin.rb @@ -1,5 +1,5 @@ class Admin < ActiveRecord::Base - devise :all, :except => [:recoverable, :confirmable, :rememberable, :validatable, :trackable] + devise :all, :timeoutable, :except => [:recoverable, :confirmable, :rememberable, :validatable, :trackable] def self.find_for_authentication(conditions) last(:conditions => conditions) diff --git a/test/rails_app/app/models/user.rb b/test/rails_app/app/models/user.rb index 781fa145..fd05f40e 100644 --- a/test/rails_app/app/models/user.rb +++ b/test/rails_app/app/models/user.rb @@ -1,4 +1,4 @@ class User < ActiveRecord::Base - devise :all + devise :all, :timeoutable attr_accessible :username, :email, :password, :password_confirmation end diff --git a/test/rails_app/config/initializers/devise.rb b/test/rails_app/config/initializers/devise.rb new file mode 100644 index 00000000..af7a0140 --- /dev/null +++ b/test/rails_app/config/initializers/devise.rb @@ -0,0 +1,76 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. The first +# four configuration values can also be set straight in your models. +Devise.setup do |config| + # Configure Devise modules used by default. You should always set this value + # because if Devise adds a new strategy, it won't be added to your application + # by default, unless you configure it here. + # + # Remember that Devise includes other modules on its own (like :activatable + # and :timeoutable) which are not included here and also plugins. So be sure + # to check the docs for a complete set. + config.all = [:authenticatable, :confirmable, :recoverable, :rememberable, :trackable, :validatable] + + # Invoke `rake secret` and use the printed value to setup a pepper to generate + # the encrypted password. By default no pepper is used. + # config.pepper = "rake secret output" + + # Configure how many times you want the password is reencrypted. Default is 10. + # config.stretches = 10 + + # Define which will be the encryption algorithm. Supported algorithms are :sha1 + # (default) and :sha512. Devise also supports encryptors from others authentication + # frameworks as :clearance_sha1, :authlogic_sha512 (then you should set stretches + # above to 20 for default behavior) and :restful_authentication_sha1 (then you + # should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper) + # config.encryptor = :sha1 + + # Configure which keys are used when authenticating an user. By default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating an user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # config.authentication_keys = [ :email ] + + # The time you want give to your user to confirm his account. During this time + # he will be able to access your application without confirming. Default is nil. + # config.confirm_within = 2.days + + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. + # config.timeout_in = 10.minutes + + # Configure the e-mail address which will be shown in DeviseMailer. + # config.mailer_sender = "foo.bar@yourapp.com" + + # Load and configure the ORM. Supports :active_record, :data_mapper and :mongo_mapper. + # require 'devise/orm/mongo_mapper' + # config.orm = :mongo_mapper + + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "sessions/users/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = true + + # If you want to use other strategies, that are not (yet) supported by Devise, + # you can configure them inside the config.warden block. The example below + # allows you to setup OAuth, using http://github.com/roman/warden_oauth + # + # config.warden do |manager| + # manager.oauth(:twitter) do |twitter| + # twitter.consumer_secret = + # twitter.consumer_key = + # twitter.options :site => 'http://twitter.com' + # end + # manager.default_strategies.unshift :twitter_oauth + # end + + # Configure default_url_options if you are using dynamic segments in :path_prefix + # for devise_for. + # + # config.default_url_options do + # { :locale => I18n.locale } + # end +end diff --git a/test/rails_app/config/locales/en.yml b/test/rails_app/config/locales/en.yml deleted file mode 100644 index f265c068..00000000 --- a/test/rails_app/config/locales/en.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Sample localization file for English. Add more files in this directory for other locales. -# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. - -en: - hello: "Hello world" \ No newline at end of file