authentication.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import base64
  2. from django.contrib.auth.hashers import PBKDF2PasswordHasher
  3. from django.utils import timezone
  4. from rest_framework import exceptions, HTTP_HEADER_ENCODING
  5. from rest_framework.authentication import (
  6. BaseAuthentication,
  7. get_authorization_header,
  8. TokenAuthentication as RestFrameworkTokenAuthentication,
  9. BasicAuthentication)
  10. from desecapi.models import Token
  11. from desecapi.serializers import AuthenticatedBasicUserActionSerializer, EmailPasswordSerializer
  12. class TokenAuthentication(RestFrameworkTokenAuthentication):
  13. model = Token
  14. def authenticate_credentials(self, key):
  15. key = Token.make_hash(key)
  16. user, token = super().authenticate_credentials(key)
  17. token.last_used = timezone.now()
  18. token.save()
  19. return user, token
  20. class BasicTokenAuthentication(BaseAuthentication):
  21. """
  22. HTTP Basic authentication that uses username and token.
  23. Clients should authenticate by passing the username and the token as a
  24. password in the "Authorization" HTTP header, according to the HTTP
  25. Basic Authentication Scheme
  26. Authorization: Basic dXNlcm5hbWU6dG9rZW4=
  27. For username "username" and password "token".
  28. """
  29. # A custom token model may be used, but must have the following properties.
  30. #
  31. # * key -- The string identifying the token
  32. # * user -- The user to which the token belongs
  33. model = Token
  34. def authenticate(self, request):
  35. auth = get_authorization_header(request).split()
  36. if not auth or auth[0].lower() != b'basic':
  37. return None
  38. if len(auth) == 1:
  39. msg = 'Invalid basic auth token header. No credentials provided.'
  40. raise exceptions.AuthenticationFailed(msg)
  41. elif len(auth) > 2:
  42. msg = 'Invalid basic auth token header. Basic authentication string should not contain spaces.'
  43. raise exceptions.AuthenticationFailed(msg)
  44. return self.authenticate_credentials(auth[1])
  45. def authenticate_credentials(self, basic):
  46. invalid_token_message = 'Invalid basic auth token'
  47. try:
  48. username, key = base64.b64decode(basic).decode(HTTP_HEADER_ENCODING).split(':')
  49. user, token = TokenAuthentication().authenticate_credentials(key)
  50. domain_names = user.domains.values_list('name', flat=True)
  51. if username not in ['', user.email] and not username.lower() in domain_names:
  52. raise Exception
  53. except Exception:
  54. raise exceptions.AuthenticationFailed(invalid_token_message)
  55. if not user.is_active:
  56. raise exceptions.AuthenticationFailed(invalid_token_message)
  57. return user, token
  58. def authenticate_header(self, request):
  59. return 'Basic'
  60. class URLParamAuthentication(BaseAuthentication):
  61. """
  62. Authentication against username/password as provided in URL parameters.
  63. """
  64. model = Token
  65. def authenticate(self, request):
  66. """
  67. Returns a `User` if a correct username and password have been supplied
  68. using URL parameters. Otherwise returns `None`.
  69. """
  70. if 'username' not in request.query_params:
  71. msg = 'No username URL parameter provided.'
  72. raise exceptions.AuthenticationFailed(msg)
  73. if 'password' not in request.query_params:
  74. msg = 'No password URL parameter provided.'
  75. raise exceptions.AuthenticationFailed(msg)
  76. return self.authenticate_credentials(request.query_params['username'], request.query_params['password'])
  77. def authenticate_credentials(self, _, key):
  78. try:
  79. user, token = TokenAuthentication().authenticate_credentials(key)
  80. except self.model.DoesNotExist:
  81. raise exceptions.AuthenticationFailed('badauth')
  82. if not user.is_active:
  83. raise exceptions.AuthenticationFailed('badauth')
  84. return token.user, token
  85. class EmailPasswordPayloadAuthentication(BaseAuthentication):
  86. authenticate_credentials = BasicAuthentication.authenticate_credentials
  87. def authenticate(self, request):
  88. serializer = EmailPasswordSerializer(data=request.data)
  89. serializer.is_valid(raise_exception=True)
  90. return self.authenticate_credentials(serializer.data['email'], serializer.data['password'], request)
  91. class AuthenticatedBasicUserActionAuthentication(BaseAuthentication):
  92. """
  93. Authenticates a request based on whether the serializer determines the validity of the given verification code
  94. and additional data (using `serializer.is_valid()`). The serializer's input data will be determined by (a) the
  95. view's 'code' kwarg and (b) the request payload for POST requests.
  96. If the request is valid, the AuthenticatedAction instance will be attached to the request as `auth` attribute.
  97. """
  98. def authenticate(self, request):
  99. view = request.parser_context['view']
  100. serializer = AuthenticatedBasicUserActionSerializer(data=request.data, context=view.get_serializer_context())
  101. serializer.is_valid(raise_exception=True)
  102. return serializer.validated_data['user'], None
  103. class TokenHasher(PBKDF2PasswordHasher):
  104. algorithm = 'pbkdf2_sha256_iter1'
  105. iterations = 1