diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 8f6d103c0..f6bb60516 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -18,22 +18,28 @@ class ApplicationSerializerMixin(serializers.Serializer): attrs = MethodSerializer() def get_attrs_serializer(self): - serializer_class = None + default_serializer = serializers.Serializer(read_only=True) if isinstance(self.instance, models.Application): - instance_type = self.instance.type - serializer_class = type_serializer_classes_mapping.get(instance_type) + _type = self.instance.type + _category = self.instance.category else: - request = self.context['request'] - query_type = request.query_params.get('type') - query_category = request.query_params.get('category') - if query_type: - serializer_class = type_serializer_classes_mapping.get(query_type) - elif query_category: - serializer_class = category_serializer_classes_mapping.get(query_category) + _type = self.context['request'].query_params.get('type') + _category = self.context['request'].query_params.get('category') - if serializer_class is None: - serializer_class = serializers.Serializer - serializer = serializer_class() + if _type: + serializer_class = type_serializer_classes_mapping.get(_type) + elif _category: + serializer_class = category_serializer_classes_mapping.get(_category) + else: + serializer_class = default_serializer + + if not serializer_class: + serializer_class = default_serializer + + if isinstance(serializer_class, type): + serializer = serializer_class() + else: + serializer = serializer_class return serializer diff --git a/apps/assets/api/asset_user.py b/apps/assets/api/asset_user.py index 7c78f3f51..e2844a5e5 100644 --- a/apps/assets/api/asset_user.py +++ b/apps/assets/api/asset_user.py @@ -28,7 +28,7 @@ logger = get_logger(__name__) class AssetUserFilterBackend(filters.BaseFilterBackend): def filter_queryset(self, request, queryset, view): kwargs = {} - for field in view.filter_fields: + for field in view.filterset_fields: value = request.GET.get(field) if not value: continue diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 66effa20b..a8ce0f3ee 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -2,7 +2,7 @@ # from rest_framework import serializers from django.db.models import F - +from django.core.validators import RegexValidator from django.utils.translation import ugettext_lazy as _ from orgs.mixins.serializers import BulkOrgResourceModelSerializer @@ -177,6 +177,14 @@ class AssetDisplaySerializer(AssetSerializer): class PlatformSerializer(serializers.ModelSerializer): meta = serializers.DictField(required=False, allow_null=True) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # TODO 修复 drf SlugField RegexValidator bug,之后记得删除 + validators = self.fields['name'].validators + if isinstance(validators[-1], RegexValidator): + validators.pop() + class Meta: model = Platform fields = [ diff --git a/apps/audits/serializers.py b/apps/audits/serializers.py index 53a17298d..b94ffa8c9 100644 --- a/apps/audits/serializers.py +++ b/apps/audits/serializers.py @@ -86,8 +86,7 @@ class CommandExecutionSerializer(serializers.ModelSerializer): @classmethod def setup_eager_loading(cls, queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.annotate(user_display=F('user__name'))\ - .annotate(run_as_display=F('run_as__name')) + queryset = queryset.prefetch_related('user', 'run_as', 'hosts') return queryset diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 310aa3d4a..6886fa2f0 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -4,7 +4,6 @@ import uuid from django.core.cache import cache from django.shortcuts import get_object_or_404 -from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index eda1dd561..7d2332006 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -73,9 +73,8 @@ class SimpleMetadataWithFilters(SimpleMetadata): elif getattr(field, 'fields', None): field_info['children'] = self.get_serializer_info(field) - if (not field_info.get('read_only') and - not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) and - hasattr(field, 'choices')): + if not isinstance(field, (serializers.RelatedField, serializers.ManyRelatedField)) \ + and hasattr(field, 'choices'): field_info['choices'] = [ { 'value': choice_value, diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index b346f4804..0e3edb301 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 88119e6f5..d5e9ddc26 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-17 16:12+0800\n" +"POT-Creation-Date: 2021-01-19 20:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -58,23 +58,22 @@ msgid "Name" msgstr "名称" #: applications/models/application.py:12 -#: applications/serializers/application.py:41 assets/models/label.py:21 +#: applications/serializers/application.py:47 assets/models/label.py:21 #: perms/models/application_permission.py:20 #: perms/serializers/application/permission.py:16 #: perms/serializers/application/user_permission.py:33 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:18 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20 msgid "Category" msgstr "种类" #: applications/models/application.py:15 -#: applications/serializers/application.py:42 assets/models/cmd_filter.py:52 +#: applications/serializers/application.py:48 assets/models/cmd_filter.py:52 #: perms/models/application_permission.py:23 #: perms/serializers/application/permission.py:17 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:18 terminal/models/storage.py:58 #: tickets/models/ticket.py:38 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:25 -#: tickets/serializers/ticket/ticket.py:19 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27 #: users/templates/users/user_granted_database_app.html:35 msgid "Type" msgstr "类型" @@ -269,7 +268,7 @@ msgstr "主机名" #: assets/models/asset.py:194 assets/models/domain.py:54 #: assets/models/user.py:120 terminal/serializers/session.py:29 -#: terminal/serializers/storage.py:59 +#: terminal/serializers/storage.py:68 msgid "Protocol" msgstr "协议" @@ -293,7 +292,7 @@ msgstr "激活" #: assets/models/asset.py:203 assets/models/cluster.py:19 #: assets/models/user.py:66 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:126 +#: xpack/plugins/cloud/models.py:143 xpack/plugins/cloud/serializers.py:137 msgid "Admin user" msgstr "管理用户" @@ -508,8 +507,7 @@ msgstr "每行一个命令" #: assets/models/cmd_filter.py:56 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 -#: tickets/models/ticket.py:43 tickets/serializers/ticket/ticket.py:20 -#: users/templates/users/_granted_assets.html:29 +#: tickets/models/ticket.py:43 users/templates/users/_granted_assets.html:29 #: users/templates/users/user_asset_permission.html:44 #: users/templates/users/user_asset_permission.html:79 #: users/templates/users/user_database_app_permission.html:42 @@ -604,7 +602,7 @@ msgstr "ssh私钥" #: 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:139 xpack/plugins/cloud/serializers.py:127 +#: xpack/plugins/cloud/models.py:139 xpack/plugins/cloud/serializers.py:138 msgid "Node" msgstr "节点" @@ -996,10 +994,10 @@ msgstr "成功" #: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52 #: terminal/models/session.py:51 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:41 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:69 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:38 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:73 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:43 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:74 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:40 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:78 #: xpack/plugins/change_auth_plan/models.py:177 #: xpack/plugins/change_auth_plan/models.py:307 #: xpack/plugins/gathered_user/models.py:76 @@ -1093,8 +1091,7 @@ msgid "Reason" msgstr "原因" #: audits/models.py:106 tickets/models/ticket.py:47 -#: tickets/serializers/ticket/ticket.py:21 xpack/plugins/cloud/models.py:224 -#: xpack/plugins/cloud/models.py:282 +#: xpack/plugins/cloud/models.py:224 xpack/plugins/cloud/models.py:282 msgid "Status" msgstr "状态" @@ -1132,7 +1129,7 @@ msgstr "是否成功" msgid "Result" msgstr "结果" -#: audits/serializers.py:79 terminal/serializers/storage.py:157 +#: audits/serializers.py:79 terminal/serializers/storage.py:177 msgid "Hosts" msgstr "主机" @@ -1786,11 +1783,11 @@ msgstr "定期清除Celery日志" msgid "Task log" msgstr "任务列表" -#: ops/utils.py:62 +#: ops/utils.py:64 msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: ops/utils.py:72 +#: ops/utils.py:74 msgid "Disk used more than 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}" @@ -1804,7 +1801,7 @@ msgstr "当前组织不能被删除" #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:41 #: orgs/models.py:422 orgs/serializers.py:100 -#: tickets/serializers/ticket/ticket.py:74 +#: tickets/serializers/ticket/ticket.py:81 msgid "Organization" msgstr "组织" @@ -1904,10 +1901,10 @@ msgid "User group" msgstr "用户组" #: perms/models/base.py:53 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:44 -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:72 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:41 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:76 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:46 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81 #: users/models/user.py:556 users/templates/users/user_detail.html:93 #: users/templates/users/user_profile.html:120 msgid "Date expired" @@ -2717,60 +2714,64 @@ msgstr "是否可重放" msgid "Can join" msgstr "是否可加入" -#: terminal/serializers/storage.py:19 +#: terminal/serializers/storage.py:20 +msgid "Endpoint invalid: remove path `{}`" +msgstr "端点无效: 移除路径 `{}`" + +#: terminal/serializers/storage.py:26 msgid "Bucket" msgstr "桶名称" -#: terminal/serializers/storage.py:22 +#: terminal/serializers/storage.py:29 msgid "Access key" msgstr "" -#: terminal/serializers/storage.py:26 +#: terminal/serializers/storage.py:33 msgid "Secret key" msgstr "" -#: terminal/serializers/storage.py:30 terminal/serializers/storage.py:41 -#: terminal/serializers/storage.py:70 +#: terminal/serializers/storage.py:38 terminal/serializers/storage.py:50 +#: terminal/serializers/storage.py:80 msgid "Endpoint" msgstr "端点" -#: terminal/serializers/storage.py:56 xpack/plugins/cloud/models.py:276 +#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:276 msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:80 +#: terminal/serializers/storage.py:90 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:82 +#: terminal/serializers/storage.py:92 msgid "Account name" msgstr "账户名称" -#: terminal/serializers/storage.py:83 +#: terminal/serializers/storage.py:93 msgid "Account key" msgstr "账户密钥" -#: terminal/serializers/storage.py:86 +#: terminal/serializers/storage.py:96 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:135 +#: terminal/serializers/storage.py:154 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:142 +#: terminal/serializers/storage.py:161 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:145 +#: terminal/serializers/storage.py:164 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:161 +#: terminal/serializers/storage.py:180 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:164 +#: terminal/serializers/storage.py:183 msgid "Doc type" msgstr "文档类型" @@ -2778,14 +2779,14 @@ msgstr "文档类型" msgid "Not found" msgstr "没有发现" -#: terminal/utils.py:74 +#: terminal/utils.py:79 #, python-format msgid "" "Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $" "%(command)s" msgstr "危险命令告警: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s" -#: terminal/utils.py:81 +#: terminal/utils.py:87 #, python-format msgid "" "\n" @@ -2842,6 +2843,129 @@ msgstr "拒绝" msgid "Closed" msgstr "关闭" +#: tickets/handler/apply_application.py:55 +msgid "Applied category" +msgstr "申请的种类" + +#: tickets/handler/apply_application.py:56 +msgid "Applied type" +msgstr "申请的类型" + +#: tickets/handler/apply_application.py:57 +msgid "Applied application group" +msgstr "申请的应用组" + +#: tickets/handler/apply_application.py:58 tickets/handler/apply_asset.py:59 +msgid "Applied system user group" +msgstr "申请的系统用户组" + +#: tickets/handler/apply_application.py:59 tickets/handler/apply_asset.py:61 +msgid "Applied date start" +msgstr "申请的开始日期" + +#: tickets/handler/apply_application.py:60 tickets/handler/apply_asset.py:62 +msgid "Applied date expired" +msgstr "申请的失效日期" + +#: tickets/handler/apply_application.py:75 +msgid "Approved applications" +msgstr "批准的应用" + +#: tickets/handler/apply_application.py:76 tickets/handler/apply_asset.py:79 +msgid "Approved system users" +msgstr "批准的系统用户" + +#: tickets/handler/apply_application.py:77 tickets/handler/apply_asset.py:81 +msgid "Approved date start" +msgstr "批准的开始日期" + +#: tickets/handler/apply_application.py:78 tickets/handler/apply_asset.py:82 +msgid "Approved date expired" +msgstr "批准的失效日期" + +#: tickets/handler/apply_application.py:100 tickets/handler/apply_asset.py:103 +msgid "" +"Created by the ticket, ticket title: {}, ticket applicant: {}, ticket " +"processor: {}, ticket ID: {}" +msgstr "" +"通过工单创建, 工单标题: {}, 工单申请人: {}, 工单处理人: {}, 工单 ID: {}" + +#: tickets/handler/apply_asset.py:57 +msgid "Applied IP group" +msgstr "申请的IP组" + +#: tickets/handler/apply_asset.py:58 +msgid "Applied hostname group" +msgstr "申请的主机名组" + +#: tickets/handler/apply_asset.py:60 +msgid "Applied actions" +msgstr "申请的动作" + +#: tickets/handler/apply_asset.py:78 +msgid "Approved assets" +msgstr "批准的资产" + +#: tickets/handler/apply_asset.py:80 +msgid "Approved actions" +msgstr "批准的动作" + +#: tickets/handler/base.py:62 +msgid "User {} {} the ticket" +msgstr "用户 {} {} 这个工单" + +#: tickets/handler/base.py:91 +msgid "Ticket title" +msgstr "工单标题" + +#: tickets/handler/base.py:92 +msgid "Ticket type" +msgstr "工单类型" + +#: tickets/handler/base.py:93 +msgid "Ticket status" +msgstr "工单状态" + +#: tickets/handler/base.py:94 +msgid "Ticket action" +msgstr "工单动作" + +#: tickets/handler/base.py:95 +msgid "Ticket applicant" +msgstr "工单申请人" + +#: tickets/handler/base.py:96 +msgid "Ticket assignees" +msgstr "工单受理人" + +#: tickets/handler/base.py:99 +msgid "Ticket processor" +msgstr "工单处理人" + +#: tickets/handler/base.py:100 +msgid "Ticket basic info" +msgstr "工单基本信息" + +#: tickets/handler/base.py:114 +msgid "Ticket applied info" +msgstr "工单申请信息" + +#: tickets/handler/base.py:119 +msgid "Ticket approved info" +msgstr "工单批准信息" + +#: tickets/handler/login_confirm.py:16 +msgid "Applied login IP" +msgstr "申请的登录IP" + +#: tickets/handler/login_confirm.py:17 +msgid "Applied login city" +msgstr "申请的登录城市" + +#: tickets/handler/login_confirm.py:18 +msgid "Applied login datetime" +msgstr "申请的登录日期" + #: tickets/models/comment.py:19 msgid "User display name" msgstr "用户显示名称" @@ -2864,7 +2988,7 @@ msgstr "申请人" #: tickets/models/ticket.py:55 msgid "Applicant display" -msgstr "申请人" +msgstr "申请人 (显示名称)" #: tickets/models/ticket.py:60 msgid "Processor" @@ -2872,7 +2996,7 @@ msgstr "处理人" #: tickets/models/ticket.py:63 msgid "Processor display" -msgstr "处理人" +msgstr "处理人 (显示名称)" #: tickets/models/ticket.py:67 msgid "Assignees" @@ -2880,153 +3004,134 @@ msgstr "受理人" #: tickets/models/ticket.py:70 msgid "Assignees display" -msgstr "受理人" +msgstr "受理人 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:22 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24 msgid "Category display" -msgstr "种类" +msgstr "种类 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:29 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31 +#: tickets/serializers/ticket/ticket.py:19 msgid "Type display" -msgstr "类型" +msgstr "类型 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:33 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:35 msgid "Application group" msgstr "应用组" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:37 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:26 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:39 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:28 msgid "System user group" msgstr "系统用户组" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:51 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:53 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:50 +msgid "Permission name" +msgstr "授权名称" + +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:56 msgid "Approve applications" msgstr "批准的应用" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:56 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:61 msgid "Approve applications display" -msgstr "批准的应用" +msgstr "批准的应用 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:60 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:65 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:62 msgid "Approve system users" msgstr "批准的系统用户" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:65 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:70 msgid "Approve system user display" -msgstr "批准的系统用户" +msgstr "批准的系统用户 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:89 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:90 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:94 +msgid "Permission named `{}` already exists" +msgstr "授权名称 `{}` 已存在" + +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:107 msgid "No `Application` are found under Organization `{}`" msgstr "在组织 `{}` 下没有发现 `应用`" -#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:107 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:106 +#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:125 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:124 msgid "No `SystemUser` are found under Organization `{}`" msgstr "在组织 `{}` 下没有发现 `系统用户`" -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:18 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:20 msgid "IP group" msgstr "IP组" -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:22 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:24 msgid "Hostname group" msgstr "主机名组" -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:34 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:52 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:61 -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:69 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:36 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:57 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:66 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:74 msgid "Approve assets display" -msgstr "批准的资产" +msgstr "批准的资产 (显示名称)" -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:48 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:53 msgid "Approve assets" msgstr "批准的资产" -#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:90 +#: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:108 msgid "No `Asset` are found under Organization `{}`" msgstr "在组织 `{}` 下没有发现 `资产`" +#: tickets/serializers/ticket/meta/ticket_type/common.py:11 +msgid "Created by ticket ({}-{})" +msgstr "通过工单创建 ({}-{})" + #: tickets/serializers/ticket/meta/ticket_type/login_confirm.py:20 msgid "Login datetime" msgstr "登录日期" -#: tickets/serializers/ticket/ticket.py:92 +#: tickets/serializers/ticket/ticket.py:21 +msgid "Action display" +msgstr "动作 (显示名称)" + +#: tickets/serializers/ticket/ticket.py:24 +msgid "Status display" +msgstr "状态(显示名称)" + +#: tickets/serializers/ticket/ticket.py:99 msgid "" "The `type` in the submission data (`{}`) is different from the type in the " "request url (`{}`)" msgstr "提交数据中的类型 (`{}`) 与请求URL地址中的类型 (`{}`) 不一致" -#: tickets/serializers/ticket/ticket.py:102 +#: tickets/serializers/ticket/ticket.py:109 msgid "The organization `{}` does not exist" msgstr "组织 `{}` 不存在" -#: tickets/serializers/ticket/ticket.py:113 +#: tickets/serializers/ticket/ticket.py:120 msgid "None of the assignees belong to Organization `{}` admins" msgstr "所有受理人都不属于组织 `{}` 下的管理员" -#: tickets/utils.py:21 -msgid "New Ticket: {} ({})" -msgstr "新建工单: {} ({})" +#: tickets/utils.py:36 +msgid "New Ticket - {} ({})" +msgstr "新工单 - {} ({})" -#: tickets/utils.py:26 -#, python-brace-format -msgid "" -"
\n" -"

Your has a new ticket

\n" -"
\n" -" Ticket: \n" -"
\n" -" {body}\n" -"
\n" -" click here to review \n" -"
\n" -"
\n" -" " -msgstr "" -"
\n" -"

你有一个新工单

\n" -"
\n" -" 工单: \n" -"
\n" -" {body}\n" -"
\n" -" 点击查看 \n" -"
\n" -"
\n" -" " +#: tickets/utils.py:38 +msgid "Your has a new ticket, applicant - {}" +msgstr "你有一个新的工单, 申请人 - {}" -#: tickets/utils.py:51 -msgid "Ticket has processed: {} ({})" -msgstr "工单已处理: {} ({})" +#: tickets/utils.py:40 tickets/utils.py:59 +msgid "click here to review" +msgstr "点击查看" -#: tickets/utils.py:53 -#, python-brace-format -msgid "" -"\n" -"
\n" -"

Your ticket has been processed

\n" -"
\n" -" Ticket: \n" -"
\n" -" {body}\n" -"
\n" -"
\n" -"
\n" -" " -msgstr "" -"\n" -"
\n" -"

你的工单已被处理

\n" -"
\n" -" 工单: \n" -"
\n" -" {body}\n" -"
\n" -"
\n" -"
\n" -" " +#: tickets/utils.py:55 +msgid "Ticket has processed - {} ({})" +msgstr "工单已处理 - {} ({})" + +#: tickets/utils.py:57 +msgid "Your ticket has been processed, processor - {}" +msgstr "你的工单已被处理, 处理人 - {}" #: users/api/user.py:199 msgid "Could not reset self otp, use profile reset instead" @@ -3263,7 +3368,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:125 xpack/plugins/cloud/serializers.py:125 +#: xpack/plugins/cloud/models.py:125 xpack/plugins/cloud/serializers.py:136 msgid "Account" msgstr "账户" @@ -4079,7 +4184,7 @@ msgstr "邮箱地址错误,重新输入" #: users/views/profile/reset.py:51 msgid "" -"The user is from A, please go to the corresponding system to change the " +"The user is from {}, please go to the corresponding system to change the " "password" msgstr "用户来自 {} 请去相应系统修改密码" @@ -4227,7 +4332,7 @@ msgstr "实例名称" msgid "Instance name and Partial IP" msgstr "实例名称和部分IP" -#: xpack/plugins/cloud/models.py:128 xpack/plugins/cloud/serializers.py:101 +#: xpack/plugins/cloud/models.py:128 xpack/plugins/cloud/serializers.py:112 msgid "Regions" msgstr "地域" @@ -4239,7 +4344,7 @@ msgstr "实例" msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:147 xpack/plugins/cloud/serializers.py:129 +#: xpack/plugins/cloud/models.py:147 xpack/plugins/cloud/serializers.py:140 msgid "Always update" msgstr "总是更新" @@ -4371,15 +4476,15 @@ msgstr "租户ID" msgid "Subscription ID" msgstr "订阅ID" -#: xpack/plugins/cloud/serializers.py:99 +#: xpack/plugins/cloud/serializers.py:110 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers.py:100 +#: xpack/plugins/cloud/serializers.py:111 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers.py:128 +#: xpack/plugins/cloud/serializers.py:139 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" @@ -4471,3 +4576,6 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" + +#~ msgid "No" +#~ msgstr "无" diff --git a/apps/ops/utils.py b/apps/ops/utils.py index d8c186dd6..5ce4494a6 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -1,11 +1,13 @@ # ~*~ coding: utf-8 ~*~ import os +import uuid from django.utils.translation import ugettext_lazy as _ from common.utils import get_logger, get_object_or_none from common.tasks import send_mail_async from orgs.utils import org_aware_func +from jumpserver.const import PROJECT_DIR from .models import Task, AdHoc @@ -79,8 +81,12 @@ def send_server_performance_mail(path, usage, usages): def get_task_log_path(base_path, task_id, level=2): task_id = str(task_id) + try: + uuid.UUID(task_id) + except: + return os.path.join(PROJECT_DIR, 'data', 'caution.txt') + rel_path = os.path.join(*task_id[:level], task_id + '.log') path = os.path.join(base_path, rel_path) os.makedirs(os.path.dirname(path), exist_ok=True) return path - diff --git a/apps/ops/ws.py b/apps/ops/ws.py index f21c79570..e9cb38d28 100644 --- a/apps/ops/ws.py +++ b/apps/ops/ws.py @@ -22,7 +22,7 @@ class TaskLogWebsocket(JsonWebsocketConsumer): def connect(self): user = self.scope["user"] - if user.is_authenticated and user.is_org_admin: + if user.is_authenticated: self.accept() else: self.close() diff --git a/apps/orgs/signals_handler.py b/apps/orgs/signals_handler.py index 5f918ee21..57c7f5490 100644 --- a/apps/orgs/signals_handler.py +++ b/apps/orgs/signals_handler.py @@ -122,18 +122,18 @@ def refresh_user_amount_on_user_create_or_delete(user_id): @receiver(post_save, sender=User) -def on_user_create(sender, instance, created, **kwargs): +def on_user_create_refresh_cache(sender, instance, created, **kwargs): if created: refresh_user_amount_on_user_create_or_delete(instance.id) @receiver(pre_delete, sender=User) -def on_user_delete(sender, instance, **kwargs): +def on_user_delete_refresh_cache(sender, instance, **kwargs): refresh_user_amount_on_user_create_or_delete(instance.id) @receiver(m2m_changed, sender=OrganizationMember) -def on_org_user_changed(sender, action, instance, reverse, pk_set, **kwargs): +def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set, **kwargs): if not action.startswith(POST_PREFIX): return diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/terminal.py index 14213316c..26b030ff8 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/terminal.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -from collections import OrderedDict import logging import uuid @@ -8,7 +7,6 @@ from django.core.cache import cache from django.shortcuts import get_object_or_404 from rest_framework import viewsets from rest_framework.views import APIView, Response -from rest_framework.permissions import AllowAny from common.drf.api import JMSBulkModelViewSet from common.utils import get_object_or_none @@ -18,7 +16,7 @@ from .. import serializers from .. import exceptions __all__ = [ - 'TerminalViewSet', 'TerminalTokenApi', 'StatusViewSet', 'TerminalConfig', + 'TerminalViewSet', 'StatusViewSet', 'TerminalConfig', ] logger = logging.getLogger(__file__) @@ -69,41 +67,6 @@ class TerminalViewSet(JMSBulkModelViewSet): queryset = queryset.filter(id__in=filtered_queryset_id) return queryset - def get_permissions(self): - if self.action == "create": - self.permission_classes = (AllowAny,) - return super().get_permissions() - - -class TerminalTokenApi(APIView): - permission_classes = (AllowAny,) - queryset = Terminal.objects.filter(is_deleted=False) - - def get(self, request, *args, **kwargs): - try: - terminal = self.queryset.get(id=kwargs.get('terminal')) - except Terminal.DoesNotExist: - terminal = None - - token = request.query_params.get("token") - - if terminal is None: - return Response('May be reject by administrator', status=401) - - if token is None or cache.get(token, "") != str(terminal.id): - return Response('Token is not valid', status=401) - - if not terminal.is_accepted: - return Response("Terminal was not accepted yet", status=400) - - if not terminal.user or not terminal.user.access_key: - return Response("No access key generate", status=401) - - access_key = terminal.user.access_key() - data = OrderedDict() - data['access_key'] = {'id': access_key.id, 'secret': access_key.secret} - return Response(data, status=200) - class StatusViewSet(viewsets.ModelViewSet): queryset = Status.objects.all() diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index dcac31c16..0f3bb4df4 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -14,6 +14,13 @@ from .. import const # -------------------------- +def replay_storage_endpoint_format_validator(endpoint): + h = urlparse(endpoint) + if h.path: + raise serializers.ValidationError(_('Endpoint invalid: remove path `{}`').format(h.path)) + return endpoint + + class ReplayStorageTypeBaseSerializer(serializers.Serializer): BUCKET = serializers.CharField( required=True, max_length=1024, label=_('Bucket'), allow_null=True @@ -27,6 +34,7 @@ class ReplayStorageTypeBaseSerializer(serializers.Serializer): allow_null=True, ) ENDPOINT = serializers.CharField( + validators=[replay_storage_endpoint_format_validator], required=True, max_length=1024, label=_('Endpoint'), allow_null=True, ) @@ -38,6 +46,7 @@ class ReplayStorageTypeS3Serializer(ReplayStorageTypeBaseSerializer): Such as: http://s3.cn-north-1.amazonaws.com.cn ''' ENDPOINT = serializers.CharField( + validators=[replay_storage_endpoint_format_validator], required=True, max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True, ) @@ -67,6 +76,7 @@ class ReplayStorageTypeOSSSerializer(ReplayStorageTypeBaseSerializer): Such as: http://oss-cn-hangzhou.aliyuncs.com ''' ENDPOINT = serializers.CharField( + validators=[replay_storage_endpoint_format_validator], max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True, ) @@ -113,16 +123,25 @@ class ReplayStorageSerializer(serializers.ModelSerializer): return _meta def get_meta_serializer(self): - serializer_class = None - query_type = self.context['request'].query_params.get('type') - if query_type: - serializer_class = replay_storage_type_serializer_classes_mapping.get(query_type) + default_serializer = serializers.Serializer(read_only=True) + if isinstance(self.instance, ReplayStorage): - instance_type = self.instance.type - serializer_class = replay_storage_type_serializer_classes_mapping.get(instance_type) - if serializer_class is None: - serializer_class = serializers.Serializer - serializer = serializer_class() + _type = self.instance.type + else: + _type = self.context['request'].query_params.get('type') + + if _type: + serializer_class = replay_storage_type_serializer_classes_mapping.get(_type) + else: + serializer_class = default_serializer + + if not serializer_class: + serializer_class = default_serializer + + if isinstance(serializer_class, type): + serializer = serializer_class() + else: + serializer = serializer_class return serializer @@ -130,7 +149,7 @@ class ReplayStorageSerializer(serializers.ModelSerializer): # --------------------------- -def es_host_format_validator(host): +def command_storage_es_host_format_validator(host): h = urlparse(host) default_error_msg = _('The address format is incorrect') if h.scheme not in ['http', 'https']: @@ -154,8 +173,8 @@ class CommandStorageTypeESSerializer(serializers.Serializer): (eg: http://www.jumpserver.a.com, http://www.jumpserver.b.com) ''' HOSTS = serializers.ListField( - child=serializers.CharField(validators=[es_host_format_validator]), label=_('Hosts'), - help_text=_(hosts_help_text), allow_null=True + child=serializers.CharField(validators=[command_storage_es_host_format_validator]), + label=_('Hosts'), help_text=_(hosts_help_text), allow_null=True ) INDEX = serializers.CharField( max_length=1024, default='jumpserver', label=_('Index'), allow_null=True @@ -187,14 +206,23 @@ class CommandStorageSerializer(serializers.ModelSerializer): return _meta def get_meta_serializer(self): - serializer_class = None - query_type = self.context['request'].query_params.get('type') - if query_type: - serializer_class = command_storage_type_serializer_classes_mapping.get(query_type) + default_serializer = serializers.Serializer(read_only=True) + if isinstance(self.instance, CommandStorage): - instance_type = self.instance.type - serializer_class = command_storage_type_serializer_classes_mapping.get(instance_type) - if serializer_class is None: - serializer_class = serializers.Serializer - serializer = serializer_class() + _type = self.instance.type + else: + _type = self.context['request'].query_params.get('type') + + if _type: + serializer_class = command_storage_type_serializer_classes_mapping.get(_type) + else: + serializer_class = default_serializer + + if not serializer_class: + serializer_class = default_serializer + + if isinstance(serializer_class, type): + serializer = serializer_class() + else: + serializer = serializer_class return serializer diff --git a/apps/terminal/urls/api_urls.py b/apps/terminal/urls/api_urls.py index 5a8efe8e8..077494d01 100644 --- a/apps/terminal/urls/api_urls.py +++ b/apps/terminal/urls/api_urls.py @@ -27,8 +27,6 @@ urlpatterns = [ api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), name='session-replay'), path('tasks/kill-session/', api.KillSessionAPI.as_view(), name='kill-session'), - path('terminals//access-key/', api.TerminalTokenApi.as_view(), - name='terminal-access-key'), path('terminals/config/', api.TerminalConfig.as_view(), name='terminal-config'), path('commands/export/', api.CommandExportApi.as_view(), name="command-export"), path('commands/insecure-command/', api.InsecureCommandAlertAPI.as_view(), name="command-alert"), diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index d2e5b8e91..918266b56 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -71,12 +71,18 @@ def get_session_replay_url(session): def send_command_alert_mail(command): session_obj = Session.objects.get(id=command['session']) + + input = command['input'] + if isinstance(input, str): + input = input.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ') + subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % { 'name': command['user'], 'login_from': session_obj.get_login_from_display(), 'remote_addr': session_obj.remote_addr, - 'command': command['input'] + 'command': input } + recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',') message = _(""" Command: %(command)s diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index 79d49b37d..91a585298 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -12,7 +12,7 @@ from common.permissions import IsValidUser, IsOrgAdmin from tickets import serializers from tickets.models import Ticket -from tickets.permissions.ticket import IsAssignee, NotClosed +from tickets.permissions.ticket import IsAssignee, IsAssigneeOrApplicant, NotClosed __all__ = ['TicketViewSet'] @@ -50,7 +50,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): instance = serializer.save() instance.open(applicant=self.request.user) - @action(detail=False, methods=[POST]) + @action(detail=False, methods=[POST], permission_classes=[IsValidUser, ]) def open(self, request, *args, **kwargs): return super().create(request, *args, **kwargs) @@ -68,7 +68,7 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): instance.reject(processor=request.user) return Response(serializer.data) - @action(detail=True, methods=[PUT], permission_classes=[IsOrgAdmin, IsAssignee, NotClosed]) + @action(detail=True, methods=[PUT], permission_classes=[IsAssigneeOrApplicant, NotClosed]) def close(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance) diff --git a/apps/tickets/handler/apply_application.py b/apps/tickets/handler/apply_application.py index 28eb82697..83f62b417 100644 --- a/apps/tickets/handler/apply_application.py +++ b/apps/tickets/handler/apply_application.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as __ +from django.utils.translation import ugettext as _ from orgs.utils import tmp_to_org, tmp_to_root_org from applications.models import Application from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices @@ -52,12 +52,12 @@ class Handler(BaseHandler): {}: {}, {}: {}, '''.format( - __('Applied category'), apply_category_display, - __('Applied type'), apply_type_display, - __('Applied application group'), apply_application_group, - __('Applied system user group'), apply_system_user_group, - __('Applied date start'), apply_date_start, - __('Applied date expired'), apply_date_expired, + _('Applied category'), apply_category_display, + _('Applied type'), apply_type_display, + _('Applied application group'), apply_application_group, + _('Applied system user group'), apply_system_user_group, + _('Applied date start'), apply_date_start, + _('Applied date expired'), apply_date_expired, ) return applied_body @@ -72,10 +72,10 @@ class Handler(BaseHandler): {}: {}, {}: {}, '''.format( - __('Approved applications'), approve_applications_display, - __('Approved system users'), approve_system_users_display, - __('Approved date start'), approve_date_start, - __('Approved date expired'), approve_date_expired + _('Approved applications'), approve_applications_display, + _('Approved system users'), approve_system_users_display, + _('Approved date start'), approve_date_start, + _('Approved date expired'), approve_date_expired ) return approved_body @@ -88,31 +88,33 @@ class Handler(BaseHandler): apply_category = self.ticket.meta.get('apply_category') apply_type = self.ticket.meta.get('apply_type') + approve_permission_name = self.ticket.meta.get('approve_permission_name', '') approved_applications_id = self.ticket.meta.get('approve_applications', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_expired = self.ticket.meta.get('approve_date_expired') - permission_name = '{}({})'.format( - __('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] + permission_created_by = '{}:{}'.format( + str(self.ticket.__class__.__name__), str(self.ticket.id) ) - permission_comment = __( + permission_comment = _( 'Created by the ticket, ' 'ticket title: {}, ' 'ticket applicant: {}, ' 'ticket processor: {}, ' 'ticket ID: {}' - ''.format( - self.ticket.title, self.ticket.applicant_display, - self.ticket.processor_display, str(self.ticket.id) - ) + ).format( + self.ticket.title, + self.ticket.applicant_display, + self.ticket.processor_display, + str(self.ticket.id) ) permissions_data = { 'id': self.ticket.id, - 'name': permission_name, + 'name': approve_permission_name, 'category': apply_category, 'type': apply_type, - 'comment': permission_comment, - 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), + 'comment': str(permission_comment), + 'created_by': permission_created_by, 'date_start': approve_date_start, 'date_expired': approve_date_expired, } diff --git a/apps/tickets/handler/apply_asset.py b/apps/tickets/handler/apply_asset.py index 4114c04b3..71507e7ae 100644 --- a/apps/tickets/handler/apply_asset.py +++ b/apps/tickets/handler/apply_asset.py @@ -1,5 +1,5 @@ from .base import BaseHandler -from django.utils.translation import ugettext as __ +from django.utils.translation import ugettext as _ from perms.models import AssetPermission, Action from assets.models import Asset, SystemUser @@ -54,12 +54,12 @@ class Handler(BaseHandler): {}: {}, {}: {} '''.format( - __('Applied IP group'), apply_ip_group, - __("Applied hostname group"), apply_hostname_group, - __("Applied system user group"), apply_system_user_group, - __("Applied actions"), apply_actions_display, - __('Applied date start'), apply_date_start, - __('Applied date expired'), apply_date_expired, + _('Applied IP group'), apply_ip_group, + _("Applied hostname group"), apply_hostname_group, + _("Applied system user group"), apply_system_user_group, + _("Applied actions"), apply_actions_display, + _('Applied date start'), apply_date_start, + _('Applied date expired'), apply_date_expired, ) return applied_body @@ -75,11 +75,11 @@ class Handler(BaseHandler): {}: {}, {}: {} '''.format( - __('Approved assets'), approve_assets_display, - __('Approved system users'), approve_system_users_display, - __('Approved actions'), ', '.join(approve_actions_display), - __('Approved date start'), approve_date_start, - __('Approved date expired'), approve_date_expired, + _('Approved assets'), approve_assets_display, + _('Approved system users'), approve_system_users_display, + _('Approved actions'), ', '.join(approve_actions_display), + _('Approved date start'), approve_date_start, + _('Approved date expired'), approve_date_expired, ) return approved_body @@ -90,30 +90,33 @@ class Handler(BaseHandler): if asset_permission: return asset_permission + approve_permission_name = self.ticket.meta.get('approve_permission_name', ) approve_assets_id = self.ticket.meta.get('approve_assets', []) approve_system_users_id = self.ticket.meta.get('approve_system_users', []) approve_actions = self.ticket.meta.get('approve_actions', Action.NONE) approve_date_start = self.ticket.meta.get('approve_date_start') approve_date_expired = self.ticket.meta.get('approve_date_expired') - permission_name = '{}({})'.format( - __('Created by ticket ({})'.format(self.ticket.title)), str(self.ticket.id)[:4] + permission_created_by = '{}:{}'.format( + str(self.ticket.__class__.__name__), str(self.ticket.id) ) - permission_comment = __( + permission_comment = _( 'Created by the ticket, ' 'ticket title: {}, ' 'ticket applicant: {}, ' 'ticket processor: {}, ' 'ticket ID: {}' - ''.format( - self.ticket.title, self.ticket.applicant_display, self.ticket.processor_display, - str(self.ticket.id) - ) + ).format( + self.ticket.title, + self.ticket.applicant_display, + self.ticket.processor_display, + str(self.ticket.id) ) + permission_data = { 'id': self.ticket.id, - 'name': permission_name, - 'comment': permission_comment, - 'created_by': '{}:{}'.format(str(self.__class__.__name__), str(self.ticket.id)), + 'name': approve_permission_name, + 'comment': str(permission_comment), + 'created_by': permission_created_by, 'actions': approve_actions, 'date_start': approve_date_start, 'date_expired': approve_date_expired, diff --git a/apps/tickets/handler/base.py b/apps/tickets/handler/base.py index e1820dc0b..a0092a96a 100644 --- a/apps/tickets/handler/base.py +++ b/apps/tickets/handler/base.py @@ -1,6 +1,8 @@ -from django.utils.translation import ugettext as __ +from django.utils.translation import ugettext as _ from common.utils import get_logger -from tickets.utils import send_ticket_processed_mail_to_applicant +from tickets.utils import ( + send_ticket_processed_mail_to_applicant, send_ticket_applied_mail_to_assignees +) logger = get_logger(__name__) @@ -14,9 +16,11 @@ class BaseHandler(object): # on action def _on_open(self): self.ticket.applicant_display = str(self.ticket.applicant) + self.ticket.assignees_display = [str(assignee) for assignee in self.ticket.assignees.all()] meta_display = getattr(self, '_construct_meta_display_of_open', lambda: {})() self.ticket.meta.update(meta_display) self.ticket.save() + self._send_applied_mail_to_assignees() def _on_approve(self): meta_display = getattr(self, '_construct_meta_display_of_approve', lambda: {})() @@ -41,11 +45,12 @@ class BaseHandler(object): return method() # email + def _send_applied_mail_to_assignees(self): + logger.debug('Send applied email to assignees: {}'.format(self.ticket.assignees_display)) + send_ticket_applied_mail_to_assignees(self.ticket) + def _send_processed_mail_to_applicant(self): - msg = 'Ticket ({}) has processed, send mail to applicant ({})'.format( - self.ticket.title, self.ticket.applicant_display - ) - logger.debug(msg) + logger.debug('Send processed mail to applicant: {}'.format(self.ticket.applicant_display)) send_ticket_processed_mail_to_applicant(self.ticket) # comments @@ -54,13 +59,18 @@ class BaseHandler(object): user_display = str(user) action_display = self.ticket.get_action_display() data = { - 'body': __('User {} {} the ticket'.format(user_display, action_display)), + 'body': _('User {} {} the ticket'.format(user_display, action_display)), 'user': user, 'user_display': user_display } return self.ticket.comments.create(**data) # body + body_html_format = ''' + {}: +
{}
+ ''' + def get_body(self): old_body = self.ticket.meta.get('body') if old_body: @@ -71,25 +81,23 @@ class BaseHandler(object): return basic_body + meta_body def _construct_basic_body(self): - body = ''' - {}: - {}: {}, - {}: {}, + basic_body = '''{}: {}, {}: {}, {}: {}, {}: {}, {}: {}, {}: {} '''.format( - __("Ticket basic info"), - __('Ticket title'), self.ticket.title, - __('Ticket type'), self.ticket.get_type_display(), - __('Ticket applicant'), self.ticket.applicant_display, - __('Ticket assignees'), self.ticket.assignees_display, - __('Ticket processor'), self.ticket.processor_display, - __('Ticket action'), self.ticket.get_action_display(), - __('Ticket status'), self.ticket.get_status_display() + _('Ticket title'), self.ticket.title, + _('Ticket type'), self.ticket.get_type_display(), + _('Ticket status'), self.ticket.get_status_display(), + _('Ticket action'), self.ticket.get_action_display(), + _('Ticket applicant'), self.ticket.applicant_display, + _('Ticket assignees'), self.ticket.assignees_display, ) + if self.ticket.status_closed: + basic_body += '''{}: {}'''.format(_('Ticket processor'), self.ticket.processor_display) + body = self.body_html_format.format(_("Ticket basic info"), basic_body) return body def _construct_meta_body(self): @@ -102,21 +110,11 @@ class BaseHandler(object): return body def _base_construct_meta_body_of_open(self): - open_body = ''' - {}: - {} - '''.format( - __('Ticket applied info'), - getattr(self, '_construct_meta_body_of_open', lambda: 'No')() - ) - return open_body + meta_body_of_open = getattr(self, '_construct_meta_body_of_open', lambda: 'No')() + body = self.body_html_format.format(_('Ticket applied info'), meta_body_of_open) + return body def _base_construct_meta_body_of_approve(self): - approve_body = ''' - {}: - {} - '''.format( - __('Ticket approved info'), - getattr(self, '_construct_meta_body_of_approve', lambda: 'No')() - ) - return approve_body + meta_body_of_approve = getattr(self, '_construct_meta_body_of_approve', lambda: 'No')() + body = self.body_html_format.format(_('Ticket approved info'), meta_body_of_approve) + return body diff --git a/apps/tickets/handler/login_confirm.py b/apps/tickets/handler/login_confirm.py index 21419022f..b1d491cfc 100644 --- a/apps/tickets/handler/login_confirm.py +++ b/apps/tickets/handler/login_confirm.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as __ +from django.utils.translation import ugettext as _ from .base import BaseHandler @@ -13,8 +13,8 @@ class Handler(BaseHandler): {}: {}, {}: {} '''.format( - __("Applied login IP"), apply_login_ip, - __("Applied login city"), apply_login_city, - __("Applied login datetime"), apply_login_datetime, + _("Applied login IP"), apply_login_ip, + _("Applied login city"), apply_login_city, + _("Applied login datetime"), apply_login_datetime, ) return applied_body diff --git a/apps/tickets/models/ticket.py b/apps/tickets/models/ticket.py index 97d99e4b3..41d23c8b0 100644 --- a/apps/tickets/models/ticket.py +++ b/apps/tickets/models/ticket.py @@ -148,35 +148,9 @@ class Ticket(CommonModelMixin, OrgModelMixin): @classmethod def get_user_related_tickets(cls, user): - queries = None - tickets = cls.all() - if user.is_superuser: - pass - elif user.is_super_auditor: - pass - elif user.is_org_admin: - admin_orgs_id = [ - str(org_id) for org_id in user.admin_orgs.values_list('id', flat=True) - ] - assigned_tickets_id = [ - str(ticket_id) for ticket_id in user.assigned_tickets.values_list('id', flat=True) - ] - queries = Q(applicant=user) - queries |= Q(processor=user) - queries |= Q(org_id__in=admin_orgs_id) - queries |= Q(id__in=assigned_tickets_id) - elif user.is_org_auditor: - audit_orgs_id = [ - str(org_id) for org_id in user.audit_orgs.values_list('id', flat=True) - ] - queries = Q(org_id__in=audit_orgs_id) - elif user.is_common_user: - queries = Q(applicant=user) - else: - tickets = cls.objects.none() - if queries: - tickets = tickets.filter(queries) - return tickets.distinct() + queries = Q(applicant=user) | Q(assignees=user) + tickets = cls.all().filter(queries).distinct() + return tickets @classmethod def all(cls): diff --git a/apps/tickets/permissions/ticket.py b/apps/tickets/permissions/ticket.py index c16db9fe6..dbc74e6a9 100644 --- a/apps/tickets/permissions/ticket.py +++ b/apps/tickets/permissions/ticket.py @@ -7,6 +7,12 @@ class IsAssignee(permissions.BasePermission): return obj.has_assignee(request.user) +class IsAssigneeOrApplicant(IsAssignee): + + def has_object_permission(self, request, view, obj): + return super().has_object_permission(request, view, obj) or obj.applicant == request.user + + class NotClosed(permissions.BasePermission): def has_object_permission(self, request, view, obj): return not obj.status_closed diff --git a/apps/tickets/serializers/ticket/meta/meta.py b/apps/tickets/serializers/ticket/meta/meta.py index 1b3c071a8..b423805f4 100644 --- a/apps/tickets/serializers/ticket/meta/meta.py +++ b/apps/tickets/serializers/ticket/meta/meta.py @@ -29,5 +29,6 @@ type_serializer_classes_mapping = { const.TicketTypeChoices.login_confirm.value: { 'default': login_confirm.LoginConfirmSerializer, action_open: login_confirm.ApplySerializer, + action_approve: login_confirm.LoginConfirmSerializer(read_only=True), } } diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/apply_application.py b/apps/tickets/serializers/ticket/meta/ticket_type/apply_application.py index 220c04199..4b5bb00e2 100644 --- a/apps/tickets/serializers/ticket/meta/ticket_type/apply_application.py +++ b/apps/tickets/serializers/ticket/meta/ticket_type/apply_application.py @@ -1,11 +1,13 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ from django.db.models import Q +from perms.models import ApplicationPermission from applications.models import Application from applications.const import ApplicationCategoryChoices, ApplicationTypeChoices from assets.models import SystemUser from orgs.utils import tmp_to_org from tickets.models import Ticket +from .common import DefaultPermissionName __all__ = [ 'ApplyApplicationSerializer', 'ApplySerializer', 'ApproveSerializer', @@ -47,6 +49,9 @@ class ApplySerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer): # 审批信息 + approve_permission_name = serializers.CharField( + max_length=128, default=DefaultPermissionName(), label=_('Permission name') + ) approve_applications = serializers.ListField( required=True, child=serializers.UUIDField(), label=_('Approve applications'), allow_null=True @@ -72,6 +77,19 @@ class ApproveSerializer(serializers.Serializer): required=True, label=_('Date expired'), allow_null=True ) + def validate_approve_permission_name(self, permission_name): + if not isinstance(self.root.instance, Ticket): + return permission_name + + with tmp_to_org(self.root.instance.org_id): + already_exists = ApplicationPermission.objects.filter(name=permission_name).exists() + if not already_exists: + return permission_name + + raise serializers.ValidationError(_( + 'Permission named `{}` already exists'.format(permission_name) + )) + def validate_approve_applications(self, approve_applications): if not isinstance(self.root.instance, Ticket): return [] @@ -118,6 +136,9 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer): return [] apply_application_group = value.get('apply_application_group', []) + if not apply_application_group: + return [] + apply_type = value.get('apply_type') queries = Q() for application in apply_application_group: @@ -133,8 +154,11 @@ class ApplyApplicationSerializer(ApplySerializer, ApproveSerializer): if not isinstance(self.root.instance, Ticket): return [] - apply_type = value.get('apply_type') apply_system_user_group = value.get('apply_system_user_group', []) + if not apply_system_user_group: + return [] + + apply_type = value.get('apply_type') protocol = SystemUser.get_protocol_by_application_type(apply_type) queries = Q() for system_user in apply_system_user_group: diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py b/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py index ab613e19c..acdd217e1 100644 --- a/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py +++ b/apps/tickets/serializers/ticket/meta/ticket_type/apply_asset.py @@ -2,9 +2,11 @@ from django.utils.translation import ugettext_lazy as _ from django.db.models import Q from rest_framework import serializers from perms.serializers import ActionsField +from perms.models import AssetPermission from assets.models import Asset, SystemUser from orgs.utils import tmp_to_org from tickets.models import Ticket +from .common import DefaultPermissionName __all__ = [ @@ -44,6 +46,9 @@ class ApplySerializer(serializers.Serializer): class ApproveSerializer(serializers.Serializer): # 审批信息 + approve_permission_name = serializers.CharField( + max_length=128, default=DefaultPermissionName(), label=_('Permission name') + ) approve_assets = serializers.ListField( required=True, allow_null=True, child=serializers.UUIDField(), label=_('Approve assets') ) @@ -76,6 +81,19 @@ class ApproveSerializer(serializers.Serializer): required=True, label=_('Date expired'), allow_null=True ) + def validate_approve_permission_name(self, permission_name): + if not isinstance(self.root.instance, Ticket): + return permission_name + + with tmp_to_org(self.root.instance.org_id): + already_exists = AssetPermission.objects.filter(name=permission_name).exists() + if not already_exists: + return permission_name + + raise serializers.ValidationError(_( + 'Permission named `{}` already exists'.format(permission_name) + )) + def validate_approve_assets(self, approve_assets): if not isinstance(self.root.instance, Ticket): return [] @@ -118,10 +136,13 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer): apply_ip_group = value.get('apply_ip_group', []) apply_hostname_group = value.get('apply_hostname_group', []) - queries = Q(ip__in=apply_ip_group) + queries = Q() + if apply_ip_group: + queries |= Q(ip__in=apply_ip_group) for hostname in apply_hostname_group: queries |= Q(hostname__icontains=hostname) - + if not queries: + return [] with tmp_to_org(self.root.instance.org_id): assets_id = Asset.objects.filter(queries).values_list('id', flat=True)[:5] assets_id = [str(asset_id) for asset_id in assets_id] @@ -132,6 +153,9 @@ class ApplyAssetSerializer(ApplySerializer, ApproveSerializer): return [] apply_system_user_group = value.get('apply_system_user_group', []) + if not apply_system_user_group: + return [] + queries = Q() for system_user in apply_system_user_group: queries |= Q(username__icontains=system_user) diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/common.py b/apps/tickets/serializers/ticket/meta/ticket_type/common.py new file mode 100644 index 000000000..2d43f6a81 --- /dev/null +++ b/apps/tickets/serializers/ticket/meta/ticket_type/common.py @@ -0,0 +1,30 @@ +from django.utils.translation import ugettext as _ +from tickets.models import Ticket + + +__all__ = ['DefaultPermissionName', 'get_default_permission_name'] + + +def get_default_permission_name(ticket): + name = '' + if isinstance(ticket, Ticket): + name = _('Created by ticket ({}-{})').format(ticket.title, str(ticket.id)[:4]) + return name + + +class DefaultPermissionName(object): + default = None + + @staticmethod + def _construct_default_permission_name(serializer_field): + permission_name = '' + ticket = serializer_field.root.instance + if isinstance(ticket, Ticket): + permission_name = get_default_permission_name(ticket) + return permission_name + + def set_context(self, serializer_field): + self.default = self._construct_default_permission_name(serializer_field) + + def __call__(self): + return self.default diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index b6ffc5140..c28b9a9f1 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -16,9 +16,13 @@ __all__ = [ class TicketSerializer(OrgResourceModelSerializerMixin): - type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type')) - action_display = serializers.ReadOnlyField(source='get_action_display', label=_('Action')) - status_display = serializers.ReadOnlyField(source='get_status_display', label=_('Status')) + type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) + action_display = serializers.ReadOnlyField( + source='get_action_display', label=_('Action display') + ) + status_display = serializers.ReadOnlyField( + source='get_status_display', label=_('Status display') + ) meta = MethodSerializer() class Meta: @@ -34,31 +38,34 @@ class TicketSerializer(OrgResourceModelSerializerMixin): ] def get_meta_serializer(self): - request = self.context['request'] - default_serializer_class = serializers.Serializer + default_serializer = serializers.Serializer(read_only=True) if isinstance(self.instance, Ticket): _type = self.instance.type else: - _type = request.query_params.get('type') + _type = self.context['request'].query_params.get('type') - if not _type: - return default_serializer_class() + if _type: + action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type) + if action_serializer_classes_mapping: + query_action = self.context['request'].query_params.get('action') + action = query_action if query_action else self.context['view'].action + serializer_class = action_serializer_classes_mapping.get(action) + if not serializer_class: + serializer_class = action_serializer_classes_mapping.get('default') + else: + serializer_class = default_serializer + else: + serializer_class = default_serializer - action_serializer_classes_mapping = type_serializer_classes_mapping.get(_type) - if not action_serializer_classes_mapping: - return default_serializer_class() + if not serializer_class: + serializer_class = default_serializer - query_action = request.query_params.get('action') - _action = query_action if query_action else self.context['view'].action - serializer_class = action_serializer_classes_mapping.get(_action) - if serializer_class: - return serializer_class() + if isinstance(serializer_class, type): + serializer = serializer_class() + else: + serializer = serializer_class - serializer_class = action_serializer_classes_mapping.get('default') - if serializer_class: - return serializer_class() - - return default_serializer_class() + return serializer class TicketDisplaySerializer(TicketSerializer): diff --git a/apps/tickets/signals_handler/ticket.py b/apps/tickets/signals_handler/ticket.py index 7e8bc8d5d..aae620295 100644 --- a/apps/tickets/signals_handler/ticket.py +++ b/apps/tickets/signals_handler/ticket.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- # from django.dispatch import receiver -from django.db.models.signals import m2m_changed from common.utils import get_logger from tickets.models import Ticket -from tickets.utils import send_ticket_applied_mail_to_assignees from ..signals import post_change_ticket_action @@ -15,19 +13,3 @@ logger = get_logger(__name__) @receiver(post_change_ticket_action, sender=Ticket) def on_post_change_ticket_action(sender, ticket, action, **kwargs): ticket.handler.dispatch(action) - - -@receiver(m2m_changed, sender=Ticket.assignees.through) -def on_ticket_assignees_changed(sender, instance, action, reverse, model, pk_set, **kwargs): - if reverse: - return - if action != 'post_add': - return - logger.debug('Receives ticket and assignees changed signal, ticket: {}'.format(instance.title)) - instance.assignees_display = [str(assignee) for assignee in instance.assignees.all()] - instance.save() - assignees = model.objects.filter(pk__in=pk_set) - assignees_display = [str(assignee) for assignee in assignees] - logger.debug('Send applied email to assignees: {}'.format(assignees_display)) - send_ticket_applied_mail_to_assignees(instance, assignees) - diff --git a/apps/tickets/utils.py b/apps/tickets/utils.py index ebd4c22b8..1bd789fda 100644 --- a/apps/tickets/utils.py +++ b/apps/tickets/utils.py @@ -10,37 +10,39 @@ from . import const logger = get_logger(__file__) +EMAIL_TEMPLATE = ''' +
+

+ {title} + + {ticket_detail_url_description} + +

+
+ {body} +
+
+''' -def send_ticket_applied_mail_to_assignees(ticket, assignees): - if not assignees: + +def send_ticket_applied_mail_to_assignees(ticket): + if not ticket.assignees: logger.debug("Not found assignees, ticket: {}({}), assignees: {}".format( - ticket, str(ticket.id), assignees) + ticket, str(ticket.id), ticket.assignees) ) return - subject = _('New Ticket: {} ({})'.format(ticket.title, ticket.get_type_display())) - ticket_detail_url = urljoin( - settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id)) - ) - message = _( - """
-

Your has a new ticket

-
- Ticket: -
- {body} -
- click here to review -
-
- """.format( - body=ticket.body.replace('\n', '
'), - ticket_detail_url=ticket_detail_url - ) + ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id))) + subject = _('New Ticket - {} ({})').format(ticket.title, ticket.get_type_display()) + message = EMAIL_TEMPLATE.format( + title=_('Your has a new ticket, applicant - {}').format(str(ticket.applicant_display)), + ticket_detail_url=ticket_detail_url, + ticket_detail_url_description=_('click here to review'), + body=ticket.body.replace('\n', '
'), ) if settings.DEBUG: logger.debug(message) - recipient_list = [assignee.email for assignee in assignees] + recipient_list = [assignee.email for assignee in ticket.assignees.all()] send_mail_async.delay(subject, message, recipient_list, html_message=message) @@ -48,21 +50,14 @@ def send_ticket_processed_mail_to_applicant(ticket): if not ticket.applicant: logger.error("Not found applicant: {}({})".format(ticket.title, ticket.id)) return - subject = _('Ticket has processed: {} ({})').format(ticket.title, ticket.get_type_display()) - message = _( - """ -
-

Your ticket has been processed

-
- Ticket: -
- {body} -
-
-
- """.format( - body=ticket.body.replace('\n', '
'), - ) + + ticket_detail_url = urljoin(settings.SITE_URL, const.TICKET_DETAIL_URL.format(id=str(ticket.id))) + subject = _('Ticket has processed - {} ({})').format(ticket.title, ticket.processor_display) + message = EMAIL_TEMPLATE.format( + title=_('Your ticket has been processed, processor - {}').format(ticket.processor_display), + ticket_detail_url=ticket_detail_url, + ticket_detail_url_description=_('click here to review'), + body=ticket.body.replace('\n', '
'), ) if settings.DEBUG: logger.debug(message) diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index 50897e112..c5fa09eb5 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -48,7 +48,7 @@ class UserForgotPasswordView(FormView): if not user.is_local: error = _( - 'The user is from A, please go to the corresponding system to change the password' + 'The user is from {}, please go to the corresponding system to change the password' ''.format(user.get_source_display()) ) form.add_error('email', error) diff --git a/data/caution.txt b/data/caution.txt new file mode 100644 index 000000000..4e85670a1 --- /dev/null +++ b/data/caution.txt @@ -0,0 +1,2 @@ + 你想偷看啥 !!! + What are you trying to peek at !!! \ No newline at end of file