Changeset 446 for elixir

Show
Ignore:
Timestamp:
02/02/09 17:25:51 (3 years ago)
Author:
ged
Message:

- Fixed encrypted extension to not encrypt several times an instance attributes

when that instance is flushed several times before being expunged from the
session.

- added note about how the encrypted extension works

Location:
elixir/trunk
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • elixir/trunk/elixir/ext/encrypted.py

    r440 r446  
    2525ssn columns on save, update, and load.  Different secrets can be specified on 
    2626an entity by entity basis, for added security. 
     27 
     28**Important note**: instance attributes are encrypted in-place. This means that 
     29if one of the encrypted attributes of an instance is accessed after the 
     30instance has been flushed to the database (and thus encrypted), the value for 
     31that attribute will be crypted in the in-memory object in addition to the 
     32database row. 
    2733''' 
    2834 
    29 from Crypto.Cipher          import Blowfish 
    30 from elixir.statements      import Statement 
    31 from sqlalchemy.orm         import MapperExtension, EXT_CONTINUE 
     35from Crypto.Cipher import Blowfish 
     36from elixir.statements import Statement 
     37from sqlalchemy.orm import MapperExtension, EXT_CONTINUE, EXT_STOP 
    3238 
    3339try: 
     
    6268    def __init__(self, entity, for_fields=[], with_secret='abcdef'): 
    6369 
    64         def perform_encryption(instance, decrypt=False): 
     70        def perform_encryption(instance, encrypt=True): 
     71            encrypted = getattr(instance, '_elixir_encrypted', None) 
     72            if encrypted is encrypt: 
     73                # skipping encryption or decryption, as it is already done 
     74                return 
     75            else: 
     76                # marking instance as already encrypted/decrypted 
     77                instance._elixir_encrypted = encrypt 
     78 
     79            if encrypt: 
     80                func = encrypt_value 
     81            else: 
     82                func = decrypt_value 
     83 
    6584            for column_name in for_fields: 
    6685                current_value = getattr(instance, column_name) 
    6786                if current_value: 
    68                     if decrypt: 
    69                         new_value = decrypt_value(current_value, with_secret) 
    70                     else: 
    71                         new_value = encrypt_value(current_value, with_secret) 
    72                     setattr(instance, column_name, new_value) 
     87                    setattr(instance, column_name, 
     88                            func(current_value, with_secret)) 
    7389 
    7490        def perform_decryption(instance): 
    75             perform_encryption(instance, decrypt=True) 
     91            perform_encryption(instance, encrypt=False) 
    7692 
    7793        class EncryptedMapperExtension(MapperExtension): 
     
    85101                return EXT_CONTINUE 
    86102 
    87         if SA05orlater: 
    88             def reconstruct_instance(self, mapper, instance): 
    89                 perform_decryption(instance) 
    90                 return True 
    91             EncryptedMapperExtension.reconstruct_instance = reconstruct_instance 
    92         else: 
    93             def populate_instance(self, mapper, selectcontext, row, instance, 
    94                                   *args, **kwargs): 
    95                 mapper.populate_instance(selectcontext, instance, row, 
    96                                          *args, **kwargs) 
    97                 perform_decryption(instance) 
    98                 return True 
    99             EncryptedMapperExtension.populate_instance = populate_instance 
     103            if SA05orlater: 
     104                def reconstruct_instance(self, mapper, instance): 
     105                    perform_decryption(instance) 
     106                    # no special return value is required for 
     107                    # reconstruct_instance, but you never know... 
     108                    return EXT_CONTINUE 
     109            else: 
     110                def populate_instance(self, mapper, selectcontext, row, 
     111                                      instance, *args, **kwargs): 
     112                    mapper.populate_instance(selectcontext, instance, row, 
     113                                             *args, **kwargs) 
     114                    perform_decryption(instance) 
     115                    # EXT_STOP because we already did populate the instance and 
     116                    # the normal processing should not happen 
     117                    return EXT_STOP 
    100118 
    101119        # make sure that the entity's mapper has our mapper extension 
  • elixir/trunk/tests/test_encryption.py

    r349 r446  
    2121 
    2222    metadata.bind = 'sqlite:///' 
     23    setup_all() 
    2324 
    2425 
     
    2930class TestEncryption(object): 
    3031    def setup(self): 
    31         setup_all(True) 
     32        create_all() 
    3233 
    3334    def teardown(self): 
    3435        drop_all() 
    35         session.clear() 
     36        session.close() 
    3637 
    3738    def test_encryption(self): 
    3839        jonathan = Person( 
    39             name=u'Jonathan LaCour', 
    40             password=u's3cr3tw0RD', 
    41             ssn=u'123-45-6789' 
     40            name='Jonathan LaCour', 
     41            password='s3cr3tw0RD', 
     42            ssn='123-45-6789' 
    4243        ) 
    4344        winston = Pet( 
     
    5152        jonathan.pets = [winston, nelson] 
    5253 
    53         session.commit(); session.clear() 
     54        session.commit() 
     55        session.clear() 
    5456 
    5557        p = Person.get_by(name='Jonathan LaCour') 
     
    6365        p.password = 'N3wpAzzw0rd' 
    6466 
    65         session.commit(); session.clear() 
     67        session.commit() 
     68        session.clear() 
    6669 
    6770        p = Person.get_by(name='Jonathan LaCour') 
    6871        assert p.password == 'N3wpAzzw0rd' 
    69         p.name = 'Jon LaCour' 
    7072 
    71         session.commit(); session.clear() 
     73    def test_two_consecutive_updates(self): 
     74        jonathan = Person( 
     75            name='Jonathan LaCour', 
     76            password='s3cr3tw0RD', 
     77            ssn='123-45-6789' 
     78        ) 
     79        session.commit() 
     80        session.clear() 
     81 
     82        p = Person.get_by(name='Jonathan LaCour') 
     83        assert p.password == 's3cr3tw0RD' 
     84        p.name = 'JONATHAN LACOUR' 
     85        session.flush() 
     86 
     87        assert p.password == 'r\\x9d\\xa8\\xb4\\x8d|\\xffp\\xf5\\x0e' 
     88 
     89        p.name = 'Jonathan LaCour' 
     90        session.flush() 
     91 
     92        # check that it is not further encrypted 
     93        assert p.password == 'r\\x9d\\xa8\\xb4\\x8d|\\xffp\\xf5\\x0e' 
     94 
     95        session.commit() 
     96        session.clear() 
     97 
     98        p = Person.get_by(name='Jonathan LaCour') 
     99        assert p.password == 's3cr3tw0RD' 
     100