perf: 优化 role bingding,优化 is_superuser

pull/7950/head
ibuler 2022-03-21 16:40:14 +08:00 committed by Jiangjie.Bai
parent 3345456dc2
commit 87c6eec619
4 changed files with 110 additions and 25 deletions

View File

@ -118,6 +118,9 @@ class Role(JMSModel):
return self.name return self.name
return gettext(self.name) return gettext(self.name)
def is_org(self):
return self.scope == const.Scope.org
class SystemRole(Role): class SystemRole(Role):
objects = SystemRoleManager() objects = SystemRoleManager()

View File

@ -1,6 +1,7 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.core.exceptions import ValidationError
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from common.db.models import JMSModel from common.db.models import JMSModel
@ -67,6 +68,7 @@ class RoleBinding(JMSModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.scope = self.role.scope self.scope = self.role.scope
self.clean()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@classmethod @classmethod
@ -95,6 +97,9 @@ class RoleBinding(JMSModel):
def role_display(self): def role_display(self):
return self.role.display_name return self.role.display_name
def is_scope_org(self):
return self.scope == Scope.org
class OrgRoleBindingManager(RoleBindingManager): class OrgRoleBindingManager(RoleBindingManager):
def get_queryset(self): def get_queryset(self):
@ -147,3 +152,12 @@ class SystemRoleBinding(RoleBinding):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.scope = Scope.system self.scope = Scope.system
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def clean(self):
kwargs = dict(role=self.role, user=self.user, scope=self.scope)
exists = self.__class__.objects.filter(**kwargs).exists()
if exists:
msg = "Duplicate for key 'role_user' of system role binding, {}_{}".format(
self.role.id, self.user.id
)
raise ValidationError(msg)

View File

@ -172,6 +172,22 @@ class RoleManager(models.Manager):
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.user = user self.user = user
self.role_binding_cls = self.get_role_binding_cls()
self.role_cls = self.get_role_cls()
def get_role_binding_cls(self):
from rbac.models import SystemRoleBinding, OrgRoleBinding
if self.scope == 'org':
return OrgRoleBinding
else:
return SystemRoleBinding
def get_role_cls(self):
from rbac.models import SystemRole, OrgRole
if self.scope == 'org':
return OrgRole
else:
return SystemRole
@property @property
def display(self): def display(self):
@ -181,15 +197,13 @@ class RoleManager(models.Manager):
@property @property
def role_bindings(self): def role_bindings(self):
from rbac.models import RoleBinding queryset = self.role_binding_cls.objects.filter(user=self.user)
queryset = RoleBinding.objects.filter(user=self.user)
if self.scope: if self.scope:
queryset = queryset.filter(scope=self.scope) queryset = queryset.filter(scope=self.scope)
return queryset return queryset
def _get_queryset(self): def _get_queryset(self):
from rbac.models import RoleBinding queryset = self.role_binding_cls.get_user_roles(self.user)
queryset = RoleBinding.get_user_roles(self.user)
if self.scope: if self.scope:
queryset = queryset.filter(scope=self.scope) queryset = queryset.filter(scope=self.scope)
return queryset return queryset
@ -204,36 +218,84 @@ class RoleManager(models.Manager):
return return
return self.role_bindings.delete() return self.role_bindings.delete()
def add(self, *roles): def _clean_roles(self, roles_or_ids):
from rbac.models import RoleBinding if not roles_or_ids:
items = [] return
is_model = isinstance(roles_or_ids[0], models.Model)
if not is_model:
roles = self.role_cls.objects.filter(id__in=roles_or_ids)
else:
roles = roles_or_ids
roles = list([r for r in roles if r.scope == self.scope])
return roles
for role in roles: def add(self, *roles):
if not roles:
return
roles = self._clean_roles(roles)
old_ids = self.role_bindings.values_list('role', flat=True)
need_adds = [r for r in roles if r.id not in old_ids]
items = []
for role in need_adds:
kwargs = { kwargs = {
'role': role, 'role': role,
'user': self.user, 'user': self.user,
'scope': role.scope 'scope': self.scope
} }
if self.scope and role.scope != self.scope: if not current_org.is_root():
continue
if not current_org.is_root() and role.scope == RoleBinding.Scope.org:
kwargs['org_id'] = current_org.id kwargs['org_id'] = current_org.id
items.append(RoleBinding(**kwargs)) items.append(self.role_binding_cls(**kwargs))
try: try:
RoleBinding.objects.bulk_create(items, ignore_conflicts=True) self.role_binding_cls.objects.bulk_create(items, ignore_conflicts=True)
except Exception as e: except Exception as e:
logger.error('Create role binding error: {}'.format(e)) logger.error('Create role binding error: {}'.format(e))
def set(self, roles): def set(self, roles, clear=False):
if clear:
self.clear() self.clear()
self.add(*roles) self.add(*roles)
return
role_ids = set([r.id for r in roles])
old_ids = self.role_bindings.values_list('role', flat=True)
old_ids = set(old_ids)
del_ids = old_ids - role_ids
add_ids = role_ids - old_ids
self.remove(*del_ids)
self.add(*add_ids)
def remove(self, *roles):
if not roles:
return
roles = self._clean_roles(roles)
return self.role_bindings.filter(role__in=roles).delete()
def cache_set(self, roles): def cache_set(self, roles):
query = self._get_queryset() query = self._get_queryset()
query._result_cache = roles query._result_cache = roles
self._cache = query self._cache = query
def remove_role_system_admin(self):
role = self.builtin_role.system_admin.get_role()
return self.remove(role)
def add_role_system_admin(self):
role = self.builtin_role.system_admin.get_role()
return self.add(role)
def add_role_system_user(self):
role = self.builtin_role.system_user.get_role()
return self.add(role)
@property
def builtin_role(self):
from rbac.builtin import BuiltinRole
return BuiltinRole
class OrgRoleManager(RoleManager): class OrgRoleManager(RoleManager):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -257,6 +319,7 @@ class RoleMixin:
_org_roles = None _org_roles = None
_system_roles = None _system_roles = None
PERM_CACHE_KEY = 'USER_PERMS_{}_{}' PERM_CACHE_KEY = 'USER_PERMS_{}_{}'
_is_superuser = None
@lazyproperty @lazyproperty
def roles(self): def roles(self):
@ -288,17 +351,27 @@ class RoleMixin:
key = cls.PERM_CACHE_KEY.format('*', '*') key = cls.PERM_CACHE_KEY.format('*', '*')
cache.delete_pattern(key) cache.delete_pattern(key)
@lazyproperty @property
def is_superuser(self): def is_superuser(self):
""" """
由于这里用了 cache 所以不能改成 self.system_roles.filter().exists() 会查询的 由于这里用了 cache 所以不能改成 self.system_roles.filter().exists() 会查询的
""" """
if not self._is_superuser:
return self._is_superuser
from rbac.builtin import BuiltinRole from rbac.builtin import BuiltinRole
# return self.system_roles.all().filter(id=BuiltinRole.system_admin.id).exists()
ids = [str(r.id) for r in self.system_roles.all()] ids = [str(r.id) for r in self.system_roles.all()]
yes = BuiltinRole.system_admin.id in ids yes = BuiltinRole.system_admin.id in ids
self._is_superuser = yes
return yes return yes
@is_superuser.setter
def is_superuser(self, value):
if value:
self.system_roles.add_role_system_admin()
else:
self.system_roles.remove_role_system_admin()
@lazyproperty @lazyproperty
def is_org_admin(self): def is_org_admin(self):
from rbac.builtin import BuiltinRole from rbac.builtin import BuiltinRole
@ -382,11 +455,6 @@ 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.system_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

@ -68,7 +68,7 @@ def on_user_create_set_default_system_role(sender, instance, created, **kwargs):
has_system_role = instance.system_roles.all().exists() has_system_role = instance.system_roles.all().exists()
if not has_system_role: if not has_system_role:
logger.debug("Receive user create signal, set default role") logger.debug("Receive user create signal, set default role")
instance.set_default_system_role() instance.system_roles.add_role_system_user()
@receiver(post_user_create) @receiver(post_user_create)