-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
0726_apitoken_backfill_hashes.py
153 lines (128 loc) · 5.98 KB
/
0726_apitoken_backfill_hashes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# Generated by Django 5.0.6 on 2024-05-29 21:28
import hashlib
import logging
from enum import IntEnum
from django.db import migrations, router
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
from sentry.new_migrations.migrations import CheckedMigration
from sentry.utils.query import RangeQuerySetWrapperWithProgressBar
logger = logging.getLogger(__name__)
def backfill_hash_values(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None:
ApiToken = apps.get_model("sentry", "ApiToken")
ControlOutbox = apps.get_model("sentry", "ControlOutbox")
OrganizationMemberMapping = apps.get_model("sentry", "OrganizationMemberMapping")
OrganizationMapping = apps.get_model("sentry", "OrganizationMapping")
try:
from collections.abc import Container
from django.conf import settings
from sentry.silo.base import SiloMode, control_silo_function
from sentry.silo.safety import unguarded_write
except ImportError:
logger.exception("Cannot execute migration. Required symbols could not be imported")
return
# copied from src/sentry/models/outbox.py
class OutboxCategory(IntEnum):
USER_UPDATE = 0
UNUSED_TWO = 4
UNUSUED_THREE = 13
UNUSED_ONE = 19
AUTH_IDENTITY_UPDATE = 25
API_TOKEN_UPDATE = 32
# copied from src/sentry/models/outbox.py
_outbox_categories_for_scope: dict[int, set[OutboxCategory]] = {}
_used_categories: set[OutboxCategory] = set()
# copied from src/sentry/models/outbox.py
def scope_categories(enum_value: int, categories: set[OutboxCategory]) -> int:
_outbox_categories_for_scope[enum_value] = categories
inter = _used_categories.intersection(categories)
assert not inter, f"OutboxCategories {inter} were already registered to a different scope"
_used_categories.update(categories)
return enum_value
# copied from src/sentry/models/outbox.py
class OutboxScope(IntEnum):
USER_SCOPE = scope_categories(
1,
{
OutboxCategory.USER_UPDATE,
OutboxCategory.API_TOKEN_UPDATE,
OutboxCategory.UNUSED_ONE,
OutboxCategory.UNUSED_TWO,
OutboxCategory.UNUSUED_THREE,
OutboxCategory.AUTH_IDENTITY_UPDATE,
},
)
@control_silo_function
def _find_orgs_for_user(user_id: int) -> set[int]:
return {
m["organization_id"]
for m in OrganizationMemberMapping.objects.filter(user_id=user_id).values(
"organization_id"
)
}
@control_silo_function
def find_regions_for_orgs(org_ids: Container[int]) -> set[str]:
if SiloMode.get_current_mode() == SiloMode.MONOLITH:
return {settings.SENTRY_MONOLITH_REGION}
else:
return set(
OrganizationMapping.objects.filter(organization_id__in=org_ids).values_list(
"region_name", flat=True
)
)
@control_silo_function
def find_regions_for_user(user_id: int) -> set[str]:
if SiloMode.get_current_mode() == SiloMode.MONOLITH:
return {settings.SENTRY_MONOLITH_REGION}
org_ids = _find_orgs_for_user(user_id)
return find_regions_for_orgs(org_ids)
for api_token in RangeQuerySetWrapperWithProgressBar(ApiToken.objects.all()):
hashed_token = None
if api_token.hashed_token is None:
hashed_token = hashlib.sha256(api_token.token.encode()).hexdigest()
api_token.hashed_token = hashed_token
# if there's a refresh token make sure it is hashed as well
hashed_refresh_token = None
if api_token.refresh_token:
hashed_refresh_token = hashlib.sha256(api_token.refresh_token.encode()).hexdigest()
api_token.hashed_refresh_token = hashed_refresh_token
# only save if we've actually had to hash values
if hashed_token or hashed_refresh_token:
with unguarded_write(using=router.db_for_write(ApiToken)):
api_token.save(update_fields=["hashed_token", "hashed_refresh_token"])
user_regions = find_regions_for_user(api_token.user_id)
for region in user_regions:
ControlOutbox.objects.create(
shard_scope=OutboxScope.USER_SCOPE,
shard_identifier=api_token.user_id,
category=OutboxCategory.API_TOKEN_UPDATE,
region_name=region,
object_identifier=api_token.id,
)
class Migration(CheckedMigration):
# This flag is used to mark that a migration shouldn't be automatically run in production.
# This should only be used for operations where it's safe to run the migration after your
# code has deployed. So this should not be used for most operations that alter the schema
# of a table.
# Here are some things that make sense to mark as post deployment:
# - Large data migrations. Typically we want these to be run manually so that they can be
# monitored and not block the deploy for a long period of time while they run.
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
# run this outside deployments so that we don't block them. Note that while adding an index
# is a schema change, it's completely safe to run the operation after the code has deployed.
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment
is_post_deployment = True
dependencies = [
("sentry", "0725_create_sentry_groupsearchview_table"),
]
operations = [
migrations.RunPython(
backfill_hash_values,
migrations.RunPython.noop,
hints={
"tables": [
"sentry_apitoken",
]
},
)
]