Commit a1889397 authored by Tim Graham's avatar Tim Graham
Browse files

Fixed #12103 -- Added AuthenticationForm.confirm_login_allowed to allow...

Fixed #12103 -- Added AuthenticationForm.confirm_login_allowed to allow customizing the logic policy.

Thanks ejucovy and lasko for work on the patch.
parent 1c3c21b3
Loading
Loading
Loading
Loading
+20 −5
Original line number Diff line number Diff line
@@ -191,12 +191,27 @@ class AuthenticationForm(forms.Form):
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
            elif not self.user_cache.is_active:
            else:
                self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data

    def confirm_login_allowed(self, user):
        """
        Controls whether the given User may log in. This is a policy setting,
        independent of end-user authentication. This default behavior is to
        allow login by active users, and reject login by inactive users.

        If the given user cannot log in, this method should raise a
        ``forms.ValidationError``.

        If the given user may log in, this method should return None.
        """
        if not user.is_active:
            raise forms.ValidationError(
                self.error_messages['inactive'],
                code='inactive',
            )
        return self.cleaned_data

    def get_user_id(self):
        if self.user_cache:
+35 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ from __future__ import unicode_literals

import os

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
@@ -131,6 +132,40 @@ class AuthenticationFormTest(TestCase):
                self.assertEqual(form.non_field_errors(),
                                 [force_text(form.error_messages['inactive'])])

    def test_custom_login_allowed_policy(self):
        # The user is inactive, but our custom form policy allows him to log in.
        data = {
            'username': 'inactive',
            'password': 'password',
            }

        class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
            def confirm_login_allowed(self, user):
                pass

        form = AuthenticationFormWithInactiveUsersOkay(None, data)
        self.assertTrue(form.is_valid())

        # If we want to disallow some logins according to custom logic,
        # we should raise a django.forms.ValidationError in the form.
        class PickyAuthenticationForm(AuthenticationForm):
            def confirm_login_allowed(self, user):
                if user.username == "inactive":
                    raise forms.ValidationError(_("This user is disallowed."))
                raise forms.ValidationError(_("Sorry, nobody's allowed in."))

        form = PickyAuthenticationForm(None, data)
        self.assertFalse(form.is_valid())
        self.assertEqual(form.non_field_errors(), ['This user is disallowed.'])

        data = {
            'username': 'testclient',
            'password': 'password',
            }
        form = PickyAuthenticationForm(None, data)
        self.assertFalse(form.is_valid())
        self.assertEqual(form.non_field_errors(), ["Sorry, nobody's allowed in."])

    def test_success(self):
        # The success case
        data = {
+4 −0
Original line number Diff line number Diff line
@@ -101,6 +101,10 @@ Minor features
  :class:`~django.middleware.http.ConditionalGetMiddleware` to handle
  conditional ``GET`` requests for sitemaps which set ``lastmod``.

* You can override the new :meth:`AuthenticationForm.confirm_login_allowed()
  <django.contrib.auth.forms.AuthenticationForm.confirm_login_allowed>` method
  to more easily customize the login policy.

Backwards incompatible changes in 1.7
=====================================

+34 −0
Original line number Diff line number Diff line
@@ -959,6 +959,40 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
    Takes ``request`` as its first positional argument, which is stored on the
    form instance for use by sub-classes.

    .. method:: confirm_login_allowed(user)

    .. versionadded:: 1.7

    By default, ``AuthenticationForm`` rejects users whose ``is_active`` flag
    is set to ``False``. You may override this behavior with a custom policy to
    determine which users can log in. Do this with a custom form that subclasses
    ``AuthenticationForm`` and overrides the ``confirm_login_allowed`` method.
    This method should raise a :exc:`~django.core.exceptions.ValidationError`
    if the given user may not log in.

    For example, to allow all users to log in, regardless of "active" status::

        from django.contrib.auth.forms import AuthenticationForm

        class AuthenticationFormWithInactiveUsersOkay(AuthenticationForm):
            def confirm_login_allowed(self, user):
                pass

    Or to allow only some active users to log in::

        class PickyAuthenticationForm(AuthenticationForm):
            def confirm_login_allowed(self, user):
                if not user.is_active:
                    raise forms.ValidationError(
                        _("This account is inactive."),
                        code='inactive',
                    )
                if user.username.startswith('b'):
                    raise forms.ValidationError(
                        _("Sorry, accounts starting with 'b' aren't welcome here."),
                        code='no_b_users',
                    )

.. class:: PasswordChangeForm

    A form for allowing a user to change their password.