Skip to content

Commit

Permalink
Edits.
Browse files Browse the repository at this point in the history
  • Loading branch information
felixxm committed Aug 29, 2019
1 parent a1703f2 commit c622b97
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 174 deletions.
33 changes: 20 additions & 13 deletions django/contrib/auth/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,33 +121,40 @@ def has_module_perms(self, user_obj, app_label):
)

def with_perm(self, perm, is_active=True, include_superusers=True, obj=None):
if not isinstance(perm, (str, Permission)):
raise TypeError('The `perm` argument must be a string or a permission instance.')
elif isinstance(perm, str):
"""
Return users that have permission "perm". By default, filter out
inactive users and include superusers.
"""
if isinstance(perm, str):
try:
app_label, codename = perm.split('.')
except ValueError:
raise ValueError("Permission name should be in the form 'app_label.perm_name'.") from None
raise ValueError(
'Permission name should be in the form '
'app_label.permission_codename.'
)
elif not isinstance(perm, Permission):
raise TypeError(
'The `perm` argument must be a string or a permission instance.'
)

UserModel = get_user_model()

if obj is not None:
return UserModel._default_manager.none()

q0 = Q(group__user=OuterRef('pk')) | Q(user=OuterRef('pk'))
permission_q = Q(group__user=OuterRef('pk')) | Q(user=OuterRef('pk'))
if isinstance(perm, Permission):
q0 &= Q(pk=perm.pk)
permission_q &= Q(pk=perm.pk)
else:
q0 &= Q(codename=codename, content_type__app_label=app_label)
permission_q &= Q(codename=codename, content_type__app_label=app_label)

q1 = Q(has_permission=True)
user_q = Exists(Permission.objects.filter(permission_q))
if include_superusers:
q1 |= Q(is_superuser=True)
user_q |= Q(is_superuser=True)
if is_active is not None:
q1 &= Q(is_active=is_active)
user_q &= Q(is_active=is_active)

has_permission = Exists(Permission.objects.filter(q0))
return UserModel._default_manager.annotate(has_permission=has_permission).filter(q1)
return UserModel._default_manager.filter(user_q)

def get_user(self, user_id):
try:
Expand Down
22 changes: 16 additions & 6 deletions django/contrib/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,28 @@ def create_superuser(self, username, email=None, password=None, **extra_fields):
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
if backend is None:
backends = auth._get_backends(return_tuples=True)
if len(backends) != 1:
if len(backends) == 1:
backend, _ = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument.'
)
_, backend = backends[0]
elif not isinstance(backend, str):
raise TypeError('The `backend` argument must be a string.')
backend = auth.load_backend(backend)
raise TypeError(
'backend must be a dotted import path string (got %r).'
% backend
)
else:
backend = auth.load_backend(backend)
if hasattr(backend, 'with_perm'):
return backend.with_perm(perm, is_active=is_active, include_superusers=include_superusers, obj=obj)
return self.get_queryset().none()
return backend.with_perm(
perm,
is_active=is_active,
include_superusers=include_superusers,
obj=obj,
)
return self.none()


# A few helper functions for common logic between User and AnonymousUser.
Expand Down
37 changes: 24 additions & 13 deletions docs/ref/contrib/auth.txt
Original file line number Diff line number Diff line change
Expand Up @@ -295,18 +295,23 @@ Manager methods

.. versionadded:: 3.0

Returns a queryset containing :class:`~django.contrib.auth.models.User`
objects that have the given permission ``perm`` either in the form of
``"<app label>.<permission codename>"`` or a
:class:`~django.contrib.auth.models.Permission` instance, including
superusers (if ``include_superusers`` is ``True``.)
Returns users that have the given permission ``perm`` either in the
``"<app label>.<permission codename>"`` format or as a
:class:`~django.contrib.auth.models.Permission` instance. Returns an
empty queryset if no users who have the ``perm`` found.

If ``is_active`` is ``True`` (default), returns only active users, or
if ``False``, returns only inactive users. Use ``None`` to return all
users irrespective of active state.

If ``is_active`` is ``True``, only active users will be included, or if
``False``, only inactive users will be included. Use ``None`` to return
all users irrespective of active state.
If ``include_superusers`` is ``True`` (default), the result will
include superusers.

If ``backend`` is given, it will be used if it's defined in
the :setting:`AUTHENTICATION_BACKENDS` setting.
If ``backend`` is passed in and it's defined in
:setting:`AUTHENTICATION_BACKENDS`, then this method will use it.
Otherwise, it will use the ``backend`` in
:setting:`AUTHENTICATION_BACKENDS`, if there is only one, or raise an
exception.

``AnonymousUser`` object
========================
Expand Down Expand Up @@ -537,9 +542,8 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
implement them other than returning an empty set of permissions if
``obj is not None``.

:meth:`with_perm` also allow an object to be passed as a parameter for
object-specific permissions, but it returns an empty queryset if
``obj is not None``.
:meth:`with_perm` also allows an object to be passed as a parameter, but
unlike others methods it returns an empty queryset if ``obj is not None``.

.. method:: authenticate(request, username=None, password=None, **kwargs)

Expand Down Expand Up @@ -607,6 +611,13 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
:class:`~django.contrib.auth.models.Permission` instance. Returns an
empty queryset if no users who have the ``perm`` found.

If ``is_active`` is ``True`` (default), returns only active users, or
if ``False``, returns only inactive users. Use ``None`` to return all
users irrespective of active state.

If ``include_superusers`` is ``True`` (default), the result will
include superusers.

.. class:: AllowAllUsersModelBackend

Same as :class:`ModelBackend` except that it doesn't reject inactive users
Expand Down
6 changes: 3 additions & 3 deletions docs/releases/3.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ Minor features

* :attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports
:class:`~django.db.models.ManyToManyField`\s.
* The new
:meth:`UserManager.with_perm() <django.contrib.auth.models.UserManager.with_perm>`
method returns all users that have the specified permission.

* The new :meth:`.UserManager.with_perm` method returns users that have the
specified permission.

:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/auth/customizing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ Handling authorization in custom backends

Custom auth backends can provide their own permissions.

The user model will delegate permission lookup functions
The user model and its manager will delegate permission lookup functions
(:meth:`~django.contrib.auth.models.User.get_user_permissions()`,
:meth:`~django.contrib.auth.models.User.get_group_permissions()`,
:meth:`~django.contrib.auth.models.User.get_all_permissions()`,
Expand Down
3 changes: 1 addition & 2 deletions tests/auth_tests/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
)
from .invalid_models import CustomUserNonUniqueUsername
from .is_active import IsActiveTestUser1
from .minimal import CustomModel, MinimalUser
from .minimal import MinimalUser
from .no_password import NoPasswordUser
from .proxy import Proxy, UserProxy
from .uuid_pk import UUIDUser
Expand All @@ -16,7 +16,6 @@
)

__all__ = (
'CustomModel',
'CustomPermissionsUser', 'CustomUser', 'CustomUserNonUniqueUsername',
'CustomUserWithFK', 'CustomUserWithM2M', 'CustomUserWithM2MThrough',
'CustomUserWithoutIsActiveField', 'Email', 'ExtensionUser',
Expand Down
6 changes: 0 additions & 6 deletions tests/auth_tests/models/minimal.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
from django.contrib.auth.models import User
from django.db import models


class MinimalUser(models.Model):
REQUIRED_FIELDS = ()
USERNAME_FIELD = 'id'


class CustomModel(models.Model):
# Used by with_perm() tests.
user = models.ForeignKey(User, on_delete=models.CASCADE)
5 changes: 1 addition & 4 deletions tests/auth_tests/test_auth_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,7 @@ class ImportedModelBackend(ModelBackend):


class CustomModelBackend(ModelBackend):
def with_perm(self, perm, is_active=True, include_superusers=True, backend=None, obj=None):
if obj is not None and obj.user.username == 'charliebrown':
return User.objects.filter(username='charliebrown')
return User.objects.filter(username__startswith='charlie')
pass


class OtherModelBackend(ModelBackend):
Expand Down

0 comments on commit c622b97

Please sign in to comment.