diff options
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 5 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/has_one_through_association.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/associations/has_one_through_associations_test.rb | 9 | ||||
-rw-r--r-- | activesupport/CHANGELOG | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support.rb | 3 | ||||
-rw-r--r-- | activesupport/lib/active_support/clean_logger.rb | 127 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/logger.rb | 129 | ||||
-rw-r--r-- | activesupport/lib/active_support/core_ext/time.rb | 23 | ||||
-rw-r--r-- | activesupport/lib/active_support/secure_random.rb | 197 | ||||
-rw-r--r-- | activesupport/test/core_ext/time_ext_test.rb | 34 | ||||
-rw-r--r-- | activesupport/test/secure_random_test.rb | 15 | ||||
-rw-r--r-- | railties/lib/rails_generator/generators/applications/app/app_generator.rb | 3 | ||||
-rw-r--r-- | railties/lib/rails_generator/secret_key_generator.rb | 152 | ||||
-rw-r--r-- | railties/lib/tasks/misc.rake | 7 | ||||
-rw-r--r-- | railties/test/secret_key_generation_test.rb | 8 |
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 |