aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xactiverecord/lib/active_record/associations.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb4
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb9
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/clean_logger.rb127
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb129
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb23
-rw-r--r--activesupport/lib/active_support/secure_random.rb197
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb34
-rw-r--r--activesupport/test/secure_random_test.rb15
-rw-r--r--railties/lib/rails_generator/generators/applications/app/app_generator.rb3
-rw-r--r--railties/lib/rails_generator/secret_key_generator.rb152
-rw-r--r--railties/lib/tasks/misc.rake7
-rw-r--r--railties/test/secret_key_generation_test.rb8
15 files changed, 424 insertions, 294 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f915daafba..4d935612ca 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1270,10 +1270,9 @@ module ActiveRecord
association.create_through_record(new_value)
self.send(reflection.name, new_value)
else
- association.replace(new_value)
+ association.replace(new_value)
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
end
-
- instance_variable_set(ivar, new_value.nil? ? nil : association)
end
define_method("set_#{reflection.name}_target") do |target|
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index c846956e1f..b78bd5d931 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -22,6 +22,10 @@ module ActiveRecord
def find_target
super.first
+ end
+
+ def reset_target!
+ @target = nil
end
end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index b61a3711e3..77e3cb1776 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -101,4 +101,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:crazy_club), members[0].sponsor_club
end
+ def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record
+ assert_nil Member.new.club
+ end
+
+ def test_assigning_association_correctly_assigns_target
+ new_member = Member.create(:name => "Chris")
+ new_member.club = new_club = Club.create(:name => "LRUG")
+ assert_equal new_club, new_member.club.target
+ end
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 8d3b136d80..0170b95b1b 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*
+* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing]
+
* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
a = (1..10).to_a
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 2500fd79fb..7e34a871e3 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -28,7 +28,6 @@ require 'active_support/callbacks'
require 'active_support/core_ext'
-require 'active_support/clean_logger'
require 'active_support/buffered_logger'
require 'active_support/gzip'
@@ -55,6 +54,8 @@ require 'active_support/base64'
require 'active_support/time_with_zone'
+require 'active_support/secure_random'
+
I18n.load_translations File.dirname(__FILE__) + '/active_support/locale/en-US.yml'
Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector')
diff --git a/activesupport/lib/active_support/clean_logger.rb b/activesupport/lib/active_support/clean_logger.rb
deleted file mode 100644
index b4c27ebc9d..0000000000
--- a/activesupport/lib/active_support/clean_logger.rb
+++ /dev/null
@@ -1,127 +0,0 @@
-require 'logger'
-require 'active_support/core_ext/class/attribute_accessors'
-
-# Extensions to the built in Ruby logger.
-#
-# If you want to use the default log formatter as defined in the Ruby core, then you
-# will need to set the formatter for the logger as in:
-#
-# logger.formatter = Formatter.new
-#
-# You can then specify the datetime format, for example:
-#
-# logger.datetime_format = "%Y-%m-%d"
-#
-# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
-class Logger
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = Logger::ERROR)
- if silencer
- begin
- old_logger_level, self.level = level, temporary_level
- yield self
- ensure
- self.level = old_logger_level
- end
- else
- yield self
- end
- end
-
- alias :old_datetime_format= :datetime_format=
- # Logging date-time format (string passed to +strftime+). Ignored if the formatter
- # does not respond to datetime_format=.
- def datetime_format=(datetime_format)
- formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
- end
-
- alias :old_datetime_format :datetime_format
- # Get the logging datetime format. Returns nil if the formatter does not support
- # datetime formatting.
- def datetime_format
- formatter.datetime_format if formatter.respond_to?(:datetime_format)
- end
-
- alias :old_formatter :formatter if method_defined?(:formatter)
- # Get the current formatter. The default formatter is a SimpleFormatter which only
- # displays the log message
- def formatter
- @formatter ||= SimpleFormatter.new
- end
-
- unless const_defined? :Formatter
- class Formatter
- Format = "%s, [%s#%d] %5s -- %s: %s\n"
-
- attr_accessor :datetime_format
-
- def initialize
- @datetime_format = nil
- end
-
- def call(severity, time, progname, msg)
- Format % [severity[0..0], format_datetime(time), $$, severity, progname,
- msg2str(msg)]
- end
-
- private
- def format_datetime(time)
- if @datetime_format.nil?
- time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
- else
- time.strftime(@datetime_format)
- end
- end
-
- def msg2str(msg)
- case msg
- when ::String
- msg
- when ::Exception
- "#{ msg.message } (#{ msg.class })\n" <<
- (msg.backtrace || []).join("\n")
- else
- msg.inspect
- end
- end
- end
- end
-
- # Simple formatter which only displays the message.
- class SimpleFormatter < Logger::Formatter
- # This method is invoked when a log event occurs
- def call(severity, timestamp, progname, msg)
- "#{String === msg ? msg : msg.inspect}\n"
- end
- end
-
- private
- alias old_format_message format_message
-
- # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
- # We can't test RUBY_VERSION because some distributions don't keep Ruby
- # and its standard library in sync, leading to installations of Ruby 1.8.2
- # with Logger from 1.8.3 and vice versa.
- if method_defined?(:formatter=)
- def format_message(severity, timestamp, progname, msg)
- formatter.call(severity, timestamp, progname, msg)
- end
- else
- def format_message(severity, timestamp, msg, progname)
- formatter.call(severity, timestamp, progname, msg)
- end
-
- attr_writer :formatter
- public :formatter=
-
- alias old_format_datetime format_datetime
- def format_datetime(datetime) datetime end
-
- alias old_msg2str msg2str
- def msg2str(msg) msg end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index 9c1fd274ac..c622554860 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -12,5 +12,132 @@ class Logger
end_eval
end
[:debug, :info, :error, :fatal].each {|level| define_around_helper(level) }
+end
-end \ No newline at end of file
+
+require 'logger'
+
+# Extensions to the built in Ruby logger.
+#
+# If you want to use the default log formatter as defined in the Ruby core, then you
+# will need to set the formatter for the logger as in:
+#
+# logger.formatter = Formatter.new
+#
+# You can then specify the datetime format, for example:
+#
+# logger.datetime_format = "%Y-%m-%d"
+#
+# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
+class Logger
+ # Set to false to disable the silencer
+ cattr_accessor :silencer
+ self.silencer = true
+
+ # Silences the logger for the duration of the block.
+ def silence(temporary_level = Logger::ERROR)
+ if silencer
+ begin
+ old_logger_level, self.level = level, temporary_level
+ yield self
+ ensure
+ self.level = old_logger_level
+ end
+ else
+ yield self
+ end
+ end
+
+ alias :old_datetime_format= :datetime_format=
+ # Logging date-time format (string passed to +strftime+). Ignored if the formatter
+ # does not respond to datetime_format=.
+ def datetime_format=(datetime_format)
+ formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
+ end
+
+ alias :old_datetime_format :datetime_format
+ # Get the logging datetime format. Returns nil if the formatter does not support
+ # datetime formatting.
+ def datetime_format
+ formatter.datetime_format if formatter.respond_to?(:datetime_format)
+ end
+
+ alias :old_formatter :formatter if method_defined?(:formatter)
+ # Get the current formatter. The default formatter is a SimpleFormatter which only
+ # displays the log message
+ def formatter
+ @formatter ||= SimpleFormatter.new
+ end
+
+ unless const_defined? :Formatter
+ class Formatter
+ Format = "%s, [%s#%d] %5s -- %s: %s\n"
+
+ attr_accessor :datetime_format
+
+ def initialize
+ @datetime_format = nil
+ end
+
+ def call(severity, time, progname, msg)
+ Format % [severity[0..0], format_datetime(time), $$, severity, progname,
+ msg2str(msg)]
+ end
+
+ private
+ def format_datetime(time)
+ if @datetime_format.nil?
+ time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
+ else
+ time.strftime(@datetime_format)
+ end
+ end
+
+ def msg2str(msg)
+ case msg
+ when ::String
+ msg
+ when ::Exception
+ "#{ msg.message } (#{ msg.class })\n" <<
+ (msg.backtrace || []).join("\n")
+ else
+ msg.inspect
+ end
+ end
+ end
+ end
+
+ # Simple formatter which only displays the message.
+ class SimpleFormatter < Logger::Formatter
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ "#{String === msg ? msg : msg.inspect}\n"
+ end
+ end
+
+ private
+ alias old_format_message format_message
+
+ # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
+ # We can't test RUBY_VERSION because some distributions don't keep Ruby
+ # and its standard library in sync, leading to installations of Ruby 1.8.2
+ # with Logger from 1.8.3 and vice versa.
+ if method_defined?(:formatter=)
+ def format_message(severity, timestamp, progname, msg)
+ formatter.call(severity, timestamp, progname, msg)
+ end
+ else
+ def format_message(severity, timestamp, msg, progname)
+ formatter.call(severity, timestamp, progname, msg)
+ end
+
+ attr_writer :formatter
+ public :formatter=
+
+ alias old_format_datetime format_datetime
+ def format_datetime(datetime) datetime end
+
+ alias old_msg2str msg2str
+ def msg2str(msg) msg end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index ea50511a96..2006cf4946 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,11 +1,32 @@
require 'date'
require 'time'
-# Ruby 1.8-cvs and 1.9 define private Time#to_date
class Time
+ # Ruby 1.8-cvs and 1.9 define private Time#to_date
%w(to_date to_datetime).each do |method|
public method if private_instance_methods.include?(method)
end
+
+ # Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are
+ # unmarshaled in the local zone, instead of utc. We're layering behavior on the _dump and _load
+ # methods so that utc instances can be flagged on dump, and coerced back to utc on load.
+ if RUBY_VERSION < '1.9'
+ class << self
+ alias_method :_original_load, :_load
+ def _load(marshaled_time)
+ time = _original_load(marshaled_time)
+ utc = time.send(:remove_instance_variable, '@marshal_with_utc_coercion')
+ utc ? time.utc : time
+ end
+ end
+
+ alias_method :_original_dump, :_dump
+ def _dump(*args)
+ obj = self.frozen? ? self.dup : self
+ obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
+ obj._original_dump(*args)
+ end
+ end
end
require 'active_support/core_ext/time/behavior'
diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb
new file mode 100644
index 0000000000..688165f9a3
--- /dev/null
+++ b/activesupport/lib/active_support/secure_random.rb
@@ -0,0 +1,197 @@
+begin
+ require 'openssl'
+rescue LoadError
+end
+
+begin
+ require 'securerandom'
+rescue LoadError
+end
+
+module ActiveSupport
+ if defined?(::SecureRandom)
+ # Use Ruby 1.9's SecureRandom library whenever possible.
+ SecureRandom = ::SecureRandom # :nodoc:
+ else
+ # = Secure random number generator interface.
+ #
+ # This library is an interface for secure random number generator which is
+ # suitable for generating session key in HTTP cookies, etc.
+ #
+ # It supports following secure random number generators.
+ #
+ # * openssl
+ # * /dev/urandom
+ # * Win32
+ #
+ # *Note*: This module is based on the SecureRandom library from Ruby 1.9,
+ # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's
+ # SecureRandom library.
+ #
+ # == Example
+ #
+ # # random hexadecimal string.
+ # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
+ # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
+ # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
+ # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
+ # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
+ # ...
+ #
+ # # random base64 string.
+ # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
+ # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
+ # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
+ # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
+ # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
+ # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
+ # ...
+ #
+ # # random binary string.
+ # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
+ # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
+ # ...
+ module SecureRandom
+ # SecureRandom.random_bytes generates a random binary string.
+ #
+ # The argument n specifies the length of the result string.
+ #
+ # If n is not specified, 16 is assumed.
+ # It may be larger in future.
+ #
+ # If secure random number generator is not available,
+ # NotImplementedError is raised.
+ def self.random_bytes(n=nil)
+ n ||= 16
+
+ if defined? OpenSSL::Random
+ return OpenSSL::Random.random_bytes(n)
+ end
+
+ if !defined?(@has_urandom) || @has_urandom
+ flags = File::RDONLY
+ flags |= File::NONBLOCK if defined? File::NONBLOCK
+ flags |= File::NOCTTY if defined? File::NOCTTY
+ flags |= File::NOFOLLOW if defined? File::NOFOLLOW
+ begin
+ File.open("/dev/urandom", flags) {|f|
+ unless f.stat.chardev?
+ raise Errno::ENOENT
+ end
+ @has_urandom = true
+ ret = f.readpartial(n)
+ if ret.length != n
+ raise NotImplementedError, "Unexpected partial read from random device"
+ end
+ return ret
+ }
+ rescue Errno::ENOENT
+ @has_urandom = false
+ end
+ end
+
+ if !defined?(@has_win32)
+ begin
+ require 'Win32API'
+
+ crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
+ @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
+
+ hProvStr = " " * 4
+ prov_rsa_full = 1
+ crypt_verifycontext = 0xF0000000
+
+ if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
+ raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
+ end
+ @hProv, = hProvStr.unpack('L')
+
+ @has_win32 = true
+ rescue LoadError
+ @has_win32 = false
+ end
+ end
+ if @has_win32
+ bytes = " " * n
+ if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
+ raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
+ end
+ return bytes
+ end
+
+ raise NotImplementedError, "No random device"
+ end
+
+ # SecureRandom.hex generates a random hex string.
+ #
+ # The argument n specifies the length of the random length.
+ # The length of the result string is twice of n.
+ #
+ # If n is not specified, 16 is assumed.
+ # It may be larger in future.
+ #
+ # If secure random number generator is not available,
+ # NotImplementedError is raised.
+ def self.hex(n=nil)
+ random_bytes(n).unpack("H*")[0]
+ end
+
+ # SecureRandom.base64 generates a random base64 string.
+ #
+ # The argument n specifies the length of the random length.
+ # The length of the result string is about 4/3 of n.
+ #
+ # If n is not specified, 16 is assumed.
+ # It may be larger in future.
+ #
+ # If secure random number generator is not available,
+ # NotImplementedError is raised.
+ def self.base64(n=nil)
+ [random_bytes(n)].pack("m*").delete("\n")
+ end
+
+ # SecureRandom.random_number generates a random number.
+ #
+ # If an positive integer is given as n,
+ # SecureRandom.random_number returns an integer:
+ # 0 <= SecureRandom.random_number(n) < n.
+ #
+ # If 0 is given or an argument is not given,
+ # SecureRandom.random_number returns an float:
+ # 0.0 <= SecureRandom.random_number() < 1.0.
+ def self.random_number(n=0)
+ if 0 < n
+ hex = n.to_s(16)
+ hex = '0' + hex if (hex.length & 1) == 1
+ bin = [hex].pack("H*")
+ mask = bin[0].ord
+ mask |= mask >> 1
+ mask |= mask >> 2
+ mask |= mask >> 4
+ begin
+ rnd = SecureRandom.random_bytes(bin.length)
+ rnd[0] = (rnd[0].ord & mask).chr
+ end until rnd < bin
+ rnd.unpack("H*")[0].hex
+ else
+ # assumption: Float::MANT_DIG <= 64
+ i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
+ Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
+ end
+ end
+
+ # Following code is based on David Garamond's GUID library for Ruby.
+ def self.lastWin32ErrorMessage # :nodoc:
+ get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
+ format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
+ format_message_ignore_inserts = 0x00000200
+ format_message_from_system = 0x00001000
+
+ code = get_last_error.call
+ msg = "\0" * 1024
+ len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
+ msg[0, len].tr("\r", '').chomp
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 8740497b3d..4749950f25 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -625,3 +625,37 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
+
+class TimeExtMarshalingTest < Test::Unit::TestCase
+ def test_marshaling_with_utc_instance
+ t = Time.utc(2000)
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_local_instance
+ t = Time.local(2000)
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_frozen_utc_instance
+ t = Time.utc(2000).freeze
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+
+ def test_marshaling_with_frozen_local_instance
+ t = Time.local(2000).freeze
+ marshaled = Marshal.dump t
+ unmarshaled = Marshal.load marshaled
+ assert_equal t, unmarshaled
+ assert_equal t.zone, unmarshaled.zone
+ end
+end
diff --git a/activesupport/test/secure_random_test.rb b/activesupport/test/secure_random_test.rb
new file mode 100644
index 0000000000..b0b6c21a81
--- /dev/null
+++ b/activesupport/test/secure_random_test.rb
@@ -0,0 +1,15 @@
+require 'abstract_unit'
+
+class SecureRandomTest < Test::Unit::TestCase
+ def test_random_bytes
+ b1 = ActiveSupport::SecureRandom.random_bytes(64)
+ b2 = ActiveSupport::SecureRandom.random_bytes(64)
+ assert_not_equal b1, b2
+ end
+
+ def test_hex
+ b1 = ActiveSupport::SecureRandom.hex(64)
+ b2 = ActiveSupport::SecureRandom.hex(64)
+ assert_not_equal b1, b2
+ end
+end
diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
index 80e8eabfd3..9849948339 100644
--- a/railties/lib/rails_generator/generators/applications/app/app_generator.rb
+++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -1,6 +1,5 @@
require 'rbconfig'
require 'digest/md5'
-require 'rails_generator/secret_key_generator'
class AppGenerator < Rails::Generator::Base
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
@@ -36,7 +35,7 @@ class AppGenerator < Rails::Generator::Base
md5 << @app_name
# Do our best to generate a secure secret key for CookieStore
- secret = Rails::SecretKeyGenerator.new(@app_name).generate_secret
+ secret = ActiveSupport::SecureRandom.hex(64)
record do |m|
# Root directory and all subdirectories.
diff --git a/railties/lib/rails_generator/secret_key_generator.rb b/railties/lib/rails_generator/secret_key_generator.rb
index 5ae492312e..553811d35d 100644
--- a/railties/lib/rails_generator/secret_key_generator.rb
+++ b/railties/lib/rails_generator/secret_key_generator.rb
@@ -5,160 +5,18 @@ module Rails
#
# generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name")
# generator.generate_secret # => "f3f1be90053fa851... (some long string)"
+ #
+ # This class is *deprecated* in Rails 2.2 in favor of ActiveSupport::SecureRandom.
+ # It is currently a wrapper around ActiveSupport::SecureRandom.
class SecretKeyGenerator
- GENERATORS = [ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze
-
def initialize(identifier)
- @identifier = identifier
end
# Generate a random secret key with the best possible method available on
# the current platform.
def generate_secret
- generator = GENERATORS.find do |g|
- self.class.send("supports_#{g}?")
- end
- send("generate_secret_with_#{generator}")
- end
-
- # Generate a random secret key by using the Win32 API. Raises LoadError
- # if the current platform cannot make use of the Win32 API. Raises
- # SystemCallError if some other error occurred.
- def generate_secret_with_win32_api
- # Following code is based on David Garamond's GUID library for Ruby.
- require 'Win32API'
-
- crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext",
- 'PPPII', 'L')
- crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",
- 'LIP', 'L')
- crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext",
- 'LI', 'L')
- prov_rsa_full = 1
- crypt_verifycontext = 0xF0000000
-
- hProvStr = " " * 4
- if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full,
- crypt_verifycontext) == 0
- raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
- end
- hProv, = hProvStr.unpack('L')
- bytes = " " * 64
- if crypt_gen_random.call(hProv, bytes.size, bytes) == 0
- raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
- end
- if crypt_release_context.call(hProv, 0) == 0
- raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}"
- end
- bytes.unpack("H*")[0]
- end
-
- # Generate a random secret key with Ruby 1.9's SecureRandom module.
- # Raises LoadError if the current Ruby version does not support
- # SecureRandom.
- def generate_secret_with_secure_random
- require 'securerandom'
- return SecureRandom.hex(64)
- end
-
- # Generate a random secret key with OpenSSL. If OpenSSL is not
- # already loaded, then this method will attempt to load it.
- # LoadError will be raised if that fails.
- def generate_secret_with_openssl
- require 'openssl'
- if !File.exist?("/dev/urandom")
- # OpenSSL transparently seeds the random number generator with
- # data from /dev/urandom. On platforms where that is not
- # available, such as Windows, we have to provide OpenSSL with
- # our own seed. Unfortunately there's no way to provide a
- # secure seed without OS support, so we'll have to do with
- # rand() and Time.now.usec().
- OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
- end
- data = OpenSSL::BN.rand(2048, -1, false).to_s
-
- if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
- OpenSSL::Digest::SHA512.new(data).hexdigest
- else
- generate_secret_with_prng
- end
+ ActiveSupport::SecureRandom.hex(64)
end
-
- # Generate a random secret key with /dev/urandom.
- # Raises SystemCallError on failure.
- def generate_secret_with_urandom
- return File.read("/dev/urandom", 64).unpack("H*")[0]
- end
-
- # Generate a random secret key with Ruby's pseudo random number generator,
- # as well as some environment information.
- #
- # This is the least cryptographically secure way to generate a secret key,
- # and should be avoided whenever possible.
- def generate_secret_with_prng
- require 'digest/sha2'
- sha = Digest::SHA2.new(512)
- now = Time.now
- sha << now.to_s
- sha << String(now.usec)
- sha << String(rand(0))
- sha << String($$)
- sha << @identifier
- return sha.hexdigest
- end
-
- private
- def lastWin32ErrorMessage
- # Following code is based on David Garamond's GUID library for Ruby.
- get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
- format_message = Win32API.new("kernel32", "FormatMessageA",
- 'LPLLPLPPPPPPPP', 'L')
- format_message_ignore_inserts = 0x00000200
- format_message_from_system = 0x00001000
-
- code = get_last_error.call
- msg = "\0" * 1024
- len = format_message.call(format_message_ignore_inserts +
- format_message_from_system, 0,
- code, 0, msg, 1024, nil, nil,
- nil, nil, nil, nil, nil, nil)
- msg[0, len].tr("\r", '').chomp
- end
-
- def self.supports_secure_random?
- begin
- require 'securerandom'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_win32_api?
- return false unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
- begin
- require 'Win32API'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_urandom?
- File.exist?('/dev/urandom')
- end
-
- def self.supports_openssl?
- begin
- require 'openssl'
- true
- rescue LoadError
- false
- end
- end
-
- def self.supports_prng?
- true
- end
+ deprecate :generate_secret=>"You should use ActiveSupport::SecureRandom.hex(64)"
end
end
diff --git a/railties/lib/tasks/misc.rake b/railties/lib/tasks/misc.rake
index 33bbba1101..5c99725203 100644
--- a/railties/lib/tasks/misc.rake
+++ b/railties/lib/tasks/misc.rake
@@ -3,10 +3,9 @@ task :environment do
require(File.join(RAILS_ROOT, 'config', 'environment'))
end
-require 'rails_generator/secret_key_generator'
-desc 'Generate a crytographically secure secret key. This is typically used to generate a secret for cookie sessions. Pass a unique identifier to the generator using ID="some unique identifier" for greater security.'
+desc 'Generate a crytographically secure secret key. This is typically used to generate a secret for cookie sessions.'
task :secret do
- puts Rails::SecretKeyGenerator.new(ENV['ID']).generate_secret
+ puts ActiveSupport::SecureRandom.hex(64)
end
require 'active_support'
@@ -54,4 +53,4 @@ namespace :time do
puts "\n"
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/secret_key_generation_test.rb b/railties/test/secret_key_generation_test.rb
index ea1b0dae31..2a04cff856 100644
--- a/railties/test/secret_key_generation_test.rb
+++ b/railties/test/secret_key_generation_test.rb
@@ -33,12 +33,4 @@ class SecretKeyGenerationTest < Test::Unit::TestCase
def test_secret_key_generation
assert @generator.generate_secret.length >= SECRET_KEY_MIN_LENGTH
end
-
- Rails::SecretKeyGenerator::GENERATORS.each do |generator|
- if Rails::SecretKeyGenerator.send("supports_#{generator}?")
- define_method("test_secret_key_generation_with_#{generator}") do
- assert @generator.send("generate_secret_with_#{generator}").length >= SECRET_KEY_MIN_LENGTH
- end
- end
- end
end