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_person
、 change_person
、 delete_person
和 view_person
。而permissions = [('can_eat_pizzas','Can eat pizzas')]
為Person
增加了can_eat_pizzas
許可權。
系統需求分析
- 角色有編碼,可以便捷的在程式設計過程中使用,比如:
if user.role.has("manager") :
dosomething()
複製程式碼
- 許可權可以使用選單組織成二級目錄,比如:
* 許可權管理
- 使用者管理
* 增加
* 編輯
* 刪除
* 搜尋
- 角色管理
- 許可權管理
* 論壇管理
- 版面管理
* 新增版面
* 修改版面
* 檢視版面
* 關閉
- 文章管理
* ...
複製程式碼
- 許可權可以配合RESTFul規範進行遠端攔截(沒有model情況下)
GET /articles/
DELETE /articles/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',)
...
複製程式碼
- 操作許可權攔截如下:
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
複製程式碼
- 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)
...
複製程式碼
- 實現部分
簡單的攔截:
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
複製程式碼