perf: 设置默认的角色,系统用户角色添加权限 (#7898)

* perf: 修改 role handler

* perf: 设置默认的角色,系统用户角色添加权限

* perf: authentication 还是放到系统中吧

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Jiangjie.Bai <32935519+BaiJiangJie@users.noreply.github.com>
pull/7901/head
fit2bot 2022-03-17 14:08:16 +08:00 committed by GitHub
parent 8fe84345e4
commit 34e75099a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 61 additions and 33 deletions

View File

@ -18,7 +18,7 @@ from .signals import post_auth_success, post_auth_failed
@receiver(user_logged_in) @receiver(user_logged_in)
def on_user_auth_login_success(sender, user, request, **kwargs): def on_user_auth_login_success(sender, user, request, **kwargs):
# 失效 perms 缓存 # 失效 perms 缓存
user.expire_perms_cache() user.expire_rbac_perms_cache()
# 开启了 MFA且没有校验过, 可以全局校验, middleware 中可以全局管理 oidc 等第三方认证的 MFA # 开启了 MFA且没有校验过, 可以全局校验, middleware 中可以全局管理 oidc 等第三方认证的 MFA
if settings.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY \ if settings.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY \

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f529bbca004aeba7532d9faf50f6f8ab5532b19bf0afd650f8360f418c03c15c oid sha256:5775daad77f39025e7ddd39f04fdc0cb67c9537754caa3b0b520421df65e5041
size 104629 size 104739

View File

@ -41,13 +41,3 @@ class PermissionViewSet(JMSModelViewSet):
queryset = Permission.get_permissions(self.scope) queryset = Permission.get_permissions(self.scope)
queryset = queryset.prefetch_related('content_type') queryset = queryset.prefetch_related('content_type')
return queryset return queryset
# class UserPermsApi(ListAPIView):
# serializer_class = UserPermsSerializer
# permission_classes = (IsValidUser,)
#
# def list(self, request, *args, **kwargs):
# perms = RoleBinding.get_user_perms(request.user)
# serializer = super().get_serializer(data={'perms': perms})
# return Res

View File

@ -2,6 +2,8 @@ from django.utils.translation import ugettext_noop
from .const import Scope, system_exclude_permissions, org_exclude_permissions from .const import Scope, system_exclude_permissions, org_exclude_permissions
# Todo: 获取应该区分 系统用户,和组织用户的权限
# 工作台也区分组织后再考虑
user_perms = ( user_perms = (
('rbac', 'menupermission', 'view', 'workspace'), ('rbac', 'menupermission', 'view', 'workspace'),
('rbac', 'menupermission', 'view', 'webterminal'), ('rbac', 'menupermission', 'view', 'webterminal'),
@ -12,14 +14,13 @@ user_perms = (
('assets', 'systemuser', 'match', 'systemuser'), ('assets', 'systemuser', 'match', 'systemuser'),
('assets', 'node', 'match', 'node'), ('assets', 'node', 'match', 'node'),
('applications', 'application', 'match', 'application'), ('applications', 'application', 'match', 'application'),
('tickets', 'ticket', 'view', 'ticket'),
('ops', 'commandexecution', 'add', 'commandexecution'), ('ops', 'commandexecution', 'add', 'commandexecution'),
('authentication', 'connectiontoken', 'add', 'connectiontoken'), ('authentication', 'connectiontoken', 'add', 'connectiontoken'),
('tickets', 'ticket', 'view', 'ticket'),
) )
auditor_perms = user_perms + ( auditor_perms = user_perms + (
('rbac', 'menupermission', 'view', 'audit'), ('rbac', 'menupermission', 'view', 'audit'),
('rbac', 'menupermission', 'view', 'dashboard'),
('audits', '*', '*', '*'), ('audits', '*', '*', '*'),
('terminal', 'commandstorage', 'view', 'commandstorage'), ('terminal', 'commandstorage', 'view', 'commandstorage'),
('terminal', 'sessionreplay', 'view,download', 'sessionreplay'), ('terminal', 'sessionreplay', 'view,download', 'sessionreplay'),
@ -88,7 +89,7 @@ class PredefineRole:
class BuiltinRole: class BuiltinRole:
system_admin = PredefineRole( system_admin = PredefineRole(
'1', ugettext_noop('SystemAdmin'), Scope.system, [] '1', ugettext_noop('SystemAdmin'), Scope.system, user_perms
) )
system_auditor = PredefineRole( system_auditor = PredefineRole(
'2', ugettext_noop('SystemAuditor'), Scope.system, auditor_perms '2', ugettext_noop('SystemAuditor'), Scope.system, auditor_perms

View File

@ -1,7 +1,8 @@
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_migrate from django.db.models.signals import post_migrate, post_save
from django.apps import apps from django.apps import apps
from .models import SystemRole, OrgRole
from .builtin import BuiltinRole from .builtin import BuiltinRole
@ -12,3 +13,15 @@ def after_migrate_update_builtin_role_permissions(sender, app_config, **kwargs):
if app_config.name == last_app.name: if app_config.name == last_app.name:
print("After migration, update builtin role permissions") print("After migration, update builtin role permissions")
BuiltinRole.sync_to_db() BuiltinRole.sync_to_db()
@receiver(post_save, sender=SystemRole)
def on_system_role_update(sender, instance, created, **kwargs):
from users.models import User
User.expire_users_rbac_perms_cache()
@receiver(post_save, sender=OrgRole)
def on_org_role_update(sender, instance, created, **kwargs):
from users.models import User
User.expire_users_rbac_perms_cache()

View File

@ -1,6 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
from collections import defaultdict from collections import defaultdict
from typing import Callable from typing import Callable
import os
from django.utils.translation import gettext_lazy as _, gettext, get_language from django.utils.translation import gettext_lazy as _, gettext, get_language
from django.conf import settings from django.conf import settings
@ -10,6 +11,8 @@ from django.db.models import F, Count
from common.tree import TreeNode from common.tree import TreeNode
from .models import Permission, ContentType from .models import Permission, ContentType
DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1'
# 根节点 # 根节点
root_node_data = { root_node_data = {
'id': '$ROOT$', 'id': '$ROOT$',
@ -315,7 +318,7 @@ class PermissionTreeUtil:
continue continue
# name 要特殊处理,解决 i18n 问题 # name 要特殊处理,解决 i18n 问题
name, icon = self._get_permission_name_icon(p, content_types_name_mapper) name, icon = self._get_permission_name_icon(p, content_types_name_mapper)
if settings.DEBUG: if DEBUG_DB:
name += '[{}]'.format(p.app_label_codename) name += '[{}]'.format(p.app_label_codename)
title = p.app_label_codename title = p.app_label_codename
@ -366,9 +369,9 @@ class PermissionTreeUtil:
} }
node_data['title'] = node_data['id'] node_data['title'] = node_data['id']
node = TreeNode(**node_data) node = TreeNode(**node_data)
if settings.DEBUG: if DEBUG_DB:
node.name += ('[' + node.id + ']') node.name += ('[' + node.id + ']')
if settings.DEBUG: if DEBUG_DB:
node.name += ('-' + node.id) node.name += ('-' + node.id)
node.name += f'({checked_count}/{total_count})' node.name += f'({checked_count}/{total_count})'
return node return node

View File

@ -279,10 +279,15 @@ class RoleMixin:
cache.set(key, perms, 3600) cache.set(key, perms, 3600)
return perms return perms
def expire_perms_cache(self): def expire_rbac_perms_cache(self):
key = self.PERM_CACHE_KEY.format(self.id, '*') key = self.PERM_CACHE_KEY.format(self.id, '*')
cache.delete_pattern(key) cache.delete_pattern(key)
@classmethod
def expire_users_rbac_perms_cache(cls):
key = cls.PERM_CACHE_KEY.format('*', '*')
cache.delete_pattern(key)
@lazyproperty @lazyproperty
def is_superuser(self): def is_superuser(self):
""" """
@ -377,6 +382,11 @@ class RoleMixin:
perms = RoleBinding.get_user_perms(self) perms = RoleBinding.get_user_perms(self)
return perms return perms
def set_default_system_role(self):
from rbac.builtin import BuiltinRole
role_user = BuiltinRole.org_user.get_role()
self.system_roles.add(role_user)
class TokenMixin: class TokenMixin:
CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}" CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"

View File

@ -6,13 +6,11 @@ from rest_framework import serializers
from common.mixins import CommonBulkSerializerMixin from common.mixins import CommonBulkSerializerMixin
from common.validators import PhoneValidator from common.validators import PhoneValidator
from rbac.models import Role
from rbac.builtin import BuiltinRole from rbac.builtin import BuiltinRole
from rbac.permissions import RBACPermission from rbac.permissions import RBACPermission
from rbac.models import OrgRoleBinding, SystemRoleBinding from rbac.models import OrgRoleBinding, SystemRoleBinding, Role
from ..models import User from ..models import User
from ..const import PasswordStrategy from ..const import PasswordStrategy
from rbac.models import Role
__all__ = [ __all__ = [
'UserSerializer', 'MiniUserSerializer', 'UserSerializer', 'MiniUserSerializer',

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.dispatch import receiver from django.dispatch import receiver
from django_auth_ldap.backend import populate_user from django_auth_ldap.backend import populate_user
from django.conf import settings from django.conf import settings
@ -9,9 +8,9 @@ from django_cas_ng.signals import cas_user_authenticated
from django.db.models.signals import post_save from django.db.models.signals import post_save
from authentication.backends.oidc.signals import openid_create_or_update_user from authentication.backends.oidc.signals import openid_create_or_update_user
from authentication.backends.saml2.signals import saml2_create_or_update_user from authentication.backends.saml2.signals import saml2_create_or_update_user
from common.utils import get_logger from common.utils import get_logger
from common.decorator import on_transaction_commit
from .signals import post_user_create from .signals import post_user_create
from .models import User, UserPasswordHistory from .models import User, UserPasswordHistory
@ -26,7 +25,8 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
if created: if created:
user.source = source user.source = source
user.save() user.save()
elif not created and settings.AUTH_SAML2_ALWAYS_UPDATE_USER:
if not created and settings.AUTH_SAML2_ALWAYS_UPDATE_USER:
attr_whitelist = ('user', 'username', 'email', 'phone', 'comment') attr_whitelist = ('user', 'username', 'email', 'phone', 'comment')
logger.debug( logger.debug(
"Receive saml2 user updated signal: {}, " "Receive saml2 user updated signal: {}, "
@ -34,16 +34,18 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
"(Update only properties in the whitelist. [{}])" "(Update only properties in the whitelist. [{}])"
"".format(user, str(attrs), ','.join(attr_whitelist)) "".format(user, str(attrs), ','.join(attr_whitelist))
) )
if attrs is not None: if not attrs:
for key, value in attrs.items(): return
if key in attr_whitelist and value: for key, value in attrs.items():
setattr(user, key, value) if key in attr_whitelist and value:
user.save() setattr(user, key, value)
user.save()
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def save_passwd_change(sender, instance: User, **kwargs): def save_passwd_change(sender, instance: User, **kwargs):
passwords = UserPasswordHistory.objects.filter(user=instance) \ passwords = UserPasswordHistory.objects\
.filter(user=instance) \
.order_by('-date_created')\ .order_by('-date_created')\
.values_list('password', flat=True) .values_list('password', flat=True)
passwords = passwords[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)] passwords = passwords[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)]
@ -58,6 +60,17 @@ def save_passwd_change(sender, instance: User, **kwargs):
) )
@receiver(post_save, sender=User)
@on_transaction_commit
def on_user_create_set_default_system_role(sender, instance, created, **kwargs):
if not created:
return
has_system_role = instance.system_roles.all().exists()
if not has_system_role:
logger.debug("Receive user create signal, set default role")
instance.set_default_system_role()
@receiver(post_user_create) @receiver(post_user_create)
def on_user_create(sender, user=None, **kwargs): def on_user_create(sender, user=None, **kwargs):
logger.debug("Receive user `{}` create signal".format(user.name)) logger.debug("Receive user `{}` create signal".format(user.name))