Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #7 from multiplegeorges/master

Add :charset option for forcing decrypted string charset.
  • Loading branch information...
commit 8875f8486f57175bf5f4ba2620dc3590601b8981 2 parents 795e88f + 5984442
@danpal authored
Showing with 50 additions and 4 deletions.
  1. +9 −1 README.md
  2. +13 −3 lib/attr_encryptor.rb
  3. +28 −0 test/attr_encrypted_test.rb
View
10 README.md
@@ -15,7 +15,7 @@ It works with ANY class, however, you get a few extra features when you're using
Encrypting attributes has never been easier:
-### You database
+### Your database
add a `encrypted_ssn`, `encrypted_ssn_salt`, `encrypted_ssn_iv`. All of
them will be populated automatically
@@ -243,6 +243,14 @@ encrypted string. I've had this problem myself using MySQL. You can simply pass
The default encoding is `m*` (base64). You can change this by setting `:encode => 'some encoding'`. See the `Array#pack` method at http://www.ruby-doc.org/core/classes/Array.html#M002245 for more encoding options.
+### String Encoding/Charset
+
+If you are trying to store UTF-8 (or other non-english charsets) strings in your encrypted parameters, you may get the decrypted string back as a US-ASCII or ASCII-8BIT string.
+
+To fix this, add the `:charset => 'UTF-8'` option to your encrypted attribute definitions.
+This option defaults to whatever your `Encoding.default_internal` is set to. `:charset => :default` will force it to use the default value.
+
+This option only applies to encrypted strings and will be ignored when encrypting marshalled objects.
### Marshaling
View
16 lib/attr_encryptor.rb
@@ -69,6 +69,8 @@ def self.extended(base) # :nodoc:
# :unless => Attributes are only encrypted if this option evaluates to false. If you pass a symbol representing an instance
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
# Defaults to false.
+ # :charset => Forces the decrypted string to be interpreted as the specified encoding. Does not change the underlying bits.
+ # Use :default to use Ruby's default encoding.
#
# You can specify your own default options
#
@@ -111,10 +113,12 @@ def attr_encrypted(*attributes)
:load_method => 'load',
:encryptor => Encryptor,
:encrypt_method => 'encrypt',
- :decrypt_method => 'decrypt'
+ :decrypt_method => 'decrypt',
+ :charset => :default
}.merge!(attr_encrypted_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
options[:encode] = options[:default_encoding] if options[:encode] == true
+ options[:charset] = Encoding.default_internal if options[:charset] == :default
attributes.each do |attribute|
encrypted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym
@@ -195,10 +199,16 @@ def decrypt(attribute, encrypted_value, options = {})
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
value = options[:encryptor].send(options[:decrypt_method], options.merge!(:value => encrypted_value))
value = options[:marshaler].send(options[:load_method], value) if options[:marshal]
- value
+ return_value = value
else
- encrypted_value
+ return_value = encrypted_value
end
+
+ if RUBY_VERSION > '1.9' && options[:charset].present? && return_value.present? && return_value.is_a?(String)
+ return_value.force_encoding(options[:charset])
+ end
+
+ return_value
end
# Encrypts a value for the attribute specified
View
28 test/attr_encrypted_test.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require File.expand_path('../test_helper', __FILE__)
class SillyEncryptor
@@ -26,6 +27,10 @@ class User
attr_encrypted :with_false_unless, :key => 'secret key', :unless => false
attr_encrypted :with_if_changed, :key => 'secret key', :if => :should_encrypt
+ attr_encrypted :utf8, :key => 'secret key', :charset => 'UTF-8'
+ attr_encrypted :default_enc, :key => 'secret key'
+ attr_encrypted :us_ascii, :key => 'secret key', :charset => 'US-ASCII'
+
attr_encryptor :aliased, :key => 'secret_key'
attr_accessor :salt
@@ -269,6 +274,29 @@ def test_should_cast_values_as_strings_before_encrypting
assert_equal '3', User.decrypt_email(string_encrypted_email)
end
+ def test_should_force_utf8_charset_on_decrypted_string
+ utf8_str = 'thïs should bé UTF-8'.force_encoding('UTF-8')
+ encrypted_utf8 = User.encrypt_utf8(utf8_str)
+ decrypted_utf8 = User.decrypt_utf8(encrypted_utf8)
+ assert_equal decrypted_utf8.encoding, Encoding::UTF_8
+ end
+
+ def test_should_force_default_encoding_on_decrypted_string
+ Encoding.default_internal = 'ASCII-8BIT' #Provide a default.
+ default_str = 'this is a default encoding'
+ encrypted_def = User.encrypt_default_enc(default_str)
+ decrypted_def = User.decrypt_default_enc(encrypted_def)
+ assert_equal decrypted_def.encoding, Encoding.default_internal
+ Encoding.default_internal = nil
+ end
+
+ def test_should_force_ascii_charset_on_decrypted_string
+ ascii_str = 'this should be US-ASCII'.force_encoding('US-ASCII')
+ encrypted_ascii = User.encrypt_us_ascii(ascii_str)
+ decrypted_ascii = User.decrypt_us_ascii(encrypted_ascii)
+ assert_equal decrypted_ascii.encoding, Encoding::US_ASCII
+ end
+
def test_should_create_query_accessor
@user = User.new
assert !@user.email?
Please sign in to comment.
Something went wrong with that request. Please try again.