perf(orgs): 默认组织改为实体组织,并支持全局组织 (#5617)

* perf(orgs): 默认组织改为实体组织

* perf: 添加获取当前组织信息的api

* perf: 资产列表在 root 组织下的表现

* fix: 修复 root 组织引起的问题

* perf: 优化OrgModelMixin save; org_root获取; org_roles获取; UserCanUseCurrentOrg权限类

Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: Bai <bugatti_it@163.com>
pull/5669/head
fit2bot 2021-03-02 14:57:48 +08:00 committed by GitHub
parent 51c9a89b1f
commit a56ac7b34e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 381 additions and 298 deletions

View File

@ -127,9 +127,13 @@ class NodeChildrenApi(generics.ListCreateAPIView):
def get_object(self): def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id') pk = self.kwargs.get('pk') or self.request.query_params.get('id')
key = self.request.query_params.get("key") key = self.request.query_params.get("key")
if not pk and not key: if not pk and not key:
node = Node.org_root()
self.is_initial = True self.is_initial = True
if current_org.is_root():
node = None
else:
node = Node.org_root()
return node return node
if pk: if pk:
node = get_object_or_404(Node, pk=pk) node = get_object_or_404(Node, pk=pk)
@ -137,16 +141,26 @@ class NodeChildrenApi(generics.ListCreateAPIView):
node = get_object_or_404(Node, key=key) node = get_object_or_404(Node, key=key)
return node return node
def get_org_root_queryset(self, query_all):
if query_all:
return Node.objects.all()
else:
return Node.org_root_nodes()
def get_queryset(self): def get_queryset(self):
query_all = self.request.query_params.get("all", "0") == "all" query_all = self.request.query_params.get("all", "0") == "all"
if not self.instance:
return Node.objects.none() if self.is_initial and current_org.is_root():
return self.get_org_root_queryset(query_all)
if self.is_initial: if self.is_initial:
with_self = True with_self = True
else: else:
with_self = False with_self = False
if not self.instance:
return Node.objects.none()
if query_all: if query_all:
queryset = self.instance.get_all_children(with_self=with_self) queryset = self.instance.get_all_children(with_self=with_self)
else: else:
@ -178,7 +192,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
def get_assets(self): def get_assets(self):
include_assets = self.request.query_params.get('assets', '0') == '1' include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets: if not self.instance or not include_assets:
return [] return []
assets = self.instance.get_assets().only( assets = self.instance.get_assets().only(
"id", "hostname", "ip", "os", "platform_id", "id", "hostname", "ip", "os", "platform_id",
@ -240,7 +254,10 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
node.assets.remove(*assets) node.assets.remove(*assets)
# 把孤儿资产添加到 root 节点 # 把孤儿资产添加到 root 节点
orphan_assets = Asset.objects.filter(id__in=[a.id for a in assets], nodes__isnull=True).distinct() orphan_assets = Asset.objects.filter(
id__in=[a.id for a in assets],
nodes__isnull=True
).distinct()
Node.org_root().assets.add(*orphan_assets) Node.org_root().assets.add(*orphan_assets)

View File

@ -40,7 +40,7 @@ def compute_parent_key(key):
class NodeQuerySet(models.QuerySet): class NodeQuerySet(models.QuerySet):
def delete(self): def delete(self):
raise NotImplementedError raise NotImplementedError
#
class FamilyMixin: class FamilyMixin:
__parents = None __parents = None
@ -446,8 +446,9 @@ class SomeNodesMixin:
@classmethod @classmethod
def default_node(cls): def default_node(cls):
with tmp_to_org(Organization.default()): default_org = Organization.default()
defaults = {'value': cls.default_value} with tmp_to_org(default_org):
defaults = {'value': default_org.name}
try: try:
obj, created = cls.objects.get_or_create( obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.default_key, defaults=defaults, key=cls.default_key,
@ -482,25 +483,34 @@ class SomeNodesMixin:
@classmethod @classmethod
def create_org_root_node(cls): def create_org_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
ori_org = get_current_org() ori_org = get_current_org()
with transaction.atomic(): with transaction.atomic():
if not ori_org.is_real():
return cls.default_node()
key = cls.get_next_org_root_node_key() key = cls.get_next_org_root_node_key()
root = cls.objects.create(key=key, value=ori_org.name) root = cls.objects.create(key=key, value=ori_org.name)
return root return root
@classmethod @classmethod
def org_root(cls): def org_root_nodes(cls):
root = cls.objects.filter(parent_key='')\ nodes = cls.objects.filter(parent_key='') \
.filter(key__regex=r'^[0-9]+$')\ .filter(key__regex=r'^[0-9]+$') \
.exclude(key__startswith='-')\ .exclude(key__startswith='-') \
.order_by('key') .order_by('key')
if root: return nodes
return root[0]
@classmethod
def org_root(cls):
org_roots = cls.org_root_nodes()
if org_roots:
return org_roots[0]
ori_org = get_current_org()
# 如果使用current_org 在set_current_org时会死循环
if ori_org.is_root():
root = cls.default_node()
elif ori_org.is_default():
root = cls.default_node()
else: else:
return cls.create_org_root_node() root = cls.create_org_root_node()
return root
@classmethod @classmethod
def initial_some_nodes(cls): def initial_some_nodes(cls):
@ -519,9 +529,6 @@ class SomeNodesMixin:
if not node_key1: if not node_key1:
logger.info("Not found node that `key` = 1") logger.info("Not found node that `key` = 1")
return return
if not node_key1.org.is_real():
logger.info("Org is not real for node that `key` = 1")
return
with transaction.atomic(): with transaction.atomic():
with tmp_to_org(node_key1.org): with tmp_to_org(node_key1.org):

View File

@ -13,18 +13,20 @@ logger = get_logger(__file__)
@shared_task @shared_task
def check_node_assets_amount_task(orgid=None): def check_node_assets_amount_task(org_id=None):
if orgid is None: if org_id is None:
orgs = [*Organization.objects.all(), Organization.default()] orgs = Organization.objects.all()
else: else:
orgs = [Organization.get_instance(orgid)] orgs = [Organization.get_instance(org_id)]
for org in orgs: for org in orgs:
try: try:
with tmp_to_org(org): with tmp_to_org(org):
check_node_assets_amount() check_node_assets_amount()
except AcquireFailed: except AcquireFailed:
logger.error(_('The task of self-checking is already running and cannot be started repeatedly')) error = _('The task of self-checking is already running '
'and cannot be started repeatedly')
logger.error(error)
@register_as_period_task(crontab='0 2 * * *') @register_as_period_task(crontab='0 2 * * *')

View File

@ -110,12 +110,17 @@ class PermissionsMixin(UserPassesTestMixin):
return True return True
class UserCanUpdatePassword: class UserCanUseCurrentOrg(permissions.BasePermission):
def has_permission(self, request, view):
return current_org.can_use_by(request.user)
class UserCanUpdatePassword(permissions.BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
return request.user.can_update_password() return request.user.can_update_password()
class UserCanUpdateSSHKey: class UserCanUpdateSSHKey(permissions.BasePermission):
def has_permission(self, request, view): def has_permission(self, request, view):
return request.user.can_update_ssh_key() return request.user.can_update_ssh_key()

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-01-26 17:25+0800\n" "POT-Creation-Date: 2021-02-20 15:17+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -32,8 +32,8 @@ msgstr "远程应用"
msgid "Custom" msgid "Custom"
msgstr "自定义" msgstr "自定义"
#: applications/models/application.py:10 assets/models/asset.py:149 #: applications/models/application.py:10 assets/models/asset.py:142
#: assets/models/base.py:234 assets/models/cluster.py:18 #: assets/models/base.py:235 assets/models/cluster.py:18
#: assets/models/cmd_filter.py:21 assets/models/domain.py:21 #: assets/models/cmd_filter.py:21 assets/models/domain.py:21
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:23 perms/models/base.py:48 settings/models.py:29 #: orgs/models.py:23 perms/models/base.py:48 settings/models.py:29
@ -78,7 +78,7 @@ msgstr "类别"
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"
#: applications/models/application.py:19 assets/models/asset.py:198 #: applications/models/application.py:19 assets/models/asset.py:191
#: assets/models/domain.py:27 assets/models/domain.py:55 #: assets/models/domain.py:27 assets/models/domain.py:55
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
@ -89,8 +89,8 @@ msgstr ""
# msgid "Date created" # msgid "Date created"
# msgstr "创建日期" # msgstr "创建日期"
#: applications/models/application.py:23 assets/models/asset.py:154 #: applications/models/application.py:23 assets/models/asset.py:147
#: assets/models/asset.py:230 assets/models/base.py:239 #: assets/models/asset.py:223 assets/models/base.py:240
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:23 #: assets/models/cluster.py:29 assets/models/cmd_filter.py:23
#: assets/models/cmd_filter.py:57 assets/models/domain.py:22 #: assets/models/cmd_filter.py:57 assets/models/domain.py:22
#: assets/models/domain.py:56 assets/models/group.py:23 #: assets/models/domain.py:56 assets/models/group.py:23
@ -125,16 +125,16 @@ msgstr "主机"
#: applications/serializers/attrs/application_type/mysql_workbench.py:22 #: applications/serializers/attrs/application_type/mysql_workbench.py:22
#: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/oracle.py:11
#: applications/serializers/attrs/application_type/pgsql.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11
#: assets/models/asset.py:195 assets/models/domain.py:53 #: assets/models/asset.py:188 assets/models/domain.py:53
msgid "Port" msgid "Port"
msgstr "端口" msgstr "端口"
#: applications/serializers/attrs/application_category/remote_app.py:33 #: applications/serializers/attrs/application_category/remote_app.py:33
#: assets/models/asset.py:363 assets/models/authbook.py:26 #: assets/models/asset.py:355 assets/models/authbook.py:26
#: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:32
#: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84 #: assets/serializers/asset_user.py:47 assets/serializers/asset_user.py:84
#: assets/serializers/system_user.py:191 audits/models.py:38 #: assets/serializers/system_user.py:191 audits/models.py:38
#: perms/models/asset_permission.py:96 templates/index.html:82 #: perms/models/asset_permission.py:99 templates/index.html:82
#: terminal/backends/command/models.py:19 #: terminal/backends/command/models.py:19
#: terminal/backends/command/serializers.py:13 terminal/models/session.py:39 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:39
#: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:40
@ -161,7 +161,7 @@ msgstr "目标URL"
#: applications/serializers/attrs/application_type/custom.py:21 #: applications/serializers/attrs/application_type/custom.py:21
#: applications/serializers/attrs/application_type/mysql_workbench.py:30 #: applications/serializers/attrs/application_type/mysql_workbench.py:30
#: applications/serializers/attrs/application_type/vmware_client.py:26 #: applications/serializers/attrs/application_type/vmware_client.py:26
#: assets/models/base.py:235 assets/models/gathered_user.py:15 #: assets/models/base.py:236 assets/models/gathered_user.py:15
#: audits/models.py:99 authentication/forms.py:11 #: audits/models.py:99 authentication/forms.py:11
#: authentication/templates/authentication/login.html:101 #: authentication/templates/authentication/login.html:101
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:515 #: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:515
@ -178,7 +178,7 @@ msgstr "用户名"
#: applications/serializers/attrs/application_type/custom.py:25 #: applications/serializers/attrs/application_type/custom.py:25
#: applications/serializers/attrs/application_type/mysql_workbench.py:34 #: applications/serializers/attrs/application_type/mysql_workbench.py:34
#: applications/serializers/attrs/application_type/vmware_client.py:30 #: applications/serializers/attrs/application_type/vmware_client.py:30
#: assets/models/base.py:236 assets/serializers/asset_user.py:71 #: assets/models/base.py:237 assets/serializers/asset_user.py:71
#: audits/signals_handler.py:42 authentication/forms.py:13 #: audits/signals_handler.py:42 authentication/forms.py:13
#: authentication/templates/authentication/login.html:109 #: authentication/templates/authentication/login.html:109
#: settings/serializers/settings.py:84 users/forms/user.py:22 #: settings/serializers/settings.py:84 users/forms/user.py:22
@ -204,7 +204,7 @@ msgid "Target url"
msgstr "目标URL" msgstr "目标URL"
#: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: applications/serializers/attrs/application_type/mysql_workbench.py:18
#: assets/models/asset.py:190 assets/models/domain.py:52 #: assets/models/asset.py:183 assets/models/domain.py:52
#: assets/serializers/asset_user.py:46 settings/serializers/settings.py:103 #: assets/serializers/asset_user.py:46 settings/serializers/settings.py:103
#: users/templates/users/_granted_assets.html:26 #: users/templates/users/_granted_assets.html:26
#: users/templates/users/user_asset_permission.html:156 #: users/templates/users/user_asset_permission.html:156
@ -219,15 +219,15 @@ msgstr "删除失败,存在关联资产"
msgid "Number required" msgid "Number required"
msgstr "需要为数字" msgstr "需要为数字"
#: assets/api/node.py:67 #: assets/api/node.py:60
msgid "You can't update the root node name" msgid "You can't update the root node name"
msgstr "不能修改根节点名称" msgstr "不能修改根节点名称"
#: assets/api/node.py:74 #: assets/api/node.py:67
msgid "You can't delete the root node ({})" msgid "You can't delete the root node ({})"
msgstr "不能删除根节点 ({})" msgstr "不能删除根节点 ({})"
#: assets/api/node.py:77 #: assets/api/node.py:70
msgid "Deletion failed and the node contains children or assets" msgid "Deletion failed and the node contains children or assets"
msgstr "删除失败,节点包含子节点或资产" msgstr "删除失败,节点包含子节点或资产"
@ -239,137 +239,137 @@ msgstr "不能移除资产的管理用户账号"
msgid "Latest version could not be delete" msgid "Latest version could not be delete"
msgstr "最新版本的不能被删除" msgstr "最新版本的不能被删除"
#: assets/models/asset.py:150 xpack/plugins/cloud/providers/base.py:17 #: assets/models/asset.py:143 xpack/plugins/cloud/providers/base.py:17
msgid "Base" msgid "Base"
msgstr "基础" msgstr "基础"
#: assets/models/asset.py:151 #: assets/models/asset.py:144
msgid "Charset" msgid "Charset"
msgstr "编码" msgstr "编码"
#: assets/models/asset.py:152 tickets/models/ticket.py:40 #: assets/models/asset.py:145 tickets/models/ticket.py:40
msgid "Meta" msgid "Meta"
msgstr "元数据" msgstr "元数据"
#: assets/models/asset.py:153 #: assets/models/asset.py:146
msgid "Internal" msgid "Internal"
msgstr "内部的" msgstr "内部的"
#: assets/models/asset.py:173 assets/models/asset.py:197 #: assets/models/asset.py:166 assets/models/asset.py:190
#: assets/serializers/asset.py:66 #: assets/serializers/asset.py:66
msgid "Platform" msgid "Platform"
msgstr "系统平台" msgstr "系统平台"
#: assets/models/asset.py:191 assets/serializers/asset_user.py:45 #: assets/models/asset.py:184 assets/serializers/asset_user.py:45
#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:102 #: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:102
#: users/templates/users/_granted_assets.html:25 #: users/templates/users/_granted_assets.html:25
#: users/templates/users/user_asset_permission.html:157 #: users/templates/users/user_asset_permission.html:157
msgid "Hostname" msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:194 assets/models/domain.py:54 #: assets/models/asset.py:187 assets/models/domain.py:54
#: assets/models/user.py:120 terminal/serializers/session.py:29 #: assets/models/user.py:120 terminal/serializers/session.py:29
#: terminal/serializers/storage.py:69 #: terminal/serializers/storage.py:69
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
#: assets/models/asset.py:196 assets/serializers/asset.py:68 #: assets/models/asset.py:189 assets/serializers/asset.py:68
#: perms/serializers/asset/user_permission.py:41 #: perms/serializers/asset/user_permission.py:41
msgid "Protocols" msgid "Protocols"
msgstr "协议组" msgstr "协议组"
#: assets/models/asset.py:199 assets/models/user.py:115 #: assets/models/asset.py:192 assets/models/user.py:115
#: perms/models/asset_permission.py:97 #: perms/models/asset_permission.py:100
#: xpack/plugins/change_auth_plan/models.py:56 #: xpack/plugins/change_auth_plan/models.py:56
#: xpack/plugins/gathered_user/models.py:24 #: xpack/plugins/gathered_user/models.py:24
msgid "Nodes" msgid "Nodes"
msgstr "节点" msgstr "节点"
#: assets/models/asset.py:200 assets/models/cmd_filter.py:22 #: assets/models/asset.py:193 assets/models/cmd_filter.py:22
#: assets/models/domain.py:57 assets/models/label.py:22 #: assets/models/domain.py:57 assets/models/label.py:22
#: authentication/models.py:46 #: authentication/models.py:46
msgid "Is active" msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:203 assets/models/cluster.py:19 #: assets/models/asset.py:196 assets/models/cluster.py:19
#: assets/models/user.py:66 templates/_nav.html:44 #: assets/models/user.py:66 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:137 #: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:137
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
#: assets/models/asset.py:206 #: assets/models/asset.py:199
msgid "Public IP" msgid "Public IP"
msgstr "公网IP" msgstr "公网IP"
#: assets/models/asset.py:207 #: assets/models/asset.py:200
msgid "Asset number" msgid "Asset number"
msgstr "资产编号" msgstr "资产编号"
#: assets/models/asset.py:210 #: assets/models/asset.py:203
msgid "Vendor" msgid "Vendor"
msgstr "制造商" msgstr "制造商"
#: assets/models/asset.py:211 #: assets/models/asset.py:204
msgid "Model" msgid "Model"
msgstr "型号" msgstr "型号"
#: assets/models/asset.py:212 #: assets/models/asset.py:205
msgid "Serial number" msgid "Serial number"
msgstr "序列号" msgstr "序列号"
#: assets/models/asset.py:214 #: assets/models/asset.py:207
msgid "CPU model" msgid "CPU model"
msgstr "CPU型号" msgstr "CPU型号"
#: assets/models/asset.py:215 #: assets/models/asset.py:208
msgid "CPU count" msgid "CPU count"
msgstr "CPU数量" msgstr "CPU数量"
#: assets/models/asset.py:216 #: assets/models/asset.py:209
msgid "CPU cores" msgid "CPU cores"
msgstr "CPU核数" msgstr "CPU核数"
#: assets/models/asset.py:217 #: assets/models/asset.py:210
msgid "CPU vcpus" msgid "CPU vcpus"
msgstr "CPU总数" msgstr "CPU总数"
#: assets/models/asset.py:218 #: assets/models/asset.py:211
msgid "Memory" msgid "Memory"
msgstr "内存" msgstr "内存"
#: assets/models/asset.py:219 #: assets/models/asset.py:212
msgid "Disk total" msgid "Disk total"
msgstr "硬盘大小" msgstr "硬盘大小"
#: assets/models/asset.py:220 #: assets/models/asset.py:213
msgid "Disk info" msgid "Disk info"
msgstr "硬盘信息" msgstr "硬盘信息"
#: assets/models/asset.py:222 #: assets/models/asset.py:215
msgid "OS" msgid "OS"
msgstr "操作系统" msgstr "操作系统"
#: assets/models/asset.py:223 #: assets/models/asset.py:216
msgid "OS version" msgid "OS version"
msgstr "系统版本" msgstr "系统版本"
#: assets/models/asset.py:224 #: assets/models/asset.py:217
msgid "OS arch" msgid "OS arch"
msgstr "系统架构" msgstr "系统架构"
#: assets/models/asset.py:225 #: assets/models/asset.py:218
msgid "Hostname raw" msgid "Hostname raw"
msgstr "主机名原始" msgstr "主机名原始"
#: assets/models/asset.py:227 templates/_nav.html:46 #: assets/models/asset.py:220 templates/_nav.html:46
msgid "Labels" msgid "Labels"
msgstr "标签管理" msgstr "标签管理"
#: assets/models/asset.py:228 assets/models/base.py:242 #: assets/models/asset.py:221 assets/models/base.py:243
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26
#: assets/models/cmd_filter.py:60 assets/models/group.py:21 #: assets/models/cmd_filter.py:60 assets/models/group.py:21
#: common/db/models.py:67 common/mixins/models.py:49 orgs/models.py:24 #: common/db/models.py:67 common/mixins/models.py:49 orgs/models.py:24
#: orgs/models.py:427 perms/models/base.py:54 users/models/user.py:558 #: orgs/models.py:412 perms/models/base.py:54 users/models/user.py:558
#: users/serializers/group.py:35 users/templates/users/user_detail.html:97 #: users/serializers/group.py:35 users/templates/users/user_detail.html:97
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:58 #: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:58
#: xpack/plugins/cloud/models.py:156 xpack/plugins/gathered_user/models.py:30 #: xpack/plugins/cloud/models.py:156 xpack/plugins/gathered_user/models.py:30
@ -378,12 +378,12 @@ msgstr "创建者"
# msgid "Created by" # msgid "Created by"
# msgstr "创建者" # msgstr "创建者"
#: assets/models/asset.py:229 assets/models/base.py:240 #: assets/models/asset.py:222 assets/models/base.py:241
#: assets/models/cluster.py:26 assets/models/domain.py:24 #: assets/models/cluster.py:26 assets/models/domain.py:24
#: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/gathered_user.py:19 assets/models/group.py:22
#: assets/models/label.py:25 common/db/models.py:69 common/mixins/models.py:50 #: assets/models/label.py:25 common/db/models.py:69 common/mixins/models.py:50
#: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25
#: orgs/models.py:425 perms/models/base.py:55 users/models/group.py:18 #: orgs/models.py:410 perms/models/base.py:55 users/models/group.py:18
#: users/templates/users/user_group_detail.html:58 #: users/templates/users/user_group_detail.html:58
#: xpack/plugins/cloud/models.py:61 xpack/plugins/cloud/models.py:159 #: xpack/plugins/cloud/models.py:61 xpack/plugins/cloud/models.py:159
msgid "Date created" msgid "Date created"
@ -405,21 +405,21 @@ msgstr "版本"
msgid "AuthBook" msgid "AuthBook"
msgstr "" msgstr ""
#: assets/models/base.py:237 xpack/plugins/change_auth_plan/models.py:72 #: assets/models/base.py:238 xpack/plugins/change_auth_plan/models.py:72
#: xpack/plugins/change_auth_plan/models.py:197 #: xpack/plugins/change_auth_plan/models.py:197
#: xpack/plugins/change_auth_plan/models.py:292 #: xpack/plugins/change_auth_plan/models.py:292
msgid "SSH private key" msgid "SSH private key"
msgstr "SSH密钥" msgstr "SSH密钥"
#: assets/models/base.py:238 xpack/plugins/change_auth_plan/models.py:75 #: assets/models/base.py:239 xpack/plugins/change_auth_plan/models.py:75
#: xpack/plugins/change_auth_plan/models.py:193 #: xpack/plugins/change_auth_plan/models.py:193
#: xpack/plugins/change_auth_plan/models.py:288 #: xpack/plugins/change_auth_plan/models.py:288
msgid "SSH public key" msgid "SSH public key"
msgstr "SSH公钥" msgstr "SSH公钥"
#: assets/models/base.py:241 assets/models/gathered_user.py:20 #: assets/models/base.py:242 assets/models/gathered_user.py:20
#: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39 #: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:426 #: orgs/models.py:411
msgid "Date updated" msgid "Date updated"
msgstr "更新日期" msgstr "更新日期"
@ -556,9 +556,9 @@ msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 #: assets/models/label.py:15 audits/models.py:36 audits/models.py:56
#: audits/models.py:69 audits/serializers.py:81 authentication/models.py:44 #: audits/models.py:69 audits/serializers.py:81 authentication/models.py:44
#: authentication/models.py:95 orgs/models.py:18 orgs/models.py:423 #: authentication/models.py:95 orgs/models.py:18 orgs/models.py:408
#: perms/models/asset_permission.py:173 perms/models/base.py:49 #: perms/models/base.py:49 templates/index.html:78
#: templates/index.html:78 terminal/backends/command/models.py:18 #: terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:37 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:37
#: tickets/models/comment.py:17 users/forms/group.py:15 #: tickets/models/comment.py:17 users/forms/group.py:15
#: users/models/user.py:158 users/models/user.py:665 #: users/models/user.py:158 users/models/user.py:665
@ -575,31 +575,31 @@ msgstr "默认资产组"
msgid "User" msgid "User"
msgstr "用户" msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:413 settings/models.py:30 #: assets/models/label.py:19 assets/models/node.py:545 settings/models.py:30
msgid "Value" msgid "Value"
msgstr "值" msgstr "值"
#: assets/models/node.py:143 #: assets/models/node.py:152
msgid "New node" msgid "New node"
msgstr "新节点" msgstr "新节点"
#: assets/models/node.py:316 users/templates/users/_granted_assets.html:130 #: assets/models/node.py:450 users/templates/users/_granted_assets.html:130
msgid "empty" msgid "empty"
msgstr "空" msgstr "空"
#: assets/models/node.py:412 perms/models/asset_permission.py:148 #: assets/models/node.py:544 perms/models/asset_permission.py:156
msgid "Key" msgid "Key"
msgstr "键" msgstr "键"
#: assets/models/node.py:414 #: assets/models/node.py:546
msgid "Full value" msgid "Full value"
msgstr "全称" msgstr "全称"
#: assets/models/node.py:417 perms/models/asset_permission.py:152 #: assets/models/node.py:549 perms/models/asset_permission.py:157
msgid "Parent key" msgid "Parent key"
msgstr "ssh私钥" msgstr "ssh私钥"
#: assets/models/node.py:426 assets/serializers/system_user.py:190 #: assets/models/node.py:557 assets/serializers/system_user.py:190
#: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158 #: users/templates/users/user_asset_permission.html:158
@ -668,7 +668,7 @@ msgstr "用户组"
#: assets/models/user.py:221 audits/models.py:39 #: assets/models/user.py:221 audits/models.py:39
#: perms/models/application_permission.py:31 #: perms/models/application_permission.py:31
#: perms/models/asset_permission.py:98 templates/_nav.html:45 #: perms/models/asset_permission.py:101 templates/_nav.html:45
#: terminal/backends/command/models.py:20 #: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:14 terminal/models/session.py:41 #: terminal/backends/command/serializers.py:14 terminal/models/session.py:41
#: users/templates/users/_granted_assets.html:27 #: users/templates/users/_granted_assets.html:27
@ -727,7 +727,7 @@ msgstr "硬件信息"
msgid "Org name" msgid "Org name"
msgstr "组织名称" msgstr "组织名称"
#: assets/serializers/asset.py:162 assets/serializers/asset.py:201 #: assets/serializers/asset.py:162 assets/serializers/asset.py:194
msgid "Connectivity" msgid "Connectivity"
msgstr "连接" msgstr "连接"
@ -873,11 +873,6 @@ msgstr "更新节点资产硬件信息: {}"
msgid "Gather assets users" msgid "Gather assets users"
msgstr "收集资产上的用户" msgstr "收集资产上的用户"
#: assets/tasks/nodes_amount.py:21
msgid ""
"The task of self-checking is already running and cannot be started repeatedly"
msgstr "自检程序已经在运行,不能重复启动"
#: assets/tasks/push_system_user.py:184 #: assets/tasks/push_system_user.py:184
#: assets/tasks/system_user_connectivity.py:89 #: assets/tasks/system_user_connectivity.py:89
msgid "System user is dynamic: {}" msgid "System user is dynamic: {}"
@ -1081,7 +1076,7 @@ msgstr "用户代理"
#: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: authentication/templates/authentication/login_otp.html:6 #: authentication/templates/authentication/login_otp.html:6
#: users/forms/profile.py:52 users/models/user.py:539 #: users/forms/profile.py:52 users/models/user.py:539
#: users/serializers/user.py:232 users/templates/users/user_detail.html:77 #: users/serializers/profile.py:102 users/templates/users/user_detail.html:77
#: users/templates/users/user_profile.html:87 #: users/templates/users/user_profile.html:87
msgid "MFA" msgid "MFA"
msgstr "多因子认证" msgstr "多因子认证"
@ -1355,7 +1350,7 @@ msgid "Show"
msgstr "显示" msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66 #: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:443 users/serializers/user.py:229 #: users/models/user.py:443 users/serializers/profile.py:99
#: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:94
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166 #: users/templates/users/user_profile.html:166
@ -1364,7 +1359,7 @@ msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67 #: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:444 users/serializers/user.py:230 #: users/models/user.py:444 users/serializers/profile.py:100
#: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:170
msgid "Enable" msgid "Enable"
@ -1605,7 +1600,7 @@ msgstr "不能包含特殊字符"
msgid "<h1>Flow service unavailable, check it</h1>" msgid "<h1>Flow service unavailable, check it</h1>"
msgstr "" msgstr ""
#: jumpserver/views/other.py:26 #: jumpserver/views/other.py:27
msgid "" msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, koko, " "<div>Luna is a separately deployed program, you need to deploy Luna, koko, "
"configure nginx for url distribution,</div> </div>If you see this page, " "configure nginx for url distribution,</div> </div>If you see this page, "
@ -1614,11 +1609,11 @@ msgstr ""
"<div>Luna是单独部署的一个程序你需要部署lunakoko, </div><div>如果你看到了" "<div>Luna是单独部署的一个程序你需要部署lunakoko, </div><div>如果你看到了"
"这个页面证明你访问的不是nginx监听的端口祝你好运</div>" "这个页面证明你访问的不是nginx监听的端口祝你好运</div>"
#: jumpserver/views/other.py:77 #: jumpserver/views/other.py:78
msgid "Websocket server run on port: {}, you should proxy it on nginx" msgid "Websocket server run on port: {}, you should proxy it on nginx"
msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置" msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置"
#: jumpserver/views/other.py:91 #: jumpserver/views/other.py:92
msgid "" msgid ""
"<div>Koko is a separately deployed program, you need to deploy Koko, " "<div>Koko is a separately deployed program, you need to deploy Koko, "
"configure nginx for url distribution,</div> </div>If you see this page, " "configure nginx for url distribution,</div> </div>If you see this page, "
@ -1795,8 +1790,8 @@ msgstr "组织包含未删除的资源"
msgid "The current organization cannot be deleted" msgid "The current organization cannot be deleted"
msgstr "当前组织不能被删除" msgstr "当前组织不能被删除"
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:41 #: orgs/mixins/models.py:53 orgs/mixins/serializers.py:25 orgs/models.py:39
#: orgs/models.py:422 orgs/serializers.py:100 #: orgs/models.py:407 orgs/serializers.py:100
#: tickets/serializers/ticket/ticket.py:81 #: tickets/serializers/ticket/ticket.py:81
msgid "Organization" msgid "Organization"
msgstr "组织" msgstr "组织"
@ -1809,7 +1804,11 @@ msgstr "组织管理员"
msgid "Organization auditor" msgid "Organization auditor"
msgstr "组织审计员" msgstr "组织审计员"
#: orgs/models.py:424 users/forms/user.py:27 users/models/user.py:527 #: orgs/models.py:33
msgid "GLOBAL"
msgstr "全局组织"
#: orgs/models.py:409 users/forms/user.py:27 users/models/user.py:527
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:73 #: users/templates/users/user_detail.html:73
#: users/templates/users/user_list.html:16 #: users/templates/users/user_list.html:16
@ -1817,7 +1816,7 @@ msgstr "组织审计员"
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: perms/const.py:7 perms/utils/asset/user_permission.py:28 #: perms/const.py:7 perms/models/asset_permission.py:189
msgid "Ungrouped" msgid "Ungrouped"
msgstr "未分组" msgstr "未分组"
@ -1841,47 +1840,52 @@ msgstr "应用程序"
msgid "Application permission" msgid "Application permission"
msgstr "应用管理" msgstr "应用管理"
#: perms/models/asset_permission.py:34 settings/serializers/settings.py:107 #: perms/models/asset_permission.py:37 settings/serializers/settings.py:107
msgid "All" msgid "All"
msgstr "全部" msgstr "全部"
#: perms/models/asset_permission.py:35 #: perms/models/asset_permission.py:38
msgid "Connect" msgid "Connect"
msgstr "连接" msgstr "连接"
#: perms/models/asset_permission.py:36 #: perms/models/asset_permission.py:39
msgid "Upload file" msgid "Upload file"
msgstr "上传文件" msgstr "上传文件"
#: perms/models/asset_permission.py:37 #: perms/models/asset_permission.py:40
msgid "Download file" msgid "Download file"
msgstr "下载文件" msgstr "下载文件"
#: perms/models/asset_permission.py:38 #: perms/models/asset_permission.py:41
msgid "Upload download" msgid "Upload download"
msgstr "上传下载" msgstr "上传下载"
#: perms/models/asset_permission.py:39 #: perms/models/asset_permission.py:42
msgid "Clipboard copy" msgid "Clipboard copy"
msgstr "剪贴板复制" msgstr "剪贴板复制"
#: perms/models/asset_permission.py:40 #: perms/models/asset_permission.py:43
msgid "Clipboard paste" msgid "Clipboard paste"
msgstr "剪贴板粘贴" msgstr "剪贴板粘贴"
#: perms/models/asset_permission.py:41 #: perms/models/asset_permission.py:44
msgid "Clipboard copy paste" msgid "Clipboard copy paste"
msgstr "剪贴板复制粘贴" msgstr "剪贴板复制粘贴"
#: perms/models/asset_permission.py:99 perms/serializers/asset/permission.py:60 #: perms/models/asset_permission.py:102
#: perms/serializers/asset/permission.py:60
msgid "Actions" msgid "Actions"
msgstr "动作" msgstr "动作"
#: perms/models/asset_permission.py:103 templates/_nav.html:78 #: perms/models/asset_permission.py:106 templates/_nav.html:78
#: users/templates/users/_user_detail_nav_header.html:31 #: users/templates/users/_user_detail_nav_header.html:31
msgid "Asset permission" msgid "Asset permission"
msgstr "资产授权" msgstr "资产授权"
#: perms/models/asset_permission.py:191
msgid "Favorite"
msgstr "收藏夹"
#: perms/models/base.py:50 templates/_nav.html:21 users/forms/user.py:168 #: perms/models/base.py:50 templates/_nav.html:21 users/forms/user.py:168
#: users/models/group.py:31 users/models/user.py:523 #: users/models/group.py:31 users/models/user.py:523
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
@ -1912,11 +1916,11 @@ msgid ""
"permission type. ({})" "permission type. ({})"
msgstr "应用列表中包含与授权类型不同的应用。({})" msgstr "应用列表中包含与授权类型不同的应用。({})"
#: perms/serializers/asset/permission.py:58 users/serializers/user.py:80 #: perms/serializers/asset/permission.py:58 users/serializers/user.py:65
msgid "Is expired" msgid "Is expired"
msgstr "是否过期" msgstr "是否过期"
#: perms/serializers/asset/permission.py:59 users/serializers/user.py:79 #: perms/serializers/asset/permission.py:59 users/serializers/user.py:64
msgid "Is valid" msgid "Is valid"
msgstr "账户是否有效" msgstr "账户是否有效"
@ -1932,14 +1936,6 @@ msgstr "用户组数量"
msgid "System users amount" msgid "System users amount"
msgstr "系统用户数量" msgstr "系统用户数量"
#: perms/utils/asset/user_permission.py:30
msgid "Favorite"
msgstr "收藏夹"
#: perms/utils/asset/user_permission.py:522
msgid "Please wait while your data is being initialized"
msgstr "数据正在初始化,请稍等"
#: settings/api/common.py:24 #: settings/api/common.py:24
msgid "Test mail sent to {}, please check" msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查" msgstr "邮件已经发送{}, 请检查"
@ -3054,7 +3050,7 @@ msgstr "索引"
msgid "Doc type" msgid "Doc type"
msgstr "文档类型" msgstr "文档类型"
#: terminal/serializers/terminal.py:44 terminal/serializers/terminal.py:52 #: terminal/serializers/terminal.py:47 terminal/serializers/terminal.py:55
msgid "Not found" msgid "Not found"
msgstr "没有发现" msgstr "没有发现"
@ -3530,8 +3526,8 @@ msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms/profile.py:137 users/forms/user.py:90 #: users/forms/profile.py:137 users/forms/user.py:90
#: users/serializers/user.py:192 users/serializers/user.py:277 #: users/serializers/profile.py:74 users/serializers/profile.py:147
#: users/serializers/user.py:335 #: users/serializers/profile.py:160
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法" msgstr "SSH密钥不合法"
@ -3555,15 +3551,15 @@ msgstr "添加到用户组"
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/forms/user.py:124 users/serializers/user.py:35 #: users/forms/user.py:124 users/serializers/user.py:20
msgid "Reset link will be generated and sent to the user" msgid "Reset link will be generated and sent to the user"
msgstr "生成重置密码链接,通过邮件发送给用户" msgstr "生成重置密码链接,通过邮件发送给用户"
#: users/forms/user.py:125 users/serializers/user.py:36 #: users/forms/user.py:125 users/serializers/user.py:21
msgid "Set password" msgid "Set password"
msgstr "设置密码" msgstr "设置密码"
#: users/forms/user.py:132 users/serializers/user.py:43 #: users/forms/user.py:132 users/serializers/user.py:28
#: xpack/plugins/change_auth_plan/models.py:61 #: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/serializers.py:30 #: xpack/plugins/change_auth_plan/serializers.py:30
msgid "Password strategy" msgid "Password strategy"
@ -3605,75 +3601,75 @@ msgstr "管理员"
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/serializers/user.py:45 #: users/serializers/profile.py:32
msgid "MFA level for display"
msgstr "多因子认证等级(显示名称)"
#: users/serializers/user.py:46
msgid "Login blocked"
msgstr "登录被阻塞"
#: users/serializers/user.py:47
msgid "Can update"
msgstr "是否可更新"
#: users/serializers/user.py:48
msgid "Can delete"
msgstr "是否可删除"
#: users/serializers/user.py:49 users/serializers/user.py:85
msgid "Organization role name"
msgstr "组织角色名称"
#: users/serializers/user.py:78 users/serializers/user.py:248
msgid "Is first login"
msgstr "首次登录"
#: users/serializers/user.py:81
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:83
msgid "Groups name"
msgstr "用户组名"
#: users/serializers/user.py:84
msgid "Source name"
msgstr "用户来源名"
#: users/serializers/user.py:86
msgid "Super role name"
msgstr "超级角色名称"
#: users/serializers/user.py:87
msgid "Total role name"
msgstr "汇总角色名称"
#: users/serializers/user.py:88
msgid "MFA enabled"
msgstr "是否开启多因子认证"
#: users/serializers/user.py:89
msgid "MFA force enabled"
msgstr "强制启用多因子认证"
#: users/serializers/user.py:112
msgid "Role limit to {}"
msgstr "角色只能为 {}"
#: users/serializers/user.py:124 users/serializers/user.py:301
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
#: users/serializers/user.py:293
msgid "The old password is incorrect" msgid "The old password is incorrect"
msgstr "旧密码错误" msgstr "旧密码错误"
#: users/serializers/user.py:307 #: users/serializers/profile.py:40 users/serializers/user.py:109
msgid "Password does not match security rules"
msgstr "密码不满足安全规则"
#: users/serializers/profile.py:46
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "两次密码不一致" msgstr "两次密码不一致"
#: users/serializers_v2/user.py:36 #: users/serializers/profile.py:118 users/serializers/user.py:63
msgid "Is first login"
msgstr "首次登录"
#: users/serializers/user.py:30
msgid "MFA level for display"
msgstr "多因子认证等级(显示名称)"
#: users/serializers/user.py:31
msgid "Login blocked"
msgstr "登录被阻塞"
#: users/serializers/user.py:32
msgid "Can update"
msgstr "是否可更新"
#: users/serializers/user.py:33
msgid "Can delete"
msgstr "是否可删除"
#: users/serializers/user.py:34 users/serializers/user.py:70
msgid "Organization role name"
msgstr "组织角色名称"
#: users/serializers/user.py:66
msgid "Avatar url"
msgstr "头像路径"
#: users/serializers/user.py:68
msgid "Groups name"
msgstr "用户组名"
#: users/serializers/user.py:69
msgid "Source name"
msgstr "用户来源名"
#: users/serializers/user.py:71
msgid "Super role name"
msgstr "超级角色名称"
#: users/serializers/user.py:72
msgid "Total role name"
msgstr "汇总角色名称"
#: users/serializers/user.py:73
msgid "MFA enabled"
msgstr "是否开启多因子认证"
#: users/serializers/user.py:74
msgid "MFA force enabled"
msgstr "强制启用多因子认证"
#: users/serializers/user.py:97
msgid "Role limit to {}"
msgstr "角色只能为 {}"
#: users/serializers/user.py:210
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"
@ -4891,3 +4887,11 @@ msgstr "旗舰版"
#: xpack/plugins/license/models.py:77 #: xpack/plugins/license/models.py:77
msgid "Community edition" msgid "Community edition"
msgstr "社区版" msgstr "社区版"
#~ msgid ""
#~ "The task of self-checking is already running and cannot be started "
#~ "repeatedly"
#~ msgstr "自检程序已经在运行,不能重复启动"
#~ msgid "Please wait while your data is being initialized"
#~ msgstr "数据正在初始化,请稍等"

View File

@ -5,14 +5,17 @@ from django.utils.translation import ugettext as _
from rest_framework import status from rest_framework import status
from rest_framework.views import Response from rest_framework.views import Response
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from rest_framework.generics import RetrieveAPIView
from rest_framework.exceptions import PermissionDenied
from common.permissions import IsSuperUserOrAppUser from common.permissions import IsSuperUserOrAppUser, IsValidUser, UserCanUseCurrentOrg
from common.drf.api import JMSBulkRelationModelViewSet from common.drf.api import JMSBulkRelationModelViewSet
from .models import Organization, ROLE from .models import Organization, ROLE
from .serializers import ( from .serializers import (
OrgSerializer, OrgReadSerializer, OrgSerializer, OrgReadSerializer,
OrgRetrieveSerializer, OrgMemberSerializer, OrgRetrieveSerializer, OrgMemberSerializer,
OrgMemberAdminSerializer, OrgMemberUserSerializer OrgMemberAdminSerializer, OrgMemberUserSerializer,
CurrentOrgSerializer
) )
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label from assets.models import Asset, Domain, AdminUser, SystemUser, Label
@ -129,3 +132,11 @@ class OrgMemberUserRelationBulkViewSet(JMSBulkRelationModelViewSet):
objs = list(queryset.all().prefetch_related('user', 'org')) objs = list(queryset.all().prefetch_related('user', 'org'))
queryset.delete() queryset.delete()
self.send_m2m_changed_signal(objs, action='post_remove') self.send_m2m_changed_signal(objs, action='post_remove')
class CurrentOrgDetailApi(RetrieveAPIView):
serializer_class = CurrentOrgSerializer
permission_classes = (IsValidUser, UserCanUseCurrentOrg)
def get_object(self):
return current_org

View File

@ -33,12 +33,12 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
return self.org return self.org
def compute_users_amount(self): def compute_users_amount(self):
if self.org.is_real(): if self.org.is_root():
users_amount = User.objects.all().count()
else:
users_amount = OrganizationMember.objects.values( users_amount = OrganizationMember.objects.values(
'user_id' 'user_id'
).filter(org_id=self.org.id).distinct().count() ).filter(org_id=self.org.id).distinct().count()
else:
users_amount = User.objects.all().distinct().count()
return users_amount return users_amount
def compute_assets_amount(self): def compute_assets_amount(self):

View File

@ -0,0 +1,56 @@
# Generated by Django 3.1 on 2021-02-19 04:41
import time
from django.db import migrations
default_id = '00000000-0000-0000-0000-000000000001'
def add_default_org(apps, schema_editor):
org_cls = apps.get_model('orgs', 'Organization')
defaults = {'name': 'DEFAULT', 'id': default_id}
org_cls.objects.get_or_create(defaults=defaults, id=default_id)
def migrate_default_org_id(apps, schema_editor):
org_app_models = [
('applications', ['Application']),
('assets', [
'AdminUser', 'Asset', 'AuthBook', 'CommandFilter',
'CommandFilterRule', 'Domain', 'Gateway', 'GatheredUser',
'Label', 'Node', 'SystemUser'
]),
('audits', ['FTPLog', 'OperateLog']),
('ops', ['AdHoc', 'AdHocExecution', 'CommandExecution', 'Task']),
('perms', ['ApplicationPermission', 'AssetPermission', 'UserAssetGrantedTreeNodeRelation']),
('terminal', ['Session', 'Command']),
('tickets', ['Ticket']),
('users', ['UserGroup']),
('xpack', [
'Account', 'SyncInstanceDetail', 'SyncInstanceTask', 'SyncInstanceTaskExecution',
'ChangeAuthPlan', 'ChangeAuthPlanExecution', 'ChangeAuthPlanTask',
'GatherUserTask', 'GatherUserTaskExecution',
]),
]
print("")
for app, models_name in org_app_models:
for model_name in models_name:
t_start = time.time()
print("Migrate model org id: {}".format(model_name), end='')
model_cls = apps.get_model(app, model_name)
model_cls.objects.filter(org_id='').update(org_id=default_id)
interval = round((time.time() - t_start) * 1000, 2)
print(" done, use {} ms".format(interval))
class Migration(migrations.Migration):
dependencies = [
('orgs', '0009_auto_20201023_1628'),
]
operations = [
migrations.RunPython(add_default_org),
migrations.RunPython(migrate_default_org_id)
]

View File

@ -23,14 +23,11 @@ class OrgManager(models.Manager):
def all_group_by_org(self): def all_group_by_org(self):
from ..models import Organization from ..models import Organization
orgs = list(Organization.objects.all()) orgs = list(Organization.objects.all())
orgs.append(Organization.default())
querysets = {} querysets = {}
for org in orgs: for org in orgs:
if org.is_real(): org_id = org.id
org_id = org.id queryset = super(OrgManager, self).get_queryset().filter(org_id=org_id)
else: querysets[org] = queryset
org_id = ''
querysets[org] = super(OrgManager, self).get_queryset().filter(org_id=org_id)
return querysets return querysets
def get_queryset(self): def get_queryset(self):
@ -53,12 +50,11 @@ class OrgModelMixin(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
org = get_current_org() org = get_current_org()
if org is None: if org.is_root():
return super().save(*args, **kwargs) if not self.org_id:
if org.is_real() or org.is_system(): raise ValidationError('Please save in a organization')
else:
self.org_id = org.id self.org_id = org.id
elif org.is_default():
self.org_id = ''
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@property @property
@ -78,10 +74,7 @@ class OrgModelMixin(models.Model):
name = self.name name = self.name
elif hasattr(self, 'hostname'): elif hasattr(self, 'hostname'):
name = self.hostname name = self.hostname
if self.org.is_real(): return name + self.sep + self.org_name
return name + self.sep + self.org_name
else:
return name
def validate_unique(self, exclude=None): def validate_unique(self, exclude=None):
""" """
@ -89,7 +82,7 @@ class OrgModelMixin(models.Model):
failed. failed.
Form 提交时会使用这个检验 Form 提交时会使用这个检验
""" """
self.org_id = current_org.id if current_org.is_real() else '' self.org_id = current_org.id
if exclude and 'org_id' in exclude: if exclude and 'org_id' in exclude:
exclude.remove('org_id') exclude.remove('org_id')
unique_checks, date_checks = self._get_unique_checks(exclude=exclude) unique_checks, date_checks = self._get_unique_checks(exclude=exclude)

View File

@ -30,11 +30,9 @@ class Organization(models.Model):
orgs = None orgs = None
CACHE_PREFIX = 'JMS_ORG_{}' CACHE_PREFIX = 'JMS_ORG_{}'
ROOT_ID = '00000000-0000-0000-0000-000000000000' ROOT_ID = '00000000-0000-0000-0000-000000000000'
ROOT_NAME = 'ROOT' ROOT_NAME = _('GLOBAL')
DEFAULT_ID = 'DEFAULT' DEFAULT_ID = '00000000-0000-0000-0000-000000000001'
DEFAULT_NAME = 'DEFAULT' DEFAULT_NAME = 'DEFAULT'
SYSTEM_ID = '00000000-0000-0000-0000-000000000002'
SYSTEM_NAME = 'SYSTEM'
_user_admin_orgs = None _user_admin_orgs = None
class Meta: class Meta:
@ -69,8 +67,6 @@ class Organization(models.Model):
return cls.default() return cls.default()
elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]: elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]:
return cls.root() return cls.root()
elif id_or_name in [cls.SYSTEM_ID, cls.SYSTEM_NAME]:
return cls.system()
try: try:
if is_uuid(id_or_name): if is_uuid(id_or_name):
@ -87,7 +83,7 @@ class Organization(models.Model):
def get_org_members_by_role(self, role): def get_org_members_by_role(self, role):
from users.models import User from users.models import User
if self.is_real(): if not self.is_root():
return self.members.filter(m2m_org_members__role=role) return self.members.filter(m2m_org_members__role=role)
users = User.objects.filter(role=role) users = User.objects.filter(role=role)
return users return users
@ -105,20 +101,14 @@ class Organization(models.Model):
return self.get_org_members_by_role(ROLE.AUDITOR) return self.get_org_members_by_role(ROLE.AUDITOR)
def org_id(self): def org_id(self):
if self.is_real(): return self.id
return self.id
elif self.is_root():
return self.ROOT_ID
else:
return ''
def get_members(self, exclude=()): def get_members(self, exclude=()):
from users.models import User from users.models import User
if self.is_real(): if self.is_root():
members = self.members.exclude(m2m_org_members__role__in=exclude)
else:
members = User.objects.exclude(role__in=exclude) members = User.objects.exclude(role__in=exclude)
else:
members = self.members.exclude(m2m_org_members__role__in=exclude)
return members.exclude(role=User.ROLE.APP).distinct() return members.exclude(role=User.ROLE.APP).distinct()
def can_admin_by(self, user): def can_admin_by(self, user):
@ -129,20 +119,19 @@ class Organization(models.Model):
return False return False
def can_audit_by(self, user): def can_audit_by(self, user):
if user.is_super_auditor: if user.is_superuser or user.is_super_auditor:
return True return True
if self.auditors.filter(id=user.id).exists(): if self.auditors.filter(id=user.id).exists():
return True return True
return False return False
def can_user_by(self, user): def can_use_by(self, user):
if user.is_superuser or user.is_super_auditor:
return True
if self.users.filter(id=user.id).exists(): if self.users.filter(id=user.id).exists():
return True return True
return False return False
def is_real(self):
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID, self.SYSTEM_ID)
@classmethod @classmethod
def get_user_orgs_by_role(cls, user, role): def get_user_orgs_by_role(cls, user, role):
if not isinstance(role, (tuple, list)): if not isinstance(role, (tuple, list)):
@ -165,7 +154,7 @@ class Organization(models.Model):
if user.is_anonymous: if user.is_anonymous:
return cls.objects.none() return cls.objects.none()
if user.is_superuser: if user.is_superuser:
return [*cls.objects.all(), cls.default()] return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, ROLE.ADMIN) return cls.get_user_orgs_by_role(user, ROLE.ADMIN)
@classmethod @classmethod
@ -182,7 +171,7 @@ class Organization(models.Model):
if user.is_anonymous: if user.is_anonymous:
return cls.objects.none() return cls.objects.none()
if user.is_super_auditor: if user.is_super_auditor:
return [*cls.objects.all(), cls.default()] return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, ROLE.AUDITOR) return cls.get_user_orgs_by_role(user, ROLE.AUDITOR)
@classmethod @classmethod
@ -190,29 +179,24 @@ class Organization(models.Model):
if user.is_anonymous: if user.is_anonymous:
return cls.objects.none() return cls.objects.none()
if user.is_superuser or user.is_super_auditor: if user.is_superuser or user.is_super_auditor:
return [*cls.objects.all(), cls.default()] return [cls.root(), *cls.objects.all()]
return cls.get_user_orgs_by_role(user, (ROLE.AUDITOR, ROLE.ADMIN)) return cls.get_user_orgs_by_role(user, (ROLE.AUDITOR, ROLE.ADMIN))
@classmethod @classmethod
def default(cls): def default(cls):
return cls(id=cls.DEFAULT_ID, name=cls.DEFAULT_NAME) defaults = dict(name=cls.DEFAULT_NAME, id=cls.DEFAULT_ID)
obj, created = cls.objects.get_or_create(defaults=defaults, id=cls.DEFAULT_ID)
return obj
@classmethod @classmethod
def root(cls): def root(cls):
return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME) return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME)
@classmethod
def system(cls):
return cls(id=cls.SYSTEM_ID, name=cls.SYSTEM_NAME)
def is_root(self): def is_root(self):
return self.id is self.ROOT_ID return self.id == self.ROOT_ID
def is_default(self): def is_default(self):
return self.id is self.DEFAULT_ID return str(self.id) == self.DEFAULT_ID
def is_system(self):
return self.id is self.SYSTEM_ID
def change_to(self): def change_to(self):
from .utils import set_current_org from .utils import set_current_org
@ -282,7 +266,7 @@ class UserRoleMapper(dict):
self[ROLE.AUDITOR] = self.auditors self[ROLE.AUDITOR] = self.auditors
class OrgMemeberManager(models.Manager): class OrgMemberManager(models.Manager):
def remove_users(self, org, users): def remove_users(self, org, users):
from users.models import User from users.models import User
@ -337,8 +321,11 @@ class OrgMemeberManager(models.Manager):
_user = _user.id _user = _user.id
oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role)) oms_add.append(self.model(org_id=org.id, user_id=_user, role=_role))
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, pk_set = _users2pks_if_need(users, admins, auditors)
model=User, pk_set=_users2pks_if_need(users, admins, auditors), using=self.db) send = partial(
signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=pk_set, using=self.db
)
send(action='pre_add') send(action='pre_add')
self.bulk_create(oms_add, ignore_conflicts=True) self.bulk_create(oms_add, ignore_conflicts=True)
@ -429,7 +416,7 @@ class OrganizationMember(models.Model):
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated")) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
objects = OrgMemeberManager() objects = OrgMemberManager()
class Meta: class Meta:
unique_together = [('org', 'user', 'role')] unique_together = [('org', 'user', 'role')]

View File

@ -38,7 +38,8 @@ class OrgSerializer(ModelSerializer):
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields_mini = ['id', 'name'] fields_mini = ['id', 'name']
fields_small = fields_mini + [ fields_small = fields_mini + [
'created_by', 'date_created', 'comment', 'resource_statistics' 'is_default', 'is_root', 'comment',
'created_by', 'date_created', 'resource_statistics'
] ]
fields_m2m = ['users', 'admins', 'auditors'] fields_m2m = ['users', 'admins', 'auditors']
@ -127,3 +128,9 @@ class OrgRetrieveSerializer(OrgReadSerializer):
class Meta(OrgReadSerializer.Meta): class Meta(OrgReadSerializer.Meta):
pass pass
class CurrentOrgSerializer(ModelSerializer):
class Meta:
model = Organization
fields = ['id', 'name', 'is_default', 'is_root', 'comment']

View File

@ -1,28 +1,26 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.urls import re_path from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter from rest_framework_bulk.routes import BulkRouter
from common import api as capi
from .. import api from .. import api
app_name = 'orgs' app_name = 'orgs'
router = DefaultRouter() router = BulkRouter()
bulk_router = BulkRouter()
router.register(r'orgs', api.OrgViewSet, 'org') router.register(r'orgs', api.OrgViewSet, 'org')
bulk_router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation') router.register(r'org-member-relation', api.OrgMemberRelationBulkViewSet, 'org-member-relation')
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins', router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/admins',
api.OrgMemberAdminRelationBulkViewSet, 'membership-admins') api.OrgMemberAdminRelationBulkViewSet, 'membership-admins')
router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users', router.register(r'orgs/(?P<org_id>[0-9a-zA-Z\-]{36})/membership/users',
api.OrgMemberUserRelationBulkViewSet, 'membership-users'), api.OrgMemberUserRelationBulkViewSet, 'membership-users'),
old_version_urlpatterns = [
re_path('(?P<resource>org)/.*', capi.redirect_plural_name_api) urlpatterns = [
path('orgs/current/', api.CurrentOrgDetailApi.as_view(), name='current-org-detail'),
] ]
urlpatterns = router.urls + bulk_router.urls + old_version_urlpatterns urlpatterns += router.urls

View File

@ -68,12 +68,8 @@ def get_current_org_id():
def construct_org_mapper(): def construct_org_mapper():
orgs = Organization.objects.all() orgs = Organization.objects.all()
org_mapper = {str(org.id): org for org in orgs} org_mapper = {str(org.id): org for org in orgs}
default_org = Organization.default()
org_mapper.update({ org_mapper.update({
'': default_org,
Organization.DEFAULT_ID: default_org,
Organization.ROOT_ID: Organization.root(), Organization.ROOT_ID: Organization.root(),
Organization.SYSTEM_ID: Organization.system()
}) })
return org_mapper return org_mapper
@ -137,11 +133,9 @@ def get_org_filters():
_current_org = get_current_org() _current_org = get_current_org()
if _current_org is None: if _current_org is None:
return kwargs return kwargs
if _current_org.is_root():
if _current_org.is_real(): return kwargs
kwargs['org_id'] = _current_org.id kwargs['org_id'] = _current_org.id
elif _current_org.is_default():
kwargs["org_id"] = ''
return kwargs return kwargs

View File

@ -168,7 +168,7 @@ class Session(OrgModelMixin):
from common.utils.random import random_datetime, random_ip from common.utils.random import random_datetime, random_ip
org = get_current_org() org = get_current_org()
if not org or not org.is_real(): if not org or org.is_root():
Organization.default().change_to() Organization.default().change_to()
i = 0 i = 0
users = User.objects.all()[:100] users = User.objects.all()[:100]

View File

@ -8,7 +8,7 @@ from orgs.utils import current_org
class UserQuerysetMixin: class UserQuerysetMixin:
def get_queryset(self): def get_queryset(self):
if self.request.query_params.get('all') or not current_org.is_real(): if self.request.query_params.get('all') or current_org.is_root():
queryset = User.objects.exclude(role=User.ROLE.APP) queryset = User.objects.exclude(role=User.ROLE.APP)
else: else:
queryset = utils.get_current_org_members() queryset = utils.get_current_org_members()

View File

@ -2,6 +2,7 @@
import uuid import uuid
from rest_framework import generics from rest_framework import generics
from common.permissions import IsOrgAdmin
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from django.conf import settings from django.conf import settings
@ -23,7 +24,7 @@ __all__ = [
class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView): class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsOrgAdmin,)
def perform_update(self, serializer): def perform_update(self, serializer):
# Note: we are not updating the user object here. # Note: we are not updating the user object here.
@ -37,7 +38,7 @@ class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
class UserResetPKApi(UserQuerysetMixin, generics.UpdateAPIView): class UserResetPKApi(UserQuerysetMixin, generics.UpdateAPIView):
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsOrgAdmin,)
def perform_update(self, serializer): def perform_update(self, serializer):
from ..utils import send_reset_ssh_key_mail from ..utils import send_reset_ssh_key_mail

View File

@ -48,7 +48,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
queryset = super().get_queryset().prefetch_related( queryset = super().get_queryset().prefetch_related(
'groups' 'groups'
) )
if current_org.is_real(): if not current_org.is_root():
# 为在列表中计算用户在真实组织里的角色 # 为在列表中计算用户在真实组织里的角色
queryset = queryset.prefetch_related( queryset = queryset.prefetch_related(
Prefetch( Prefetch(
@ -67,7 +67,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
@staticmethod @staticmethod
def set_users_to_org(users, org_roles, update=False): def set_users_to_org(users, org_roles, update=False):
# 只有真实存在的组织才真正关联用户 # 只有真实存在的组织才真正关联用户
if not current_org or not current_org.is_real(): if not current_org or current_org.is_root():
return return
for user, roles in zip(users, org_roles): for user, roles in zip(users, org_roles):
if update and roles is None: if update and roles is None:
@ -94,7 +94,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
return super().get_permissions() return super().get_permissions()
def perform_destroy(self, instance): def perform_destroy(self, instance):
if current_org.is_real(): if not current_org.is_root():
instance.remove() instance.remove()
else: else:
return super().perform_destroy(instance) return super().perform_destroy(instance)
@ -150,7 +150,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
data = request.data data = request.data
if not isinstance(data, list): if not isinstance(data, list):
data = [request.data] data = [request.data]
if not current_org or not current_org.is_real(): if not current_org or current_org.is_root():
error = {"error": "Not a valid org"} error = {"error": "Not a valid org"}
return Response(error, status=400) return Response(error, status=400)

View File

@ -169,20 +169,21 @@ class RoleMixin:
def org_roles(self): def org_roles(self):
from orgs.models import ROLE as ORG_ROLE from orgs.models import ROLE as ORG_ROLE
if not current_org.is_real(): if current_org.is_root():
# 不是真实的组织,取 User 本身的角色 # root 组织, 取 User 本身的角色
if self.is_superuser: if self.is_superuser:
return [ORG_ROLE.ADMIN] roles = [ORG_ROLE.ADMIN]
elif self.is_super_auditor:
roles = [ORG_ROLE.AUDITOR]
else: else:
return [ORG_ROLE.USER] roles = [ORG_ROLE.USER]
else:
# 是真实组织,取 OrganizationMember 中的角色 # 是真实组织, 取 OrganizationMember 中的角色
roles = [ roles = [
org_member.role org_member.role for org_member in self.m2m_org_members.all()
for org_member in self.m2m_org_members.all() if org_member.org_id == current_org.id
if org_member.org_id == current_org.id ]
] roles.sort()
roles.sort()
return roles return roles
@lazyproperty @lazyproperty
@ -202,7 +203,7 @@ class RoleMixin:
def current_org_roles(self): def current_org_roles(self):
from orgs.models import OrganizationMember, ROLE as ORG_ROLE from orgs.models import OrganizationMember, ROLE as ORG_ROLE
if not current_org.is_real(): if current_org.is_root():
if self.is_superuser: if self.is_superuser:
return [ORG_ROLE.ADMIN] return [ORG_ROLE.ADMIN]
else: else:
@ -297,7 +298,7 @@ class RoleMixin:
@lazyproperty @lazyproperty
def can_user_current_org(self): def can_user_current_org(self):
return current_org.can_user_by(self) return current_org.can_use_by(self)
@lazyproperty @lazyproperty
def can_admin_or_audit_current_org(self): def can_admin_or_audit_current_org(self):
@ -325,7 +326,7 @@ class RoleMixin:
return app, access_key return app, access_key
def remove(self): def remove(self):
if not current_org.is_real(): if current_org.is_root():
return return
org = Organization.get_instance(current_org.id) org = Organization.get_instance(current_org.id)
OrganizationMember.objects.remove_users(org, [self]) OrganizationMember.objects.remove_users(org, [self])