diff --git a/tests/functional/accounts/test_login.py b/tests/functional/accounts/test_login.py index da9ea64e764b..2d186c6182fe 100644 --- a/tests/functional/accounts/test_login.py +++ b/tests/functional/accounts/test_login.py @@ -42,6 +42,23 @@ def test_login_invalid_user(webtest): assert pq("div.alert")[0].text == "Invalid username or password" +@pytest.mark.django_db(transaction=True) +def test_login_inactive_user(webtest): + u = User.objects.create_user(username="testuser", password="password!") + u.is_active = False + u.save() + + form = webtest.get(reverse("accounts.login") + "?next=/").form + form["username"] = "testuser" + form["password"] = "password!" + response = form.submit() + + pq = response.pyquery + assert response.status_code == 200 + assert pq("div.alert")[0].text == ("This account is inactive or has been " + "locked by an administrator.") + + @pytest.mark.django_db(transaction=True) def test_login_already_logged_in(webtest): response = webtest.get(reverse("accounts.login"), user="testuser") diff --git a/tests/unit/accounts/test_views.py b/tests/unit/accounts/test_views.py index 8137b3d164ca..fa2bb9aa0060 100644 --- a/tests/unit/accounts/test_views.py +++ b/tests/unit/accounts/test_views.py @@ -54,7 +54,7 @@ def test_login_already_logged_in(rf): def test_login_flow(rf): - user = stub(is_authenticated=lambda: False) + user = stub(is_authenticated=lambda: False, is_active=True) authenticator = mock.Mock(return_value=user) login = mock.Mock() view = LoginView.as_view(authenticator=authenticator, login=login) @@ -111,7 +111,7 @@ def test_login_invalid_user(rf): login = mock.Mock() view = LoginView.as_view(authenticator=authenticator, login=login) - # Attempt to login with invalid form data + # Attempt to login with an invalid user data = {"username": "testuser", "password": "test password"} request = rf.post(reverse("accounts.login"), data) request.user = user @@ -125,6 +125,33 @@ def test_login_invalid_user(rf): } +def test_login_inactive_user(rf): + user = stub(is_authenticated=lambda: True, is_active=False) + authenticator = mock.Mock(return_value=user) + login = mock.Mock() + view = LoginView.as_view(authenticator=authenticator, login=login) + + # Attempt to login with an inactive user + data = {"username": "testuser", "password": "test password"} + request = rf.post(reverse("accounts.login"), data) + request.user = stub(is_authenticated=lambda: False) + response = view(request) + + assert authenticator.call_count == 1 + assert authenticator.call_args == (tuple(), data) + assert login.call_count == 0 + + assert response.status_code == 200 + assert response.context_data.keys() == set(["next", "form"]) + assert response.context_data["next"] is None + assert response.context_data["form"].errors == { + "__all__": [ + ("This account is inactive or has been locked by " + "an administrator.") + ], + } + + def test_signup_flow(rf): class TestSignupForm(SignupForm): model = stub(api=stub(username_exists=lambda x: False)) diff --git a/warehouse/accounts/views.py b/warehouse/accounts/views.py index e4fe480b53fa..a39c7948805b 100644 --- a/warehouse/accounts/views.py +++ b/warehouse/accounts/views.py @@ -38,17 +38,24 @@ def post(self, request): password=form.cleaned_data["password"], ) - if user is not None: + if user is not None and user.is_active: # We have a valid user, so add their session to the request self.login(request, user) return HttpResponseRedirect(self._get_next_url(request), status=303, ) - - # We don't have a valid user, so send an error back to the form - m = _("Invalid username or password") - form._errors.setdefault("__all__", form.error_class([])).append(m) + elif user is not None: + # We have a valid user, but their account is locked + msg = _("This account is inactive or has been locked by an " + "administrator.") + e = form._errors.setdefault("__all__", form.error_class([])) + e.append(msg) + else: + # We don't have a valid user, so send an error back to the form + msg = _("Invalid username or password") + e = form._errors.setdefault("__all__", form.error_class([])) + e.append(msg) return self.render_to_response(dict(form=form, next=next_url))