incuna / django-pgcrypto-fields

Transparent field level encryption for Django using the pgcrypto postgresql extension.
BSD 2-Clause "Simplified" License
231 stars 51 forks source link

TypeError: %i format: a number is required, not str #338

Closed GvS666 closed 3 years ago

GvS666 commented 3 years ago

I've used django-pgcrypto-fields for about a week now without a problem and then I created a migration to add an encrypted field:

# Generated by Django 2.2.19 on 2021-03-13 13:45

from django.db import migrations

def copy_field(apps, schema_editor):
    GenericEvent = apps.get_model('generic_event', 'GenericEvent')
    for ep in GenericEvent.objects.all():
        ep.pp_receiver_encrypted = ep.pp_receiver
        ep.save()

class Migration(migrations.Migration):

    dependencies = [
        ('generic_event', '0015_auto_20210313_0745'),
    ]

    operations = [
        migrations.RunPython(copy_field)
    ]

I've done a similar one with hashed fields before and it worked, but this one fails with:

Running migrations:
  Applying generic_event.0016_encrypt_payment_fields...Traceback (most recent call last):
  File "manage.py", line 24, in <module>
    execute_from_command_line(sys.argv)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/__init__.py", line 375, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/base.py", line 323, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/base.py", line 364, in execute
    output = self.handle(*args, **options)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/base.py", line 83, in wrapped
    res = handle_func(*args, **kwargs)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/core/management/commands/migrate.py", line 232, in handle
    post_migrate_state = executor.migrate(
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/migrations/executor.py", line 117, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/migrations/executor.py", line 147, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/migrations/executor.py", line 245, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/migrations/migration.py", line 124, in apply
    operation.database_forwards(self.app_label, schema_editor, old_state, project_state)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/migrations/operations/special.py", line 190, in database_forwards
    self.code(from_state.apps, schema_editor)
  File "/var/www/project/projectproject/generic_event/migrations/0016_encrypt_payment_fields.py", line 7, in copy_field
    for ep in GenericEvent.objects.all():
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/query.py", line 274, in __iter__
    self._fetch_all()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/cacheops/query.py", line 303, in _fetch_all
    return self._no_monkey._fetch_all(self)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/query.py", line 1242, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/query.py", line 55, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 1129, in execute_sql
    sql, params = self.as_sql()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 474, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 54, in pre_sql_setup
    self.setup_query()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 45, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select()
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 254, in get_select
    sql, params = self.compile(col, select_format=True)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/django/db/models/sql/compiler.py", line 405, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/var/www/envs/myenv/lib/pyston3.8/site-packages/pgcrypto/mixins.py", line 33, in as_sql
    sql = self.target.get_decrypt_sql(connection) % (sql, self.target.get_cast_sql())
TypeError: %i format: a number is required, not str

Local variables from Sentry:


__class__ | <class 'pgcrypto.mixins.DecryptedCol'>
compiler | <django.db.models.sql.compiler.SQLCompiler object at 0x7fbc3a838a30>
connection | <django.db.backends.postgresql.base.DatabaseWrapper object at 0x7fbc3a827550>
params | []
self | DecryptedCol(organizations_organization, organizations.Organization.pp_receiver_encrypted)
sql | '"organizations_organization"."pp_receiver_encrypted"'

Model field looks like this: pp_receiver_encrypted = EmailPGPSymmetricKeyField(_('This is account (email)'), blank=True, null=True)

I have no idea how to debug or fix it. It worked fine on my local env. I'm using PostgreSQL 11.11 (through pgbouncer) on Ubuntu 20 server. I've set PGCRYPTO_KEY in settings. Any help will be greatly appreciated!

peterfarrell commented 3 years ago

Questions:

GvS666 commented 3 years ago

I use django-pgcrypto-fields 2.6.0. PGCRYPTO_KEY in development was a simple string, but for production, I generated a complex one with my password manager and it indeed has '%i' in it! I'll check next week if it works after removing %. Thank you!

GvS666 commented 3 years ago

It works now, thanks again!