Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix encrypted fields bulk_update corrupting data #4366

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES/4359.bugfix
@@ -0,0 +1,2 @@
Fix encrypted fields to use json instead of repr/eval and make them fit for ``bulk_update``.
This solves an issue with ``pulpcore-manager rotate-db-key`` corrupting data.
30 changes: 20 additions & 10 deletions pulpcore/app/models/fields.py
@@ -1,3 +1,4 @@
import json
import logging
import os
from gettext import gettext as _
Expand Down Expand Up @@ -94,14 +95,16 @@ def _fernet(self):
with open(settings.DB_ENCRYPTION_KEY, "rb") as key_file:
return Fernet(key_file.read())

def get_db_prep_save(self, value, connection):
value = super().get_db_prep_save(value, connection)
def get_prep_value(self, value):
if value is not None:
return force_str(self._fernet.encrypt(force_bytes(value)))
assert isinstance(value, str)
value = force_str(self._fernet.encrypt(force_bytes(value)))
return super().get_prep_value(value)

def from_db_value(self, value, expression, connection):
if value is not None:
return force_str(self._fernet.decrypt(force_bytes(value)))
value = force_str(self._fernet.decrypt(force_bytes(value)))
return value


class EncryptedJSONField(JSONField):
Expand Down Expand Up @@ -136,16 +139,23 @@ def decrypt(self, value):
elif isinstance(value, (list, tuple, set)):
return [self.decrypt(v) for v in value]

return eval(force_str(self._fernet.decrypt(force_bytes(value))))
dec_value = force_str(self._fernet.decrypt(force_bytes(value)))
try:
return json.loads(dec_value, cls=self.decoder)
except json.JSONDecodeError:
return eval(dec_value)

def get_db_prep_save(self, value, connection):
value = self.encrypt(value)
return super().get_db_prep_save(value, connection)
def get_prep_value(self, value):
if value is not None:
if hasattr(value, "as_sql"):
return value
value = self.encrypt(value)
return super().get_prep_value(value)

def from_db_value(self, value, expression, connection):
if value is not None:
value = super().from_db_value(value, expression, connection)
return self.decrypt(value)
value = self.decrypt(super().from_db_value(value, expression, connection))
return value


@Field.register_lookup
Expand Down