Skip to content

Commit

Permalink
Merge pull request #48 from peterfarrell/issue_47
Browse files Browse the repository at this point in the history
Add support to hash fields to optionally base hash value off another field value
  • Loading branch information
Kévin Etienne committed Aug 2, 2018
2 parents 177d151 + d391982 commit 6281aaa
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ class MyModelManager(managers.PGPManager):

class MyModel(models.Model):
digest_field = fields.TextDigestField()
digest_with_original_field = fields.TextDigestField(original='pgp_sym_field')
hmac_field = fields.TextHMACField()
hmac_with_original_field = fields.TextHMACField(original='pgp_sym_field')

integer_pgp_pub_field = fields.IntegerPGPPublicKeyField()
pgp_pub_field = fields.TextPGPPublicKeyField()
Expand All @@ -161,6 +163,21 @@ Example:
>>> MyModel.objects.create(value='Value to be encrypted...')
```

Hash fields can have hashes auto updated if you use the `original` attribute. This
attribute allows you to indicate another field name to base the hash value on.

```python

class User(models.Models):
first_name = fields.TextPGPSymmetricKeyField(max_length=20, verbose_name='First Name')
first_name_hashed = fields.TextHMACField(original='first_name')
```

In the above example, if you specify the optional original attribute it would
take the unencrypted value from the first_name model field as the input value
to create the hash. If you did not specify an original attribute, the field
would work as it does now and would remain backwards compatible.

#### Decryption using custom model managers

If you use the bundled `PGPManager` with your custom model manager, all encrypted
Expand Down
12 changes: 12 additions & 0 deletions pgcrypto/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ class HashMixin:
`HashMixin` uses 'pgcrypto' to encrypt data in a postgres database.
"""
def __init__(self, original=None, *args, **kwargs):
self.original = original

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

def pre_save(self, model_instance, add):
if self.original:
original_value = getattr(model_instance, self.original)
setattr(model_instance, self.attname, original_value)

return super(HashMixin, self).pre_save(model_instance, add)

def get_placeholder(self, value=None, compiler=None, connection=None):
"""
Tell postgres to encrypt this field with a hashing function.
Expand Down
2 changes: 2 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ class EncryptedModelManager(managers.PGPManager):
class EncryptedModel(models.Model):
"""Dummy model used for tests to check the fields."""
digest_field = fields.TextDigestField(blank=True, null=True)
digest_with_original_field = fields.TextDigestField(blank=True, null=True, original='pgp_sym_field')
hmac_field = fields.TextHMACField(blank=True, null=True)
hmac_with_original_field = fields.TextHMACField(blank=True, null=True, original='pgp_sym_field')

email_pgp_pub_field = fields.EmailPGPPublicKeyField(blank=True, null=True)
integer_pgp_pub_field = fields.IntegerPGPPublicKeyField(blank=True, null=True)
Expand Down
20 changes: 20 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ def test_fields(self):
expected = (
'id',
'digest_field',
'digest_with_original_field',
'hmac_field',
'hmac_with_original_field',
'email_pgp_pub_field',
'integer_pgp_pub_field',
'pgp_pub_field',
Expand Down Expand Up @@ -199,6 +201,15 @@ def test_digest_lookup(self):

self.assertCountEqual(queryset, [expected])

def test_digest_with_original_lookup(self):
"""Assert we can filter a digest value."""
value = 'bonjour'
expected = EncryptedModelFactory.create(pgp_sym_field=value)
EncryptedModelFactory.create()

queryset = EncryptedModel.objects.filter(digest_with_original_field__hash_of=value)
self.assertCountEqual(queryset, [expected])

def test_hmac_lookup(self):
"""Assert we can filter a digest value."""
value = 'bonjour'
Expand All @@ -208,6 +219,15 @@ def test_hmac_lookup(self):
queryset = EncryptedModel.objects.filter(hmac_field__hash_of=value)
self.assertCountEqual(queryset, [expected])

def test_hmac_with_original_lookup(self):
"""Assert we can filter a digest value."""
value = 'bonjour'
expected = EncryptedModelFactory.create(pgp_sym_field=value)
EncryptedModelFactory.create()

queryset = EncryptedModel.objects.filter(hmac_with_original_field__hash_of=value)
self.assertCountEqual(queryset, [expected])

def test_default_lookup(self):
"""Assert default lookup can be called."""
queryset = EncryptedModel.objects.filter(hmac_field__isnull=True)
Expand Down

0 comments on commit 6281aaa

Please sign in to comment.