FAQ | This is a LIVE service | Changelog

Skip to content
Snippets Groups Projects
Commit f6bee38e authored by Robin Goodall's avatar Robin Goodall :speech_balloon:
Browse files

feat: correct password app reset token call and handle missing users

parent c9a131f3
No related branches found
No related tags found
1 merge request!80feat: correct password app reset token call and handle missing users
Pipeline #677909 failed
...@@ -3,7 +3,12 @@ from dataclasses import dataclass ...@@ -3,7 +3,12 @@ from dataclasses import dataclass
import requests import requests
import structlog import structlog
from django.conf import settings from django.conf import settings
from tenacity import retry, stop_after_attempt, wait_exponential from tenacity import (
retry,
retry_if_not_exception_type,
stop_after_attempt,
wait_exponential,
)
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
...@@ -19,10 +24,16 @@ class PasswordAppTokenResponse: ...@@ -19,10 +24,16 @@ class PasswordAppTokenResponse:
token: str token: str
class PasswordAppNotFound(Exception):
message = "Password App was unable to find the user"
@retry( @retry(
reraise=True, reraise=True,
stop=stop_after_attempt(PASSWORD_APP_RETRIES + 1), stop=stop_after_attempt(PASSWORD_APP_RETRIES + 1),
wait=wait_exponential(multiplier=2, min=PASSWORD_APP_INITIAL_RETRY_DELAY), wait=wait_exponential(multiplier=2, min=PASSWORD_APP_INITIAL_RETRY_DELAY),
# Don't retry if the user wasn't found by the Password App
retry=retry_if_not_exception_type(PasswordAppNotFound),
) )
def get_reset_token(crsid: str, duration=PASSWORD_APP_TOKEN_DURATION) -> PasswordAppTokenResponse: def get_reset_token(crsid: str, duration=PASSWORD_APP_TOKEN_DURATION) -> PasswordAppTokenResponse:
""" """
...@@ -36,11 +47,13 @@ def get_reset_token(crsid: str, duration=PASSWORD_APP_TOKEN_DURATION) -> Passwor ...@@ -36,11 +47,13 @@ def get_reset_token(crsid: str, duration=PASSWORD_APP_TOKEN_DURATION) -> Passwor
request = requests.get( request = requests.get(
settings.PASSWORD_APP_RESET_TOKEN_URL, settings.PASSWORD_APP_RESET_TOKEN_URL,
headers={"Authorization": f"Token {settings.PASSWORD_APP_TOKEN}"}, headers={"Authorization": f"Token {settings.PASSWORD_APP_TOKEN}"},
params={"crsid": crsid, "duration": duration}, params={"id": crsid, "duration": duration},
timeout=(PASSWORD_APP_CONNECTION_TIMEOUT, PASSWORD_APP_READ_TIMEOUT), timeout=(PASSWORD_APP_CONNECTION_TIMEOUT, PASSWORD_APP_READ_TIMEOUT),
) )
request.raise_for_status() request.raise_for_status()
return PasswordAppTokenResponse(**request.json()) return PasswordAppTokenResponse(**request.json())
except Exception as e: except Exception as e:
logger.error("get_reset_token", error="Failed to get reset token", exc=repr(e)) logger.error("get_reset_token", error="Failed to get reset token", exc=repr(e))
if isinstance(e, requests.HTTPError) and e.response.status_code == 404:
raise PasswordAppNotFound
raise e raise e
...@@ -4,9 +4,9 @@ Views implementing the API endpoints. ...@@ -4,9 +4,9 @@ Views implementing the API endpoints.
""" """
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import exceptions, generics from rest_framework import exceptions, generics, status
from activate_account.reset_tokens import get_reset_token from activate_account.reset_tokens import PasswordAppNotFound, get_reset_token
from api.v1alpha.serializers import ( from api.v1alpha.serializers import (
AccountSerializer, AccountSerializer,
MethodNotAllowedErrorSerializer, MethodNotAllowedErrorSerializer,
...@@ -62,14 +62,25 @@ class AccountDetailsView(generics.RetrieveUpdateAPIView): ...@@ -62,14 +62,25 @@ class AccountDetailsView(generics.RetrieveUpdateAPIView):
return super().patch(request, *args, **kwargs) return super().patch(request, *args, **kwargs)
class ResetTokenUnavailable(exceptions.APIException):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
default_detail = "Failed to retrieve reset token"
default_code = "service_unavailable"
class ResetTokenView(generics.RetrieveAPIView): class ResetTokenView(generics.RetrieveAPIView):
serializer_class = ResetTokenSerializer serializer_class = ResetTokenSerializer
def get_object(self): def get_object(self):
try: try:
return get_reset_token(self.request.user.crsid) return get_reset_token(self.request.user.crsid)
except PasswordAppNotFound:
# Raising a validation error here rather than a 404 which could be misunderstand as
# the endpoint not existing
raise exceptions.ValidationError({"crsid": "Password App was unable to find the user"})
except Exception: except Exception:
raise exceptions.APIException("Failed to retrieve reset token") # Otherwise, report a 503 as this service is temporarily unavailable
raise ResetTokenUnavailable
@extend_schema( @extend_schema(
tags=["account management"], tags=["account management"],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment