Merge pull request #4270 from jumpserver/request-asset-ticket-dev

feat(ticket): 添加申请资产工单
pull/4282/head
xinwen 2020-07-08 15:42:04 +08:00 committed by GitHub
commit f430c9e435
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 568 additions and 148 deletions

View File

@ -14,7 +14,7 @@ from .. import serializers
from ..tasks import (
update_asset_hardware_info_manual, test_asset_connectivity_manual
)
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend
logger = get_logger(__file__)
@ -32,7 +32,7 @@ class AssetViewSet(OrgBulkModelViewSet):
model = Asset
filter_fields = (
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
"is_active"
"is_active", 'ip'
)
search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
@ -41,7 +41,7 @@ class AssetViewSet(OrgBulkModelViewSet):
'display': serializers.AssetDisplaySerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
def set_assets_node(self, assets):
if not isinstance(assets, list):

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
import coreapi
from rest_framework.compat import coreapi, coreschema
from rest_framework import filters
from django.db.models import Q
@ -117,3 +117,23 @@ class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
def perform_query(pattern, queryset):
return queryset.filter(asset__nodes__key__regex=pattern).distinct()
class IpInFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
ips = request.query_params.get('ips')
if not ips:
return queryset
ip_list = [i.strip() for i in ips.split(',')]
queryset = queryset.filter(ip__in=ip_list)
return queryset
def get_schema_fields(self, view):
return [
coreapi.Field(
name='ips', location='query', required=False, type='string',
schema=coreschema.String(
title='ips',
description='ip in filter'
)
)
]

View File

View File

@ -0,0 +1,28 @@
from django.db.models import Aggregate
class GroupConcat(Aggregate):
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s %(expressions)s %(order_by)s %(separator))'
allow_distinct = False
def __init__(self, expression, distinct=False, order_by=None, separator=',', **extra):
order_by_clause = ''
if order_by is not None:
order = 'ASC'
prefix, body = order_by[1], order_by[1:]
if prefix == '-':
order = 'DESC'
elif prefix == '+':
pass
else:
body = order_by
order_by_clause = f'ORDER BY {body} {order}'
super().__init__(
expression,
distinct='DISTINCT' if distinct else '',
order_by=order_by_clause,
separator=f'SEPARATOR {separator}',
**extra
)

11
apps/common/drf/api.py Normal file
View File

@ -0,0 +1,11 @@
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from ..mixins.api import SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin
class JmsGenericViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, GenericViewSet):
pass
class JMSModelViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, ModelViewSet):
pass

View File

@ -0,0 +1,5 @@
from rest_framework.serializers import Serializer
class EmptySerializer(Serializer):
pass

View File

@ -1,3 +1,7 @@
# -*- coding: utf-8 -*-
#
from rest_framework.exceptions import APIException
class JMSException(APIException):
pass

View File

@ -4,6 +4,7 @@ import time
from hashlib import md5
from threading import Thread
from collections import defaultdict
from itertools import chain
from django.db.models.signals import m2m_changed
from django.core.cache import cache
@ -15,8 +16,8 @@ from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
from ..utils import lazyproperty
__all__ = [
"JSONResponseMixin", "CommonApiMixin",
'AsyncApiMixin', 'RelationMixin'
'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin',
'SerializerMixin2', 'QuerySetMixin', 'ExtraFilterFieldsMixin'
]
@ -54,9 +55,10 @@ class ExtraFilterFieldsMixin:
def get_filter_backends(self):
if self.filter_backends != self.__class__.filter_backends:
return self.filter_backends
backends = list(self.filter_backends) + \
list(self.default_added_filters) + \
list(self.extra_filter_backends)
backends = list(chain(
self.filter_backends,
self.default_added_filters,
self.extra_filter_backends))
return backends
def filter_queryset(self, queryset):
@ -233,3 +235,32 @@ class RelationMixin:
def perform_create(self, serializer):
instance = serializer.save()
self.send_post_add_signal(instance)
class SerializerMixin2:
serializer_classes = {}
def get_serializer_class(self):
if self.serializer_classes:
serializer_class = self.serializer_classes.get(
self.action, self.serializer_classes.get('default')
)
if isinstance(serializer_class, dict):
serializer_class = serializer_class.get(
self.request.method.lower, serializer_class.get('default')
)
assert serializer_class, '`serializer_classes` config error'
return serializer_class
return super().get_serializer_class()
class QuerySetMixin:
def get_queryset(self):
queryset = super().get_queryset()
serializer_class = self.get_serializer_class()
if serializer_class and hasattr(serializer_class, 'setup_eager_loading'):
queryset = serializer_class.setup_eager_loading(queryset)
return queryset

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-07-07 14:11+0800\n"
"POT-Creation-Date: 2020-07-08 15:05+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -47,7 +47,7 @@ msgid "Name"
msgstr "名称"
#: applications/models/database_app.py:22 assets/models/cmd_filter.py:51
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:43
#: terminal/models.py:376 terminal/models.py:413 tickets/models/ticket.py:45
#: users/templates/users/user_granted_database_app.html:35
msgid "Type"
msgstr "类型"
@ -84,8 +84,8 @@ msgstr "数据库"
#: users/templates/users/user_group_detail.html:62
#: users/templates/users/user_group_list.html:16
#: users/templates/users/user_profile.html:138
#: xpack/plugins/change_auth_plan/models.py:76 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/models.py:140 xpack/plugins/gathered_user/models.py:26
#: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:53
#: xpack/plugins/cloud/models.py:139 xpack/plugins/gathered_user/models.py:26
msgid "Comment"
msgstr "备注"
@ -110,8 +110,8 @@ msgstr "数据库应用"
#: users/templates/users/user_asset_permission.html:40
#: users/templates/users/user_asset_permission.html:70
#: users/templates/users/user_granted_remote_app.html:36
#: xpack/plugins/change_auth_plan/models.py:282
#: xpack/plugins/cloud/models.py:266
#: xpack/plugins/change_auth_plan/models.py:283
#: xpack/plugins/cloud/models.py:269
msgid "Asset"
msgstr "资产"
@ -134,8 +134,8 @@ msgstr "参数"
#: assets/models/group.py:21 common/mixins/models.py:49 orgs/models.py:16
#: perms/models/base.py:54 users/models/user.py:508
#: users/serializers/group.py:35 users/templates/users/user_detail.html:97
#: xpack/plugins/change_auth_plan/models.py:80 xpack/plugins/cloud/models.py:56
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
#: xpack/plugins/cloud/models.py:145 xpack/plugins/gathered_user/models.py:30
msgid "Created by"
msgstr "创建者"
@ -148,7 +148,7 @@ msgstr "创建者"
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27
#: orgs/models.py:17 perms/models/base.py:55 users/models/group.py:18
#: users/templates/users/user_group_detail.html:58
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:148
msgid "Date created"
msgstr "创建日期"
@ -189,7 +189,7 @@ msgstr "基础"
msgid "Charset"
msgstr "编码"
#: assets/models/asset.py:148 tickets/models/ticket.py:38
#: assets/models/asset.py:148 tickets/models/ticket.py:40
msgid "Meta"
msgstr "元数据"
@ -211,6 +211,7 @@ msgstr "IP"
#: assets/models/asset.py:187 assets/serializers/asset_user.py:45
#: assets/serializers/gathered_user.py:20 settings/serializers/settings.py:51
#: tickets/serializers/request_asset_perm.py:13
#: users/templates/users/_granted_assets.html:25
#: users/templates/users/user_asset_permission.html:157
msgid "Hostname"
@ -233,7 +234,7 @@ msgstr "网域"
#: assets/models/asset.py:195 assets/models/user.py:109
#: perms/models/asset_permission.py:81
#: xpack/plugins/change_auth_plan/models.py:55
#: xpack/plugins/change_auth_plan/models.py:56
#: xpack/plugins/gathered_user/models.py:24
msgid "Nodes"
msgstr "节点"
@ -246,7 +247,7 @@ msgstr "激活"
#: assets/models/asset.py:199 assets/models/cluster.py:19
#: assets/models/user.py:65 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:133 xpack/plugins/cloud/serializers.py:82
#: xpack/plugins/cloud/models.py:133
msgid "Admin user"
msgstr "管理用户"
@ -343,8 +344,8 @@ msgstr ""
#: users/templates/users/user_detail.html:53
#: users/templates/users/user_list.html:15
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/models.py:46
#: xpack/plugins/change_auth_plan/models.py:278
#: xpack/plugins/change_auth_plan/models.py:47
#: xpack/plugins/change_auth_plan/models.py:279
msgid "Username"
msgstr "用户名"
@ -359,21 +360,21 @@ msgstr "用户名"
#: users/templates/users/user_profile_update.html:41
#: users/templates/users/user_pubkey_update.html:41
#: users/templates/users/user_update.html:20
#: xpack/plugins/change_auth_plan/models.py:67
#: xpack/plugins/change_auth_plan/models.py:190
#: xpack/plugins/change_auth_plan/models.py:285
#: xpack/plugins/change_auth_plan/models.py:68
#: xpack/plugins/change_auth_plan/models.py:191
#: xpack/plugins/change_auth_plan/models.py:286
msgid "Password"
msgstr "密码"
#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:71
#: xpack/plugins/change_auth_plan/models.py:197
#: xpack/plugins/change_auth_plan/models.py:292
#: assets/models/base.py:235 xpack/plugins/change_auth_plan/models.py:72
#: xpack/plugins/change_auth_plan/models.py:198
#: xpack/plugins/change_auth_plan/models.py:293
msgid "SSH private key"
msgstr "SSH密钥"
#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:74
#: xpack/plugins/change_auth_plan/models.py:193
#: xpack/plugins/change_auth_plan/models.py:288
#: assets/models/base.py:236 xpack/plugins/change_auth_plan/models.py:75
#: xpack/plugins/change_auth_plan/models.py:194
#: xpack/plugins/change_auth_plan/models.py:289
msgid "SSH public key"
msgstr "SSH公钥"
@ -483,7 +484,9 @@ msgstr "每行一个命令"
#: assets/models/cmd_filter.py:55 audits/models.py:57
#: authentication/templates/authentication/_access_key_modal.html:34
#: perms/forms/asset_permission.py:20 tickets/serializers/ticket.py:26
#: perms/forms/asset_permission.py:20
#: tickets/serializers/request_asset_perm.py:51
#: tickets/serializers/ticket.py:26
#: users/templates/users/_granted_assets.html:29
#: users/templates/users/user_asset_permission.html:44
#: users/templates/users/user_asset_permission.html:79
@ -536,7 +539,8 @@ msgstr "默认资产组"
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
#: templates/index.html:78 terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models.py:185
#: tickets/models/ticket.py:33 tickets/models/ticket.py:128
#: tickets/models/ticket.py:35 tickets/models/ticket.py:130
#: tickets/serializers/request_asset_perm.py:52
#: tickets/serializers/ticket.py:27 users/forms/group.py:15
#: users/models/user.py:160 users/models/user.py:176 users/models/user.py:615
#: users/serializers/group.py:20
@ -586,7 +590,7 @@ msgstr "键"
#: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158
#: xpack/plugins/cloud/models.py:129 xpack/plugins/cloud/serializers.py:83
#: xpack/plugins/cloud/models.py:129
msgid "Node"
msgstr "节点"
@ -603,7 +607,7 @@ msgid "Username same with user"
msgstr "用户名与用户相同"
#: assets/models/user.py:110 templates/_nav.html:39
#: xpack/plugins/change_auth_plan/models.py:51
#: xpack/plugins/change_auth_plan/models.py:52
msgid "Assets"
msgstr "资产管理"
@ -799,7 +803,7 @@ msgid "Gather assets users"
msgstr "收集资产上的用户"
#: assets/tasks/push_system_user.py:148
#: assets/tasks/system_user_connectivity.py:86
#: assets/tasks/system_user_connectivity.py:89
msgid "System user is dynamic: {}"
msgstr "系统用户是动态的: {}"
@ -808,7 +812,7 @@ msgid "Start push system user for platform: [{}]"
msgstr "推送系统用户到平台: [{}]"
#: assets/tasks/push_system_user.py:180
#: assets/tasks/system_user_connectivity.py:78
#: assets/tasks/system_user_connectivity.py:81
msgid "Hosts count: {}"
msgstr "主机数量: {}"
@ -820,19 +824,19 @@ msgstr "推送系统用户到入资产: {}"
msgid "Push system users to asset: {}({}) => {}"
msgstr "推送系统用户到入资产: {}({}) => {}"
#: assets/tasks/system_user_connectivity.py:77
#: assets/tasks/system_user_connectivity.py:80
msgid "Start test system user connectivity for platform: [{}]"
msgstr "开始测试系统用户在该系统平台的可连接性: [{}]"
#: assets/tasks/system_user_connectivity.py:97
#: assets/tasks/system_user_connectivity.py:100
msgid "Test system user connectivity: {}"
msgstr "测试系统用户可连接性: {}"
#: assets/tasks/system_user_connectivity.py:105
#: assets/tasks/system_user_connectivity.py:108
msgid "Test system user connectivity: {} => {}"
msgstr "测试系统用户可连接性: {} => {}"
#: assets/tasks/system_user_connectivity.py:118
#: assets/tasks/system_user_connectivity.py:121
msgid "Test system user connectivity period: {}"
msgstr "定期测试系统用户可连接性: {}"
@ -914,8 +918,9 @@ msgid "Success"
msgstr "成功"
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
#: terminal/models.py:199 xpack/plugins/change_auth_plan/models.py:176
#: xpack/plugins/change_auth_plan/models.py:307
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:15
#: xpack/plugins/change_auth_plan/models.py:177
#: xpack/plugins/change_auth_plan/models.py:308
#: xpack/plugins/gathered_user/models.py:76
msgid "Date start"
msgstr "开始日期"
@ -970,7 +975,7 @@ msgstr "启用"
msgid "-"
msgstr ""
#: audits/models.py:96 xpack/plugins/cloud/models.py:201
#: audits/models.py:96 xpack/plugins/cloud/models.py:204
msgid "Failed"
msgstr "失败"
@ -999,13 +1004,14 @@ msgstr "Agent"
msgid "MFA"
msgstr "多因子认证"
#: audits/models.py:105 xpack/plugins/change_auth_plan/models.py:303
#: xpack/plugins/cloud/models.py:214
#: audits/models.py:105 xpack/plugins/change_auth_plan/models.py:304
#: xpack/plugins/cloud/models.py:217
msgid "Reason"
msgstr "原因"
#: audits/models.py:106 tickets/serializers/ticket.py:25
#: xpack/plugins/cloud/models.py:211 xpack/plugins/cloud/models.py:269
#: audits/models.py:106 tickets/serializers/request_asset_perm.py:50
#: tickets/serializers/ticket.py:25 xpack/plugins/cloud/models.py:214
#: xpack/plugins/cloud/models.py:272
msgid "Status"
msgstr "状态"
@ -1018,7 +1024,7 @@ msgid "Is success"
msgstr "是否成功"
#: audits/serializers.py:73 ops/models/command.py:24
#: xpack/plugins/cloud/models.py:209
#: xpack/plugins/cloud/models.py:212
msgid "Result"
msgstr "结果"
@ -1182,7 +1188,7 @@ msgstr "SSH密钥"
msgid "Reviewers"
msgstr "审批人"
#: authentication/models.py:53 tickets/models/ticket.py:25
#: authentication/models.py:53 tickets/models/ticket.py:26
#: users/templates/users/user_detail.html:250
msgid "Login confirm"
msgstr "登录复核"
@ -1238,7 +1244,7 @@ msgstr "删除成功"
#: authentication/templates/authentication/_access_key_modal.html:155
#: authentication/templates/authentication/_mfa_confirm_modal.html:53
#: templates/_modal.html:22 tickets/models/ticket.py:68
#: templates/_modal.html:22 tickets/models/ticket.py:70
msgid "Close"
msgstr "关闭"
@ -1556,8 +1562,8 @@ msgstr "开始时间"
msgid "End time"
msgstr "完成时间"
#: ops/models/adhoc.py:242 xpack/plugins/change_auth_plan/models.py:179
#: xpack/plugins/change_auth_plan/models.py:310
#: ops/models/adhoc.py:242 xpack/plugins/change_auth_plan/models.py:180
#: xpack/plugins/change_auth_plan/models.py:311
#: xpack/plugins/gathered_user/models.py:79
msgid "Time"
msgstr "时间"
@ -1693,8 +1699,8 @@ msgstr "动作"
msgid "Asset permission"
msgstr "资产授权"
#: perms/models/base.py:53 users/models/user.py:505
#: users/templates/users/user_detail.html:93
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:17
#: users/models/user.py:505 users/templates/users/user_detail.html:93
#: users/templates/users/user_profile.html:120
msgid "Date expired"
msgstr "失效日期"
@ -2426,7 +2432,40 @@ msgstr "结束日期"
msgid "Args"
msgstr "参数"
#: tickets/models/ticket.py:18 tickets/models/ticket.py:70
#: tickets/api/request_asset_perm.py:36
msgid "Ticket closed"
msgstr "工单已关闭"
#: tickets/api/request_asset_perm.py:39
#, python-format
msgid "Ticket has %s"
msgstr "工单已%s"
#: tickets/api/request_asset_perm.py:59
msgid "Confirm assets first"
msgstr "请先确认资产"
#: tickets/api/request_asset_perm.py:62
msgid "Confirmed assets changed"
msgstr "确认的资产变更了"
#: tickets/api/request_asset_perm.py:66
msgid "Confirm system-user first"
msgstr "请先确认系统用户"
#: tickets/api/request_asset_perm.py:70
msgid "Confirmed system-user changed"
msgstr "确认的系统用户变更了"
#: tickets/api/request_asset_perm.py:73 xpack/plugins/cloud/models.py:205
msgid "Succeed"
msgstr "成功"
#: tickets/api/request_asset_perm.py:81
msgid "{} request assets, approved by {}"
msgstr "{} 申请资产,通过人 {}"
#: tickets/models/ticket.py:18 tickets/models/ticket.py:72
msgid "Open"
msgstr "开启"
@ -2434,54 +2473,74 @@ msgstr "开启"
msgid "Closed"
msgstr "关闭"
#: tickets/models/ticket.py:24
#: tickets/models/ticket.py:25
msgid "General"
msgstr "一般"
#: tickets/models/ticket.py:30
#: tickets/models/ticket.py:27
msgid "Request asset permission"
msgstr "申请资产权限"
#: tickets/models/ticket.py:32
msgid "Approve"
msgstr "同意"
#: tickets/models/ticket.py:31
#: tickets/models/ticket.py:33
msgid "Reject"
msgstr "拒绝"
#: tickets/models/ticket.py:34 tickets/models/ticket.py:129
#: tickets/models/ticket.py:36 tickets/models/ticket.py:131
msgid "User display name"
msgstr "用户显示名称"
#: tickets/models/ticket.py:36
#: tickets/models/ticket.py:38
msgid "Title"
msgstr "标题"
#: tickets/models/ticket.py:37 tickets/models/ticket.py:130
#: tickets/models/ticket.py:39 tickets/models/ticket.py:132
msgid "Body"
msgstr "内容"
#: tickets/models/ticket.py:39
#: tickets/models/ticket.py:41
msgid "Assignee"
msgstr "处理人"
#: tickets/models/ticket.py:40
#: tickets/models/ticket.py:42
msgid "Assignee display name"
msgstr "处理人名称"
#: tickets/models/ticket.py:41
#: tickets/models/ticket.py:43
msgid "Assignees"
msgstr "待处理人"
#: tickets/models/ticket.py:42
#: tickets/models/ticket.py:44
msgid "Assignees display name"
msgstr "待处理人名称"
#: tickets/models/ticket.py:71
#: tickets/models/ticket.py:73
msgid "{} {} this ticket"
msgstr "{} {} 这个工单"
#: tickets/models/ticket.py:82
#: tickets/models/ticket.py:84
msgid "this ticket"
msgstr "这个工单"
#: tickets/serializers/request_asset_perm.py:11
msgid "IP group"
msgstr "IP组"
#: tickets/serializers/request_asset_perm.py:21
msgid "Confirmed assets"
msgstr "确认的资产"
#: tickets/serializers/request_asset_perm.py:25
msgid "Confirmed system user"
msgstr "确认的系统用户"
#: tickets/serializers/request_asset_perm.py:58
msgid "Must be organization admin or superuser"
msgstr "必须是组织管理员或者超级管理员"
#: tickets/utils.py:18
msgid "New ticket"
msgstr "新工单"
@ -2546,7 +2605,7 @@ msgstr ""
" </div>\n"
" "
#: users/api/user.py:117
#: users/api/user.py:119
msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
@ -2670,7 +2729,7 @@ msgid "Set password"
msgstr "设置密码"
#: users/forms/user.py:132 users/serializers/user.py:38
#: xpack/plugins/change_auth_plan/models.py:60
#: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/serializers.py:30
msgid "Password strategy"
msgstr "密码策略"
@ -2777,7 +2836,7 @@ msgstr "安全令牌验证"
#: users/templates/users/_base_otp.html:14 users/templates/users/_user.html:13
#: users/templates/users/user_profile_update.html:55
#: xpack/plugins/cloud/models.py:119 xpack/plugins/cloud/serializers.py:81
#: xpack/plugins/cloud/models.py:119
msgid "Account"
msgstr "账户"
@ -3546,65 +3605,65 @@ msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: xpack/plugins/change_auth_plan/meta.py:9
#: xpack/plugins/change_auth_plan/models.py:88
#: xpack/plugins/change_auth_plan/models.py:183
#: xpack/plugins/change_auth_plan/models.py:89
#: xpack/plugins/change_auth_plan/models.py:184
msgid "Change auth plan"
msgstr "改密计划"
#: xpack/plugins/change_auth_plan/models.py:40
#: xpack/plugins/change_auth_plan/models.py:41
msgid "Custom password"
msgstr "自定义密码"
#: xpack/plugins/change_auth_plan/models.py:41
#: xpack/plugins/change_auth_plan/models.py:42
msgid "All assets use the same random password"
msgstr "所有资产使用相同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:42
#: xpack/plugins/change_auth_plan/models.py:43
msgid "All assets use different random password"
msgstr "所有资产使用不同的随机密码"
#: xpack/plugins/change_auth_plan/models.py:64
#: xpack/plugins/change_auth_plan/models.py:65
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:187
#: xpack/plugins/change_auth_plan/models.py:188
msgid "Change auth plan snapshot"
msgstr "改密计划快照"
#: xpack/plugins/change_auth_plan/models.py:202
#: xpack/plugins/change_auth_plan/models.py:296
#: xpack/plugins/change_auth_plan/models.py:203
#: xpack/plugins/change_auth_plan/models.py:297
msgid "Change auth plan execution"
msgstr "改密计划执行"
#: xpack/plugins/change_auth_plan/models.py:269
#: xpack/plugins/change_auth_plan/models.py:270
msgid "Ready"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:270
#: xpack/plugins/change_auth_plan/models.py:271
msgid "Preflight check"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:271
#: xpack/plugins/change_auth_plan/models.py:272
msgid "Change auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:272
#: xpack/plugins/change_auth_plan/models.py:273
msgid "Verify auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:273
#: xpack/plugins/change_auth_plan/models.py:274
msgid "Keep auth"
msgstr ""
#: xpack/plugins/change_auth_plan/models.py:274
#: xpack/plugins/change_auth_plan/models.py:275
msgid "Finished"
msgstr "结束"
#: xpack/plugins/change_auth_plan/models.py:300
#: xpack/plugins/change_auth_plan/models.py:301
msgid "Step"
msgstr "步骤"
#: xpack/plugins/change_auth_plan/models.py:317
#: xpack/plugins/change_auth_plan/models.py:318
msgid "Change auth plan task"
msgstr "改密计划任务"
@ -3672,59 +3731,55 @@ msgstr "地域"
msgid "Instances"
msgstr "实例"
#: xpack/plugins/cloud/models.py:137 xpack/plugins/cloud/serializers.py:85
msgid "Always update"
msgstr "总是更新"
#: xpack/plugins/cloud/models.py:136 xpack/plugins/cloud/serializers.py:80
msgid "Covered always"
msgstr "总是被覆盖"
#: xpack/plugins/cloud/models.py:143
#: xpack/plugins/cloud/models.py:142
msgid "Date last sync"
msgstr "最后同步日期"
#: xpack/plugins/cloud/models.py:154 xpack/plugins/cloud/models.py:207
#: xpack/plugins/cloud/models.py:153 xpack/plugins/cloud/models.py:210
msgid "Sync instance task"
msgstr "同步实例任务"
#: xpack/plugins/cloud/models.py:202
msgid "Succeed"
msgstr "成功"
#: xpack/plugins/cloud/models.py:217 xpack/plugins/cloud/models.py:272
#: xpack/plugins/cloud/models.py:220 xpack/plugins/cloud/models.py:275
msgid "Date sync"
msgstr "同步日期"
#: xpack/plugins/cloud/models.py:245
#: xpack/plugins/cloud/models.py:248
msgid "Unsync"
msgstr "未同步"
#: xpack/plugins/cloud/models.py:246 xpack/plugins/cloud/models.py:247
#: xpack/plugins/cloud/models.py:249 xpack/plugins/cloud/models.py:250
msgid "Synced"
msgstr "已同步"
#: xpack/plugins/cloud/models.py:248
#: xpack/plugins/cloud/models.py:251
msgid "Released"
msgstr "已释放"
#: xpack/plugins/cloud/models.py:253
#: xpack/plugins/cloud/models.py:256
msgid "Sync task"
msgstr "同步任务"
#: xpack/plugins/cloud/models.py:257
#: xpack/plugins/cloud/models.py:260
msgid "Sync instance task history"
msgstr "同步实例任务历史"
#: xpack/plugins/cloud/models.py:260
#: xpack/plugins/cloud/models.py:263
msgid "Instance"
msgstr "实例"
#: xpack/plugins/cloud/models.py:263
#: xpack/plugins/cloud/models.py:266
msgid "Region"
msgstr "地域"
#: xpack/plugins/cloud/providers/aliyun.py:22
#: xpack/plugins/cloud/providers/aliyun.py:19
msgid "Alibaba Cloud"
msgstr "阿里云"
#: xpack/plugins/cloud/providers/aws.py:18
#: xpack/plugins/cloud/providers/aws.py:15
msgid "AWS (International)"
msgstr "AWS (国际)"
@ -3732,63 +3787,63 @@ msgstr "AWS (国际)"
msgid "AWS (China)"
msgstr "AWS (中国)"
#: xpack/plugins/cloud/providers/huaweicloud.py:20
#: xpack/plugins/cloud/providers/huaweicloud.py:17
msgid "Huawei Cloud"
msgstr "华为云"
#: xpack/plugins/cloud/providers/huaweicloud.py:23
#: xpack/plugins/cloud/providers/huaweicloud.py:20
msgid "AF-Johannesburg"
msgstr "非洲-约翰内斯堡"
#: xpack/plugins/cloud/providers/huaweicloud.py:24
#: xpack/plugins/cloud/providers/huaweicloud.py:21
msgid "AP-Bangkok"
msgstr "亚太-曼谷"
#: xpack/plugins/cloud/providers/huaweicloud.py:25
#: xpack/plugins/cloud/providers/huaweicloud.py:22
msgid "AP-Hong Kong"
msgstr "亚太-香港"
#: xpack/plugins/cloud/providers/huaweicloud.py:26
#: xpack/plugins/cloud/providers/huaweicloud.py:23
msgid "AP-Singapore"
msgstr "亚太-新加坡"
#: xpack/plugins/cloud/providers/huaweicloud.py:27
#: xpack/plugins/cloud/providers/huaweicloud.py:24
msgid "CN East-Shanghai1"
msgstr "华东-上海1"
#: xpack/plugins/cloud/providers/huaweicloud.py:28
#: xpack/plugins/cloud/providers/huaweicloud.py:25
msgid "CN East-Shanghai2"
msgstr "华东-上海2"
#: xpack/plugins/cloud/providers/huaweicloud.py:29
#: xpack/plugins/cloud/providers/huaweicloud.py:26
msgid "CN North-Beijing1"
msgstr "华北-北京1"
#: xpack/plugins/cloud/providers/huaweicloud.py:30
#: xpack/plugins/cloud/providers/huaweicloud.py:27
msgid "CN North-Beijing4"
msgstr "华北-北京4"
#: xpack/plugins/cloud/providers/huaweicloud.py:31
#: xpack/plugins/cloud/providers/huaweicloud.py:28
msgid "CN Northeast-Dalian"
msgstr "华北-大连"
#: xpack/plugins/cloud/providers/huaweicloud.py:32
#: xpack/plugins/cloud/providers/huaweicloud.py:29
msgid "CN South-Guangzhou"
msgstr "华南-广州"
#: xpack/plugins/cloud/providers/huaweicloud.py:33
#: xpack/plugins/cloud/providers/huaweicloud.py:30
msgid "CN Southwest-Guiyang1"
msgstr "西南-贵阳1"
#: xpack/plugins/cloud/providers/huaweicloud.py:34
#: xpack/plugins/cloud/providers/huaweicloud.py:31
msgid "EU-Paris"
msgstr "欧洲-巴黎"
#: xpack/plugins/cloud/providers/huaweicloud.py:35
#: xpack/plugins/cloud/providers/huaweicloud.py:32
msgid "LA-Santiago"
msgstr "拉美-圣地亚哥"
#: xpack/plugins/cloud/providers/qcloud.py:20
#: xpack/plugins/cloud/providers/qcloud.py:17
msgid "Tencent Cloud"
msgstr "腾讯云"
@ -3800,7 +3855,11 @@ msgstr "用户数量"
msgid "Instance count"
msgstr "实例个数"
#: xpack/plugins/cloud/serializers.py:84
#: xpack/plugins/cloud/serializers.py:78
msgid "Account name"
msgstr "账户名称"
#: xpack/plugins/cloud/serializers.py:79
#: xpack/plugins/gathered_user/serializers.py:20
msgid "Periodic display"
msgstr "定时执行"
@ -3889,11 +3948,8 @@ msgstr "企业版"
msgid "Ultimate edition"
msgstr "旗舰版"
#~ msgid "Covered always"
#~ msgstr "总是被覆盖"
#~ msgid "Account name"
#~ msgstr "账户名称"
#~ msgid "Always update"
#~ msgstr "总是更新"
#~ msgid "Target URL"
#~ msgstr "目标URL"
@ -4328,9 +4384,6 @@ msgstr "旗舰版"
#~ "系统用户创建时如果选择了自动推送JumpServer 会使用 Ansible 自动推送系统"
#~ "用户到资产中,如果资产(交换机)不支持 Ansible请手动填写账号密码。"
#~ msgid "Create system user"
#~ msgstr "创建系统用户"
#~ msgid "Remove success"
#~ msgstr "移除成功"
@ -4397,9 +4450,6 @@ msgstr "旗舰版"
#~ msgid "Platform detail"
#~ msgstr "平台详情"
#~ msgid "System user list"
#~ msgstr "系统用户列表"
#~ msgid "Update system user"
#~ msgstr "更新系统用户"
@ -4469,9 +4519,6 @@ msgstr "旗舰版"
#~ msgid "Task name"
#~ msgstr "任务名称"
#~ msgid "Failed assets"
#~ msgstr "失败资产"
#~ msgid "No assets"
#~ msgstr "没有资产"
@ -4633,9 +4680,6 @@ msgstr "旗舰版"
#~ msgid "Asset permission list"
#~ msgstr "资产授权列表"
#~ msgid "Create asset permission"
#~ msgstr "创建权限规则"
#~ msgid "Update asset permission"
#~ msgstr "更新资产授权"
@ -5164,9 +5208,6 @@ msgstr "旗舰版"
#~ msgid "Create ticket"
#~ msgstr "提交工单"
#~ msgid "Ticket list"
#~ msgstr "工单列表"
#~ msgid "Ticket detail"
#~ msgstr "工单详情"
@ -5536,9 +5577,6 @@ msgstr "旗舰版"
#~ msgid "Have child node, cancel"
#~ msgstr "存在子节点,不能删除"
#~ msgid "Have assets, cancel"
#~ msgstr "存在资产,不能删除"
#~ msgid "Add to node"
#~ msgstr "添加到节点"

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
from .ticket import *
from .request_asset_perm import *

View File

@ -0,0 +1,97 @@
from django.db.transaction import atomic
from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action
from rest_framework.response import Response
from common.const.http import POST
from common.drf.api import JMSModelViewSet
from common.permissions import IsValidUser
from common.utils.django import get_object_or_none
from common.drf.serializers import EmptySerializer
from perms.models.asset_permission import AssetPermission, Asset
from assets.models.user import SystemUser
from ..exceptions import (
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
TicketClosed, TicketActionYet, NotHaveConfirmedAssets,
NotHaveConfirmedSystemUser
)
from .. import serializers
from ..models import Ticket
from ..permissions import IsAssignee
class RequestAssetPermTicketViewSet(JMSModelViewSet):
queryset = Ticket.objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
serializer_classes = {
'default': serializers.RequestAssetPermTicketSerializer,
'approve': EmptySerializer,
'reject': EmptySerializer,
}
permission_classes = (IsValidUser,)
filter_fields = ['status', 'title', 'action', 'user_display']
search_fields = ['user_display', 'title']
def _check_can_set_action(self, instance, action):
if instance.status == instance.STATUS_CLOSED:
raise TicketClosed(detail=_('Ticket closed'))
if instance.action == action:
action_display = dict(instance.ACTION_CHOICES).get(action)
raise TicketActionYet(detail=_('Ticket has %s') % action_display)
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def reject(self, request, *args, **kwargs):
instance = self.get_object()
action = instance.ACTION_REJECT
self._check_can_set_action(instance, action)
instance.perform_action(action, request.user)
return Response()
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def approve(self, request, *args, **kwargs):
instance = self.get_object()
action = instance.ACTION_APPROVE
self._check_can_set_action(instance, action)
meta = instance.meta
confirmed_assets = meta.get('confirmed_assets', [])
assets = list(Asset.objects.filter(id__in=confirmed_assets))
if not assets:
raise NotHaveConfirmedAssets(detail=_('Confirm assets first'))
if len(assets) != len(confirmed_assets):
raise ConfirmedAssetsChanged(detail=_('Confirmed assets changed'))
confirmed_system_user = meta.get('confirmed_system_user')
if not confirmed_system_user:
raise NotHaveConfirmedSystemUser(detail=_('Confirm system-user first'))
system_user = get_object_or_none(SystemUser, id=confirmed_system_user)
if system_user is None:
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
self._create_asset_permission(instance, assets, system_user)
return Response({'detail': _('Succeed')})
def _create_asset_permission(self, instance: Ticket, assets, system_user):
meta = instance.meta
request = self.request
ap_kwargs = {
'name': meta.get('name', ''),
'created_by': self.request.user.username,
'comment': _('{} request assets, approved by {}').format(instance.user_display,
instance.assignee_display)
}
date_start = meta.get('date_start')
date_expired = meta.get('date_expired')
if date_start:
ap_kwargs['date_start'] = date_start
if date_expired:
ap_kwargs['date_expired'] = date_expired
with atomic():
instance.perform_action(instance.ACTION_APPROVE, request.user)
ap = AssetPermission.objects.create(**ap_kwargs)
ap.system_users.add(system_user)
ap.assets.add(*assets)
return ap

View File

@ -0,0 +1,25 @@
from common.exceptions import JMSException
class NotHaveConfirmedAssets(JMSException):
pass
class ConfirmedAssetsChanged(JMSException):
pass
class NotHaveConfirmedSystemUser(JMSException):
pass
class ConfirmedSystemUserChanged(JMSException):
pass
class TicketClosed(JMSException):
pass
class TicketActionYet(JMSException):
pass

View File

@ -20,9 +20,11 @@ class Ticket(CommonModelMixin):
)
TYPE_GENERAL = 'general'
TYPE_LOGIN_CONFIRM = 'login_confirm'
TYPE_REQUEST_ASSET_PERM = 'request_asset'
TYPE_CHOICES = (
(TYPE_GENERAL, _("General")),
(TYPE_LOGIN_CONFIRM, _("Login confirm"))
(TYPE_LOGIN_CONFIRM, _("Login confirm")),
(TYPE_REQUEST_ASSET_PERM, _('Request asset permission'))
)
ACTION_APPROVE = 'approve'
ACTION_REJECT = 'reject'

View File

@ -4,3 +4,6 @@
from rest_framework.permissions import BasePermission
class IsAssignee(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.is_assignee(request.user)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
#
from .ticket import *
from .request_asset_perm import *

View File

@ -0,0 +1,120 @@
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from orgs.utils import current_org
from ..models import Ticket
class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
ips = serializers.ListField(child=serializers.IPAddressField(), source='meta.ips',
default=list, label=_('IP group'))
hostname = serializers.CharField(max_length=256, source='meta.hostname', default=None,
allow_blank=True, label=_('Hostname'))
date_start = serializers.DateTimeField(source='meta.date_start', allow_null=True,
required=False, label=_('Date start'))
date_expired = serializers.DateTimeField(source='meta.date_expired', allow_null=True,
required=False, label=_('Date expired'))
confirmed_assets = serializers.ListField(child=serializers.UUIDField(),
source='meta.confirmed_assets',
default=list, required=False,
label=_('Confirmed assets'))
confirmed_system_user = serializers.ListField(child=serializers.UUIDField(),
source='meta.confirmed_system_user',
default=list, required=False,
label=_('Confirmed system user'))
assets_waitlist_url = serializers.SerializerMethodField()
system_user_waitlist_url = serializers.SerializerMethodField()
class Meta:
model = Ticket
mini_fields = ['id', 'title']
small_fields = [
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
'assets_waitlist_url'
]
m2m_fields = [
'user', 'user_display', 'assignees', 'assignees_display',
'assignee', 'assignee_display'
]
fields = mini_fields + small_fields + m2m_fields
read_only_fields = [
'user_display', 'assignees_display', 'type', 'user', 'status',
'date_created', 'date_updated', 'action', 'id', 'assignee',
'assignee_display',
]
extra_kwargs = {
'status': {'label': _('Status')},
'action': {'label': _('Action')},
'user_display': {'label': _('User')}
}
def validate_assignees(self, assignees):
count = current_org.org_admins().filter(id__in=[assignee.id for assignee in assignees]).count()
if count != len(assignees):
raise serializers.ValidationError(_('Must be organization admin or superuser'))
return assignees
def get_system_user_waitlist_url(self, instance: Ticket):
if not self._is_assignee(instance):
return None
return {'url': reverse('api-assets:system-user-list')}
def get_assets_waitlist_url(self, instance: Ticket):
if not self._is_assignee(instance):
return None
asset_api = reverse('api-assets:asset-list')
query = ''
meta = instance.meta
ips = meta.get('ips', [])
hostname = meta.get('hostname')
if ips:
query = '?ips=%s' % ','.join(ips)
elif hostname:
query = '?search=%s' % hostname
return asset_api + query
def create(self, validated_data):
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
validated_data['user'] = self.context['request'].user
self._pop_confirmed_fields()
return super().create(validated_data)
def save(self, **kwargs):
meta = self.validated_data.get('meta', {})
date_start = meta.get('date_start')
if date_start:
meta['date_start'] = date_start.strftime('%Y-%m-%d %H:%M:%S%z')
date_expired = meta.get('date_expired')
if date_expired:
meta['date_expired'] = date_expired.strftime('%Y-%m-%d %H:%M:%S%z')
return super().save(**kwargs)
def update(self, instance, validated_data):
new_meta = validated_data['meta']
if not self._is_assignee(instance):
self._pop_confirmed_fields()
old_meta = instance.meta
meta = {}
meta.update(old_meta)
meta.update(new_meta)
validated_data['meta'] = meta
return super().update(instance, validated_data)
def _pop_confirmed_fields(self):
meta = self.validated_data['meta']
meta.pop('confirmed_assets', None)
meta.pop('confirmed_system_user', None)
def _is_assignee(self, obj: Ticket):
user = self.context['request'].user
return obj.is_assignee(user)

View File

@ -7,6 +7,7 @@ from .. import api
app_name = 'tickets'
router = BulkRouter()
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
router.register('tickets', api.TicketViewSet, 'ticket')
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')

View File

@ -18,6 +18,7 @@ from ..serializers import UserSerializer, UserRetrieveSerializer
from .mixins import UserQuerysetMixin
from ..models import User
from ..signals import post_user_create
from ..filters import OrgRoleUserFilterBackend
logger = get_logger(__name__)
@ -35,6 +36,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
'default': UserSerializer,
'retrieve': UserRetrieveSerializer
}
extra_filter_backends = [OrgRoleUserFilterBackend]
def get_queryset(self):
return super().get_queryset().prefetch_related('groups')

31
apps/users/filters.py Normal file
View File

@ -0,0 +1,31 @@
from rest_framework.compat import coreapi, coreschema
from rest_framework import filters
from orgs.utils import current_org
class OrgRoleUserFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
org_role = request.query_params.get('org_role')
if not org_role:
return queryset
if org_role == 'admins':
return queryset & current_org.get_org_admins()
elif org_role == 'auditors':
return queryset & current_org.get_org_auditors()
elif org_role == 'users':
return queryset & current_org.get_org_users()
elif org_role == 'members':
return queryset & current_org.get_org_members()
def get_schema_fields(self, view):
return [
coreapi.Field(
name='org_role', location='query', required=False, type='string',
schema=coreschema.String(
title='Organization role users',
description='Organization role users can be {admins|auditors|users|members}'
)
)
]