Browse Source

ref: add data migration for unpickling Authenticator (#49412)

<!-- Describe your PR here. -->
anthony sottile 1 year ago
parent
commit
0398c47e03

+ 1 - 1
migrations_lockfile.txt

@@ -6,5 +6,5 @@ To resolve this, rebase against latest master and regenerate your migration. Thi
 will then be regenerated, and you should be able to merge without conflicts.
 
 nodestore: 0002_nodestore_no_dictfield
-sentry: 0448_add_expected_time_config_to_cron_checkin
+sentry: 0449_pickle_to_json_authenticator
 social_auth: 0001_initial

+ 34 - 0
src/sentry/migrations/0449_pickle_to_json_authenticator.py

@@ -0,0 +1,34 @@
+# Generated by Django 2.2.28 on 2023-05-18 16:37
+
+from django.db import migrations
+
+from sentry.new_migrations.migrations import CheckedMigration
+from sentry.utils.query import RangeQuerySetWrapperWithProgressBar
+
+
+def _backfill_authenticator(apps, schema_editor):
+    Authenticator = apps.get_model("sentry", "Authenticator")
+
+    for obj in RangeQuerySetWrapperWithProgressBar(Authenticator.objects.all()):
+        # load pickle, save json
+        obj.save(update_fields=["config"])
+
+
+class Migration(CheckedMigration):
+    # data migration: must be run out of band
+    is_dangerous = True
+
+    # data migration: run outside of a transaction
+    atomic = False
+
+    dependencies = [
+        ("sentry", "0448_add_expected_time_config_to_cron_checkin"),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            _backfill_authenticator,
+            migrations.RunPython.noop,
+            hints={"tables": ["sentry_authenticator"]},
+        ),
+    ]

+ 60 - 0
tests/sentry/migrations/test_0449_pickle_to_json_authenticator.py

@@ -0,0 +1,60 @@
+from unittest import mock
+
+from django.db import connection
+
+import django_picklefield
+from sentry.db.models.fields.picklefield import PickledObjectField
+from sentry.models.authenticator import Authenticator
+from sentry.testutils.cases import TestMigrations
+
+
+def _get_config(obj_id):
+    with connection.cursor() as cur:
+        cur.execute("select config from auth_authenticator where id = %s", [obj_id])
+        (config,) = cur.fetchone()
+        return config
+
+
+class BackfillTest(TestMigrations):
+    migrate_from = "0448_add_expected_time_config_to_cron_checkin"
+    migrate_to = "0449_pickle_to_json_authenticator"
+
+    def setup_initial_state(self):
+        with mock.patch.object(
+            PickledObjectField,
+            "get_db_prep_value",
+            django_picklefield.PickledObjectField.get_db_prep_value,
+        ):
+            self.obj = Authenticator.objects.create(
+                type=3,  # u2f
+                user=self.create_user(),
+                config={
+                    "devices": [
+                        {
+                            "binding": {
+                                "publicKey": "aowekroawker",
+                                "keyHandle": "devicekeyhandle",
+                                "appId": "https://testserver/auth/2fa/u2fappid.json",
+                            },
+                            "name": "Amused Beetle",
+                            "ts": 1512505334,
+                        },
+                        {
+                            "binding": {
+                                "publicKey": "publickey",
+                                "keyHandle": "aowerkoweraowerkkro",
+                                "appId": "https://testserver/auth/2fa/u2fappid.json",
+                            },
+                            "name": "Sentry",
+                            "ts": 1512505334,
+                        },
+                    ]
+                },
+            )
+
+        # not json
+        assert not _get_config(self.obj.id).startswith("{")
+
+    def test(self):
+        # json
+        assert _get_config(self.obj.id).startswith("{")