Skip to content

Commit 7e57c8a

Browse files
ref: ensure Authenticator is exportable (#94955)
the custom JSON field needs to also properly encode for the django serialization framework resolves getsentry/self-hosted#3789 <!-- Describe your PR here. -->
1 parent 5403c10 commit 7e57c8a

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

src/sentry/testutils/helpers/backups.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
from django.apps import apps
1616
from django.db import connections, router
1717
from django.utils import timezone
18+
from fido2.ctap2 import AuthenticatorData
19+
from fido2.utils import sha256
1820
from sentry_relay.auth import generate_key_pair
1921

22+
from sentry.auth.authenticators.u2f import create_credential_object
2023
from sentry.backup.crypto import LocalFileDecryptor, LocalFileEncryptor, decrypt_encrypted_tarball
2124
from sentry.backup.dependencies import (
2225
NormalizedModelName,
@@ -372,7 +375,38 @@ def create_exhaustive_user(
372375
first_seen=datetime(2012, 4, 5, 3, 29, 45, tzinfo=UTC),
373376
last_seen=datetime(2012, 4, 5, 3, 29, 45, tzinfo=UTC),
374377
)
375-
Authenticator.objects.create(user=user, type=1, config={})
378+
Authenticator.objects.create(
379+
user=user,
380+
type=1,
381+
config={
382+
"devices": [
383+
{
384+
"binding": {
385+
"publicKey": "publickey",
386+
"keyHandle": "aowerkoweraowerkkro",
387+
"appId": "https://dev.getsentry.net:8000/auth/2fa/u2fappid.json",
388+
},
389+
"name": "Sentry",
390+
"ts": 1512505334,
391+
},
392+
{
393+
"name": "Alert Escargot",
394+
"ts": 1512505334,
395+
"binding": AuthenticatorData.create(
396+
sha256(b"test"),
397+
0x41,
398+
1,
399+
create_credential_object(
400+
{
401+
"publicKey": "webauthn",
402+
"keyHandle": "webauthn",
403+
}
404+
),
405+
),
406+
},
407+
]
408+
},
409+
)
376410

377411
if is_admin:
378412
self.add_user_permission(user, "users.admin")

src/sentry/users/models/authenticator.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,18 @@ class AuthenticatorConfig(models.JSONField):
133133
def _is_devices_config(self, value: Any) -> bool:
134134
return isinstance(value, dict) and "devices" in value
135135

136-
def get_db_prep_value(self, value: Any, *args: Any, **kwargs: Any) -> Any:
136+
def _encode_value(self, value: Any) -> Any:
137137
if self._is_devices_config(value):
138138
# avoid mutating the original object
139139
value = copy.deepcopy(value)
140140
for device in value["devices"]:
141141
# AuthenticatorData is a non-json-serializable bytes subclass
142142
if isinstance(device["binding"], AuthenticatorData):
143143
device["binding"] = base64.b64encode(device["binding"]).decode()
144+
return value
144145

145-
return super().get_db_prep_value(value, *args, **kwargs)
146+
def get_db_prep_value(self, value: Any, *args: Any, **kwargs: Any) -> Any:
147+
return super().get_db_prep_value(self._encode_value(value), *args, **kwargs)
146148

147149
def from_db_value(
148150
self, value: str | None, expression: Expression, connection: BaseDatabaseWrapper
@@ -154,6 +156,9 @@ def from_db_value(
154156
device["binding"] = AuthenticatorData(base64.b64decode(device["binding"]))
155157
return ret
156158

159+
def value_to_string(self, obj: models.Model) -> object: # type: ignore[override] # see typeddjango/django-stubs#2729
160+
return self._encode_value(self.value_from_object(obj))
161+
157162

158163
@control_silo_model
159164
class Authenticator(ControlOutboxProducingModel):

0 commit comments

Comments
 (0)