Skip to content

Commit 2ea65f7

Browse files
committed
custom exception when attempting to read more than once
1 parent 2872508 commit 2ea65f7

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

src/sentry/models/apitoken.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ def generate_token():
3333
return secrets.token_hex(nbytes=32)
3434

3535

36+
class PlaintextSecretAlreadyRead(Exception):
37+
def __init__(
38+
self,
39+
message="the secret you are trying to read is read-once and cannot be accessed directly again",
40+
):
41+
super().__init__(message)
42+
43+
3644
class ApiTokenManager(ControlOutboxProducingManager):
3745
def create(self, *args, **kwargs):
3846
token_type: AuthTokenType | None = kwargs.get("token_type", None)
@@ -128,6 +136,8 @@ def _plaintext_token(self):
128136

129137
if plaintext_token is not None:
130138
setattr(self, f"_{manager_class_name}__plaintext_token", None)
139+
else:
140+
raise PlaintextSecretAlreadyRead()
131141

132142
return plaintext_token
133143

@@ -144,9 +154,18 @@ def _plaintext_refresh_token(self):
144154
self, f"_{manager_class_name}__plaintext_refresh_token", None
145155
)
146156

147-
if plaintext_refresh_token is not None:
157+
if plaintext_refresh_token:
148158
setattr(self, f"_{manager_class_name}__plaintext_refresh_token", None)
149159

160+
# some token types do not have refresh tokens, so we check to see
161+
# if there's a hash value that exists for the refresh token.
162+
#
163+
# if there is a hash value, then a refresh token is expected
164+
# and if the plaintext_refresh_token is None, then it has already
165+
# been read once so we should throw the exception
166+
if not plaintext_refresh_token and self.refresh_token:
167+
raise PlaintextSecretAlreadyRead()
168+
150169
return plaintext_refresh_token
151170

152171
def save(self, *args: Any, **kwargs: Any) -> None:

tests/sentry/models/test_apitoken.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import hashlib
22
from datetime import timedelta
33

4+
import pytest
45
from django.utils import timezone
56

67
from sentry.conf.server import SENTRY_SCOPE_HIERARCHY_MAPPING, SENTRY_SCOPES
78
from sentry.hybridcloud.models import ApiTokenReplica
8-
from sentry.models.apitoken import ApiToken
9+
from sentry.models.apitoken import ApiToken, PlaintextSecretAlreadyRead
910
from sentry.models.integrations.sentry_app_installation import SentryAppInstallation
1011
from sentry.models.integrations.sentry_app_installation_token import SentryAppInstallationToken
1112
from sentry.silo import SiloMode
@@ -98,11 +99,10 @@ def test_plaintext_values_only_available_immediately_after_create(self):
9899
assert token._plaintext_token is not None
99100
assert token._plaintext_refresh_token is None # user auth tokens don't have refresh tokens
100101

101-
_ = token._plaintext_token
102-
103-
# we read the value above so now it should
104-
# now be None as it is a "read once" property
105-
assert token._plaintext_token is None
102+
# we accessed the plaintext token above when we asserted it was not None
103+
# accessing it again should throw an exception
104+
with pytest.raises(PlaintextSecretAlreadyRead):
105+
_ = token._plaintext_token
106106

107107
@override_options({"apitoken.save-hash-on-create": True})
108108
def test_user_auth_token_hash(self):

0 commit comments

Comments
 (0)