perf: 优化perm tree, 并添加缓存

pull/7770/head
ibuler 2022-03-08 13:35:40 +08:00 committed by Jiangjie.Bai
parent cb072123d6
commit 5081fb5fe7
4 changed files with 69 additions and 142 deletions

View File

@ -6,8 +6,9 @@ from django.core.cache import cache
from django.dispatch import receiver from django.dispatch import receiver
from django_cas_ng.signals import cas_user_authenticated from django_cas_ng.signals import cas_user_authenticated
from authentication.backends.oidc.signals import openid_user_login_failed, openid_user_login_success from authentication.backends.oidc.signals import (
openid_user_login_failed, openid_user_login_success
)
from authentication.backends.saml2.signals import ( from authentication.backends.saml2.signals import (
saml2_user_authenticated, saml2_user_authentication_failed saml2_user_authenticated, saml2_user_authentication_failed
) )
@ -16,6 +17,9 @@ 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 缓存
user.expire_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 \
and user.mfa_enabled \ and user.mfa_enabled \
@ -24,12 +28,12 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
# 单点登录,超过了自动退出 # 单点登录,超过了自动退出
if settings.USER_LOGIN_SINGLE_MACHINE_ENABLED: if settings.USER_LOGIN_SINGLE_MACHINE_ENABLED:
user_id = 'single_machine_login_' + str(user.id) lock_key = 'single_machine_login_' + str(user.id)
session_key = cache.get(user_id) session_key = cache.get(lock_key)
if session_key and session_key != request.session.session_key: if session_key and session_key != request.session.session_key:
session = import_module(settings.SESSION_ENGINE).SessionStore(session_key) session = import_module(settings.SESSION_ENGINE).SessionStore(session_key)
session.delete() session.delete()
cache.set(user_id, request.session.session_key, None) cache.set(lock_key, request.session.session_key, None)
@receiver(openid_user_login_success) @receiver(openid_user_login_success)

View File

@ -8,8 +8,8 @@ from django.apps import apps
from django.db.models import F, Count from django.db.models import F, Count
from django.utils.translation import ugettext from django.utils.translation import ugettext
from .models import Permission, ContentType
from common.tree import TreeNode from common.tree import TreeNode
from .models import Permission, ContentType
# 根节点 # 根节点
root_node_data = { root_node_data = {
@ -21,132 +21,44 @@ root_node_data = {
# 第二层 view 节点,手动创建的 # 第二层 view 节点,手动创建的
view_nodes_data = [ view_nodes_data = [
{ {'id': 'view_console', 'name': _('Console view')},
'id': 'view_console', {'id': 'view_workspace', 'name': _('Workspace view')},
'name': _('Console view'), {'id': 'view_audit', 'name': _('Audit view')},
}, {'id': 'view_setting', 'name': _('System setting')},
{ {'id': 'view_other', 'name': _('Other')},
'id': 'view_workspace',
'name': _('Workspace view'),
},
{
'id': 'view_audit',
'name': _('Audit view'),
},
{
'id': 'view_setting',
'name': _('System setting'),
},
{
'id': 'view_other',
'name': _('Other'),
}
] ]
# 第三层 app 节点,定义了父子关系 # 第三层 app 节点,手动创建
app_nodes_data = [ app_nodes_data = [
{ {'id': 'users', 'view': 'view_console'},
'id': 'users', {'id': 'assets', 'view': 'view_console'},
'view': 'view_console', {'id': 'applications', 'view': 'view_console'},
}, {'id': 'accounts', 'name': _('Accounts'), 'view': 'view_console'},
{ {'id': 'perms', 'view': 'view_console'},
'id': 'assets', {'id': 'acls', 'view': 'view_console'},
'view': 'view_console', {'id': 'ops', 'view': 'view_console'},
}, {'id': 'terminal', 'name': _('Session audits'), 'view': 'view_audit'},
{ {'id': 'audits', 'view': 'view_audit'},
'id': 'applications', {'id': 'rbac', 'view': 'view_console'},
'view': 'view_console', {'id': 'settings', 'view': 'view_setting'},
}, {'id': 'tickets', 'view': 'view_other'},
{ {'id': 'authentication', 'view': 'view_other'},
'id': 'accounts',
'name': _('Accounts'),
'view': 'view_console',
},
{
'id': 'perms',
'view': 'view_console',
},
{
'id': 'acls',
'view': 'view_console',
},
{
'id': 'ops',
'view': 'view_console',
},
{
'id': 'terminal',
'name': _('Session audits'),
'view': 'view_audit',
},
{
'id': 'audits',
'view': 'view_audit',
},
{
'id': 'rbac',
'view': 'view_console'
},
{
'id': 'settings',
'view': 'view_setting'
},
{
'id': 'tickets',
'view': 'view_other',
},
{
'id': 'authentication',
'view': 'view_other'
}
] ]
# 额外其他节点,可以在不同的层次,需要指定父节点,可以将一些 model 归类到这个节点下面 # 额外其他节点,可以在不同的层次,需要指定父节点,可以将一些 model 归类到这个节点下面
extra_nodes_data = [ extra_nodes_data = [
{ {"id": "cloud_import", "name": _("Cloud import"), "pId": "assets"},
"id": "cloud_import", {"id": "backup_account_node", "name": _("Backup account"), "pId": "accounts"},
"name": _("Cloud import"), {"id": "gather_account_node", "name": _("Gather account"), "pId": "accounts"},
"pId": "assets", {"id": "app_change_plan_node", "name": _("App change auth"), "pId": "accounts"},
}, {"id": "asset_change_plan_node", "name": _("Asset change auth"), "pId": "accounts"},
{ {"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"},
"id": "backup_account_node", {'id': "my_assets", "name": _("My assets"), "pId": "view_workspace"},
"name": _("Backup account"), {'id': "my_apps", "name": _("My apps"), "pId": "view_workspace"},
"pId": "accounts"
},
{
"id": "gather_account_node",
"name": _("Gather account"),
"pId": "accounts",
},
{
"id": "app_change_plan_node",
"name": _("App change auth"),
"pId": "accounts"
},
{
"id": "asset_change_plan_node",
"name": _("Asset change auth"),
"pId": "accounts"
},
{
"id": "terminal_node",
"name": _("Terminal setting"),
"pId": "view_setting"
},
{
'id': "my_assets",
"name": _("My assets"),
"pId": "view_workspace"
},
{
'id': "my_apps",
"name": _("My apps"),
"pId": "view_workspace"
},
] ]
# 将 model 放到其它节点下,而不是本来的 app 中 # 将 model 放到其它节点下,而不是本来的 app 中
special_model_pid_mapper = { special_pid_mapper = {
'common.permission': 'view_other', 'common.permission': 'view_other',
"assets.authbook": "accounts", "assets.authbook": "accounts",
"applications.account": "accounts", "applications.account": "accounts",
@ -181,18 +93,15 @@ special_model_pid_mapper = {
'ops.commandexecution': 'view_workspace', 'ops.commandexecution': 'view_workspace',
} }
model_verbose_name_mapper = { verbose_name_mapper = {
'orgs.organization': _("App organizations"), 'orgs.organization': _("App organizations"),
'tickets.comment': _("Ticket comment"), 'tickets.comment': _("Ticket comment"),
} }
xpack_apps = [ xpack_nodes = [
'xpack', 'tickets', 'xpack', 'tickets',
] ]
xpack_models = [
]
class PermissionTreeUtil: class PermissionTreeUtil:
get_permissions: Callable get_permissions: Callable
@ -201,7 +110,7 @@ class PermissionTreeUtil:
self.permissions = self.prefetch_permissions(permissions) self.permissions = self.prefetch_permissions(permissions)
self.all_permissions = self.prefetch_permissions( self.all_permissions = self.prefetch_permissions(
Permission.get_permissions(scope) Permission.get_permissions(scope)
) ).order_by('-codename')
self.check_disabled = check_disabled self.check_disabled = check_disabled
self.total_counts = defaultdict(int) self.total_counts = defaultdict(int)
self.checked_counts = defaultdict(int) self.checked_counts = defaultdict(int)
@ -267,9 +176,9 @@ class PermissionTreeUtil:
app, model = model_id.split('.', 2) app, model = model_id.split('.', 2)
if settings.XPACK_ENABLED: if settings.XPACK_ENABLED:
return True return True
if app in xpack_apps: if app in xpack_nodes:
return False return False
if model_id in xpack_models: if model_id in xpack_nodes:
return False return False
return True return True
@ -289,15 +198,15 @@ class PermissionTreeUtil:
# 获取 pid # 获取 pid
app = ct.app_label app = ct.app_label
if model_id in special_model_pid_mapper: if model_id in special_pid_mapper:
app = special_model_pid_mapper[model_id] app = special_pid_mapper[model_id]
self.total_counts[app] += total_count self.total_counts[app] += total_count
self.checked_counts[app] += checked_count self.checked_counts[app] += checked_count
# 获取 name # 获取 name
name = f'{ct.name}' name = f'{ct.name}'
if model_id in model_verbose_name_mapper: if model_id in verbose_name_mapper:
name = model_verbose_name_mapper[model_id] name = verbose_name_mapper[model_id]
node = self._create_node({ node = self._create_node({
'id': model_id, 'id': model_id,
@ -348,14 +257,16 @@ class PermissionTreeUtil:
model_id = f'{p.app}.{p.model}' model_id = f'{p.app}.{p.model}'
if not self._check_model_xpack(model_id): if not self._check_model_xpack(model_id):
continue continue
# name 要特殊处理,解决 i18n 问题
name = self._get_permission_name(p, content_types_name_mapper) name = self._get_permission_name(p, content_types_name_mapper)
if settings.DEBUG: if settings.DEBUG:
name += '({})'.format(p.app_label_codename) name += '({})'.format(p.app_label_codename)
title = p.app_label_codename title = p.app_label_codename
pid = model_id pid = model_id
if title in special_model_pid_mapper: # perm node 的特殊设置用的是 title因为 id 是数字,不一致
pid = special_model_pid_mapper[title] if title in special_pid_mapper:
pid = special_pid_mapper[title]
self.total_counts[pid] += 1 self.total_counts[pid] += 1
checked = p.id in permissions_id checked = p.id in permissions_id
@ -421,7 +332,7 @@ class PermissionTreeUtil:
checked_count = self.checked_counts[view] checked_count = self.checked_counts[view]
if total_count == 0: if total_count == 0:
continue continue
node = self._create_node(data, total_count, checked_count, 'view', is_open=False) node = self._create_node(data, total_count, checked_count, 'view', is_open=True)
nodes.append(node) nodes.append(node)
return nodes return nodes
@ -441,7 +352,6 @@ class PermissionTreeUtil:
'extra', is_open=False 'extra', is_open=False
) )
nodes.append(node) nodes.append(node)
return nodes return nodes
def create_tree_nodes(self): def create_tree_nodes(self):

View File

@ -253,8 +253,10 @@ class RoleMixin:
objects: models.Manager objects: models.Manager
is_authenticated: bool is_authenticated: bool
is_valid: bool is_valid: bool
id: str
_org_roles = None _org_roles = None
_system_roles = None _system_roles = None
PERM_CACHE_KEY = 'USER_PERMS_{}_{}'
@lazyproperty @lazyproperty
def roles(self): def roles(self):
@ -270,7 +272,16 @@ class RoleMixin:
@lazyproperty @lazyproperty
def perms(self): def perms(self):
return self.get_all_permissions() key = self.PERM_CACHE_KEY.format(self.id, current_org.id)
perms = cache.get(key)
if not perms:
perms = self.get_all_permissions()
cache.set(key, perms, 3600)
return perms
def expire_perms_cache(self):
key = self.PERM_CACHE_KEY.format(self.id, '*')
cache.delete_pattern(key)
@lazyproperty @lazyproperty
def is_superuser(self): def is_superuser(self):

View File

@ -43,10 +43,12 @@ def user_authenticated_handle(user, created, source, attrs=None, **kwargs):
@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):
passwds = UserPasswordHistory.objects.filter(user=instance).order_by('-date_created')\ passwords = UserPasswordHistory.objects.filter(user=instance) \
.values_list('password', flat=True)[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)] .order_by('-date_created')\
.values_list('password', flat=True)
passwords = passwords[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)]
for p in passwds: for p in passwords:
if instance.password == p: if instance.password == p:
break break
else: else: