Commit e0a3d937 authored by Alexander Gaevsky's avatar Alexander Gaevsky Committed by Tim Graham
Browse files

Fixed #25232 -- Made ModelBackend/RemoteUserBackend reject inactive users.

parent 1555d50e
Loading
Loading
Loading
Loading
+24 −4
Original line number Diff line number Diff line
@@ -15,12 +15,21 @@ class ModelBackend(object):
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            user = UserModel._default_manager.get_by_natural_key(username)
            if user.check_password(password):
                return user
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user

    def user_can_authenticate(self, user):
        """
        Reject users with is_active=False. Custom user models that don't have
        that attribute are allowed.
        """
        is_active = getattr(user, 'is_active', None)
        return is_active or is_active is None

    def _get_user_permissions(self, user_obj):
        return user_obj.user_permissions.all()
@@ -90,9 +99,15 @@ class ModelBackend(object):
    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel._default_manager.get(pk=user_id)
            user = UserModel._default_manager.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
        return user if self.user_can_authenticate(user) else None


class AllowAllUsersModelBackend(ModelBackend):
    def user_can_authenticate(self, user):
        return True


class RemoteUserBackend(ModelBackend):
@@ -140,7 +155,7 @@ class RemoteUserBackend(ModelBackend):
                user = UserModel._default_manager.get_by_natural_key(username)
            except UserModel.DoesNotExist:
                pass
        return user
        return user if self.user_can_authenticate(user) else None

    def clean_username(self, username):
        """
@@ -158,3 +173,8 @@ class RemoteUserBackend(ModelBackend):
        By default, returns the user unmodified.
        """
        return user


class AllowAllUsersRemoteUserBackend(RemoteUserBackend):
    def user_can_authenticate(self, user):
        return True
+14 −3
Original line number Diff line number Diff line
@@ -64,10 +64,21 @@ remote users. These interfaces work with users stored in the database
regardless of ``AUTHENTICATION_BACKENDS``.

.. note::

    Since the ``RemoteUserBackend`` inherits from ``ModelBackend``, you will
    still have all of the same permissions checking that is implemented in
    ``ModelBackend``.

    Users with :attr:`is_active=False
    <django.contrib.auth.models.User.is_active>` won't be allowed to
    authenticate. Use
    :class:`~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend` if
    you want to allow them to.

    .. versionchanged:: 1.10

        In older versions, inactive users weren't rejected as described above.

If your authentication mechanism uses a custom HTTP header and not
``REMOTE_USER``, you can subclass ``RemoteUserMiddleware`` and set the
``header`` attribute to the desired ``request.META`` key.  For example::
+63 −8
Original line number Diff line number Diff line
@@ -76,15 +76,26 @@ Fields

        This doesn't necessarily control whether or not the user can log in.
        Authentication backends aren't required to check for the ``is_active``
        flag, and the default backends do not. If you want to reject a login
        based on ``is_active`` being ``False``, it's up to you to check that in
        your own login view or a custom authentication backend. However, the
        flag but the default backend
        (:class:`~django.contrib.auth.backends.ModelBackend`) and the
        :class:`~django.contrib.auth.backends.RemoteUserBackend` do. You can
        use :class:`~django.contrib.auth.backends.AllowAllUsersModelBackend`
        or :class:`~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend`
        if you want to allow inactive users to login. In this case, you'll also
        want to customize the
        :class:`~django.contrib.auth.forms.AuthenticationForm` used by the
        :func:`~django.contrib.auth.views.login` view (which is the default)
        *does* perform this check, as do the permission-checking methods such
        as :meth:`~django.contrib.auth.models.User.has_perm` and the
        authentication in the Django admin. All of those functions/methods will
        return ``False`` for inactive users.
        :func:`~django.contrib.auth.views.login` view as it rejects inactive
        users. Be aware that the permission-checking methods such as
        :meth:`~django.contrib.auth.models.User.has_perm` and the
        authentication in the Django admin all return ``False`` for inactive
        users.

        .. versionchanged:: 1.10

            In older versions,
            :class:`~django.contrib.auth.backends.ModelBackend` and
            :class:`~django.contrib.auth.backends.RemoteUserBackend` allowed
            inactive users to authenticate.

    .. attribute:: is_superuser

@@ -488,6 +499,32 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
        Returns whether the ``user_obj`` has any permissions on the app
        ``app_label``.

    .. method:: ModelBackend.user_can_authenticate()

        .. versionadded:: 1.10

        Returns whether the user is allowed to authenticate. To match the
        behavior of :class:`~django.contrib.auth.forms.AuthenticationForm`
        which :meth:`prohibits inactive users from logging in
        <django.contrib.auth.forms.AuthenticationForm.confirm_login_allowed>`,
        this method returns ``False`` for users with :attr:`is_active=False
        <django.contrib.auth.models.User.is_active>`. Custom user models that
        don't have an :attr:`~django.contrib.auth.models.CustomUser.is_active`
        field are allowed.

.. class:: AllowAllUsersModelBackend

   .. versionadded:: 1.10

   Same as :class:`ModelBackend` except that it doesn't reject inactive users
   because :meth:`~ModelBackend.user_can_authenticate` always returns ``True``.

   When using this backend, you'll likely want to customize the
   :class:`~django.contrib.auth.forms.AuthenticationForm` used by the
   :func:`~django.contrib.auth.views.login` view by overriding the
   :meth:`~django.contrib.auth.forms.AuthenticationForm.confirm_login_allowed`
   method as it rejects inactive users.

.. class:: RemoteUserBackend

    Use this backend to take advantage of external-to-Django-handled
@@ -529,3 +566,21 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
   new user is created, and can be used to perform custom setup actions, such
   as setting the user's groups based on attributes in an LDAP directory.
   Returns the user object.

.. method:: RemoteUserBackend.user_can_authenticate()

    .. versionadded:: 1.10

    Returns whether the user is allowed to authenticate. This method returns
    ``False`` for users with :attr:`is_active=False
    <django.contrib.auth.models.User.is_active>`. Custom user models that don't
    have an :attr:`~django.contrib.auth.models.CustomUser.is_active` field are
    allowed.

.. class:: AllowAllUsersRemoteUserBackend

   .. versionadded:: 1.10

   Same as :class:`RemoteUserBackend` except that it doesn't reject inactive
   users because :attr:`~RemoteUserBackend.user_can_authenticate` always
   returns ``True``.
+9 −0
Original line number Diff line number Diff line
@@ -669,6 +669,15 @@ Miscellaneous
  calling ``Command.execute()``, pass the command object as the first argument
  to ``call_command()``.

* :class:`~django.contrib.auth.backends.ModelBackend` and
  :class:`~django.contrib.auth.backends.RemoteUserBackend` now reject inactive
  users. This means that inactive users can't login and will be logged
  out if they are switched from ``is_active=True`` to ``False``. If you need
  the previous behavior, use the new
  :class:`~django.contrib.auth.backends.AllowAllUsersModelBackend` or
  :class:`~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend`
  in :setting:`AUTHENTICATION_BACKENDS` instead.

.. _deprecated-features-1.10:

Features deprecated in 1.10
+15 −4
Original line number Diff line number Diff line
@@ -235,10 +235,17 @@ for example, to control anonymous access.
Authorization for inactive users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

An inactive user is a one that is authenticated but has its attribute
``is_active`` set to ``False``. However this does not mean they are not
authorized to do anything. For example they are allowed to activate their
account.
An inactive user is a one that has its
:attr:`~django.contrib.auth.models.User.is_active` field set to ``False``. The
:class:`~django.contrib.auth.backends.ModelBackend` and
:class:`~django.contrib.auth.backends.RemoteUserBackend` authentication
backends prohibits these users from authenticating. If a custom user model
doesn't have an :attr:`~django.contrib.auth.models.CustomUser.is_active` field,
all users will be allowed to authenticate.

You can use :class:`~django.contrib.auth.backends.AllowAllUsersModelBackend`
or :class:`~django.contrib.auth.backends.AllowAllUsersRemoteUserBackend` if you
want to allow inactive users to authenticate.

The support for anonymous users in the permission system allows for a scenario
where anonymous users have permissions to do something while inactive
@@ -247,6 +254,10 @@ authenticated users do not.
Do not forget to test for the ``is_active`` attribute of the user in your own
backend permission methods.

.. versionchanged:: 1.10

    In older versions, the :class:`~django.contrib.auth.backends.ModelBackend`
    allowed inactive users to authenticate.

Handling object permissions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Loading