Skip to content

Commit

Permalink
Added encrypted text and char fields.
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanOC authored and justinabrahms committed Jan 17, 2010
1 parent e401f82 commit 459276d
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
55 changes: 55 additions & 0 deletions django_extensions/db/fields/encrypted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django import forms
from django.conf import settings
from keyczar import keyczar

class BaseEncryptedField(models.Field):
prefix = 'enc_str:::'
def __init__(self, *args, **kwargs):
if not hasattr(settings, 'KEYS_DIR'):
raise ImproperlyConfigured('You must set settings.KEYS_DIR to your Keyczar keys directory.')
self.crypt = keyczar.Crypter.Read(settings.KEYS_DIR)
super(BaseEncryptedField, self).__init__(*args, **kwargs)

def to_python(self, value):
if (value.startswith(self.prefix)):
retval = self.crypt.Decrypt(value[len(self.prefix):])
else:
retval = value

return retval

def get_db_prep_value(self, value):
if not value.startswith(self.prefix):
value = self.prefix + self.crypt.Encrypt(value)
return value

class EncryptedTextField(BaseEncryptedField):
__metaclass__ = models.SubfieldBase

def get_internal_type(self):
return 'TextField'

def formfield(self, **kwargs):
defaults = {'widget': forms.Textarea}
defaults.update(kwargs)
return super(EncryptedTextField, self).formfield(**defaults)

class EncryptedCharField(BaseEncryptedField):
__metaclass__ = models.SubfieldBase

def __init__(self, max_length=None, *args, **kwargs):
if max_length:
max_length += len(self.prefix)

super(EncryptedCharField, self).__init__(max_length=max_length, *args, **kwargs)


def get_internal_type(self):
return "CharField"

def formfield(self, **kwargs):
defaults = {'max_length': self.max_length}
defaults.update(kwargs)
return super(EncryptedCharField, self).formfield(**defaults)
3 changes: 3 additions & 0 deletions django_extensions/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from django.db import models
from django_extensions.tests.utils import UTILS_TESTS
from django_extensions.tests.encrypted_fields import EncryptedFieldsTestCase
from django_extensions.tests.models import Secret

__test__ = {
'UTILS_TESTS': UTILS_TESTS,
Expand Down
58 changes: 58 additions & 0 deletions django_extensions/tests/encrypted_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import unittest
from keyczar import keyczar

from django.db import connection
from django.conf import settings
from django.core.management import call_command
from django.db.models import loading

from django_extensions.tests.models import Secret
from django_extensions.db.fields.encrypted import EncryptedTextField, EncryptedCharField

class EncryptedFieldsTestCase(unittest.TestCase):

def __init__(self, *args, **kwargs):
self.crypt = keyczar.Crypter.Read(settings.KEYS_DIR)

super(EncryptedFieldsTestCase, self).__init__(*args, **kwargs)

def setUp(self):
self.old_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS.append('django_extensions.tests')
loading.cache.loaded = False
call_command('syncdb', verbosity=0)

def tearDown(self):
settings.INSTALLED_APPS = self.old_installed_apps

def testCharFieldCreate(self):
test_val = "Test Secret"
secret = Secret.objects.create(name=test_val)
cursor = connection.cursor()
query = "SELECT name FROM %s WHERE id = %d" % (Secret._meta.db_table, secret.id)
cursor.execute(query)
db_val, = cursor.fetchone()
decrypted_val = self.crypt.Decrypt(db_val[len(EncryptedCharField.prefix):])
self.assertEqual(test_val, decrypted_val)

def testCharFieldRead(self):
test_val = "Test Secret"
secret = Secret.objects.create(name=test_val)
retrieved_secret = Secret.objects.get(id=secret.id)
self.assertEqual(test_val, retrieved_secret.name)

def testTextFieldCreate(self):
test_val = "Test Secret"
secret = Secret.objects.create(text=test_val)
cursor = connection.cursor()
query = "SELECT text FROM %s WHERE id = %d" % (Secret._meta.db_table, secret.id)
cursor.execute(query)
db_val, = cursor.fetchone()
decrypted_val = self.crypt.Decrypt(db_val[len(EncryptedCharField.prefix):])
self.assertEqual(test_val, decrypted_val)

def testTextFieldRead(self):
test_val = "Test Secret"
secret = Secret.objects.create(text=test_val)
retrieved_secret = Secret.objects.get(id=secret.id)
self.assertEqual(test_val, retrieved_secret.text)
7 changes: 7 additions & 0 deletions django_extensions/tests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models

from django_extensions.db.fields.encrypted import EncryptedTextField, EncryptedCharField

class Secret(models.Model):
name = EncryptedCharField(blank=True, max_length=255)
text = EncryptedTextField(blank=True)
6 changes: 5 additions & 1 deletion docs/field_extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ Current Database Model Field Extensions
deprecated auto_now keyword.

* *UUIDField* - UUIDField for Django, supports all uuid versions which are
natively supported by the uuid python module.
natively supported by the uuid python module.

* *EncryptedCharField* - CharField which transparently encrypts its value as it goes in and out of the database. Encryption is handled by `Keyczar <http://www.keyczar.org/>`_. To use this field you must have Keyczar installed, have generated a primary encryption key, and have ``settings.KEYS_DIR`` set to the full path of your keys directory.

* *EncryptedTextField* - CharField which transparently encrypts its value as it goes in and out of the database. Encryption is handled by `Keyczar <http://www.keyczar.org/>`_. To use this field you must have Keyczar installed, have generated a primary encryption key, and have ``settings.KEYS_DIR`` set to the full path of your keys directory.

0 comments on commit 459276d

Please sign in to comment.