|
19 | 19 | from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey
|
20 | 20 | from sentry.hybridcloud.outbox.base import ControlOutboxProducingManager, ReplicatedControlModel
|
21 | 21 | from sentry.hybridcloud.outbox.category import OutboxCategory
|
22 |
| -from sentry.models.apigrant import ApiGrant |
| 22 | +from sentry.locks import locks |
| 23 | +from sentry.models.apiapplication import ApiApplicationStatus |
| 24 | +from sentry.models.apigrant import ApiGrant, ExpiredGrantError, InvalidGrantError |
23 | 25 | from sentry.models.apiscopes import HasApiScopes
|
24 | 26 | from sentry.types.region import find_all_region_names
|
25 | 27 | from sentry.types.token import AuthTokenType
|
@@ -261,17 +263,33 @@ def handle_async_replication(self, region_name: str, shard_identifier: int) -> N
|
261 | 263 |
|
262 | 264 | @classmethod
|
263 | 265 | def from_grant(cls, grant: ApiGrant):
|
264 |
| - with transaction.atomic(router.db_for_write(cls)): |
265 |
| - api_token = cls.objects.create( |
266 |
| - application=grant.application, |
267 |
| - user=grant.user, |
268 |
| - scope_list=grant.get_scopes(), |
269 |
| - scoping_organization_id=grant.organization_id, |
270 |
| - ) |
271 |
| - |
272 |
| - # remove the ApiGrant from the database to prevent reuse of the same |
273 |
| - # authorization code |
274 |
| - grant.delete() |
| 266 | + if grant.application.status != ApiApplicationStatus.active: |
| 267 | + raise InvalidGrantError() |
| 268 | + |
| 269 | + if grant.is_expired(): |
| 270 | + raise ExpiredGrantError() |
| 271 | + |
| 272 | + lock = locks.get( |
| 273 | + ApiGrant.get_lock_key(grant.id), |
| 274 | + duration=10, |
| 275 | + name="api_grant", |
| 276 | + ) |
| 277 | + |
| 278 | + # we use a lock to prevent race conditions when creating the ApiToken |
| 279 | + # an attacker could send two requests to create an access/refresh token pair |
| 280 | + # at the same time, using the same grant, and get two different tokens |
| 281 | + with lock.acquire(): |
| 282 | + with transaction.atomic(router.db_for_write(cls)): |
| 283 | + api_token = cls.objects.create( |
| 284 | + application=grant.application, |
| 285 | + user=grant.user, |
| 286 | + scope_list=grant.get_scopes(), |
| 287 | + scoping_organization_id=grant.organization_id, |
| 288 | + ) |
| 289 | + |
| 290 | + # remove the ApiGrant from the database to prevent reuse of the same |
| 291 | + # authorization code |
| 292 | + grant.delete() |
275 | 293 |
|
276 | 294 | return api_token
|
277 | 295 |
|
|
0 commit comments