Django的 auth 模块讲解和装饰器 permission_required 的源码流程

Django的auth系统提供了模型级的权限控制, 即可以检查用户是否对某个数据表拥有增(add), 改(change), 删(delete)权限。
(auth系统无法提供对象级的权限控制, 即检查用户是否对数据表中某条记录拥有增改删的权限,如果需要对象级权限控制可以使用django-guardian)。每个模型默认拥有增(add), 改(change), 删(delete)权限。例如,定义一个名为『Car』model,定义好Car之后,会自动创建相应的三个permission:add_car, change_car和delete_car。Django还允许自定义permission,例如,我们可以为Car创建新的权限项:drive_car, clean_car, fix_car等等。关于自定义权限可参考Django的 auth_permission 的自定义权限

Django 的 auth 模块和其包含的三个主要 model: Permission, User, Group。 其中 User 和 Group 中定义了多对对的外键,所以涉及的数据库表如下:
auth_permissions:各种权限

auth_user:用户表
auth_user_groups:用户和组的对应关系
auth_user_user_permissions:用户和权限的对应关系

auth_group:用户组
auth_group_permissions:用户组合权限的对应关系

有了上面的铺垫,我们开始分析装饰器 permission_required 的源码:

from django.contrib.auth.decorators import login_required, permission_required
C:\Python27\Lib\site-packages\django\contrib\auth\decorators.py
def permission_required(perm, login_url=None, raise_exception=False):
    """
    Decorator for views that checks whether a user has a particular permission
    enabled, redirecting to the log-in page if necessary.
    If the raise_exception parameter is given the PermissionDenied exception
    is raised.
    """
    def check_perms(user):
        if not isinstance(perm, (list, tuple)):
            perms = (perm, )
        else:
            perms = perm
        # First check if the user has the permission (even anon users)
        if user.has_perms(perms):                    ----------------------------step 1
            return True
        # In case the 403 handler should be called raise the exception
        if raise_exception:
            raise PermissionDenied
        # As the last resort, show the login form
        return False
    return user_passes_test(check_perms, login_url=login_url)
	
	
C:\Python27\Lib\site-packages\django\contrib\auth\models.py	
class PermissionsMixin(models.Model): (该类是 AbstractUser 父类,而 AbstractUser 又是  User 的父类)
    ......
	def has_perms(self, perm_list, obj=None):
		"""
		Returns True if the user has each of the specified permissions. If
		object is passed, it checks if the user has all required perms for this
		object.
		"""
		for perm in perm_list:
			if not self.has_perm(perm, obj):            ----------------------------step 2
				return False
		return True
		
		
	def has_perm(self, perm, obj=None):
		"""
		Returns True if the user has the specified permission. This method
		queries all available auth backends, but returns immediately if any
		backend returns True. Thus, a user who has permission from a single
		auth backend is assumed to have permission in general. If an object is
		provided, permissions for this specific object are checked.
		"""
	
		# Active superusers have all permissions.
		if self.is_active and self.is_superuser:       ---------------------------step 3.0 超级用户直接返回 True
			return True
	
		# Otherwise we need to check the backends.
		return _user_has_perm(self, perm, obj)        ----------------------------step 3.1 
	
	
def _user_has_perm(user, perm, obj):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():          ----------------------------step 4
        if not hasattr(backend, 'has_perm'):
            continue
        try:
            if backend.has_perm(user, perm, obj):
                return True
        except PermissionDenied:
            return False
    return False
	
	
C:\Python27\Lib\site-packages\django\contrib\auth\__init__.py	
def get_backends():
    return _get_backends(return_tuples=False)    ----------------------------step 5
	
	
def _get_backends(return_tuples=False):
    backends = []
    for backend_path in settings.AUTHENTICATION_BACKENDS: ------------------step 6 settings.AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) from: django\conf\global_settings.py
        backend = load_backend(backend_path)
        backends.append((backend, backend_path) if return_tuples else backend)
    if not backends:
        raise ImproperlyConfigured(
            'No authentication backends have been defined. Does '
            'AUTHENTICATION_BACKENDS contain anything?'
        )
    return backends

在文件 django\conf\global_settings.py 中可以看到 settings.AUTHENTICATION_BACKENDS = (‘django.contrib.auth.backends.ModelBackend’,) 所以分析类 ModelBackend 的源码,由上面调用关系中的 step 4 发现主要是调用类 ModelBackend 的 has_perm 方法,
经过分析,发现原来装饰器 permission_required 最后落脚到:判断装饰器的参数 myapp.add_car 是否在 对应用户所属 group 的 auth_group_permissions 和 对应用户的user_permissions 中

@permission_required('myapp.add_car') 
def myviewfunc(request):
	pass

另外发现类 ModelBackend 里的 _get_permissions 方法用到了反射,真巧妙!

Python27\Lib\site-packages\django\contrib\auth\backends.py

class ModelBackend(object):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            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)

    def _get_user_permissions(self, user_obj):
        return user_obj.user_permissions.all()

    def _get_group_permissions(self, user_obj):
        user_groups_field = get_user_model()._meta.get_field('groups')
        user_groups_query = 'group__%s' % user_groups_field.related_query_name()
        return Permission.objects.filter(**{user_groups_query: user_obj})

    def _get_permissions(self, user_obj, obj, from_name):
        """
        Returns the permissions of `user_obj` from `from_name`. `from_name` can
        be either "group" or "user" to return permissions from
        `_get_group_permissions` or `_get_user_permissions` respectively.
        """
        if not user_obj.is_active or user_obj.is_anonymous() or obj is not None:
            return set()

        perm_cache_name = '_%s_perm_cache' % from_name
        if not hasattr(user_obj, perm_cache_name):
            if user_obj.is_superuser:
                perms = Permission.objects.all()
            else:
                perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj)  ---step 7.1---
            perms = perms.values_list('content_type__app_label', 'codename').order_by()  ---step 7.2---
            setattr(user_obj, perm_cache_name, set("%s.%s" % (ct, name) for ct, name in perms))
        return getattr(user_obj, perm_cache_name)

    def get_user_permissions(self, user_obj, obj=None):
        """
        Returns a set of permission strings the user `user_obj` has from their
        `user_permissions`.
        """
        return self._get_permissions(user_obj, obj, 'user')

    def get_group_permissions(self, user_obj, obj=None):
        """
        Returns a set of permission strings the user `user_obj` has from the
        groups they belong.
        """
        return self._get_permissions(user_obj, obj, 'group')

    def get_all_permissions(self, user_obj, obj=None):
        if not user_obj.is_active or user_obj.is_anonymous() or obj is not None:
            return set()
        if not hasattr(user_obj, '_perm_cache'):
            user_obj._perm_cache = self.get_user_permissions(user_obj)
            user_obj._perm_cache.update(self.get_group_permissions(user_obj))
        return user_obj._perm_cache

    def has_perm(self, user_obj, perm, obj=None):
        if not user_obj.is_active:
            return False
        return perm in self.get_all_permissions(user_obj, obj)
发布了44 篇原创文章 · 获赞 0 · 访问量 3952

猜你喜欢

转载自blog.csdn.net/cpxsxn/article/details/100512439
今日推荐