django-cryptography icon indicating copy to clipboard operation
django-cryptography copied to clipboard

Json fields

Open harry-kim opened this issue 4 years ago • 10 comments

This doesn't work for Json fields... can you propose a solution?

harry-kim avatar Jul 30 '19 14:07 harry-kim

Do you have an example of anything you've tried?

Just off the top of my head I would consider using a CharField or TextField and treating the json as a long string (which it is, basically).

thismatters avatar Jul 31 '19 17:07 thismatters

was able to get it working

encrypt(JSONWrappedTextField)

class JSONWrappedTextField(models.TextField):
    def to_python(self, value):
        if isinstance(value, six.string_types):
            value = super(JSONWrappedTextField, self).to_python(value)
            value = json.loads(value)
        return value

    def get_db_prep_value(self, value, connection, prepared=False):
        value = super(JSONWrappedTextField, self).get_db_prep_value(value, connection, prepared)
        if isinstance(value, dict):
            value = json.dumps(value)

        return value

harry-kim avatar Aug 06 '19 15:08 harry-kim

Thanks a lot @harry-kim this is just what I needed! If using python 3 I believe you can just do:

encrypt(JSONWrappedTextField)

class JSONWrappedTextField(models.TextField):
    def to_python(self, value):
        if isinstance(value, str): #changed this line
            value = super(JSONWrappedTextField, self).to_python(value)
            value = json.loads(value)
        return value

    def get_db_prep_value(self, value, connection, prepared=False):
        value = super(JSONWrappedTextField, self).get_db_prep_value(value, connection, prepared)
        if isinstance(value, dict):
            value = json.dumps(value)

        return value

ademidun avatar Jan 02 '20 19:01 ademidun

Update: I don't think this works if you already have existing JSONfield objects. I started getting weird string escaping errors.

ademidun avatar Jan 04 '20 00:01 ademidun

The other issue is default values, I am getting this on migration:

django.db.utils.ProgrammingError: column "remote" is of type jsonb but default expression is of type bytea

Code:

from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.fields import JSONField
from django_cryptography.fields import encrypt

class Repo(models.Model):
    # ...
    remote = encrypt(JSONField(default=dict, encoder=DjangoJSONEncoder))

Suor avatar Jun 29 '20 09:06 Suor

Looks like the issue is that JSONField overwrites .db_type() method and EncryptedMixin does not. This monkey-patch fixed it for me:

EncryptedMixin.db_type = models.Field.db_type

Suor avatar Jun 29 '20 11:06 Suor

I faced with the same issue! The @Suor tip could do the tricks!

However just in case, an another alternative could be to use PickledField instead:

from django.db import models
from django_cryptography.fields import encrypt, PickledField 


class Foo(models.Model):
    extra_data = encrypt(PickledField(default=dict))

In any case, data must be store in Bytea data type (encrypted string). So using JSONField or even HStoreField doesn't make sense : you won't be able to encrypt data and take power of SGBD functions with JSON or HStore. You could just store a byte string! This is why @Suor override the base field data type (db_type).

After all, you probably just needs to serializer/deserializer dict/list data into code, not in DB!

ychab avatar Oct 19 '21 14:10 ychab

This isn't working for

Thanks a lot @harry-kim this is just what I needed! If using python 3 I believe you can just do:

encrypt(JSONWrappedTextField)

class JSONWrappedTextField(models.TextField):
    def to_python(self, value):
        if isinstance(value, str): #changed this line
            value = super(JSONWrappedTextField, self).to_python(value)
            value = json.loads(value)
        return value

    def get_db_prep_value(self, value, connection, prepared=False):
        value = super(JSONWrappedTextField, self).get_db_prep_value(value, connection, prepared)
        if isinstance(value, dict):
            value = json.dumps(value)

        return value

This isn't working for me. The to_python code is never called. From examining the EncryptedMixin the only callback I see that would work is get_prep_value or get_db_prep_value

I get a String back but have no way to convert it to JSON.

geemang2022 avatar Jul 05 '23 13:07 geemang2022

Here is what I got working. Notice I had to override the get_db_converters, so there is a chance to turn the decrypted string back to JSON.

NOTE: DJANGO 4.2

class JSONWrappedTextField(JSONField):

    def get_db_converters(self, connection):
        """Override to we can call our converter"""
        return [self.my_from_db_value]

    def my_from_db_value(self, value, expression, connection):
        """Once read from the DB get the decrypted Str and turn it into JSON"""
        value = self.from_db_value(value, expression, connection)
        if value is not None:
            value = self.to_python(value)
        return value

geemang2022 avatar Jul 05 '23 17:07 geemang2022

Looks like the issue is that JSONField overwrites .db_type() method and EncryptedMixin does not. This monkey-patch fixed it for me:

EncryptedMixin.db_type = models.Field.db_type

where will this go? in settings file?

Aliq52 avatar Jan 24 '24 17:01 Aliq52