Skip to content

Commit 247b603

Browse files
authored
feat: apitoken last characters option (#62972)
Take two of #59455. - Add an option to toggle on/off automatically populating the `token_last_characters` for the `ApiToken` model. This option will ensure tokens created from here on have the field populated. It will also allow me to thoroughly test the backfill migration needed for existing tokens that will be coming in a future PR by disabling the option in tests, creating a bunch of API tokens, and then running the backfill migration test. Tracking Issue: #58918 RFC: getsentry/rfcs#32
1 parent 08b1a55 commit 247b603

File tree

4 files changed

+29
-10
lines changed

4 files changed

+29
-10
lines changed

src/sentry/backup/comparators.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,8 @@ def get_default_comparators():
764764
list,
765765
{
766766
"sentry.apitoken": [
767-
HashObfuscatingComparator("refresh_token", "token", "token_last_characters"),
767+
HashObfuscatingComparator("refresh_token", "token"),
768+
IgnoredComparator("token_last_characters"),
768769
UnorderedListComparator("scope_list"),
769770
],
770771
"sentry.apiapplication": [HashObfuscatingComparator("client_id", "client_secret")],

src/sentry/models/apitoken.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import secrets
44
from datetime import timedelta
5-
from typing import ClassVar, Collection, Optional, Tuple
5+
from typing import Any, ClassVar, Collection, Optional, Tuple
66

77
from django.db import models, router, transaction
88
from django.utils import timezone
99
from django.utils.encoding import force_str
1010

11+
from sentry import options
1112
from sentry.backup.dependencies import ImportKind
1213
from sentry.backup.helpers import ImportFlags
1314
from sentry.backup.scopes import ImportScope, RelocationScope
@@ -57,15 +58,12 @@ class Meta:
5758
def __str__(self):
5859
return force_str(self.token)
5960

60-
# TODO(mdtro): uncomment this function after 0583_apitoken_add_name_and_last_chars migration has been applied
61-
# def save(self, *args: Any, **kwargs: Any) -> None:
62-
# # when a new ApiToken is created we take the last four characters of the token
63-
# # and save them in the `token_last_characters` field so users can identify
64-
# # tokens in the UI where they're mostly obfuscated
65-
# token_last_characters = self.token[-4:]
66-
# self.token_last_characters = token_last_characters
61+
def save(self, *args: Any, **kwargs: Any) -> None:
62+
if options.get("apitoken.auto-add-last-chars"):
63+
token_last_characters = self.token[-4:]
64+
self.token_last_characters = token_last_characters
6765

68-
# return super().save(**kwargs)
66+
return super().save(**kwargs)
6967

7068
def outbox_region_names(self) -> Collection[str]:
7169
return list(find_all_region_names())

src/sentry/options/defaults.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@
265265
flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_REQUIRED,
266266
)
267267

268+
# API Tokens
269+
register(
270+
"apitoken.auto-add-last-chars",
271+
default=True,
272+
type=Bool,
273+
flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE,
274+
)
268275

269276
register(
270277
"api.rate-limit.org-create",

tests/sentry/models/test_apitoken.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken
1010
from sentry.silo import SiloMode
1111
from sentry.testutils.cases import TestCase
12+
from sentry.testutils.helpers import override_options
1213
from sentry.testutils.outbox import outbox_runner
1314
from sentry.testutils.silo import assume_test_silo_mode, control_silo_test
1415

@@ -61,6 +62,18 @@ def test_organization_id_for_non_internal(self):
6162
assert ApiTokenReplica.objects.get(apitoken_id=token.id).organization_id is None
6263
assert token.organization_id is None
6364

65+
@override_options({"apitoken.auto-add-last-chars": True})
66+
def test_last_chars_are_set(self):
67+
user = self.create_user()
68+
token = ApiToken.objects.create(user_id=user.id)
69+
assert token.token_last_characters == token.token[-4:]
70+
71+
@override_options({"apitoken.auto-add-last-chars": False})
72+
def test_last_chars_are_not_set(self):
73+
user = self.create_user()
74+
token = ApiToken.objects.create(user_id=user.id)
75+
assert token.token_last_characters is None
76+
6477

6578
class ApiTokenInternalIntegrationTest(TestCase):
6679
def setUp(self):

0 commit comments

Comments
 (0)