Django Rbac 權限設計

相關概念

  • ACL

ACL 是 Access Control List 的縮寫,稱爲訪問控制列表,包含了對一個對象或一條記錄可進行何種操做的權限定義。html

例如一個文件對象的 ACL 爲 { "Alice": { "read": true, "write": true }, "Bob": { "read": true } },web

這表明 Alice 對該文件既能讀又能寫,而 Bob 只能讀取。數據庫

  • RBAC

RBAC基於角色的權限訪問控制(Role-Based Access Control)不一樣於賦予使用者權限,而是將權限賦予角色。django

RBAC模型中「權限」只和「角色」對應,而用戶也和「角色」對應,爲用戶賦予角色,而後管理角色的權限,完成了權限與用戶的解耦。編程

  • RBAC0/RBAC1/RBAC2/RBAC3

RBAC0主要特色是:緩存

用戶和角色之間是多對一仍是多對多的關係。bash

RBAC1主要特色是:函數

角色能夠繼承,造成樹狀。測試

RBAC2主要特定是:ui

角色能夠互斥。(出納和會計) 基數約束。(ceo) 先決條件。(逐層升級) 運行時互斥。(運行時只容許一個角色,水的三態)

RBAC3,統一rabac1和rbac2。

  • 數據權限
object (row) level permissions 
model (table) level permissions
複製代碼
  • 權限的直觀表現

操做權限: web系統頁面的菜單和按鈕 數據權限: web系統中對數據記錄操做

django的默認權限

Django 帶有一個簡單的權限系統。它提供了爲指定的用戶和用戶組分配權限的方法。

這裏的group和角色實際上爲一個概念

User 對象有兩個多對多字段:groups 和 user_permissions。 User 對象能夠像訪問其餘 Django model: 同樣訪問他們的相關對象。

myuser.groups.set([group_list])
myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()
myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear()
複製代碼

注意這裏:能夠直接對用戶進行受權。

假設你有一個名爲 foo 應用程序和一個名爲 Bar 的模型,要測試基礎權限,你應該使用:

添加:user.has_perm('foo.add_bar')
修改:user.has_perm('foo.change_bar')
刪除:user.has_perm('foo.delete_bar')
查看:user.has_perm('foo.view_bar')
複製代碼

能夠擴展/屏蔽默認的權限:

class Person(models.Model):
    class Meta:
        default_permissions = ()
        permissions = [('can_eat_pizzas', 'Can eat pizzas')]
複製代碼

default_permissions = ()屏蔽了Person默認的add_personchange_persondelete_personview_person。而permissions = [('can_eat_pizzas', 'Can eat pizzas')]Person增長了can_eat_pizzas權限。

系統需求分析

  1. 角色有編碼,能夠便捷的在編程過程當中使用, 好比:
if user.role.has("manager") :
    dosomething()
複製代碼
  1. 權限可使用菜單組織成二級目錄,好比:
* 權限管理
    - 用戶管理
        * 增長
        * 編輯
        * 刪除
        * 搜索
    - 角色管理
    - 權限管理
* 論壇管理
    - 版面管理
        * 新增版面
        * 修改版面
        * 查看版面
        * 關閉
    - 文章管理
        * ...
複製代碼
  1. 權限能夠配合RESTFul規範進行遠程攔截(沒有model狀況下)
GET /articles/
DELETE /articles/1/
複製代碼

模型實現

操做權限部分

  1. model部分以下:
class Permission(models.Model):
    """約定一級表明目錄,二級表明頁面,三級表明按鈕"""
    name = models.CharField(verbose_name='名稱', max_length=32, blank=True, null=True)
    code = models.CharField(verbose_name='編碼', max_length=32, blank=True, null=True)
    higher = models.ForeignKey('self', verbose_name='上級', on_delete=models.CASCADE)
    url = models.CharField(verbose_name='路徑', max_length=32, blank=True, null=True)
    action = models.CharField(verbose_name='方法', max_length=32, blank=True, null=True)
    ...


class Role(models.Model):
    name = models.CharField(verbose_name='名稱', max_length=32, blank=True, null=True)
    code = models.CharField(verbose_name='編碼', max_length=32, blank=True, null=True)
    permissions = models.ManyToManyField(
        Permission,
        verbose_name='permissions',
        blank=True,
    )
    ...


class User(AbstractUser):
    roles = models.ManyToManyField(
        Role,
        verbose_name='roles',
        blank=True,
    )
    ...
複製代碼
  1. 操做權限攔截以下:
class RBACMiddleware:

    def __call__(self, request):
        request_url = request.path_info
        request_user = request.user
        for url in settings.SAFE_URL:
            if re.match(url, request_url):
                pass
        # 讀取數據庫/緩存
        if has_permission_url(request_user, request_url):
            pass
        else:
            return render(request, 'page403.html')
複製代碼

數據權限部分

數據權限和業務結合緊密,通常不須要作統一的數據權限攔截,各個業務自由使用。 不過能夠將數據權限抽象成下面幾種類型,規範使用,實現可配置化。

* 行限制(根據某列的條件控制可影響的行數)
    - 全部者 is_owner_required 只可以刪除本身的數據行
    - 協做者 is_teamworker_required 能夠編輯team(部門)所屬的數據行
    - 受限者 is_manager_required 能夠批准3天內請假
* 列限制 (控制可影響的列)
    - 電話號碼保密 filter_phone
    - 薪資保密 filter_salary
複製代碼
  1. model 部分
class Checker(models.Model):
    CHECKER_CLAZZ = (
        (1, 函數),
        (2, 表達式),
    )

    clazz = models.CharField(verbose_name='類別', choices=CHECKER_TYPE, max_length=15, blank=True, null=True)
    name = models.CharField(verbose_name='名稱', max_length=32, blank=True, null=True)
    code = models.CharField(verbose_name='編碼', max_length=32, blank=True, null=True)
    value = models.CharField(verbose_name='數值', max_length=32, blank=True, null=True)
    ...
複製代碼
  1. 實現部分

簡單的攔截:

def is_owner_required(model, pk_name='pk'):
    def decorator(view_func):
        def wrap(request, *args, **kwargs):
            pk = kwargs.get(pk_name, None)
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            if o.is_owner(request.user):
                return view_func(request, *args, **kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator

def is_teamworker_required(model, pk_name='pk'):
    def decorator(view_func):
        def wrap(request, *args, **kwargs):
            pk = kwargs.get(pk_name, None)
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            if o.is_teamworker(request.user):
                return view_func(request, *args, **kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator

@is_owner_required(Comment)
def delete_comment(request, pk):
    pass
    
@is_teamworker_required(Comment)
def edit_comment(request, pk):
    pass
複製代碼

複雜點的攔截:

def is_manager_required(code, pk_name='pk'):
    def decorator(view_func):
        def wrap(request, *args, **kwargs):
            pk = kwargs.get(pk_name, None)
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            c=checkModel.objects.get(code=code)
            # check request user role limit value
            if c.check(request.user):
                return view_func(request, *args, **kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator
    
@is_manager_required(code="manager_limit_3")
def audit_holiday(request, pk):
    pass
複製代碼

徹底動態的攔截:

def common_required(code, pk_name='pk'):
    def decorator(view_func):
        def wrap(request, *args, **kwargs):
            pk = kwargs.get(pk_name, None)
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            c=checkModel.objects.get(code=code)
            # 動態獲取模塊
            module = __import__(c.value.module_name, fromlist=[c.value.module_class])
            # 動態獲取驗證函數
            checker = getattr(module, c.value.name)
            # 執行驗證函數
            if checker.check(request.user, c.value.number):
                return view_func(request, *args, **kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator
    
@common_required(code="check_user_level")
def dosomething(request, pk):
    pass
複製代碼
相關文章
相關標籤/搜索