Дизайн разрешений Django Rbac

Django

Связанные концепции

  • ACL

ACL — это аббревиатура от списка управления доступом, который называется списком управления доступом и содержит определение разрешений на то, какие операции могут выполняться над объектом или записью.

Например, ACL файлового объекта: { "Алиса": { "чтение": истина, "запись": истина}, "Боб": { "чтение": истина } },

Это означает, что Алиса может и читать, и писать в файл, а Боб может только читать.

  • RBAC

Управление доступом на основе ролей RBAC (управление доступом на основе ролей) отличается от предоставления разрешений пользователям, но назначением разрешений ролям.

В модели RBAC «разрешения» соответствуют только «ролям», а пользователи также соответствуют «ролям», назначают роли пользователям, а затем управляют разрешениями ролей, завершая разделение разрешений и пользователей.

  • RBAC0/RBAC1/RBAC2/RBAC3

Основные особенности RBAC0:

Существует ли отношение «многие к одному» или «многие ко многим» между пользователями и ролями.

Основные возможности RBAC1:

Роли могут наследоваться, образуя дерево.

Основные особенности RBAC2:

Роли могут быть взаимоисключающими. (касса и бухгалтерия) Кардинальные ограничения. (Исполнительный директор) предпосылки. (обновление слой за слоем) Взаимное исключение во время выполнения. (Во время выполнения разрешена только одна роль — три состояния воды)

RBAC3 объединяет rabac1 и rbac2.

  • разрешение на данные
object (row) level permissions 
model (table) level permissions
  • Интуитивное представление разрешений

Разрешения на эксплуатацию: Меню и кнопки для страниц веб-системы Привилегия данных: операция регистрации данных в веб-системе

разрешения django по умолчанию

Django поставляется с простой системой разрешений. Он предоставляет методы для назначения разрешений указанным пользователям и группам пользователей.

Группа и роль здесь на самом деле являются концепцией

Объект User имеет два поля «многие ко многим»: groups и user_permissions. Доступ к пользовательским объектам можно получить, как и к другим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_person,change_person,delete_personиview_person. иpermissions = [('can_eat_pizzas', 'Can eat pizzas')]заPersonвыросcan_eat_pizzasразрешения.

Анализ системных требований

  1. Роли закодированы и могут быть легко использованы в процессе программирования, например:
if user.role.has("manager") :
    dosomething()
  1. Разрешения могут быть организованы в подкаталоги с помощью меню, например:
* 权限管理
    - 用户管理
        * 增加
        * 编辑
        * 删除
        * 搜索
    - 角色管理
    - 权限管理
* 论坛管理
    - 版面管理
        * 新增版面
        * 修改版面
        * 查看版面
        * 关闭
    - 文章管理
        * ...
  1. Разрешения могут взаимодействовать со спецификацией RESTFul для удаленного перехвата (без модели)
GET /articles/
DELETE /articles/1/

Реализация модели

Часть разрешения на операцию

  1. Модельная часть выглядит следующим образом:
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. Часть модели
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