1. 程式人生 > 程式設計 >Django Rbac 許可權設計

Django Rbac 許可權設計

相關概念

  • ACL

ACL 是 Access Control List 的縮寫,稱為訪問控制列表,包含了對一個物件或一條記錄可進行何種操作的許可權定義。

例如一個檔案物件的 ACL 為 { "Alice": { "read": true,"write": true },"Bob": { "read": true } },

這代表 Alice 對該檔案既能讀又能寫,而 Bob 只能讀取。

  • RBAC

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

RBAC模型中「許可權」只和「角色」對應,而使用者也和「角色」對應,為使用者賦予角色,然後管理角色的許可權,完成了許可權與使用者的解耦。

  • RBAC0/RBAC1/RBAC2/RBAC3

RBAC0主要特點是:

使用者和角色之間是多對一還是多對多的關係。

RBAC1主要特點是:

角色可以繼承,形成樹狀。

RBAC2主要特定是:

角色可以互斥。(出納和會計) 基數約束。(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,...)
myuser.groups.clear()
myuser.user_permissions.set([permission_list])
myuser.user_permissions.add(permission,permission,...)
myuser.user_permissions.remove(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='編碼',null=True)
    higher = models.ForeignKey('self',verbose_name='上級',on_delete=models.CASCADE)
    url = models.CharField(verbose_name='路徑',null=True)
    action = models.CharField(verbose_name='方法',null=True)
    ...


class Role(models.Model):
    name = models.CharField(verbose_name='名稱',null=True)
    permissions = models.ManyToManyField(
        Permission,verbose_name='permissions',)
    ...


class User(AbstractUser):
    roles = models.ManyToManyField(
        Role,verbose_name='roles',)
    ...
複製程式碼
  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,null=True)
    name = models.CharField(verbose_name='名稱',null=True)
    value = models.CharField(verbose_name='數值',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,**kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator

def is_teamworker_required(model,None)
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            if o.is_teamworker(request.user):
                return view_func(request,**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,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,**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,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,**kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator
    
@common_required(code="check_user_level")
def dosomething(request,pk):
    pass
複製程式碼