Files
jumpserver/apps/rbac/tree.py
fit2bot 56d533c802 v3.0.0-rc1 (#9322)
* perf:automation

* pref: 修改账号推送

* perf: 修改 assets

* perf: 修改 accounts

* feat: 优化代码

* fix: 修复 ObjectRelatedField 获取 value attr 时先判断是否有 attr 属性

* perf: 增加翻译

* feat: 增加部分翻译

* feat: 去除无用列

* perf: ticket remove app

* fix: 修复创建账号备份任务失败的问题

* perf: 添加 accounts app

* perf: ticket type serializer (#9252)

Co-authored-by: feng <1304903146@qq.com>

* perf: ticket

* perf: 修改 accounts api

* perf: 优化 AssetPermissionSerializer fields 顺序

* perf: 修改 accounts

* feat: 限制常用用户名api返回长度

* feat: 限制常用用户名api返回长度

* perf: 修改 LoginAssetACL 序列类,增加 users_username_group, accounts_username_group... 字段

* perf: 修改 CommandFilterACLSerializer 增加 command_groups_amount 字段

* perf: 修改rbac API啥的 (#9254)

* perf: migrate

* perf: 修改 AssetPermedSerializer domain 字段类型

* perf: 放开push account 权限位

* perf: 修改 accounts

* perf: 修改 LoginACLSerializer 字段类型

* pref: 修改数据库 migrations

* perf: filter asset systemuser

* perf: 修改 SessionSerializer 字段类型

* pref: 修改 applet host

* perf: 修改 SessionCommandSerializer 字段类型

* perf: 修改 accounts import

* perf: 修改 celery datetime

* perf: 修改 asset serializer

* pref: 修改 labeled field

* feat: 修改翻译

* perf: 修改 JobSerializer 字段类型

* feat: 支持使用 ws 发送终断任务

* perf: add AccessTokenAuthentication

* perf: 修改 BaseStorageSerializer 字段类型

* perf: 修改 AppletHostSerializer 字段类型

* perf: signal event

* perf: asset types automations (#9259)

Co-authored-by: feng <1304903146@qq.com>

* perf: 修改下载 rdp 文件时返回的 address 地址信息为空的问题

* perf: 修改 AssetSerializer.accounts.secret 为 write_only; 修改 DomainWithGatewaySerializer.gateways 返回 account 信息及 secret 字段;

* perf: automation 干库 (#9260)

Co-authored-by: feng <1304903146@qq.com>

* perf: account push api

* feat: 修改迁移文件

* feat: 删除无用代码

* feat: 优化部分资源无操作日志

* perf: 修改 account

* perf: perm tree

* perf: asset serializers retrieve

* perf: 格式化代码

* perf: AutomationExecution (#9268)

Co-authored-by: feng <1304903146@qq.com>

* perf: AssetDetailSerializer 和 Asset Model 添加 specific_info 字段;

* perf: 修改账号推送

* feat: handle ws heartbeat status

* perf: k8s tree (#9269)

Co-authored-by: feng <1304903146@qq.com>

* perf: 修改账号推送

* perf: 修改 asset detail serializer

* fix: 修复 windows 不能运行 powershell 命令的问题

* feat: 支持按照资源时间线查看操作活动

* feat: 翻译

* feat: 优化操作日志

* perf: asset clone

* fix: 错误的修改改回去

* perf: create asset account

* feat: 增加task 刷新续传功能

* fix: applet host deloypment filter host

* perf: 修改了 common 结构,和 push accounts

* perf: 整理 common 结构

* perf: 修改 const import

* perf: 修改 allow bulk destroy

* fix: applet host search fileds

* perf: applet bulk delete

* fix: applet list 404

* perf: 修改 common view

* feat: 增加一些翻译, 修复 playbook 上传的错误

* fix: 修改错别字

* perf: 修改 applets status

* perf: 修改网关 api

* perf: automateion (#9281)

Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>

* perf: 失效 connect methods 当 applet 删除 或者 host 删除

* perf: 网关账号的密码类型改成 LabelField

* perf: chrome applet script

* perf: verify code ttl (#9282)

Co-authored-by: feng <1304903146@qq.com>

* perf: database ping

* perf: ws

* perf: 修改网关创建

* perf: account task org (#9285)

Co-authored-by: feng <1304903146@qq.com>

* perf: asset test api

* perf: port 添加 account

* pref: 修改 db mapper permission

* fix: db port mapper list api

* perf: account change secret (#9286)

Co-authored-by: feng <1304903146@qq.com>

* perf: 修改 setup_eager_loading

* perf: SecretStrategy

* feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑

* feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑

* feat: 修改 ConnectionToken Create API 支持校验 ACL 逻辑

* pref: web database 信号转发

* perf: account push automation

* perf: push filter account

* perf: 修改 publish 版本

* perf: 修改网关

* fix: 修改资产 Specific 信息中 JSONField 字段返回 json.loads 对象

* feat: 远程应用内置Navicat Premium 16

* feat: 更新下载链接

* feat: 整理代码格式

* perf: 修改 terminal point

* perf: update chrome applet script

* fix: 资产 specific 获取 JSONField 时, 判断值的类型不为 list, dict

* perf: domain (#9292)

Co-authored-by: feng <1304903146@qq.com>

* perf: 优化 endpoint 监听端口,仅 oracle 动态

* perf: 修改翻译

* perf: 修改文案

* perf: 修改缺失的翻译

* perf: 修改 endpoint help text

* feat: 还原格式

* feat: 去掉基类

* feat: 增加特权账号字段

* perf: decode content

* fix: check pid

* perf: 修改 smart endpoint

* perf: 修改 endpoint mysql default port

* feat: 优化

* perf: 修改 endpoint mysql default port

* perf: gateway test (#9295)

Co-authored-by: feng <1304903146@qq.com>

* perf: migrate

* perf: 修改 endpoint mysql default port

* fix: 修复获取任务执行结果死循环

* feat: 作业审计日志增加字段

* fix: add on_transaction_commit task post save

* perf: gateway (#9297)

Co-authored-by: feng <1304903146@qq.com>

* feat: 过滤 jumpserver 自动产生的用户

* fix: 修复ops节点选择的问题

* fix: 修改 统一 connection-token 和 command 的 review API 返回数据 from_ticket_info

* perf: change secret (#9298)

Co-authored-by: feng <1304903146@qq.com>

* perf: 修改 db port manager

* perf: 修改 db port manager

* perf: add celery log mark

* perf: remove debug log data

* fix: navicat use manual type

* fix: remove navicate download url

* perf: push_account_enabled (#9301)

Co-authored-by: feng <1304903146@qq.com>

* fix: 修改navicat启动程序MD5值

* perf: push account (#9303)

Co-authored-by: feng <1304903146@qq.com>

* feat: Redis/MongoDB 支持SSL

* fix: 修改授权规则过滤字段 node_name,node_id; 修复获取授权节点下的资产为空的问题;

* perf: push account button (#9305)

Co-authored-by: feng <1304903146@qq.com>

* perf: account push

* fix: 修复获取 /user//assets/tree/ 返回用户授权的所有资产

* perf: asset ping (#9307)

Co-authored-by: feng <1304903146@qq.com>

* perf: asset enabled_info

* perf: 优化activity记录都保存至operatelog中

* feat: 远程应用navicat支持试用版连接

* perf: 优化迁移文件

* perf: 修改资产列表 API category type 字段 choices 根据 category 进行返回

* fix

* perf: 修改账号列表 API 解决根据 node_id asset_id 搜索账号列表无效的问题

* fix: navicat dba账号登录

* perf: 优化navicat连接

* perf: 修改账号列表 Model Manager 继承自 OrgManager,解决组织过滤问题

* perf: 修改账号列表 Filter 支持根据 platform,category,type 字段搜索

* perf: change secret email (#9312)

Co-authored-by: feng <1304903146@qq.com>

* feat: 保证认证信息一定清理

* perf: add mariadb

* perf: 修改资产类型树数量统计资产或账号

* perf: applet chrome quit

* perf: 优化关闭欢迎页面

* fix

* perf: executed amount

* perf: 修改 built-in applet installation

* perf: 修改资产列表增加标签搜索

* perf: 修改资产列表增加标签搜索

* perf: account task automation (#9319)

Co-authored-by: feng <1304903146@qq.com>

* perf: account trigger

* perf: 修改系统设置文案:批量命令执行 -> 作业中心

* perf: 优化migrate (#9320)

Co-authored-by: feng <1304903146@qq.com>

* perf: 修改资产节点树 API,支持搜索资产、节点

* perf: audit dashboard (#9321)

Co-authored-by: feng <1304903146@qq.com>

* fix: 修改 has_perm 权限判断兼容 list 和 str 类型

* perf: 修改一些换行

* perf: 修改 ansible config

* fix: oracle依赖文件地址错误 (#9324)

* perf: ansible mudules

* perf: 修改 runner host cwd

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Aaron3S <chenyang@fit2cloud.com>
Co-authored-by: Bai <baijiangjie@gmail.com>
Co-authored-by: feng <1304903146@qq.com>
Co-authored-by: feng626 <57284900+feng626@users.noreply.github.com>
Co-authored-by: Eric <xplzv@126.com>
Co-authored-by: jiangweidong <weidong.jiang@fit2cloud.com>
Co-authored-by: jiangweidong <80373698+Hi-JWD@users.noreply.github.com>
2023-01-16 19:02:09 +08:00

470 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/python
import os
from typing import Callable
from treelib import Tree
from treelib.exceptions import NodeIDAbsentError
from django.utils.translation import gettext_lazy as _, gettext, get_language
from django.conf import settings
from django.apps import apps
from django.db.models import F, Count
from common.tree import TreeNode
from .models import Permission, ContentType
# 根节点
root_node_data = {
'id': '$ROOT$',
'name': _('All permissions'),
'title': _('All permissions'),
'pId': '',
}
# 第二层 view 节点,手动创建的
view_nodes_data = [
{'id': 'view_console', 'name': _('Console view')},
{'id': 'view_workbench', 'name': _('Workbench view')},
{'id': 'view_audit', 'name': _('Audit view')},
{'id': 'view_setting', 'name': _('System setting')},
{'id': 'view_other', 'name': _('Other')},
]
# 第三层 app 节点,手动创建
app_nodes_data = [
{'id': 'users', 'view': 'view_console'},
{'id': 'assets', 'view': 'view_console'},
{'id': 'accounts', 'name': _('Accounts'), 'view': 'view_console'},
{'id': 'perms', '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 归类到这个节点下面
extra_nodes_data = [
{"id": "cloud_import", "name": _("Cloud import"), "pId": "assets"},
{"id": "backup_account_node", "name": _("Backup account"), "pId": "accounts"},
{"id": "gather_account_node", "name": _("Gather account"), "pId": "accounts"},
{"id": "push_account_node", "name": _("Push account"), "pId": "accounts"},
{"id": "asset_change_plan_node", "name": _("Asset change auth"), "pId": "accounts"},
{"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"},
{'id': "task_center", "name": _("Task Center"), "pId": "view_console"},
{'id': "my_assets", "name": _("My assets"), "pId": "view_workbench"},
{'id': "operation_center", "name": _('App ops'), "pId": "view_workbench"},
{'id': "remote_application", "name": _("Applet"), "pId": "view_setting"},
]
# 将 model 放到其它节点下,而不是本来的 app 中
special_pid_mapper = {
'common.permission': 'view_other',
'assets.account': 'accounts',
'assets.accounttemplate': 'accounts',
'acls.commandfilteracl': 'perms',
'acls.commandgroup': 'perms',
'acls.loginacl': 'perms',
'acls.loginassetacl': 'perms',
'xpack.account': 'cloud_import',
'xpack.syncinstancedetail': 'cloud_import',
'xpack.syncinstancetask': 'cloud_import',
'xpack.syncinstancetaskexecution': 'cloud_import',
'terminal.applet': 'remote_application',
'terminal.applethost': 'remote_application',
'accounts.accountbackupautomation': "backup_account_node",
'accounts.accountbackupexecution': "backup_account_node",
"accounts.pushaccountautomation": "push_account_node",
"accounts.view_pushaccountexecution": "push_account_node",
"accounts.add_pushaccountexecution": "push_account_node",
"accounts.gatheraccountsautomation": "gather_account_node",
"accounts.view_gatheraccountsexecution": "gather_account_node",
"accounts.add_gatheraccountsexecution": "gather_account_node",
"accounts.changesecretautomation": "asset_change_plan_node",
"accounts.view_changesecretexecution": "asset_change_plan_node",
"accounts.add_changesecretexection": "asset_change_plan_node",
"accounts.view_changesecretrecord": "asset_change_plan_node",
'orgs.organization': 'view_setting',
'settings.setting': 'view_setting',
'terminal.terminal': 'terminal_node',
'terminal.commandstorage': 'terminal_node',
'terminal.replaystorage': 'terminal_node',
'terminal.status': 'terminal_node',
'terminal.task': 'terminal_node',
'terminal.endpoint': 'terminal_node',
'terminal.endpointrule': 'terminal_node',
'audits.ftplog': 'terminal',
'perms.view_myassets': 'my_assets',
'ops.jobauditlog': 'audits',
'ops.view_celerytask': 'task_center',
'ops.view_celerytaskexecution': 'task_center',
'ops.view_taskmonitor': 'task_center',
'ops.adhocexecution': 'task_center',
'ops.job': 'operation_center',
'ops.adhoc': 'operation_center',
'ops.playbook': 'operation_center',
'ops.jobexecution': 'operation_center',
"xpack.interface": "view_setting",
"settings.change_terminal": "terminal_node",
"settings.view_setting": "view_setting",
"rbac.view_console": "view_console",
"rbac.view_audit": "view_audit",
"rbac.view_workbench": "view_workbench",
"rbac.view_webterminal": "view_workbench",
"rbac.view_filemanager": "view_workbench",
'tickets.view_ticket': 'tickets'
}
verbose_name_mapper = {
'orgs.organization': _("App organizations"),
'tickets.comment': _("Ticket comment"),
'tickets.view_ticket': _("Ticket"),
'settings.setting': _("Common setting"),
'rbac.view_permission': _('View permission tree'),
}
xpack_nodes = [
'xpack', 'tickets', 'gather_account_node',
'applications.remoteapp', "assets.accountbackupplan",
"assets.accountbackupplanexecution",
"rbac.orgrole", "rbac.orgrolebinding",
'assets.gathereduser',
'settings.change_interface', 'settings.change_sms',
'users.invite_user', 'users.remove_user',
]
def _sort_action(node):
if node.isParent:
return ['zz', 0]
action_resource = node.title.split('.')[-1]
action, resource = action_resource.split('_', 2)
action_value_mapper = {
'view': 2,
'add': 4,
'change': 6,
'delete': 8
}
v = action_value_mapper.get(action, 10)
return [resource, v]
def sort_nodes(node):
value = []
if node.isParent:
value.append(50)
else:
value.append(0)
value.extend(_sort_action(node))
return value
class CounterTree(Tree):
def get_total_count(self, node):
count = getattr(node, '_total_count', None)
if count is not None:
return count
if not node.data.isParent:
return 1
count = 0
children = self.children(node.identifier)
for child in children:
if child.data.isParent:
count += self.get_total_count(child)
else:
count += 1
node._total_count = count
return count
def get_checked_count(self, node):
count = getattr(node, '_checked_count', None)
if count is not None:
return count
if not node.data.isParent:
if node.data.checked:
return 1
else:
return 0
count = 0
children = self.children(node.identifier)
for child in children:
if child.data.isParent:
count += self.get_checked_count(child)
else:
if child.data.checked:
count += 1
node._checked_count = count
return count
def add_nodes_to_tree(self, ztree_nodes, retry=0):
failed = []
for node in ztree_nodes:
pid = node.pId
if retry == 2:
pid = '$ROOT$'
try:
self.create_node(node.name, node.id, pid, data=node)
except NodeIDAbsentError:
failed.append(node)
if retry > 2:
return
if failed:
retry += 1
return self.add_nodes_to_tree(failed, retry)
class PermissionTreeUtil:
get_permissions: Callable
action_mapper = {
'add': _('Create'),
'view': _('View'),
'change': _('Update'),
'delete': _('Delete')
}
action_icon = {
'add': 'add',
'view': 'view',
'change': 'change',
'delete': 'delete',
'invite': 'invite',
'match': 'match',
'remove': 'remove'
}
def __init__(self, permissions, scope, check_disabled=False):
self.permissions = self.prefetch_permissions(permissions)
self.all_permissions = self.prefetch_permissions(
Permission.get_permissions(scope)
)
self.check_disabled = check_disabled
self.lang = get_language()
@staticmethod
def prefetch_permissions(perms):
return perms.select_related('content_type') \
.annotate(app=F('content_type__app_label')) \
.annotate(model=F('content_type__model'))
def create_apps_nodes(self):
all_apps = apps.get_app_configs()
apps_name_mapper = {
app.name: app.verbose_name
for app in all_apps if hasattr(app, 'verbose_name')
}
nodes = []
for i in app_nodes_data:
app = i['id']
name = i.get('name') or apps_name_mapper.get(app, app)
view = i.get('view', 'other')
app_data = {
'id': app,
'name': name,
'pId': view,
}
node = self._create_node(app_data, 'app', is_open=False)
nodes.append(node)
return nodes
@staticmethod
def _check_model_xpack(model_id):
app, model = model_id.split('.', 2)
if settings.XPACK_ENABLED:
return True
if app in xpack_nodes:
return False
if model_id in xpack_nodes:
return False
return True
def _create_models_nodes(self):
content_types = ContentType.objects.all()
nodes = []
for ct in content_types:
model_id = '{}.{}'.format(ct.app_label, ct.model)
if not self._check_model_xpack(model_id):
continue
# 获取 pid
app = ct.app_label
if model_id in special_pid_mapper:
app = special_pid_mapper[model_id]
# 获取 name
name = f'{ct.name}'
if model_id in verbose_name_mapper:
name = verbose_name_mapper[model_id]
node = self._create_node({
'id': model_id,
'name': name,
'pId': app,
}, 'model', is_open=False)
nodes.append(node)
return nodes
def _get_permission_name_icon(self, p: Permission, content_types_name_mapper: dict):
action, resource = p.codename.split('_', 1)
icon = self.action_icon.get(action, 'file')
name = verbose_name_mapper.get(p.app_label_codename)
if name:
return name, icon
app_model = '%s.%s' % (p.content_type.app_label, resource)
if self.lang == 'en':
name = p.name
# 因为默认的权限位是没有翻译的,所以我们要用 action + resource name 去拼
elif action in self.action_mapper and app_model in content_types_name_mapper:
action_name = self.action_mapper[action]
resource_name = content_types_name_mapper[app_model]
sep = ''
name = '{}{}{}'.format(action_name, sep, resource_name)
# 手动创建的 permission
else:
name = gettext(p.name)
name = name.replace('Can ', '').replace('可以', '').capitalize()
return name, icon
def _create_perms_nodes(self):
permissions_id = self.permissions.values_list('id', flat=True)
nodes = []
content_types = ContentType.objects.all()
content_types_name_mapper = {ct.app_model: ct.name for ct in content_types}
for p in self.all_permissions:
model_id = f'{p.app}.{p.model}'
if not self._check_model_xpack(model_id):
continue
title = p.app_label_codename
if not settings.XPACK_ENABLED and title in xpack_nodes:
continue
# name 要特殊处理,解决 i18n 问题
name, icon = self._get_permission_name_icon(p, content_types_name_mapper)
if settings.DEBUG_DEV:
name += '[{}]'.format(p.app_label_codename)
pid = model_id
# perm node 的特殊设置用的是 title因为 id 是数字,不一致
if title in special_pid_mapper:
pid = special_pid_mapper[title]
checked = p.id in permissions_id
node = TreeNode(**{
'id': p.id,
'name': name,
'title': title,
'pId': pid,
'isParent': False,
'chkDisabled': self.check_disabled,
'iconSkin': icon,
'checked': checked,
'open': False,
'meta': {
'type': 'perm',
}
})
nodes.append(node)
return nodes
def _create_node(self, data, tp, is_parent=True, is_open=True, icon='', checked=None):
assert data.get('id')
assert data.get('name')
assert data.get('pId') is not None
node_data = {
'isParent': is_parent,
'iconSkin': icon,
'open': is_open,
'chkDisabled': self.check_disabled,
'checked': checked,
'meta': {
'type': tp,
},
**data
}
node_data['title'] = node_data['id']
node = TreeNode(**node_data)
if settings.DEBUG_DEV:
node.name += ('[' + node.id + ']')
if settings.DEBUG_DEV:
node.name += ('-' + node.id)
return node
def _create_root_tree_node(self):
node = self._create_node(root_node_data, 'root')
return node
def _create_views_node(self):
nodes = []
for view_data in view_nodes_data:
data = {
**view_data,
'pId': '$ROOT$',
}
node = self._create_node(data, 'view', is_open=True)
nodes.append(node)
return nodes
def _create_extra_nodes(self):
nodes = []
for data in extra_nodes_data:
node = self._create_node(data, 'extra', is_open=False)
nodes.append(node)
return nodes
@staticmethod
def compute_nodes_count(ztree_nodes):
tree = CounterTree()
reverse_nodes = ztree_nodes[::-1]
root = reverse_nodes[0]
tree.create_node(root.name, root.id, data=root)
tree.add_nodes_to_tree(reverse_nodes[1:])
counter_nodes = tree.all_nodes()
node_counts = {}
for n in counter_nodes:
if not n:
continue
total_count = tree.get_total_count(n)
checked_count = tree.get_checked_count(n)
node_counts[n.identifier] = [checked_count, total_count]
nodes = []
for node in ztree_nodes:
counter = node_counts[node.id]
if not counter:
counter = [0, 0]
checked_count, total_count = counter
if total_count == 0:
continue
node.name += '({}/{})'.format(checked_count, total_count)
if checked_count != 0:
node.checked = True
nodes.append(node)
return nodes
def create_tree_nodes(self):
nodes = self._create_perms_nodes()
nodes += self._create_models_nodes()
nodes += self.create_apps_nodes()
nodes += self._create_extra_nodes()
nodes += self._create_views_node()
nodes += [self._create_root_tree_node()]
nodes = self.compute_nodes_count(nodes)
nodes.sort(key=sort_nodes)
return nodes