diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index 5c5aa1abd..0d2a617f9 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -18,7 +18,7 @@ from .signals import post_auth_success, post_auth_failed @receiver(user_logged_in) def on_user_auth_login_success(sender, user, request, **kwargs): # 失效 perms 缓存 - user.expire_perms_cache() + user.expire_rbac_perms_cache() # 开启了 MFA,且没有校验过, 可以全局校验, middleware 中可以全局管理 oidc 等第三方认证的 MFA if settings.SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY \ diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 29f57f55b..e3a5c3882 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f529bbca004aeba7532d9faf50f6f8ab5532b19bf0afd650f8360f418c03c15c -size 104629 +oid sha256:5775daad77f39025e7ddd39f04fdc0cb67c9537754caa3b0b520421df65e5041 +size 104739 diff --git a/apps/rbac/api/permission.py b/apps/rbac/api/permission.py index 75025ba8a..722deb556 100644 --- a/apps/rbac/api/permission.py +++ b/apps/rbac/api/permission.py @@ -41,13 +41,3 @@ class PermissionViewSet(JMSModelViewSet): queryset = Permission.get_permissions(self.scope) queryset = queryset.prefetch_related('content_type') 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 diff --git a/apps/rbac/builtin.py b/apps/rbac/builtin.py index af9b5ec92..ec66feecc 100644 --- a/apps/rbac/builtin.py +++ b/apps/rbac/builtin.py @@ -2,6 +2,8 @@ from django.utils.translation import ugettext_noop from .const import Scope, system_exclude_permissions, org_exclude_permissions +# Todo: 获取应该区分 系统用户,和组织用户的权限 +# 工作台也区分组织后再考虑 user_perms = ( ('rbac', 'menupermission', 'view', 'workspace'), ('rbac', 'menupermission', 'view', 'webterminal'), @@ -12,14 +14,13 @@ user_perms = ( ('assets', 'systemuser', 'match', 'systemuser'), ('assets', 'node', 'match', 'node'), ('applications', 'application', 'match', 'application'), - ('tickets', 'ticket', 'view', 'ticket'), ('ops', 'commandexecution', 'add', 'commandexecution'), ('authentication', 'connectiontoken', 'add', 'connectiontoken'), + ('tickets', 'ticket', 'view', 'ticket'), ) auditor_perms = user_perms + ( ('rbac', 'menupermission', 'view', 'audit'), - ('rbac', 'menupermission', 'view', 'dashboard'), ('audits', '*', '*', '*'), ('terminal', 'commandstorage', 'view', 'commandstorage'), ('terminal', 'sessionreplay', 'view,download', 'sessionreplay'), @@ -88,7 +89,7 @@ class PredefineRole: class BuiltinRole: system_admin = PredefineRole( - '1', ugettext_noop('SystemAdmin'), Scope.system, [] + '1', ugettext_noop('SystemAdmin'), Scope.system, user_perms ) system_auditor = PredefineRole( '2', ugettext_noop('SystemAuditor'), Scope.system, auditor_perms diff --git a/apps/rbac/signal_handlers.py b/apps/rbac/signal_handlers.py index 316c38211..86eefb180 100644 --- a/apps/rbac/signal_handlers.py +++ b/apps/rbac/signal_handlers.py @@ -1,7 +1,8 @@ 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 .models import SystemRole, OrgRole 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: print("After migration, update builtin role permissions") 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() diff --git a/apps/rbac/tree.py b/apps/rbac/tree.py index b8bd2407e..6318242f3 100644 --- a/apps/rbac/tree.py +++ b/apps/rbac/tree.py @@ -1,6 +1,7 @@ #!/usr/bin/python from collections import defaultdict from typing import Callable +import os from django.utils.translation import gettext_lazy as _, gettext, get_language from django.conf import settings @@ -10,6 +11,8 @@ from django.db.models import F, Count from common.tree import TreeNode from .models import Permission, ContentType +DEBUG_DB = os.environ.get('DEBUG_DB', '0') == '1' + # 根节点 root_node_data = { 'id': '$ROOT$', @@ -315,7 +318,7 @@ class PermissionTreeUtil: continue # name 要特殊处理,解决 i18n 问题 name, icon = self._get_permission_name_icon(p, content_types_name_mapper) - if settings.DEBUG: + if DEBUG_DB: name += '[{}]'.format(p.app_label_codename) title = p.app_label_codename @@ -366,9 +369,9 @@ class PermissionTreeUtil: } node_data['title'] = node_data['id'] node = TreeNode(**node_data) - if settings.DEBUG: + if DEBUG_DB: node.name += ('[' + node.id + ']') - if settings.DEBUG: + if DEBUG_DB: node.name += ('-' + node.id) node.name += f'({checked_count}/{total_count})' return node diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 67efa8b3c..f387c7344 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -279,10 +279,15 @@ class RoleMixin: cache.set(key, perms, 3600) return perms - def expire_perms_cache(self): + def expire_rbac_perms_cache(self): key = self.PERM_CACHE_KEY.format(self.id, '*') cache.delete_pattern(key) + @classmethod + def expire_users_rbac_perms_cache(cls): + key = cls.PERM_CACHE_KEY.format('*', '*') + cache.delete_pattern(key) + @lazyproperty def is_superuser(self): """ @@ -377,6 +382,11 @@ class RoleMixin: perms = RoleBinding.get_user_perms(self) 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: CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}" diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index 21d67961b..7421c9cc1 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -6,13 +6,11 @@ from rest_framework import serializers from common.mixins import CommonBulkSerializerMixin from common.validators import PhoneValidator -from rbac.models import Role from rbac.builtin import BuiltinRole from rbac.permissions import RBACPermission -from rbac.models import OrgRoleBinding, SystemRoleBinding +from rbac.models import OrgRoleBinding, SystemRoleBinding, Role from ..models import User from ..const import PasswordStrategy -from rbac.models import Role __all__ = [ 'UserSerializer', 'MiniUserSerializer', diff --git a/apps/users/signal_handlers.py b/apps/users/signal_handlers.py index c4d84e561..8f2065ca3 100644 --- a/apps/users/signal_handlers.py +++ b/apps/users/signal_handlers.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # - from django.dispatch import receiver from django_auth_ldap.backend import populate_user 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 authentication.backends.oidc.signals import openid_create_or_update_user - from authentication.backends.saml2.signals import saml2_create_or_update_user from common.utils import get_logger +from common.decorator import on_transaction_commit from .signals import post_user_create from .models import User, UserPasswordHistory @@ -26,7 +25,8 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs): if created: user.source = source 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') logger.debug( "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. [{}])" "".format(user, str(attrs), ','.join(attr_whitelist)) ) - if attrs is not None: - for key, value in attrs.items(): - if key in attr_whitelist and value: - setattr(user, key, value) - user.save() + if not attrs: + return + for key, value in attrs.items(): + if key in attr_whitelist and value: + setattr(user, key, value) + user.save() @receiver(post_save, sender=User) def save_passwd_change(sender, instance: User, **kwargs): - passwords = UserPasswordHistory.objects.filter(user=instance) \ + passwords = UserPasswordHistory.objects\ + .filter(user=instance) \ .order_by('-date_created')\ .values_list('password', flat=True) 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) def on_user_create(sender, user=None, **kwargs): logger.debug("Receive user `{}` create signal".format(user.name))