Skip to content

Commit

Permalink
Add python3 compatibility with working tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
bpeschier committed Sep 14, 2014
1 parent db78899 commit ea7098f
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 32 deletions.
6 changes: 3 additions & 3 deletions pgcrypto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
__version__ = '.'.join(str(i) for i in __version_info__)

import base64
import datetime
import decimal
import struct

from django.utils import six

CRC24_INIT = 0xB704CE
CRC24_POLY = 0x1864CFB

Expand Down Expand Up @@ -110,7 +110,7 @@ def pad(text, block_size, zero=False):
by pgcrypto. See http://www.di-mgt.com.au/cryptopad.html for more information.
"""
num = block_size - (len(text) % block_size)
ch = '\0' if zero else chr(num)
ch = b'\0' if zero else six.b(chr(num))
return text + (ch * num)

def aes_pad_key(key):
Expand Down
39 changes: 17 additions & 22 deletions pgcrypto/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.conf import settings
from django.core import validators
from django.db import models
from django.utils import timezone
from django.utils import timezone, six
from django.utils.translation import ugettext_lazy as _
from .base import armor, dearmor, pad, unpad, aes_pad_key
import datetime
Expand All @@ -21,8 +21,8 @@ def __init__(self, *args, **kwargs):
self.cipher_key = kwargs.pop('key', getattr(settings, 'PGCRYPTO_DEFAULT_KEY', ''))
self.charset = 'utf-8'
if self.cipher_name == 'AES':
self.cipher_key = aes_pad_key(self.cipher_key)
mod = __import__('Crypto.Cipher', globals(), locals(), [self.cipher_name], -1)
self.cipher_key = aes_pad_key(self.cipher_key.encode(self.charset))
mod = __import__('Crypto.Cipher', globals(), locals(), [self.cipher_name], 0)
self.cipher_class = getattr(mod, self.cipher_name)
self.check_armor = kwargs.pop('check_armor', True)
super(BaseEncryptedField, self).__init__(*args, **kwargs)
Expand All @@ -36,7 +36,7 @@ def south_field_triple(self):
"""
from south.modelsinspector import introspector
args, kwargs = introspector(self)
return ("django.db.models.fields.TextField", args, kwargs)
return "django.db.models.fields.TextField", args, kwargs

def deconstruct(self):
"""
Expand All @@ -62,7 +62,7 @@ def is_encrypted(self, value):
"""
Returns whether the given value is encrypted (and armored) or not.
"""
return isinstance(value, basestring) and value.startswith('-----BEGIN')
return isinstance(value, six.string_types) and value.startswith('-----BEGIN')

def to_python(self, value):
if self.is_encrypted(value):
Expand All @@ -71,7 +71,8 @@ def to_python(self, value):
# 2. Decrypt the bytestring using the specified cipher.
# 3. Unpad the bytestring using the cipher's block size.
# 4. Decode the bytestring to a unicode string using the specified charset.
return unpad(self.get_cipher().decrypt(dearmor(value, verify=self.check_armor)), self.cipher_class.block_size).decode(self.charset)
return unpad(self.get_cipher().decrypt(dearmor(value, verify=self.check_armor)),
self.cipher_class.block_size).decode(self.charset)
return value or ''

def get_db_prep_save(self, value, connection):
Expand All @@ -82,12 +83,12 @@ def get_db_prep_save(self, value, connection):
# 3. Pad the bytestring for encryption, using the cipher's block size.
# 4. Encrypt the padded bytestring using the specified cipher.
# 5. Armor the encrypted bytestring for storage in the text field.
return armor(self.get_cipher().encrypt(pad(unicode(value).encode(self.charset), self.cipher_class.block_size)))
return armor(self.get_cipher().encrypt(pad(six.text_type(value).encode(self.charset),
self.cipher_class.block_size)))
return value or ''


class EncryptedTextField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedTextField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
description = _('Text')

def formfield(self, **kwargs):
Expand All @@ -96,8 +97,7 @@ def formfield(self, **kwargs):
return super(EncryptedTextField, self).formfield(**defaults)


class EncryptedCharField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedCharField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
description = _('String')

def __init__(self, **kwargs):
Expand All @@ -113,8 +113,7 @@ def formfield(self, **kwargs):
return super(EncryptedCharField, self).formfield(**defaults)


class EncryptedIntegerField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedIntegerField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
description = _('Integer')
field_cast = '::integer'

Expand All @@ -129,8 +128,7 @@ def to_python(self, value):
return value


class EncryptedDecimalField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedDecimalField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
description = _('Decimal number')
field_cast = '::numeric'

Expand All @@ -145,8 +143,7 @@ def to_python(self, value):
return value


class EncryptedDateField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedDateField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
description = _('Date (without time)')
field_cast = '::date'

Expand Down Expand Up @@ -187,8 +184,7 @@ def _get_auto_now_value(self):
return datetime.date.today()


class EncryptedDateTimeField (EncryptedDateField):
__metaclass__ = models.SubfieldBase
class EncryptedDateTimeField (six.with_metaclass(models.SubfieldBase, EncryptedDateField)):
description = _('Date (with time)')
field_cast = 'timestamp with time zone'

Expand All @@ -204,15 +200,14 @@ def _get_auto_now_value(self):
return timezone.now()


class EncryptedEmailField (BaseEncryptedField):
__metaclass__ = models.SubfieldBase
class EncryptedEmailField (six.with_metaclass(models.SubfieldBase, BaseEncryptedField)):
default_validators = [validators.validate_email]
description = _('Email address')

def formfield(self, **kwargs):
defaults = {'form_class': forms.EmailField}
defaults.update(kwargs)
return super(EncryptedCharField, self).formfield(**defaults)
return super(EncryptedEmailField, self).formfield(**defaults)


if django.VERSION >= (1, 7):
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
packages=find_packages(),
install_requires=[
'pycrypto>=2.6',
'Django>=1.6',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Database',
]
)
6 changes: 5 additions & 1 deletion tests/core/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from django.db import models
from django.utils.encoding import python_2_unicode_compatible

import pgcrypto


@python_2_unicode_compatible
class Employee (models.Model):
name = models.CharField(max_length=200)
ssn = pgcrypto.EncryptedCharField()
salary = pgcrypto.EncryptedDecimalField()
date_hired = pgcrypto.EncryptedDateField(cipher='Blowfish', key='datekey')

def __unicode__(self):
def __str__(self):
return self.name
10 changes: 5 additions & 5 deletions tests/core/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def setUp(self):

def test_encrypt(self):
c = Blowfish.new('pass', Blowfish.MODE_CBC, self.iv_blowfish)
self.assertEqual(c.encrypt(pad('sensitive information', c.block_size)), self.encrypt_bf)
self.assertEqual(c.encrypt(pad(b'sensitive information', c.block_size)), self.encrypt_bf)

def test_decrypt(self):
c = Blowfish.new('pass', Blowfish.MODE_CBC, self.iv_blowfish)
Expand All @@ -39,11 +39,11 @@ def test_armor_dearmor(self):
self.assertEqual(dearmor(a), self.encrypt_bf)

def test_aes(self):
c = AES.new(aes_pad_key('pass'), AES.MODE_CBC, self.iv_aes)
self.assertEqual(c.encrypt(pad('sensitive information', c.block_size)), self.encrypt_aes)
c = AES.new(aes_pad_key(b'pass'), AES.MODE_CBC, self.iv_aes)
self.assertEqual(c.encrypt(pad(b'sensitive information', c.block_size)), self.encrypt_aes)

def test_aes_pad(self):
c = AES.new(aes_pad_key('secret'), AES.MODE_CBC, self.iv_aes)
c = AES.new(aes_pad_key(b'secret'), AES.MODE_CBC, self.iv_aes)
self.assertEqual(unpad(c.decrypt(self.encrypt_aes_padded), c.block_size), b'xxxxxxxxxxxxxxxx')

class FieldTests (TestCase):
Expand All @@ -56,7 +56,7 @@ def setUp(self):

def test_query(self):
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'employees.json')
for obj in json.load(open(fixture_path, 'rb')):
for obj in json.load(open(fixture_path, 'r')):
if obj['model'] == 'core.employee':
e = Employee.objects.get(ssn=obj['fields']['ssn'])
self.assertEqual(e.pk, int(obj['pk']))
Expand Down

0 comments on commit ea7098f

Please sign in to comment.