Skip to content

Commit 6281aaa

Browse files
author
Kévin Etienne
authored
Merge pull request #48 from peterfarrell/issue_47
Add support to hash fields to optionally base hash value off another field value
2 parents 177d151 + d391982 commit 6281aaa

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ class MyModelManager(managers.PGPManager):
140140

141141
class MyModel(models.Model):
142142
digest_field = fields.TextDigestField()
143+
digest_with_original_field = fields.TextDigestField(original='pgp_sym_field')
143144
hmac_field = fields.TextHMACField()
145+
hmac_with_original_field = fields.TextHMACField(original='pgp_sym_field')
144146

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

166+
Hash fields can have hashes auto updated if you use the `original` attribute. This
167+
attribute allows you to indicate another field name to base the hash value on.
168+
169+
```python
170+
171+
class User(models.Models):
172+
first_name = fields.TextPGPSymmetricKeyField(max_length=20, verbose_name='First Name')
173+
first_name_hashed = fields.TextHMACField(original='first_name')
174+
```
175+
176+
In the above example, if you specify the optional original attribute it would
177+
take the unencrypted value from the first_name model field as the input value
178+
to create the hash. If you did not specify an original attribute, the field
179+
would work as it does now and would remain backwards compatible.
180+
164181
#### Decryption using custom model managers
165182

166183
If you use the bundled `PGPManager` with your custom model manager, all encrypted

pgcrypto/mixins.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ class HashMixin:
2020
2121
`HashMixin` uses 'pgcrypto' to encrypt data in a postgres database.
2222
"""
23+
def __init__(self, original=None, *args, **kwargs):
24+
self.original = original
25+
26+
super(HashMixin, self).__init__(*args, **kwargs)
27+
28+
def pre_save(self, model_instance, add):
29+
if self.original:
30+
original_value = getattr(model_instance, self.original)
31+
setattr(model_instance, self.attname, original_value)
32+
33+
return super(HashMixin, self).pre_save(model_instance, add)
34+
2335
def get_placeholder(self, value=None, compiler=None, connection=None):
2436
"""
2537
Tell postgres to encrypt this field with a hashing function.

tests/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ class EncryptedModelManager(managers.PGPManager):
1010
class EncryptedModel(models.Model):
1111
"""Dummy model used for tests to check the fields."""
1212
digest_field = fields.TextDigestField(blank=True, null=True)
13+
digest_with_original_field = fields.TextDigestField(blank=True, null=True, original='pgp_sym_field')
1314
hmac_field = fields.TextHMACField(blank=True, null=True)
15+
hmac_with_original_field = fields.TextHMACField(blank=True, null=True, original='pgp_sym_field')
1416

1517
email_pgp_pub_field = fields.EmailPGPPublicKeyField(blank=True, null=True)
1618
integer_pgp_pub_field = fields.IntegerPGPPublicKeyField(blank=True, null=True)

tests/test_fields.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ def test_fields(self):
7373
expected = (
7474
'id',
7575
'digest_field',
76+
'digest_with_original_field',
7677
'hmac_field',
78+
'hmac_with_original_field',
7779
'email_pgp_pub_field',
7880
'integer_pgp_pub_field',
7981
'pgp_pub_field',
@@ -199,6 +201,15 @@ def test_digest_lookup(self):
199201

200202
self.assertCountEqual(queryset, [expected])
201203

204+
def test_digest_with_original_lookup(self):
205+
"""Assert we can filter a digest value."""
206+
value = 'bonjour'
207+
expected = EncryptedModelFactory.create(pgp_sym_field=value)
208+
EncryptedModelFactory.create()
209+
210+
queryset = EncryptedModel.objects.filter(digest_with_original_field__hash_of=value)
211+
self.assertCountEqual(queryset, [expected])
212+
202213
def test_hmac_lookup(self):
203214
"""Assert we can filter a digest value."""
204215
value = 'bonjour'
@@ -208,6 +219,15 @@ def test_hmac_lookup(self):
208219
queryset = EncryptedModel.objects.filter(hmac_field__hash_of=value)
209220
self.assertCountEqual(queryset, [expected])
210221

222+
def test_hmac_with_original_lookup(self):
223+
"""Assert we can filter a digest value."""
224+
value = 'bonjour'
225+
expected = EncryptedModelFactory.create(pgp_sym_field=value)
226+
EncryptedModelFactory.create()
227+
228+
queryset = EncryptedModel.objects.filter(hmac_with_original_field__hash_of=value)
229+
self.assertCountEqual(queryset, [expected])
230+
211231
def test_default_lookup(self):
212232
"""Assert default lookup can be called."""
213233
queryset = EncryptedModel.objects.filter(hmac_field__isnull=True)

0 commit comments

Comments
 (0)