Browse Source

fix(itunes): Allow duplicate requests for SMS auth (#28169)

Impatient users can break the flow by hitting the Text Me button 
more than once. This makes it so that they can't break it, but they
can get themselves blocked if they click on it too many times.

Co-authored-by: Betty Da <bda@sentry.io>
Floris Bruynooghe 3 years ago
parent
commit
79c8aa6b1e

+ 5 - 1
src/sentry/api/endpoints/project_app_store_connect_credentials.py

@@ -64,6 +64,7 @@ from sentry.api.exceptions import (
     AppConnectAuthenticationError,
     AppConnectAuthenticationError,
     AppConnectMultipleSourcesError,
     AppConnectMultipleSourcesError,
     ItunesAuthenticationError,
     ItunesAuthenticationError,
+    ItunesSmsBlocked,
     ItunesTwoFactorAuthenticationRequired,
     ItunesTwoFactorAuthenticationRequired,
 )
 )
 from sentry.lang.native import appconnect
 from sentry.lang.native import appconnect
@@ -619,7 +620,10 @@ class AppStoreConnectRequestSmsEndpoint(ProjectEndpoint):  # type: ignore
         except Exception:
         except Exception:
             return Response({"session_context": ["Invalid client_state"]}, status=400)
             return Response({"session_context": ["Invalid client_state"]}, status=400)
 
 
-        itunes_client.request_sms_auth()
+        try:
+            itunes_client.request_sms_auth()
+        except itunes_connect.SmsBlockedError:
+            raise ItunesSmsBlocked
         return Response({"sessionContext": {"client_state": itunes_client.to_json()}}, status=200)
         return Response({"sessionContext": {"client_state": itunes_client.to_json()}}, status=200)
 
 
 
 

+ 6 - 0
src/sentry/api/exceptions.py

@@ -115,6 +115,12 @@ class ItunesAuthenticationError(SentryAPIException):
     message = "Itunes authentication error"
     message = "Itunes authentication error"
 
 
 
 
+class ItunesSmsBlocked(SentryAPIException):
+    status_code = status.HTTP_423_LOCKED
+    code = "itunes-sms-blocked-error"
+    message = "Blocked from requesting more SMS codes for an unspecified period of time"
+
+
 class ItunesTwoFactorAuthenticationRequired(SentryAPIException):
 class ItunesTwoFactorAuthenticationRequired(SentryAPIException):
     status_code = status.HTTP_401_UNAUTHORIZED
     status_code = status.HTTP_401_UNAUTHORIZED
     code = "itunes-2fa-required"
     code = "itunes-2fa-required"

+ 14 - 1
src/sentry/utils/appleconnect/itunes_connect.py

@@ -64,6 +64,12 @@ class ForbiddenError(ITunesError):
     pass
     pass
 
 
 
 
+class SmsBlockedError(ITunesError):
+    """Blocked from requesting more SMS codes for some period of time."""
+
+    pass
+
+
 PublicProviderId = NewType("PublicProviderId", str)
 PublicProviderId = NewType("PublicProviderId", str)
 
 
 
 
@@ -326,9 +332,14 @@ class ITunesClient:
     def request_sms_auth(self) -> None:
     def request_sms_auth(self) -> None:
         """Requests sending the authentication code to a trusted phone.
         """Requests sending the authentication code to a trusted phone.
 
 
+        :raises SmsBlockedError: if too many requests for the SMS auth code were made.
         :raises ITunesError: if there was an error requesting to use the trusted phone.
         :raises ITunesError: if there was an error requesting to use the trusted phone.
         """
         """
-        assert self.state is ClientState.AUTH_REQUESTED, f"Actual client state: {self.state}"
+
+        assert self.state in [
+            ClientState.AUTH_REQUESTED,
+            ClientState.SMS_AUTH_REQUESTED,
+        ], f"Actual client state: {self.state}"
         self._request_trusted_phone_info()
         self._request_trusted_phone_info()
         assert self._trusted_phone is not None
         assert self._trusted_phone is not None
         url = "https://idmsa.apple.com/appleauth/auth/verify/phone"
         url = "https://idmsa.apple.com/appleauth/auth/verify/phone"
@@ -348,6 +359,8 @@ class ITunesClient:
             },
             },
             timeout=REQUEST_TIMEOUT,
             timeout=REQUEST_TIMEOUT,
         )
         )
+        if response.status_code == HTTPStatus.LOCKED:
+            raise SmsBlockedError
         if response.status_code != HTTPStatus.OK:
         if response.status_code != HTTPStatus.OK:
             raise ITunesError(f"Unexpected response status: {response.status_code}")
             raise ITunesError(f"Unexpected response status: {response.status_code}")
         self.state = ClientState.SMS_AUTH_REQUESTED
         self.state = ClientState.SMS_AUTH_REQUESTED

+ 1 - 1
tests/sentry/utils/appleconnect/test_itunes_connect.py

@@ -8,7 +8,7 @@ Firstly you need to create a file named ``credentials.json`` in this directory::
      "password": "your itunes password"
      "password": "your itunes password"
    }
    }
 
 
-You account needs to have access to the "GetSentry LLC" organisation for all tests to run.
+Your account needs to have access to the "GetSentry LLC" organisation for all tests to run.
 Tests which require this are marked with ``getsentryllc`` so you can disable these tests
 Tests which require this are marked with ``getsentryllc`` so you can disable these tests
 using `pytest -m 'not getsentryllc'`.
 using `pytest -m 'not getsentryllc'`.