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

View File

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

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .ticket import * 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_GENERAL = 'general'
TYPE_LOGIN_CONFIRM = 'login_confirm' TYPE_LOGIN_CONFIRM = 'login_confirm'
TYPE_REQUEST_ASSET_PERM = 'request_asset'
TYPE_CHOICES = ( TYPE_CHOICES = (
(TYPE_GENERAL, _("General")), (TYPE_GENERAL, _("General")),
(TYPE_LOGIN_CONFIRM, _("Login confirm")) (TYPE_LOGIN_CONFIRM, _("Login confirm")),
(TYPE_REQUEST_ASSET_PERM, _('Request asset permission'))
) )
ACTION_APPROVE = 'approve' ACTION_APPROVE = 'approve'
ACTION_REJECT = 'reject' ACTION_REJECT = 'reject'

View File

@ -4,3 +4,6 @@
from rest_framework.permissions import BasePermission 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 -*- # -*- coding: utf-8 -*-
# #
from .ticket import * 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' app_name = 'tickets'
router = BulkRouter() router = BulkRouter()
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
router.register('tickets', api.TicketViewSet, 'ticket') router.register('tickets', api.TicketViewSet, 'ticket')
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment') 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 .mixins import UserQuerysetMixin
from ..models import User from ..models import User
from ..signals import post_user_create from ..signals import post_user_create
from ..filters import OrgRoleUserFilterBackend
logger = get_logger(__name__) logger = get_logger(__name__)
@ -35,6 +36,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
'default': UserSerializer, 'default': UserSerializer,
'retrieve': UserRetrieveSerializer 'retrieve': UserRetrieveSerializer
} }
extra_filter_backends = [OrgRoleUserFilterBackend]
def get_queryset(self): def get_queryset(self):
return super().get_queryset().prefetch_related('groups') 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}'
)
)
]