Skip to content

Commit

Permalink
feat(api): require User.is_active on auth actions, except activation
Browse files Browse the repository at this point in the history
  • Loading branch information
peterthomassen committed Jan 17, 2022
1 parent 032b48c commit b1c09a8
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 2 deletions.
12 changes: 10 additions & 2 deletions api/desecapi/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,17 @@ class AuthenticatedBasicUserActionAuthentication(BaseAuthentication):
"""
def authenticate(self, request):
view = request.parser_context['view']
serializer = AuthenticatedBasicUserActionSerializer(data={}, context=view.get_serializer_context())
return self.authenticate_credentials(view.get_serializer_context())

def authenticate_credentials(self, context):
serializer = AuthenticatedBasicUserActionSerializer(data={}, context=context)
serializer.is_valid(raise_exception=True)
return serializer.validated_data['user'], None

user = serializer.validated_data['user']
if user.is_active == False: # don't catch None here!
raise exceptions.AuthenticationFailed('User inactive.')

return user, None


class TokenHasher(PBKDF2PasswordHasher):
Expand Down
9 changes: 9 additions & 0 deletions api/desecapi/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
from rest_framework import permissions


class IsActiveUser(permissions.BasePermission):
"""
Allows access only to admin users.
"""

def has_permission(self, request, view):
return bool(request.user and request.user.is_active)


class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to view or edit it.
Expand Down
21 changes: 21 additions & 0 deletions api/desecapi/tests/test_user_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ def assertResetPasswordVerificationSuccessResponse(self, response):
status_code=status.HTTP_200_OK
)

def assertResetPasswordInactiveUserVerificationFailedResponse(self, response):
return self.assertContains(
response=response,
text="User inactive.",
status_code=status.HTTP_403_FORBIDDEN
)

def assertChangeEmailSuccessResponse(self, response):
return self.assertContains(
response=response,
Expand Down Expand Up @@ -705,6 +712,20 @@ def test_reset_password_inactive_user(self):
self.assertResetPasswordSuccessResponse(self.reset_password(self.email))
self.assertNoEmailSent()

def test_reset_password_inactive_user_old_confirmation_link(self):
user = User.objects.get(email=self.email)
user.needs_captcha = False

self.assertResetPasswordSuccessResponse(self.reset_password(self.email))
confirmation_link = self.assertResetPasswordEmail(self.email)

user.is_active = False
user.save()
new_password = self.random_password()
self.assertConfirmationLinkRedirect(confirmation_link)
self.assertResetPasswordInactiveUserVerificationFailedResponse(
self.client.verify(confirmation_link, data={'new_password': new_password}))

def test_reset_password_multiple_times(self):
for _ in range(3):
self._test_reset_password(self.email)
Expand Down
5 changes: 5 additions & 0 deletions api/desecapi/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,10 @@ def authentication_classes(self):
# This prevents both auth action code evaluation and user-specific throttling when we only want a redirect
return () if self.request.method in SAFE_METHODS else (auth.AuthenticatedBasicUserActionAuthentication,)

@property
def permission_classes(self):
return () if self.request.method in SAFE_METHODS else (permissions.IsActiveUser,)

@property
def throttle_scope(self):
return 'account_management_passive' if self.request.method in SAFE_METHODS else 'account_management_active'
Expand Down Expand Up @@ -680,6 +684,7 @@ def finalize(self):

class AuthenticatedActivateUserActionView(AuthenticatedActionView):
html_url = '/confirm/activate-account/{code}/'
permission_classes = () # don't require that user is activated already
serializer_class = serializers.AuthenticatedActivateUserActionSerializer

def finalize(self):
Expand Down

0 comments on commit b1c09a8

Please sign in to comment.