From a91fcb9b6b5184a582dfefab50e04dccacbc3a79 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 15 Oct 2024 10:42:52 -0700 Subject: [PATCH 1/6] basically everything oops --- src/sentry/mediators/__init__.py | 1 - .../mediators/token_exchange/__init__.py | 9 ++++- .../mediators/token_exchange/refresher.py | 13 ++++--- .../endpoints/sentry_app_authorizations.py | 2 +- .../token_exchange/grant_exchanger.py | 24 ++++++------ .../token_exchange/util.py | 0 .../token_exchange/validator.py | 39 ++++++++++--------- src/sentry/web/frontend/oauth_token.py | 10 +++-- .../token_exchange/test_refresher.py | 9 +---- .../test_sentry_app_authorizations.py | 2 +- .../token_exchange/test_grant_exchanger.py | 9 +---- .../token_exchange/test_validator.py | 14 +++---- 12 files changed, 65 insertions(+), 67 deletions(-) rename src/sentry/{mediators => sentry_apps}/token_exchange/util.py (100%) rename src/sentry/{mediators => sentry_apps}/token_exchange/validator.py (55%) rename tests/sentry/{mediators => sentry_apps}/token_exchange/test_validator.py (86%) diff --git a/src/sentry/mediators/__init__.py b/src/sentry/mediators/__init__.py index 57874a2ed719fd..f3d01f90720769 100644 --- a/src/sentry/mediators/__init__.py +++ b/src/sentry/mediators/__init__.py @@ -1,4 +1,3 @@ from .mediator import Mediator # NOQA from .param import Param # NOQA from .token_exchange.refresher import Refresher # noqa: F401 -from .token_exchange.util import AUTHORIZATION, REFRESH, GrantTypes # noqa: F401 diff --git a/src/sentry/mediators/token_exchange/__init__.py b/src/sentry/mediators/token_exchange/__init__.py index 84bcc14774369d..3739e69be0ffef 100644 --- a/src/sentry/mediators/token_exchange/__init__.py +++ b/src/sentry/mediators/token_exchange/__init__.py @@ -1,3 +1,8 @@ +from ...sentry_apps.token_exchange.util import ( # NOQA + AUTHORIZATION, + REFRESH, + GrantTypes, + token_expiration, +) +from ...sentry_apps.token_exchange.validator import Validator # NOQA from .refresher import Refresher # NOQA -from .util import AUTHORIZATION, REFRESH, GrantTypes, token_expiration # NOQA -from .validator import Validator # NOQA diff --git a/src/sentry/mediators/token_exchange/refresher.py b/src/sentry/mediators/token_exchange/refresher.py index 08bdb2d0bcd547..d8466063b7058f 100644 --- a/src/sentry/mediators/token_exchange/refresher.py +++ b/src/sentry/mediators/token_exchange/refresher.py @@ -5,13 +5,13 @@ from sentry.coreapi import APIUnauthorized from sentry.mediators.mediator import Mediator from sentry.mediators.param import Param -from sentry.mediators.token_exchange.util import token_expiration -from sentry.mediators.token_exchange.validator import Validator from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.services.app import RpcSentryAppInstallation +from sentry.sentry_apps.token_exchange.util import token_expiration +from sentry.sentry_apps.token_exchange.validator import Validator from sentry.users.models.user import User @@ -27,9 +27,9 @@ class Refresher(Mediator): using = router.db_for_write(User) def call(self): - self._validate() - self._delete_token() - return self._create_new_token() + if self._validate(): + self._delete_token() + return self._create_new_token() def record_analytics(self): analytics.record( @@ -39,9 +39,10 @@ def record_analytics(self): ) def _validate(self): - Validator.run(install=self.install, client_id=self.client_id, user=self.user) + is_valid = Validator(install=self.install, client_id=self.client_id, user=self.user).run() self._validate_token_belongs_to_app() + return is_valid def _validate_token_belongs_to_app(self): if self.token.application != self.application: diff --git a/src/sentry/sentry_apps/api/endpoints/sentry_app_authorizations.py b/src/sentry/sentry_apps/api/endpoints/sentry_app_authorizations.py index d705b2bd5a71e3..1f390414d4a7e4 100644 --- a/src/sentry/sentry_apps/api/endpoints/sentry_app_authorizations.py +++ b/src/sentry/sentry_apps/api/endpoints/sentry_app_authorizations.py @@ -11,9 +11,9 @@ from sentry.auth.services.auth.impl import promote_request_api_user from sentry.coreapi import APIUnauthorized from sentry.mediators.token_exchange.refresher import Refresher -from sentry.mediators.token_exchange.util import GrantTypes from sentry.sentry_apps.api.bases.sentryapps import SentryAppAuthorizationsBaseEndpoint from sentry.sentry_apps.token_exchange.grant_exchanger import GrantExchanger +from sentry.sentry_apps.token_exchange.util import GrantTypes logger = logging.getLogger(__name__) diff --git a/src/sentry/sentry_apps/token_exchange/grant_exchanger.py b/src/sentry/sentry_apps/token_exchange/grant_exchanger.py index 589a3126ec0785..27a3ab83947762 100644 --- a/src/sentry/sentry_apps/token_exchange/grant_exchanger.py +++ b/src/sentry/sentry_apps/token_exchange/grant_exchanger.py @@ -6,14 +6,14 @@ from sentry import analytics from sentry.coreapi import APIUnauthorized -from sentry.mediators.token_exchange.util import token_expiration -from sentry.mediators.token_exchange.validator import Validator from sentry.models.apiapplication import ApiApplication from sentry.models.apigrant import ApiGrant from sentry.models.apitoken import ApiToken from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.services.app import RpcSentryAppInstallation +from sentry.sentry_apps.token_exchange.util import token_expiration +from sentry.sentry_apps.token_exchange.validator import Validator from sentry.silo.safety import unguarded_write from sentry.users.models.user import User @@ -31,15 +31,14 @@ class GrantExchanger: def run(self): with transaction.atomic(using=router.db_for_write(ApiToken)): - self._validate() - token = self._create_token() + if self._validate(): + token = self._create_token() - # Once it's exchanged it's no longer valid and should not be - # exchangeable, so we delete it. - self._delete_grant() - self.record_analytics() - - return token + # Once it's exchanged it's no longer valid and should not be + # exchangeable, so we delete it. + self._delete_grant() + self.record_analytics() + return token def record_analytics(self) -> None: analytics.record( @@ -48,14 +47,15 @@ def record_analytics(self) -> None: exchange_type="authorization", ) - def _validate(self) -> None: - Validator.run(install=self.install, client_id=self.client_id, user=self.user) + def _validate(self) -> bool: + is_valid = Validator(install=self.install, client_id=self.client_id, user=self.user).run() if not self._grant_belongs_to_install() or not self._sentry_app_user_owns_grant(): raise APIUnauthorized("Forbidden grant") if not self._grant_is_active(): raise APIUnauthorized("Grant has already expired.") + return is_valid def _grant_belongs_to_install(self) -> bool: return self.grant.sentry_app_installation.id == self.install.id diff --git a/src/sentry/mediators/token_exchange/util.py b/src/sentry/sentry_apps/token_exchange/util.py similarity index 100% rename from src/sentry/mediators/token_exchange/util.py rename to src/sentry/sentry_apps/token_exchange/util.py diff --git a/src/sentry/mediators/token_exchange/validator.py b/src/sentry/sentry_apps/token_exchange/validator.py similarity index 55% rename from src/sentry/mediators/token_exchange/validator.py rename to src/sentry/sentry_apps/token_exchange/validator.py index 4b88fac49e4e3b..6d0cf170dcd37a 100644 --- a/src/sentry/mediators/token_exchange/validator.py +++ b/src/sentry/sentry_apps/token_exchange/validator.py @@ -1,53 +1,54 @@ -from django.db import router +from dataclasses import dataclass + from django.utils.functional import cached_property from sentry.coreapi import APIUnauthorized -from sentry.mediators.mediator import Mediator -from sentry.mediators.param import Param from sentry.models.apiapplication import ApiApplication from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import RpcSentryAppInstallation from sentry.users.models.user import User -class Validator(Mediator): +@dataclass +class Validator: """ Validates general authorization params for all types of token exchanges. """ - install = Param(RpcSentryAppInstallation) - client_id = Param(str) - user = Param(User) - using = router.db_for_write(User) + install: RpcSentryAppInstallation + client_id: str + user: User - def call(self): + def run(self) -> bool: self._validate_is_sentry_app_making_request() self._validate_app_is_owned_by_user() self._validate_installation() return True - def _validate_is_sentry_app_making_request(self): + def _validate_is_sentry_app_making_request(self) -> None: if not self.user.is_sentry_app: - raise APIUnauthorized + raise APIUnauthorized("User is not a Sentry App") - def _validate_app_is_owned_by_user(self): + def _validate_app_is_owned_by_user(self) -> None: if self.sentry_app.proxy_user != self.user: - raise APIUnauthorized + raise APIUnauthorized("Sentry App does not belong to given user") - def _validate_installation(self): + def _validate_installation(self) -> None: if self.install.sentry_app.id != self.sentry_app.id: - raise APIUnauthorized + raise APIUnauthorized( + f"Sentry App Installation is not for Sentry App: {self.sentry_app.slug}" + ) @cached_property - def sentry_app(self): + def sentry_app(self) -> SentryApp: try: return self.application.sentry_app except SentryApp.DoesNotExist: - raise APIUnauthorized + raise APIUnauthorized("Sentry App does not exist") @cached_property - def application(self): + def application(self) -> ApiApplication: try: return ApiApplication.objects.get(client_id=self.client_id) except ApiApplication.DoesNotExist: - raise APIUnauthorized + raise APIUnauthorized("Application does not exist") diff --git a/src/sentry/web/frontend/oauth_token.py b/src/sentry/web/frontend/oauth_token.py index ee8dea005f5a6f..109babbecf7a26 100644 --- a/src/sentry/web/frontend/oauth_token.py +++ b/src/sentry/web/frontend/oauth_token.py @@ -9,10 +9,10 @@ from rest_framework.request import Request from sentry import options -from sentry.mediators.token_exchange.util import GrantTypes from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus from sentry.models.apigrant import ApiGrant from sentry.models.apitoken import ApiToken +from sentry.sentry_apps.token_exchange.util import GrantTypes from sentry.utils import json, metrics from sentry.web.frontend.base import control_silo_view from sentry.web.frontend.openidtoken import OpenIDToken @@ -157,9 +157,11 @@ def process_token_details( token_information = { "access_token": token.token, "refresh_token": token.refresh_token, - "expires_in": int((token.expires_at - timezone.now()).total_seconds()) - if token.expires_at - else None, + "expires_in": ( + int((token.expires_at - timezone.now()).total_seconds()) + if token.expires_at + else None + ), "expires_at": token.expires_at, "token_type": "bearer", "scope": " ".join(token.get_scopes()), diff --git a/tests/sentry/mediators/token_exchange/test_refresher.py b/tests/sentry/mediators/token_exchange/test_refresher.py index 6937c12705e3a6..74226bcab700f4 100644 --- a/tests/sentry/mediators/token_exchange/test_refresher.py +++ b/tests/sentry/mediators/token_exchange/test_refresher.py @@ -42,13 +42,8 @@ def test_deletes_refreshed_token(self): self.refresher.call() assert not ApiToken.objects.filter(id=self.token.id).exists() - @patch("sentry.mediators.token_exchange.Validator.run") - def test_validates_generic_token_exchange_requirements(self, validator): - self.refresher.call() - - validator.assert_called_once_with( - install=self.install, client_id=self.client_id, user=self.user - ) + def test_validates_generic_token_exchange_requirements(self): + self.refresher._validate() def test_validates_token_belongs_to_sentry_app(self): refresh_token = ApiToken.objects.create( diff --git a/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_authorizations.py b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_authorizations.py index 9836e317648cfe..7ea1fcb1941648 100644 --- a/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_authorizations.py +++ b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_authorizations.py @@ -3,9 +3,9 @@ from django.urls import reverse from django.utils import timezone -from sentry.mediators.token_exchange.util import GrantTypes from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken +from sentry.sentry_apps.token_exchange.util import GrantTypes from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.silo import assume_test_silo_mode, control_silo_test diff --git a/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py b/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py index 4cc154720867d7..1441fc48ecc06f 100644 --- a/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py +++ b/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py @@ -35,13 +35,8 @@ def test_adds_token_to_installation(self): token = self.grant_exchanger.run() assert SentryAppInstallation.objects.get(id=self.install.id).api_token == token - @patch("sentry.mediators.token_exchange.Validator.run") - def test_validate_generic_token_exchange_requirements(self, validator): - self.grant_exchanger.run() - - validator.assert_called_once_with( - install=self.install, client_id=self.client_id, user=self.user - ) + def test_validate_generic_token_exchange_requirements(self): + assert self.grant_exchanger._validate() def test_grant_must_belong_to_installations(self): other_install = self.create_sentry_app_installation(prevent_token_exchange=True) diff --git a/tests/sentry/mediators/token_exchange/test_validator.py b/tests/sentry/sentry_apps/token_exchange/test_validator.py similarity index 86% rename from tests/sentry/mediators/token_exchange/test_validator.py rename to tests/sentry/sentry_apps/token_exchange/test_validator.py index 1d81eaae8f892e..d3e921bcb303e0 100644 --- a/tests/sentry/mediators/token_exchange/test_validator.py +++ b/tests/sentry/sentry_apps/token_exchange/test_validator.py @@ -3,9 +3,9 @@ import pytest from sentry.coreapi import APIUnauthorized -from sentry.mediators.token_exchange.validator import Validator from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.services.app import app_service +from sentry.sentry_apps.token_exchange.validator import Validator from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test @@ -24,35 +24,35 @@ def setUp(self): ) def test_happy_path(self): - assert self.validator.call() + assert self.validator.run() def test_request_must_be_made_by_sentry_app(self): self.validator.user = self.create_user() with pytest.raises(APIUnauthorized): - self.validator.call() + self.validator.run() def test_request_user_must_own_sentry_app(self): self.validator.user = self.create_user(is_sentry_app=True) with pytest.raises(APIUnauthorized): - self.validator.call() + self.validator.run() def test_installation_belongs_to_sentry_app_with_client_id(self): self.validator.install = self.create_sentry_app_installation() with pytest.raises(APIUnauthorized): - self.validator.call() + self.validator.run() @patch("sentry.models.ApiApplication.sentry_app") def test_raises_when_sentry_app_cannot_be_found(self, sentry_app): sentry_app.side_effect = SentryApp.DoesNotExist() with pytest.raises(APIUnauthorized): - self.validator.call() + self.validator.run() def test_raises_with_invalid_client_id(self): self.validator.client_id = "123" with pytest.raises(APIUnauthorized): - self.validator.call() + self.validator.run() From d4d8507e01815b55988bbe0b1590577214014a70 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 15 Oct 2024 10:50:54 -0700 Subject: [PATCH 2/6] missed some things --- src/sentry/mediators/token_exchange/__init__.py | 7 ------- tests/sentry/mediators/token_exchange/test_refresher.py | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/sentry/mediators/token_exchange/__init__.py b/src/sentry/mediators/token_exchange/__init__.py index 3739e69be0ffef..7d1a5e131eec07 100644 --- a/src/sentry/mediators/token_exchange/__init__.py +++ b/src/sentry/mediators/token_exchange/__init__.py @@ -1,8 +1 @@ -from ...sentry_apps.token_exchange.util import ( # NOQA - AUTHORIZATION, - REFRESH, - GrantTypes, - token_expiration, -) -from ...sentry_apps.token_exchange.validator import Validator # NOQA from .refresher import Refresher # NOQA diff --git a/tests/sentry/mediators/token_exchange/test_refresher.py b/tests/sentry/mediators/token_exchange/test_refresher.py index 74226bcab700f4..632ff74ab7fc1a 100644 --- a/tests/sentry/mediators/token_exchange/test_refresher.py +++ b/tests/sentry/mediators/token_exchange/test_refresher.py @@ -43,7 +43,7 @@ def test_deletes_refreshed_token(self): assert not ApiToken.objects.filter(id=self.token.id).exists() def test_validates_generic_token_exchange_requirements(self): - self.refresher._validate() + assert self.refresher._validate() def test_validates_token_belongs_to_sentry_app(self): refresh_token = ApiToken.objects.create( From 360d82235dbcc174fdc21aeb71adf8ac8e77b96d Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 15 Oct 2024 14:12:26 -0700 Subject: [PATCH 3/6] update refresher with changes --- src/sentry/sentry_apps/token_exchange/refresher.py | 13 +++++++------ .../sentry_apps/token_exchange/test_refresher.py | 7 +------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/sentry/sentry_apps/token_exchange/refresher.py b/src/sentry/sentry_apps/token_exchange/refresher.py index f6881700eb09ab..9b8ed3364e00f7 100644 --- a/src/sentry/sentry_apps/token_exchange/refresher.py +++ b/src/sentry/sentry_apps/token_exchange/refresher.py @@ -5,13 +5,13 @@ from sentry import analytics from sentry.coreapi import APIUnauthorized -from sentry.mediators.token_exchange.util import token_expiration -from sentry.mediators.token_exchange.validator import Validator from sentry.models.apiapplication import ApiApplication from sentry.models.apitoken import ApiToken from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation from sentry.sentry_apps.services.app import RpcSentryAppInstallation +from sentry.sentry_apps.token_exchange.util import token_expiration +from sentry.sentry_apps.token_exchange.validator import Validator from sentry.users.models.user import User @@ -28,8 +28,8 @@ class Refresher: def run(self) -> ApiToken: with transaction.atomic(router.db_for_write(ApiToken)): - self._validate() - self.token.delete() + if self._validate(): + self.token.delete() self.record_analytics() return self._create_new_token() @@ -41,11 +41,12 @@ def record_analytics(self) -> None: exchange_type="refresh", ) - def _validate(self) -> None: - Validator.run(install=self.install, client_id=self.client_id, user=self.user) + def _validate(self) -> bool: + is_valid = Validator(install=self.install, client_id=self.client_id, user=self.user).run() if self.token.application != self.application: raise APIUnauthorized("Token does not belong to the application") + return is_valid def _create_new_token(self) -> ApiToken: token = ApiToken.objects.create( diff --git a/tests/sentry/sentry_apps/token_exchange/test_refresher.py b/tests/sentry/sentry_apps/token_exchange/test_refresher.py index 6ff4723803cf89..9e7da2f057f090 100644 --- a/tests/sentry/sentry_apps/token_exchange/test_refresher.py +++ b/tests/sentry/sentry_apps/token_exchange/test_refresher.py @@ -42,13 +42,8 @@ def test_deletes_refreshed_token(self): self.refresher.run() assert not ApiToken.objects.filter(id=self.token.id).exists() - @patch("sentry.mediators.token_exchange.Validator.run") def test_validates_generic_token_exchange_requirements(self, validator): - self.refresher.run() - - validator.assert_called_once_with( - install=self.install, client_id=self.client_id, user=self.user - ) + assert self.refresher._validate() def test_validates_token_belongs_to_sentry_app(self): refresh_token = ApiToken.objects.create( From 114e66de56651e4e7e77516d1f6ddfa4eb66c495 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Tue, 15 Oct 2024 15:47:37 -0700 Subject: [PATCH 4/6] forgot to remove patch variable --- tests/sentry/sentry_apps/token_exchange/test_refresher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sentry/sentry_apps/token_exchange/test_refresher.py b/tests/sentry/sentry_apps/token_exchange/test_refresher.py index 9e7da2f057f090..68f8cca4d4edf2 100644 --- a/tests/sentry/sentry_apps/token_exchange/test_refresher.py +++ b/tests/sentry/sentry_apps/token_exchange/test_refresher.py @@ -42,7 +42,7 @@ def test_deletes_refreshed_token(self): self.refresher.run() assert not ApiToken.objects.filter(id=self.token.id).exists() - def test_validates_generic_token_exchange_requirements(self, validator): + def test_validates_generic_token_exchange_requirements(self): assert self.refresher._validate() def test_validates_token_belongs_to_sentry_app(self): From c8ca5fc36f46c9988b3de51282048aecdd03a69e Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 16 Oct 2024 10:47:08 -0700 Subject: [PATCH 5/6] move operations outside of transaction --- src/sentry/sentry_apps/token_exchange/grant_exchanger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sentry/sentry_apps/token_exchange/grant_exchanger.py b/src/sentry/sentry_apps/token_exchange/grant_exchanger.py index 27a3ab83947762..6a6089c2f4f514 100644 --- a/src/sentry/sentry_apps/token_exchange/grant_exchanger.py +++ b/src/sentry/sentry_apps/token_exchange/grant_exchanger.py @@ -37,8 +37,8 @@ def run(self): # Once it's exchanged it's no longer valid and should not be # exchangeable, so we delete it. self._delete_grant() - self.record_analytics() - return token + self.record_analytics() + return token def record_analytics(self) -> None: analytics.record( From 7034eab43122415081008cdf1329aa25e55a041e Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 16 Oct 2024 10:50:23 -0700 Subject: [PATCH 6/6] deleting unneeded tests --- .../sentry/sentry_apps/token_exchange/test_grant_exchanger.py | 3 --- tests/sentry/sentry_apps/token_exchange/test_refresher.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py b/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py index 1441fc48ecc06f..2385ab75cf14f5 100644 --- a/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py +++ b/tests/sentry/sentry_apps/token_exchange/test_grant_exchanger.py @@ -35,9 +35,6 @@ def test_adds_token_to_installation(self): token = self.grant_exchanger.run() assert SentryAppInstallation.objects.get(id=self.install.id).api_token == token - def test_validate_generic_token_exchange_requirements(self): - assert self.grant_exchanger._validate() - def test_grant_must_belong_to_installations(self): other_install = self.create_sentry_app_installation(prevent_token_exchange=True) self.grant_exchanger.code = other_install.api_grant.code diff --git a/tests/sentry/sentry_apps/token_exchange/test_refresher.py b/tests/sentry/sentry_apps/token_exchange/test_refresher.py index 68f8cca4d4edf2..3624b56895129a 100644 --- a/tests/sentry/sentry_apps/token_exchange/test_refresher.py +++ b/tests/sentry/sentry_apps/token_exchange/test_refresher.py @@ -42,9 +42,6 @@ def test_deletes_refreshed_token(self): self.refresher.run() assert not ApiToken.objects.filter(id=self.token.id).exists() - def test_validates_generic_token_exchange_requirements(self): - assert self.refresher._validate() - def test_validates_token_belongs_to_sentry_app(self): refresh_token = ApiToken.objects.create( user=self.user,