From 32648027e282eb4c0f4f42e9c9cc0c961765faa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 5 Aug 2013 11:47:36 +0200 Subject: [PATCH] Add Devise::KeyGenerator --- Gemfile.lock | 1 + devise.gemspec | 1 + lib/devise.rb | 4 ++ lib/devise/key_generator.rb | 43 ++++++++++++++++++++ lib/devise/rails.rb | 20 ++++----- lib/devise/rails/warden_compat.rb | 1 + lib/generators/templates/devise.rb | 22 ++++++---- test/rails_app/config/initializers/devise.rb | 3 ++ 8 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 lib/devise/key_generator.rb diff --git a/Gemfile.lock b/Gemfile.lock index 7827ed8f..c159b3e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,6 +16,7 @@ PATH bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) + thread_safe (~> 0.1) warden (~> 1.2.3) GEM diff --git a/devise.gemspec b/devise.gemspec index de085992..37f4280c 100644 --- a/devise.gemspec +++ b/devise.gemspec @@ -22,5 +22,6 @@ Gem::Specification.new do |s| s.add_dependency("warden", "~> 1.2.3") s.add_dependency("orm_adapter", "~> 0.1") s.add_dependency("bcrypt-ruby", "~> 3.0") + s.add_dependency("thread_safe", "~> 0.1") s.add_dependency("railties", ">= 3.2.6", "< 5") end diff --git a/lib/devise.rb b/lib/devise.rb index f6d89bde..c7118091 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -45,6 +45,10 @@ module Devise # True values used to check params TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] + # Secret key used by the key generator + mattr_accessor :secret_key + @@secret_key = nil + # Custom domain or key for cookies. Not set by default mattr_accessor :rememberable_options @@rememberable_options = {} diff --git a/lib/devise/key_generator.rb b/lib/devise/key_generator.rb new file mode 100644 index 00000000..8248403f --- /dev/null +++ b/lib/devise/key_generator.rb @@ -0,0 +1,43 @@ +# Deprecate: Copied verbatim from Rails source, remove once we move to Rails 4 only. +require 'thread_safe' +require 'openssl' +require 'secure_random' + +module Devise + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2 + # It can be used to derive a number of keys for various purposes from a given secret. + # This lets Rails applications have a single secure secret, but avoid reusing that + # key in multiple incompatible contexts. + class KeyGenerator + def initialize(secret, options = {}) + @secret = secret + # The default iterations are higher than required for our key derivation uses + # on the off chance someone uses this for password storage + @iterations = options[:iterations] || 2**16 + end + + # Returns a derived key suitable for use. The default key_size is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size=64) + OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) + end + end + + # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid + # re-executing the key generation process when it's called using the same salt and + # key_size + class CachingKeyGenerator + def initialize(key_generator) + @key_generator = key_generator + @cache_keys = ThreadSafe::Cache.new + end + + # Returns a derived key suitable for use. The default key_size is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size=64) + @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size) + end + end +end diff --git a/lib/devise/rails.rb b/lib/devise/rails.rb index 24a5f924..15220228 100644 --- a/lib/devise/rails.rb +++ b/lib/devise/rails.rb @@ -29,21 +29,19 @@ module Devise end end - initializer "devise.mongoid_version_warning" do - if defined?(Mongoid) - require 'mongoid/version' - if Mongoid::VERSION.to_f < 2.1 - puts "\n[DEVISE] Please note that Mongoid versions prior to 2.1 handle dirty model " \ - "object attributes in such a way that the Devise `validatable` module will not apply " \ - "its usual uniqueness and format validations for the email field. It is recommended " \ - "that you upgrade to Mongoid 2.1+ for this and other fixes, but if for some reason you " \ - "are unable to do so, you should add these validations manually.\n" - end + initializer "devise.secret_key" do + unless Devise.secret_key + raise <<-ERROR +Devise.secret_key was not set. Please add the following to your Devise initializer: + + config.secret_key = '#{SecureRandom.hex(64)}' + +ERROR end end initializer "devise.fix_routes_proxy_missing_respond_to_bug" do - # We can get rid of this once we support only Rails > 3.2 + # Deprecate: Remove once we move to Rails 4 only. ActionDispatch::Routing::RoutesProxy.class_eval do def respond_to?(method, include_private = false) super || routes.url_helpers.respond_to?(method) diff --git a/lib/devise/rails/warden_compat.rb b/lib/devise/rails/warden_compat.rb index 5ec50af1..79bed409 100644 --- a/lib/devise/rails/warden_compat.rb +++ b/lib/devise/rails/warden_compat.rb @@ -3,6 +3,7 @@ module Warden::Mixins::Common @request ||= ActionDispatch::Request.new(env) end + # Deprecate: Remove this check once we move to Rails 4 only. NULL_STORE = defined?(ActionController::RequestForgeryProtection::ProtectionMethods::NullSession::NullSessionHash) ? ActionController::RequestForgeryProtection::ProtectionMethods::NullSession::NullSessionHash : nil diff --git a/lib/generators/templates/devise.rb b/lib/generators/templates/devise.rb index d1ab0534..2d67de99 100644 --- a/lib/generators/templates/devise.rb +++ b/lib/generators/templates/devise.rb @@ -1,13 +1,19 @@ # Use this hook to configure devise mailer, warden hooks and so forth. # Many of these configuration options can be set straight in your model. Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + config.secret_key = '<%= SecureRandom.hex(64) %>' + # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, - # note that it will be overwritten if you use your own mailer class with default "from" parameter. - config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' # Configure the class responsible to send e-mails. - # config.mailer = "Devise::Mailer" + # config.mailer = 'Devise::Mailer' # ==> ORM configuration # Load and configure the ORM. Supports :active_record (default) and @@ -61,8 +67,8 @@ Devise.setup do |config| # If http headers should be returned for AJAX requests. True by default. # config.http_authenticatable_on_xhr = true - # The realm used in Http Basic Authentication. "Application" by default. - # config.http_authentication_realm = "Application" + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' # It will change confirmation, password recovery and other workflows # to behave the same regardless if the e-mail provided was right or wrong. @@ -217,7 +223,7 @@ Devise.setup do |config| # should add them to the navigational formats lists. # # The "*/*" below is required to match Internet Explorer requests. - # config.navigational_formats = ["*/*", :html] + # config.navigational_formats = ['*/*', :html] # The default HTTP method used to sign out a resource. Default is :delete. config.sign_out_via = :delete @@ -241,12 +247,12 @@ Devise.setup do |config| # is mountable, there are some extra configurations to be taken into account. # The following options are available, assuming the engine is mounted as: # - # mount MyEngine, at: "/my_engine" + # mount MyEngine, at: '/my_engine' # # The router that invoked `devise_for`, in the example above, would be: # config.router_name = :my_engine # # When using omniauth, Devise cannot automatically set Omniauth path, # so you need to do it manually. For the users scope, it would be: - # config.omniauth_path_prefix = "/my_engine/users/auth" + # config.omniauth_path_prefix = '/my_engine/users/auth' end diff --git a/test/rails_app/config/initializers/devise.rb b/test/rails_app/config/initializers/devise.rb index cd2d70e3..84031881 100644 --- a/test/rails_app/config/initializers/devise.rb +++ b/test/rails_app/config/initializers/devise.rb @@ -4,6 +4,9 @@ require "omniauth-openid" # 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| + config.secret_key = "d9eb5171c59a4c817f68b0de27b8c1e340c2341b52cdbc60d3083d4e8958532" \ + "18dcc5f589cafde048faec956b61f864b9b5513ff9ce29bf9e5d58b0f234f8e3b" + # ==> Mailer Configuration # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class with default "from" parameter.