yattr_encrypted 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mdown +37 -15
- data/lib/yattr_encrypted/version.rb +1 -1
- data/lib/yattr_encrypted.rb +12 -48
- data/test/yattr_encrypted_test.rb +11 -1
- metadata +5 -5
    
        data/README.mdown
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # YattrEncrypted #
         | 
| 2 2 |  | 
| 3 | 
            -
            Version: 0.1. | 
| 3 | 
            +
            Version: 0.1.3  (but you should check lib/yattr_encrypted/version.rb to be sure)
         | 
| 4 4 |  | 
| 5 5 | 
             
            ## Applicability ##
         | 
| 6 6 |  | 
| @@ -53,7 +53,7 @@ random *iv* and including it in the encrypted data. See *openssl* documentation | |
| 53 53 | 
             
            for details [OpenSSL::Cipher]
         | 
| 54 54 | 
             
            * detects when fields are modified by actions other than assignment. This supports
         | 
| 55 55 | 
             
            encrypting complex types - such as hashes and arrays. This is implemented by adding
         | 
| 56 | 
            -
             | 
| 56 | 
            +
            a `before_save` calleback to the private method *yattr_update_encrypted_values*
         | 
| 57 57 | 
             
            * Rails 3.1 & Rails 3.2 - doesn't pretend to support anything lower (but it might work)
         | 
| 58 58 |  | 
| 59 59 |  | 
| @@ -132,18 +132,40 @@ serialized as JSON objects. This is to avoid dealing with any database idiodicy | |
| 132 132 | 
             
            and to transparently handle complex data types being used in database fields [such as
         | 
| 133 133 | 
             
            Hashes and Arrays].
         | 
| 134 134 |  | 
| 135 | 
            -
            The encoding  | 
| 135 | 
            +
            The encoding code for a field is:
         | 
| 136 136 |  | 
| 137 | 
            -
                 | 
| 138 | 
            -
                 | 
| 139 | 
            -
                 | 
| 140 | 
            -
                 | 
| 137 | 
            +
                cipher = OpenSSL::Cipher.new(ALGORITHM)
         | 
| 138 | 
            +
                cipher.encrypt
         | 
| 139 | 
            +
                cipher.key = key
         | 
| 140 | 
            +
                iv = cipher.random_iv   # ask OpenSSL for a new, random initial value
         | 
| 141 141 |  | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
                 | 
| 146 | 
            -
                 | 
| 147 | 
            -
                 | 
| 148 | 
            -
             | 
| 149 | 
            -
                 | 
| 142 | 
            +
                # jsonify data
         | 
| 143 | 
            +
                value_marshalled = Marshal.dump value
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                # encrypt data
         | 
| 146 | 
            +
                result = cipher.update value_marshalled
         | 
| 147 | 
            +
                result << cipher.final
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                # return encrypted data and iv
         | 
| 150 | 
            +
                Base64.encode64(("%04d" % iv.length) + iv + result)
         | 
| 151 | 
            +
                    
         | 
| 152 | 
            +
            The decoding code is:
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                 # initialize decryptor
         | 
| 155 | 
            +
                cipher = OpenSSL::Cipher.new(ALGORITHM)
         | 
| 156 | 
            +
                cipher.decrypt
         | 
| 157 | 
            +
                cipher.key = key
         | 
| 158 | 
            +
              
         | 
| 159 | 
            +
                # extract encrypted_value
         | 
| 160 | 
            +
                encrypted_value = Base64.decode64 marshalled_value
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                # extract and set iv
         | 
| 163 | 
            +
                iv_end = encrypted_value[0..3].to_i + 3
         | 
| 164 | 
            +
                cipher.iv = encrypted_value[4..(iv_end)]
         | 
| 165 | 
            +
                encrypted_value = encrypted_value[(iv_end+1)..-1]
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                # derypte and return
         | 
| 168 | 
            +
                result = cipher.update(encrypted_value)
         | 
| 169 | 
            +
                result << cipher.final
         | 
| 170 | 
            +
              
         | 
| 171 | 
            +
                Marshal.load result
         | 
    
        data/lib/yattr_encrypted.rb
    CHANGED
    
    | @@ -67,11 +67,14 @@ module YattrEncrypted | |
| 67 67 |  | 
| 68 68 | 
             
                    # tell self to define instance methods from the database if they have not already been generated
         | 
| 69 69 | 
             
                    define_attribute_methods unless attribute_methods_generated?
         | 
| 70 | 
            +
                    
         | 
| 71 | 
            +
                    # schedule yattr_update_encrypted_values before save
         | 
| 72 | 
            +
                    self.send :before_save, :yate_update_encrypted_values unless self.yate_encrypted_attributes
         | 
| 70 73 |  | 
| 71 74 | 
             
                    # collect existing instance methods
         | 
| 72 75 | 
             
                    instance_methods_as_symbols = instance_methods.map { |method| method.to_sym }
         | 
| 73 76 |  | 
| 74 | 
            -
                    # iterate through attributes
         | 
| 77 | 
            +
                    # iterate through attributes and create accessors, verify encryped accessors exist
         | 
| 75 78 | 
             
                    attributes.map { |x| x.to_sym }.each do |attribute|
         | 
| 76 79 | 
             
                      encrypted_attribute_name = [options[:prefix], attribute, options[:suffix]].join.to_sym
         | 
| 77 80 |  | 
| @@ -82,14 +85,13 @@ module YattrEncrypted | |
| 82 85 | 
             
                          unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
         | 
| 83 86 |  | 
| 84 87 | 
             
                      tmp =<<-XXX
         | 
| 85 | 
            -
                      puts "defining #{attribute}"
         | 
| 86 88 | 
             
                      def #{attribute}
         | 
| 87 89 | 
             
                        unless @#{attribute} && !@#{attribute}.empty?
         | 
| 88 90 | 
             
                          options = yate_encrypted_attributes[:#{attribute}]
         | 
| 89 91 | 
             
                          @#{attribute} = #{encrypted_attribute_name} ? \
         | 
| 90 92 | 
             
                              yate_decrypt(#{encrypted_attribute_name}, options[:key]) : \
         | 
| 91 93 | 
             
                              ''
         | 
| 92 | 
            -
                          self.yate_checksums[:#{attribute}] =  | 
| 94 | 
            +
                          self.yate_checksums[:#{attribute}] = yate_attribute_hash_value(:#{attribute})
         | 
| 93 95 | 
             
                          self.yate_dirty[:#{attribute}] = true
         | 
| 94 96 | 
             
                        end
         | 
| 95 97 | 
             
                        @#{attribute}
         | 
| @@ -98,12 +100,11 @@ module YattrEncrypted | |
| 98 100 | 
             
                      class_eval(tmp)
         | 
| 99 101 |  | 
| 100 102 | 
             
                      tmp =<<-XXX
         | 
| 101 | 
            -
                      puts "self: #{self}"
         | 
| 102 103 | 
             
                      def #{attribute}= value
         | 
| 103 104 | 
             
                        @#{attribute} = value
         | 
| 104 105 | 
             
                        options = yate_encrypted_attributes[:#{attribute}]
         | 
| 105 106 | 
             
                        self.#{encrypted_attribute_name} = yate_encrypt(value, options[:key])
         | 
| 106 | 
            -
                        self.yate_checksums[:#{attribute}] =  | 
| 107 | 
            +
                        self.yate_checksums[:#{attribute}] = yate_attribute_hash_value(:#{attribute})
         | 
| 107 108 | 
             
                        self.yate_dirty[:#{attribute}] = true
         | 
| 108 109 | 
             
                      end
         | 
| 109 110 | 
             
                      XXX
         | 
| @@ -123,44 +124,6 @@ module YattrEncrypted | |
| 123 124 | 
             
                end
         | 
| 124 125 | 
             
              end
         | 
| 125 126 |  | 
| 126 | 
            -
              # Checks if an attribute is configured with <tt>yattr_encrypted</tt>
         | 
| 127 | 
            -
              def yattr_encrypted?(attribute)
         | 
| 128 | 
            -
                self.class.yate_encrypted_attributes.has_key?(attribute.to_sym)
         | 
| 129 | 
            -
              end
         | 
| 130 | 
            -
             | 
| 131 | 
            -
              def save *args
         | 
| 132 | 
            -
                yate_update_encrypted_values
         | 
| 133 | 
            -
                super
         | 
| 134 | 
            -
              end
         | 
| 135 | 
            -
              
         | 
| 136 | 
            -
              def save! *args
         | 
| 137 | 
            -
                yate_update_encrypted_values
         | 
| 138 | 
            -
                super
         | 
| 139 | 
            -
              end
         | 
| 140 | 
            -
              
         | 
| 141 | 
            -
              def update_attribute attribute, value
         | 
| 142 | 
            -
                if (options = yate_encrypted_attributes[attribute])
         | 
| 143 | 
            -
                  self.send "#{attribute}=".to_sym, value
         | 
| 144 | 
            -
                  update_attribute options[:attribute], self.send(options[:attribute]) if yate_field_changed? attribute
         | 
| 145 | 
            -
                else
         | 
| 146 | 
            -
                  super
         | 
| 147 | 
            -
                end
         | 
| 148 | 
            -
              end
         | 
| 149 | 
            -
             | 
| 150 | 
            -
              def update_attributes params, options = {}
         | 
| 151 | 
            -
                tmp = {}
         | 
| 152 | 
            -
                params.keys.each do |attribute|
         | 
| 153 | 
            -
                  if (options = yate_encrypted_attributes[attribute])
         | 
| 154 | 
            -
                    self.send "#{attribute}=", params[attribute]
         | 
| 155 | 
            -
                    tmp[options[:attribute]] = self.send options[:attribute]
         | 
| 156 | 
            -
                  else
         | 
| 157 | 
            -
                    tmp[attribute] = params[attribute]
         | 
| 158 | 
            -
                  end
         | 
| 159 | 
            -
                end
         | 
| 160 | 
            -
                params = tmp
         | 
| 161 | 
            -
                super
         | 
| 162 | 
            -
              end
         | 
| 163 | 
            -
             | 
| 164 127 | 
             
              # protected methods - nobody needs to use these outside of the model
         | 
| 165 128 | 
             
              protected
         | 
| 166 129 |  | 
| @@ -184,7 +147,7 @@ module YattrEncrypted | |
| 184 147 | 
             
                cipher = OpenSSL::Cipher.new(ALGORITHM)
         | 
| 185 148 | 
             
                cipher.encrypt
         | 
| 186 149 | 
             
                cipher.key = key
         | 
| 187 | 
            -
                iv = cipher.random_iv
         | 
| 150 | 
            +
                iv = cipher.random_iv   # ask OpenSSL for a new, random initial value
         | 
| 188 151 |  | 
| 189 152 | 
             
                # jsonify data
         | 
| 190 153 | 
             
                value_marshalled = Marshal.dump value
         | 
| @@ -220,19 +183,20 @@ module YattrEncrypted | |
| 220 183 | 
             
              end
         | 
| 221 184 |  | 
| 222 185 | 
             
              # support for fields which are not atomic values
         | 
| 223 | 
            -
              def  | 
| 186 | 
            +
              def yate_attribute_hash_value(attribute)
         | 
| 224 187 | 
             
                attribute = attribute.to_s if Symbol === attribute
         | 
| 225 188 | 
             
                OpenSSL::HMAC.digest('md5', 'ersatz key', Marshal.dump(self.instance_variable_get("@#{attribute}")))
         | 
| 226 189 | 
             
              end
         | 
| 227 190 |  | 
| 228 | 
            -
              def  | 
| 191 | 
            +
              def yate_attribute_changed?(attribute)
         | 
| 229 192 | 
             
                attribute = attribute.to_sym unless Symbol === attribute
         | 
| 230 | 
            -
                 | 
| 193 | 
            +
                yate_attribute_hash_value(attribute) != self.yate_checksums[attribute] || self.yate_dirty[attribute]
         | 
| 231 194 | 
             
              end
         | 
| 232 195 |  | 
| 233 196 | 
             
              def yate_update_encrypted_values
         | 
| 197 | 
            +
                return unless yate_encrypted_attributes
         | 
| 234 198 | 
             
                yate_encrypted_attributes.each do |attribute, options|
         | 
| 235 | 
            -
                  if  | 
| 199 | 
            +
                  if yate_attribute_changed?(attribute)
         | 
| 236 200 | 
             
                    self.send "#{options[:attribute]}=".to_sym, yate_encrypt(self.send(attribute), options[:key])
         | 
| 237 201 | 
             
                    yate_dirty.delete(attribute)
         | 
| 238 202 | 
             
                  end
         | 
| @@ -10,6 +10,11 @@ module ActiveRecord | |
| 10 10 | 
             
                def self.attribute_methods_generated?
         | 
| 11 11 | 
             
                  true
         | 
| 12 12 | 
             
                end
         | 
| 13 | 
            +
                
         | 
| 14 | 
            +
                def self.before_save *methods
         | 
| 15 | 
            +
                  @before_save_hooks ||= []
         | 
| 16 | 
            +
                  @before_save_hooks += methods
         | 
| 17 | 
            +
                end
         | 
| 13 18 |  | 
| 14 19 | 
             
                def save
         | 
| 15 20 | 
             
                  true
         | 
| @@ -39,6 +44,11 @@ class TestYattrEncrypted < MiniTest::Unit::TestCase | |
| 39 44 | 
             
                @sc = SomeClass.new
         | 
| 40 45 | 
             
              end
         | 
| 41 46 |  | 
| 47 | 
            +
              def test_before_save_hook
         | 
| 48 | 
            +
                assert SomeClass.instance_variable_get(:@before_save_hooks).include?(:yate_update_encrypted_values), \
         | 
| 49 | 
            +
                    "before_save_hooks should include :yate_encrypted_attributes"
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 42 52 | 
             
              def test_yattr_encrypted_should_create_accessors
         | 
| 43 53 | 
             
                assert @sc.respond_to?(:field), "a SomeClass instance should respond to :field"
         | 
| 44 54 | 
             
                assert @sc.respond_to?(:field=), "a SomeClass instance should respond to :field="
         | 
| @@ -46,7 +56,7 @@ class TestYattrEncrypted < MiniTest::Unit::TestCase | |
| 46 56 | 
             
                assert @sc.respond_to?(:yate_encrypted_attributes), "a SomeClass instance should respond to :yate_encrypted_attributes"
         | 
| 47 57 | 
             
              end
         | 
| 48 58 |  | 
| 49 | 
            -
              def  | 
| 59 | 
            +
              def test_assigning_attribute_should_assign_attribute_encrypted
         | 
| 50 60 | 
             
                assert_nil @sc.field_encrypted, "field_encrypted should be nil prior to assignment to field"
         | 
| 51 61 | 
             
                @sc.field = 'a field value'
         | 
| 52 62 | 
             
                refute_nil @sc.field_encrypted, "field_encrypted should not be nil"
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: yattr_encrypted
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.3
         | 
| 5 5 | 
             
              prerelease: 
         | 
| 6 6 | 
             
            platform: ruby
         | 
| 7 7 | 
             
            authors:
         | 
| @@ -9,11 +9,11 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date: 2012-03- | 
| 12 | 
            +
            date: 2012-03-19 00:00:00.000000000Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: pry
         | 
| 16 | 
            -
              requirement: & | 
| 16 | 
            +
              requirement: &2151767260 !ruby/object:Gem::Requirement
         | 
| 17 17 | 
             
                none: false
         | 
| 18 18 | 
             
                requirements:
         | 
| 19 19 | 
             
                - - ! '>='
         | 
| @@ -21,7 +21,7 @@ dependencies: | |
| 21 21 | 
             
                    version: '0'
         | 
| 22 22 | 
             
              type: :development
         | 
| 23 23 | 
             
              prerelease: false
         | 
| 24 | 
            -
              version_requirements: * | 
| 24 | 
            +
              version_requirements: *2151767260
         | 
| 25 25 | 
             
            description: Generates yattr_accessors that encrypt and decrypt attributes transparently.
         | 
| 26 26 | 
             
              Based on attr_encrypted by Sean Huber [https://github.com/shuber]
         | 
| 27 27 | 
             
            email: mike@clove.com
         | 
| @@ -56,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 56 56 | 
             
                  version: '0'
         | 
| 57 57 | 
             
                  segments:
         | 
| 58 58 | 
             
                  - 0
         | 
| 59 | 
            -
                  hash: - | 
| 59 | 
            +
                  hash: -3636252519896888946
         | 
| 60 60 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 61 61 | 
             
              none: false
         | 
| 62 62 | 
             
              requirements:
         |