From c4727e1eba8538a69983c20e587eb80e8330f14c Mon Sep 17 00:00:00 2001 From: "fghbng@qq.com" <fghbng@qq.com> Date: Sun, 25 Apr 2021 14:58:06 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=E3=80=90=E4=BB=AA=E8=A1=A8=E7=9B=98?= =?UTF-8?q?=E3=80=91=E5=9C=A8=E7=BA=BF=E7=94=A8=E6=88=B7=E6=95=B0=E4=B8=8D?= =?UTF-8?q?=E5=AF=B9=EF=BC=8C=EF=BC=88=E8=BF=9E=E4=B8=8Awindows=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E4=B9=8B=E5=90=8E=EF=BC=8C=E5=9C=A8=E7=BA=BF=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=95=B0=E5=B0=B1=E4=B8=8D=E5=AF=B9=E4=BA=86=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/api.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index bda2537c8..0f0193c49 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -99,7 +99,7 @@ class DatesLoginMetricMixin: if count is not None: return count ds, de = self.get_date_start_2_end(date) - count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('user', flat=True))) + count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('user_id', flat=True))) self.__set_data_to_cache(date, tp, count) return count @@ -129,7 +129,7 @@ class DatesLoginMetricMixin: @lazyproperty def dates_total_count_active_users(self): - count = len(set(self.sessions_queryset.values_list('user', flat=True))) + count = len(set(self.sessions_queryset.values_list('user_id', flat=True))) return count @lazyproperty @@ -161,10 +161,10 @@ class DatesLoginMetricMixin: @lazyproperty def dates_total_count_disabled_assets(self): return Asset.objects.filter(is_active=False).count() - + # 以下是从week中而来 def get_dates_login_times_top5_users(self): - users = self.sessions_queryset.values_list('user', flat=True) + users = self.sessions_queryset.values_list('user_id', flat=True) users = [ {'user': user, 'total': total} for user, total in Counter(users).most_common(5) @@ -172,7 +172,7 @@ class DatesLoginMetricMixin: return users def get_dates_total_count_login_users(self): - return len(set(self.sessions_queryset.values_list('user', flat=True))) + return len(set(self.sessions_queryset.values_list('user_id', flat=True))) def get_dates_total_count_login_times(self): return self.sessions_queryset.count() @@ -186,8 +186,8 @@ class DatesLoginMetricMixin: return list(assets) def get_dates_login_times_top10_users(self): - users = self.sessions_queryset.values("user") \ - .annotate(total=Count("user")) \ + users = self.sessions_queryset.values("user_id") \ + .annotate(total=Count("user_id")) \ .annotate(last=Max("date_start")).order_by("-total")[:10] for user in users: user['last'] = str(user['last']) @@ -221,7 +221,7 @@ class TotalCountMixin: @staticmethod def get_total_count_online_users(): - count = len(set(Session.objects.filter(is_finished=False).values_list('user', flat=True))) + count = len(set(Session.objects.filter(is_finished=False).values_list('user_id', flat=True))) return count @staticmethod From 99cce185dd01a931181067af6a3e0a3ab9dabeca Mon Sep 17 00:00:00 2001 From: xinwen <coderWen@126.com> Date: Tue, 27 Apr 2021 14:21:48 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=A4=B1=E6=95=88=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/expire_caches.py | 19 +++++++++++++++++++ jms | 9 +++++++++ 2 files changed, 28 insertions(+) create mode 100644 apps/common/management/commands/expire_caches.py diff --git a/apps/common/management/commands/expire_caches.py b/apps/common/management/commands/expire_caches.py new file mode 100644 index 000000000..fb09f47eb --- /dev/null +++ b/apps/common/management/commands/expire_caches.py @@ -0,0 +1,19 @@ +from django.core.management.base import BaseCommand + +from assets.signals_handler.node_assets_mapping import expire_node_assets_mapping_for_memory +from orgs.models import Organization + + +def expire_node_assets_mapping(): + org_ids = Organization.objects.all().values_list('id', flat=True) + org_ids = [*org_ids, '00000000-0000-0000-0000-000000000000'] + + for org_id in org_ids: + expire_node_assets_mapping_for_memory(org_id) + + +class Command(BaseCommand): + help = 'Expire caches' + + def handle(self, *args, **options): + expire_node_assets_mapping() diff --git a/jms b/jms index 24b71f4e4..6bd71849d 100755 --- a/jms +++ b/jms @@ -97,6 +97,14 @@ def check_migrations(): # sys.exit(1) +def expire_caches(): + apps_dir = os.path.join(BASE_DIR, 'apps') + code = subprocess.call("python manage.py expire_caches", shell=True, cwd=apps_dir) + + if code == 1: + return + + def perform_db_migrate(): logging.info("Check database structure change ...") os.chdir(os.path.join(BASE_DIR, 'apps')) @@ -116,6 +124,7 @@ def prepare(): check_database_connection() check_migrations() upgrade_db() + expire_caches() def check_pid(pid): From e9b174f342195268b5dbd8c5e9e27b64db13ac2a Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Mon, 26 Apr 2021 15:33:51 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E8=BF=87=E6=BB=A4=E8=A7=84=E5=88=99Model:=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0Action-reconfirm;=20=E6=B7=BB=E5=8A=A0field-reviewers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0070_auto_20210426_1515.py | 25 +++++++++++++++++++ apps/assets/models/cmd_filter.py | 25 ++++++++++++------- apps/assets/models/user.py | 4 +-- apps/assets/serializers/cmd_filter.py | 2 +- 4 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 apps/assets/migrations/0070_auto_20210426_1515.py diff --git a/apps/assets/migrations/0070_auto_20210426_1515.py b/apps/assets/migrations/0070_auto_20210426_1515.py new file mode 100644 index 000000000..ca6ff4273 --- /dev/null +++ b/apps/assets/migrations/0070_auto_20210426_1515.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1 on 2021-04-26 07:15 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0069_change_node_key0_to_key1'), + ] + + operations = [ + migrations.AddField( + model_name='commandfilterrule', + name='reviewers', + field=models.ManyToManyField(blank=True, related_name='review_cmd_filter_rules', to=settings.AUTH_USER_MODEL, verbose_name='Reviewers'), + ), + migrations.AlterField( + model_name='commandfilterrule', + name='action', + field=models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow'), (2, 'Reconfirm')], default=0, verbose_name='Action'), + ), + ] diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index c1242fd7e..e0826da5d 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -41,11 +41,12 @@ class CommandFilterRule(OrgModelMixin): (TYPE_COMMAND, _('Command')), ) - ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3) - ACTION_CHOICES = ( - (ACTION_DENY, _('Deny')), - (ACTION_ALLOW, _('Allow')), - ) + ACTION_UNKNOWN = 10 + + class ActionChoices(models.IntegerChoices): + deny = 0, _('Deny') + allow = 1, _('Allow') + confirm = 2, _('Reconfirm') id = models.UUIDField(default=uuid.uuid4, primary_key=True) filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules') @@ -53,7 +54,13 @@ class CommandFilterRule(OrgModelMixin): priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the lower the value will be match first"), validators=[MinValueValidator(1), MaxValueValidator(100)]) content = models.TextField(verbose_name=_("Content"), help_text=_("One line one command")) - action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action")) + action = models.IntegerField(default=ActionChoices.deny, choices=ActionChoices.choices, verbose_name=_("Action")) + # 动作: 附加字段 + # - confirm: 命令复核人 + reviewers = models.ManyToManyField( + 'users.User', related_name='review_cmd_filter_rules', blank=True, + verbose_name=_("Reviewers") + ) comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment")) date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) @@ -89,10 +96,10 @@ class CommandFilterRule(OrgModelMixin): if not found: return self.ACTION_UNKNOWN, '' - if self.action == self.ACTION_ALLOW: - return self.ACTION_ALLOW, found.group() + if self.action == self.ActionChoices.allow: + return self.ActionChoices.allow, found.group() else: - return self.ACTION_DENY, found.group() + return self.ActionChoices.deny, found.group() def __str__(self): return '{} % {}'.format(self.type, self.content) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 1640c7f32..37af8ea86 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -196,9 +196,9 @@ class SystemUser(BaseUser): def is_command_can_run(self, command): for rule in self.cmd_filter_rules: action, matched_cmd = rule.match(command) - if action == rule.ACTION_ALLOW: + if action == rule.ActionChoices.allow: return True, None - elif action == rule.ACTION_DENY: + elif action == rule.ActionChoices.deny: return False, matched_cmd return True, None diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 0c8eca3d4..e7059d04a 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -34,7 +34,7 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): fields_mini = ['id'] fields_small = fields_mini + [ 'type', 'type_display', 'content', 'priority', - 'action', 'action_display', + 'action', 'action_display', 'reviewers', 'comment', 'created_by', 'date_created', 'date_updated' ] fields_fk = ['filter'] From 50918a3dd2b9e4c5684db1ff4d7d09e0917e9b66 Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Mon, 26 Apr 2021 17:56:06 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=8D=E6=A0=B8=E9=80=BB=E8=BE=91;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=A4=8D=E6=A0=B8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/acls/api/login_asset_check.py | 36 ++--------- apps/assets/api/cmd_filter.py | 63 ++++++++++++++++++- apps/assets/models/cmd_filter.py | 21 +++++++ apps/assets/serializers/cmd_filter.py | 35 +++++++++++ apps/assets/urls/api_urls.py | 3 + apps/terminal/models/session.py | 5 ++ apps/tickets/api/__init__.py | 1 + apps/tickets/api/common.py | 44 +++++++++++++ apps/tickets/const.py | 1 + apps/tickets/handler/command_confirm.py | 30 +++++++++ .../migrations/0009_auto_20210426_1720.py | 18 ++++++ .../meta/ticket_type/command_confirm.py | 25 ++++++++ 12 files changed, 248 insertions(+), 34 deletions(-) create mode 100644 apps/tickets/api/common.py create mode 100644 apps/tickets/handler/command_confirm.py create mode 100644 apps/tickets/migrations/0009_auto_20210426_1720.py create mode 100644 apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index ed525e6cf..d689675b0 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -5,7 +5,7 @@ from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView from common.permissions import IsAppUser from common.utils import reverse, lazyproperty from orgs.utils import tmp_to_org, tmp_to_root_org -from tickets.models import Ticket +from tickets.api import GenericTicketStatusRetrieveCloseAPI from ..models import LoginAssetACL from .. import serializers @@ -48,7 +48,7 @@ class LoginAssetCheckAPI(CreateAPIView): org_id=self.serializer.org.id ) confirm_status_url = reverse( - view_name='acls:login-asset-confirm-status', + view_name='api-acls:login-asset-confirm-status', kwargs={'pk': str(ticket.id)} ) ticket_detail_url = reverse( @@ -72,34 +72,6 @@ class LoginAssetCheckAPI(CreateAPIView): return serializer -class LoginAssetConfirmStatusAPI(RetrieveDestroyAPIView): - permission_classes = (IsAppUser, ) +class LoginAssetConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI): + pass - def retrieve(self, request, *args, **kwargs): - if self.ticket.action_open: - status = 'await' - elif self.ticket.action_approve: - status = 'approve' - else: - status = 'reject' - data = { - 'status': status, - 'action': self.ticket.action, - 'processor': self.ticket.processor_display - } - return Response(data=data, status=200) - - def destroy(self, request, *args, **kwargs): - if self.ticket.status_open: - self.ticket.close(processor=self.ticket.applicant) - data = { - 'action': self.ticket.action, - 'status': self.ticket.status, - 'processor': self.ticket.processor_display - } - return Response(data=data, status=200) - - @lazyproperty - def ticket(self): - with tmp_to_root_org(): - return get_object_or_404(Ticket, pk=self.kwargs['pk']) diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index 95aac8af9..c7ffb792f 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -1,15 +1,25 @@ # -*- coding: utf-8 -*- # +from rest_framework.response import Response +from rest_framework.generics import CreateAPIView, RetrieveDestroyAPIView from django.shortcuts import get_object_or_404 +from common.utils import reverse +from common.utils import lazyproperty from orgs.mixins.api import OrgBulkModelViewSet -from ..hands import IsOrgAdmin +from orgs.utils import tmp_to_root_org +from tickets.models import Ticket +from tickets.api import GenericTicketStatusRetrieveCloseAPI +from ..hands import IsOrgAdmin, IsAppUser from ..models import CommandFilter, CommandFilterRule from .. import serializers -__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet'] +__all__ = [ + 'CommandFilterViewSet', 'CommandFilterRuleViewSet', 'CommandConfirmAPI', + 'CommandConfirmStatusAPI' +] class CommandFilterViewSet(OrgBulkModelViewSet): @@ -35,3 +45,52 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet): return cmd_filter.rules.all() +class CommandConfirmAPI(CreateAPIView): + permission_classes = () + # permission_classes = (IsAppUser, ) + serializer_class = serializers.CommandConfirmSerializer + + def create(self, request, *args, **kwargs): + ticket = self.create_command_confirm_ticket() + response_data = self.get_response_data(ticket) + return Response(data=response_data, status=200) + + def create_command_confirm_ticket(self): + ticket = self.serializer.cmd_filter_rule.create_command_confirm_ticket( + run_command=self.serializer.data.get('run_command'), + session=self.serializer.session, + cmd_filter_rule=self.serializer.cmd_filter_rule, + org_id=self.serializer.org.id + ) + return ticket + + @staticmethod + def get_response_data(ticket): + confirm_status_url = reverse( + view_name='api-assets:command-confirm-status', + kwargs={'pk': str(ticket.id)} + ) + ticket_detail_url = reverse( + view_name='api-tickets:ticket-detail', + kwargs={'pk': str(ticket.id)}, + external=True, api_to_ui=True + ) + ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type) + return { + 'check_confirm_status': {'method': 'GET', 'url': confirm_status_url}, + 'close_confirm': {'method': 'DELETE', 'url': confirm_status_url}, + 'ticket_detail_url': ticket_detail_url, + 'reviewers': [str(user) for user in ticket.assignees.all()] + } + + @lazyproperty + def serializer(self): + serializer = self.get_serializer(data=self.request.data) + serializer.is_valid(raise_exception=True) + return serializer + + +class CommandConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI): + permission_classes = () + pass + diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index e0826da5d..dff99298c 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -103,3 +103,24 @@ class CommandFilterRule(OrgModelMixin): def __str__(self): return '{} % {}'.format(self.type, self.content) + + def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): + from tickets.const import TicketTypeChoices + from tickets.models import Ticket + data = { + 'title': _('Command confirm') + ' ({})'.format(session.user), + 'type': TicketTypeChoices.command_confirm, + 'meta': { + 'apply_run_user': session.user, + 'apply_run_asset': session.asset, + 'apply_run_system_user': session.system_user, + 'apply_run_command': run_command, + 'apply_from_session': str(session.id), + 'apply_from_cmd_filter_rule': str(cmd_filter_rule.id), + }, + 'org_id': org_id, + } + ticket = Ticket.objects.create(**data) + ticket.assignees.set(self.reviewers.all()) + ticket.open(applicant=session.user_obj) + return ticket diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index e7059d04a..2e60b31d7 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -6,6 +6,9 @@ from rest_framework import serializers from common.drf.serializers import AdaptedBulkListSerializer from ..models import CommandFilter, CommandFilterRule, SystemUser from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from orgs.utils import tmp_to_root_org +from common.utils import get_object_or_none, lazyproperty +from terminal.models import Session class CommandFilterSerializer(BulkOrgResourceModelSerializer): @@ -50,3 +53,35 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): # msg = _("Content should not be contain: {}").format(invalid_char) # raise serializers.ValidationError(msg) # return content + + +class CommandConfirmSerializer(serializers.Serializer): + session_id = serializers.UUIDField(required=True, allow_null=False) + cmd_filter_rule_id = serializers.UUIDField(required=True, allow_null=False) + run_command = serializers.CharField(required=True, allow_null=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.session = None + self.cmd_filter_rule = None + + def validate_session_id(self, session_id): + self.session = self.validate_object_exist(Session, session_id) + return session_id + + def validate_cmd_filter_rule_id(self, cmd_filter_rule_id): + self.cmd_filter_rule = self.validate_object_exist(CommandFilterRule, cmd_filter_rule_id) + return cmd_filter_rule_id + + @staticmethod + def validate_object_exist(model, field_id): + with tmp_to_root_org(): + obj = get_object_or_none(model, id=field_id) + if not obj: + error = '{} Model object does not exist'.format(model.__name__) + raise serializers.ValidationError(error) + return obj + + @lazyproperty + def org(self): + return self.session.org diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 5a5e6d803..c8413f83e 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -63,6 +63,9 @@ urlpatterns = [ path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'), + path('cmd-filters/command-confirm/', api.CommandConfirmAPI.as_view(), name='command-confirm'), + path('cmd-filters/command-confirm/<uuid:pk>/status/', api.CommandConfirmStatusAPI.as_view(), name='command-confirm-status') + ] old_version_urlpatterns = [ diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 89e338143..6d85759af 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -11,6 +11,7 @@ from django.core.files.storage import default_storage from django.core.cache import cache from assets.models import Asset +from users.models import User from orgs.mixins.models import OrgModelMixin from common.db.models import ChoiceSet from ..backends import get_multi_command_storage @@ -79,6 +80,10 @@ class Session(OrgModelMixin): def asset_obj(self): return Asset.objects.get(id=self.asset_id) + @property + def user_obj(self): + return User.objects.get(id=self.user_id) + @property def _date_start_first_has_replay_rdp_session(self): if self.__class__._DATE_START_FIRST_HAS_REPLAY_RDP_SESSION is None: diff --git a/apps/tickets/api/__init__.py b/apps/tickets/api/__init__.py index 6b519ef80..a6b5e39c6 100644 --- a/apps/tickets/api/__init__.py +++ b/apps/tickets/api/__init__.py @@ -3,3 +3,4 @@ from .ticket import * from .assignee import * from .comment import * +from .common import * diff --git a/apps/tickets/api/common.py b/apps/tickets/api/common.py new file mode 100644 index 000000000..fe5a5d1e9 --- /dev/null +++ b/apps/tickets/api/common.py @@ -0,0 +1,44 @@ +from django.shortcuts import get_object_or_404 +from rest_framework.response import Response +from rest_framework.generics import RetrieveDestroyAPIView + +from common.permissions import IsAppUser +from common.utils import lazyproperty +from orgs.utils import tmp_to_root_org +from ..models import Ticket + + +__all__ = ['GenericTicketStatusRetrieveCloseAPI'] + + +class GenericTicketStatusRetrieveCloseAPI(RetrieveDestroyAPIView): + permission_classes = (IsAppUser, ) + + def retrieve(self, request, *args, **kwargs): + if self.ticket.action_open: + status = 'await' + elif self.ticket.action_approve: + status = 'approve' + else: + status = 'reject' + data = { + 'status': status, + 'action': self.ticket.action, + 'processor': self.ticket.processor_display + } + return Response(data=data, status=200) + + def destroy(self, request, *args, **kwargs): + if self.ticket.status_open: + self.ticket.close(processor=self.ticket.applicant) + data = { + 'action': self.ticket.action, + 'status': self.ticket.status, + 'processor': self.ticket.processor_display + } + return Response(data=data, status=200) + + @lazyproperty + def ticket(self): + with tmp_to_root_org(): + return get_object_or_404(Ticket, pk=self.kwargs['pk']) diff --git a/apps/tickets/const.py b/apps/tickets/const.py index 591ead607..3397353d4 100644 --- a/apps/tickets/const.py +++ b/apps/tickets/const.py @@ -10,6 +10,7 @@ class TicketTypeChoices(TextChoices): apply_asset = 'apply_asset', _('Apply for asset') apply_application = 'apply_application', _('Apply for application') login_asset_confirm = 'login_asset_confirm', _('Login asset confirm') + command_confirm = 'command_confirm', _('Command confirm') class TicketActionChoices(TextChoices): diff --git a/apps/tickets/handler/command_confirm.py b/apps/tickets/handler/command_confirm.py new file mode 100644 index 000000000..33d6739e3 --- /dev/null +++ b/apps/tickets/handler/command_confirm.py @@ -0,0 +1,30 @@ +from django.utils.translation import ugettext as _ +from .base import BaseHandler + + +class Handler(BaseHandler): + + # body + def _construct_meta_body_of_open(self): + apply_run_user = self.ticket.meta.get('apply_run_user') + apply_run_asset = self.ticket.meta.get('apply_run_asset') + apply_run_system_user = self.ticket.meta.get('apply_run_system_user') + apply_run_command = self.ticket.meta.get('apply_run_command') + apply_from_session = self.ticket.meta.get('apply_from_session') + apply_from_cmd_filter_rule = self.ticket.meta.get('apply_from_cmd_filter_rule') + + applied_body = '''{}: {}, + {}: {}, + {}: {}, + {}: {}, + {}: {}, + {}: {}, + '''.format( + _("Applied run user"), apply_run_user, + _("Applied run asset"), apply_run_asset, + _("Applied run system user"), apply_run_system_user, + _("Applied run command"), apply_run_command, + _("Applied from session"), apply_from_session, + _("Applied from command filter rules"), apply_from_cmd_filter_rule, + ) + return applied_body diff --git a/apps/tickets/migrations/0009_auto_20210426_1720.py b/apps/tickets/migrations/0009_auto_20210426_1720.py new file mode 100644 index 000000000..e584c2560 --- /dev/null +++ b/apps/tickets/migrations/0009_auto_20210426_1720.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2021-04-26 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0008_auto_20210311_1113'), + ] + + operations = [ + migrations.AlterField( + model_name='ticket', + name='type', + field=models.CharField(choices=[('general', 'General'), ('login_confirm', 'Login confirm'), ('apply_asset', 'Apply for asset'), ('apply_application', 'Apply for application'), ('login_asset_confirm', 'Login asset confirm'), ('command_confirm', 'Command confirm')], default='general', max_length=64, verbose_name='Type'), + ), + ] diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py new file mode 100644 index 000000000..92c72f920 --- /dev/null +++ b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py @@ -0,0 +1,25 @@ +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + + +__all__ = [ + 'ApplySerializer', 'CommandConfirmSerializer', +] + + +class ApplySerializer(serializers.Serializer): + # 申请信息 + apply_run_user = serializers.CharField(required=True, label=_('Run user')) + apply_run_asset = serializers.CharField(required=True, label=_('Run asset')) + apply_run_system_user = serializers.CharField( + required=True, max_length=64, label=_('Run system user') + ) + apply_run_command = serializers.CharField(required=True, label=_('Run command')) + apply_from_session = serializers.UUIDField(required=False, label=_('From session')) + apply_from_cmd_filter_rule = serializers.UUIDField( + required=False, label=_('From cmd filter rule') + ) + + +class CommandConfirmSerializer(ApplySerializer): + pass From 5a3c67989b7ff58f81e7830f90573494661a7a46 Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Mon, 26 Apr 2021 18:52:25 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=8D=E6=A0=B8=E9=80=BB=E8=BE=91;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=A4=8D=E6=A0=B8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?;=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/cmd_filter.py | 4 ++-- apps/tickets/handler/command_confirm.py | 8 ++++---- apps/tickets/serializers/ticket/meta/meta.py | 9 ++++++++- .../ticket/meta/ticket_type/command_confirm.py | 4 ++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index dff99298c..72547da19 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -115,8 +115,8 @@ class CommandFilterRule(OrgModelMixin): 'apply_run_asset': session.asset, 'apply_run_system_user': session.system_user, 'apply_run_command': run_command, - 'apply_from_session': str(session.id), - 'apply_from_cmd_filter_rule': str(cmd_filter_rule.id), + 'apply_from_session_id': str(session.id), + 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), }, 'org_id': org_id, } diff --git a/apps/tickets/handler/command_confirm.py b/apps/tickets/handler/command_confirm.py index 33d6739e3..0e1fd0d6a 100644 --- a/apps/tickets/handler/command_confirm.py +++ b/apps/tickets/handler/command_confirm.py @@ -10,8 +10,8 @@ class Handler(BaseHandler): apply_run_asset = self.ticket.meta.get('apply_run_asset') apply_run_system_user = self.ticket.meta.get('apply_run_system_user') apply_run_command = self.ticket.meta.get('apply_run_command') - apply_from_session = self.ticket.meta.get('apply_from_session') - apply_from_cmd_filter_rule = self.ticket.meta.get('apply_from_cmd_filter_rule') + apply_from_session_id = self.ticket.meta.get('apply_from_session_id') + apply_from_cmd_filter_rule_id = self.ticket.meta.get('apply_from_cmd_filter_rule_id') applied_body = '''{}: {}, {}: {}, @@ -24,7 +24,7 @@ class Handler(BaseHandler): _("Applied run asset"), apply_run_asset, _("Applied run system user"), apply_run_system_user, _("Applied run command"), apply_run_command, - _("Applied from session"), apply_from_session, - _("Applied from command filter rules"), apply_from_cmd_filter_rule, + _("Applied from session"), apply_from_session_id, + _("Applied from command filter rules"), apply_from_cmd_filter_rule_id, ) return applied_body diff --git a/apps/tickets/serializers/ticket/meta/meta.py b/apps/tickets/serializers/ticket/meta/meta.py index ee8a402dd..12b576857 100644 --- a/apps/tickets/serializers/ticket/meta/meta.py +++ b/apps/tickets/serializers/ticket/meta/meta.py @@ -1,5 +1,7 @@ from tickets import const -from .ticket_type import apply_asset, apply_application, login_confirm, login_asset_confirm +from .ticket_type import ( + apply_asset, apply_application, login_confirm, login_asset_confirm, command_confirm +) __all__ = [ 'type_serializer_classes_mapping', @@ -35,5 +37,10 @@ type_serializer_classes_mapping = { 'default': login_asset_confirm.LoginAssetConfirmSerializer, action_open: login_asset_confirm.ApplySerializer, action_approve: login_asset_confirm.LoginAssetConfirmSerializer(read_only=True), + }, + const.TicketTypeChoices.command_confirm.value: { + 'default': command_confirm.CommandConfirmSerializer, + action_open: command_confirm.ApplySerializer, + action_approve: command_confirm.CommandConfirmSerializer(read_only=True) } } diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py index 92c72f920..4dbdc08fc 100644 --- a/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py +++ b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py @@ -15,8 +15,8 @@ class ApplySerializer(serializers.Serializer): required=True, max_length=64, label=_('Run system user') ) apply_run_command = serializers.CharField(required=True, label=_('Run command')) - apply_from_session = serializers.UUIDField(required=False, label=_('From session')) - apply_from_cmd_filter_rule = serializers.UUIDField( + apply_from_session_id = serializers.UUIDField(required=False, label=_('From session')) + apply_from_cmd_filter_rule_id = serializers.UUIDField( required=False, label=_('From cmd filter rule') ) From 74c7b18dc4ac5b9c3d85ba68f55d1afc743b099b Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Mon, 26 Apr 2021 18:54:08 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=8D=E6=A0=B8=E9=80=BB=E8=BE=91;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=A4=8D=E6=A0=B8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?;=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/cmd_filter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index c7ffb792f..56cbbd6c3 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -46,8 +46,7 @@ class CommandFilterRuleViewSet(OrgBulkModelViewSet): class CommandConfirmAPI(CreateAPIView): - permission_classes = () - # permission_classes = (IsAppUser, ) + permission_classes = (IsAppUser, ) serializer_class = serializers.CommandConfirmSerializer def create(self, request, *args, **kwargs): @@ -91,6 +90,5 @@ class CommandConfirmAPI(CreateAPIView): class CommandConfirmStatusAPI(GenericTicketStatusRetrieveCloseAPI): - permission_classes = () pass From 7712c1659e8e971c4fb59453070afc5527237f5e Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Mon, 26 Apr 2021 19:39:00 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=8D=E6=A0=B8=E9=80=BB=E8=BE=91;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=A4=8D=E6=A0=B8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?;=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 74719 -> 75634 bytes apps/locale/zh/LC_MESSAGES/django.po | 136 +++++++++++++++++++-------- 2 files changed, 96 insertions(+), 40 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 4e146d83b81d1f910f5ed64ed4565a09bf68c181..f47606a0d8ee91e55b19356e5a7572a55bcd1b20 100644 GIT binary patch delta 22848 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 Tb-nXHO}u;A+Q83eT&DjA(~;-5 delta 22039 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 49694c4a0..d1fac4de6 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-04-14 17:52+0800\n" +"POT-Creation-Date: 2021-04-26 19:33+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler <ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n" @@ -43,12 +43,12 @@ msgstr "" msgid "Name" msgstr "名称" -#: acls/models/base.py:27 assets/models/cmd_filter.py:53 +#: acls/models/base.py:27 assets/models/cmd_filter.py:54 #: assets/models/user.py:122 msgid "Priority" msgstr "优先级" -#: acls/models/base.py:28 assets/models/cmd_filter.py:53 +#: acls/models/base.py:28 assets/models/cmd_filter.py:54 #: assets/models/user.py:122 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -66,7 +66,7 @@ msgstr "激活中" #: acls/models/base.py:32 applications/models/application.py:24 #: assets/models/asset.py:147 assets/models/asset.py:223 #: assets/models/base.py:255 assets/models/cluster.py:29 -#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:57 +#: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64 #: assets/models/domain.py:22 assets/models/domain.py:56 #: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 #: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34 @@ -84,11 +84,11 @@ msgstr "激活中" msgid "Comment" msgstr "备注" -#: acls/models/login_acl.py:16 tickets/const.py:18 +#: acls/models/login_acl.py:16 tickets/const.py:19 msgid "Reject" msgstr "拒绝" -#: acls/models/login_acl.py:17 assets/models/cmd_filter.py:47 +#: acls/models/login_acl.py:17 assets/models/cmd_filter.py:48 msgid "Allow" msgstr "允许" @@ -98,7 +98,7 @@ msgstr "登录IP" #: acls/models/login_acl.py:24 acls/models/login_asset_acl.py:26 #: acls/serializers/login_acl.py:34 acls/serializers/login_asset_acl.py:75 -#: assets/models/cmd_filter.py:56 audits/models.py:57 +#: assets/models/cmd_filter.py:57 audits/models.py:57 #: authentication/templates/authentication/_access_key_modal.html:34 #: tickets/models/ticket.py:43 users/templates/users/_granted_assets.html:29 #: users/templates/users/user_asset_permission.html:44 @@ -117,7 +117,7 @@ msgstr "动作" #: authentication/models.py:97 orgs/models.py:18 orgs/models.py:418 #: perms/models/base.py:50 templates/index.html:78 #: terminal/backends/command/models.py:18 -#: terminal/backends/command/serializers.py:12 terminal/models/session.py:37 +#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 #: tickets/models/comment.py:17 users/models/user.py:159 #: users/models/user.py:707 users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 @@ -149,7 +149,7 @@ msgstr "系统用户" #: assets/serializers/system_user.py:192 audits/models.py:38 #: perms/models/asset_permission.py:99 templates/index.html:82 #: terminal/backends/command/models.py:19 -#: terminal/backends/command/serializers.py:13 terminal/models/session.py:39 +#: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_granted_remote_app.html:36 @@ -158,12 +158,12 @@ msgstr "系统用户" msgid "Asset" msgstr "资产" -#: acls/models/login_asset_acl.py:32 authentication/models.py:45 -#: users/templates/users/user_detail.html:258 +#: acls/models/login_asset_acl.py:32 assets/models/cmd_filter.py:62 +#: authentication/models.py:45 users/templates/users/user_detail.html:258 msgid "Reviewers" msgstr "审批人" -#: acls/models/login_asset_acl.py:86 tickets/const.py:12 +#: acls/models/login_asset_acl.py:89 tickets/const.py:12 msgid "Login asset confirm" msgstr "登录资产复核" @@ -281,7 +281,7 @@ msgstr "自定义" msgid "Category" msgstr "类别" -#: applications/models/application.py:16 assets/models/cmd_filter.py:52 +#: applications/models/application.py:16 assets/models/cmd_filter.py:53 #: perms/models/application_permission.py:23 #: perms/serializers/application/permission.py:17 #: perms/serializers/application/user_permission.py:34 @@ -348,7 +348,7 @@ msgstr "目标URL" #: applications/serializers/attrs/application_type/mysql_workbench.py:34 #: applications/serializers/attrs/application_type/vmware_client.py:30 #: assets/models/base.py:252 assets/serializers/asset_user.py:71 -#: audits/signals_handler.py:46 authentication/forms.py:22 +#: audits/signals_handler.py:58 authentication/forms.py:22 #: authentication/templates/authentication/login.html:155 #: settings/serializers/settings.py:93 users/forms/profile.py:21 #: users/templates/users/user_otp_check_password.html:13 @@ -516,7 +516,7 @@ msgstr "标签管理" #: assets/models/asset.py:221 assets/models/base.py:258 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 -#: assets/models/cmd_filter.py:60 assets/models/group.py:21 +#: assets/models/cmd_filter.py:67 assets/models/group.py:21 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24 #: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:571 #: users/serializers/group.py:35 users/templates/users/user_detail.html:97 @@ -624,30 +624,38 @@ msgid "Regex" msgstr "正则表达式" #: assets/models/cmd_filter.py:41 ops/models/command.py:25 -#: terminal/backends/command/serializers.py:15 terminal/models/session.py:48 +#: terminal/backends/command/serializers.py:15 terminal/models/session.py:49 msgid "Command" msgstr "命令" -#: assets/models/cmd_filter.py:46 +#: assets/models/cmd_filter.py:47 msgid "Deny" msgstr "拒绝" -#: assets/models/cmd_filter.py:51 +#: assets/models/cmd_filter.py:49 +msgid "Reconfirm" +msgstr "复核" + +#: assets/models/cmd_filter.py:52 msgid "Filter" msgstr "过滤器" -#: assets/models/cmd_filter.py:55 xpack/plugins/license/models.py:29 +#: assets/models/cmd_filter.py:56 xpack/plugins/license/models.py:29 msgid "Content" msgstr "内容" -#: assets/models/cmd_filter.py:55 +#: assets/models/cmd_filter.py:56 msgid "One line one command" msgstr "每行一个命令" -#: assets/models/cmd_filter.py:64 +#: assets/models/cmd_filter.py:71 msgid "Command filter rule" msgstr "命令过滤规则" +#: assets/models/cmd_filter.py:111 tickets/const.py:13 +msgid "Command confirm" +msgstr "命令复核" + #: assets/models/domain.py:64 msgid "Gateway" msgstr "网关" @@ -775,7 +783,7 @@ msgstr "用户组" #: perms/models/application_permission.py:31 #: perms/models/asset_permission.py:101 templates/_nav.html:45 #: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:41 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 #: users/templates/users/_granted_assets.html:27 #: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:76 @@ -984,25 +992,25 @@ msgid "" "The task of self-checking is already running and cannot be started repeatedly" msgstr "自检程序已经在运行,不能重复启动" -#: assets/tasks/push_system_user.py:192 +#: assets/tasks/push_system_user.py:193 #: assets/tasks/system_user_connectivity.py:89 msgid "System user is dynamic: {}" msgstr "系统用户是动态的: {}" -#: assets/tasks/push_system_user.py:232 +#: assets/tasks/push_system_user.py:233 msgid "Start push system user for platform: [{}]" msgstr "推送系统用户到平台: [{}]" -#: assets/tasks/push_system_user.py:233 +#: assets/tasks/push_system_user.py:234 #: assets/tasks/system_user_connectivity.py:81 msgid "Hosts count: {}" msgstr "主机数量: {}" -#: assets/tasks/push_system_user.py:272 assets/tasks/push_system_user.py:298 +#: assets/tasks/push_system_user.py:273 assets/tasks/push_system_user.py:299 msgid "Push system users to assets: {}" msgstr "推送系统用户到入资产: {}" -#: assets/tasks/push_system_user.py:284 +#: assets/tasks/push_system_user.py:285 msgid "Push system users to asset: {}({}) => {}" msgstr "推送系统用户到入资产: {}({}) => {}" @@ -1076,7 +1084,7 @@ msgid "Symlink" msgstr "建立软链接" #: audits/models.py:37 audits/models.py:60 audits/models.py:71 -#: terminal/models/session.py:44 +#: terminal/models/session.py:45 msgid "Remote addr" msgstr "远端地址" @@ -1094,7 +1102,7 @@ msgid "Success" msgstr "成功" #: audits/models.py:43 ops/models/command.py:30 perms/models/base.py:53 -#: terminal/models/session.py:51 +#: terminal/models/session.py:52 #: 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 @@ -1250,11 +1258,11 @@ msgstr "运行用户(显示名称)" msgid "User for display" msgstr "用户(显示名称)" -#: audits/signals_handler.py:45 +#: audits/signals_handler.py:57 msgid "SSH Key" msgstr "SSH 密钥" -#: audits/signals_handler.py:47 +#: audits/signals_handler.py:59 msgid "SSO" msgstr "" @@ -1486,7 +1494,7 @@ msgstr "删除成功" #: authentication/templates/authentication/_access_key_modal.html:155 #: authentication/templates/authentication/_mfa_confirm_modal.html:53 -#: templates/_modal.html:22 tickets/const.py:19 +#: templates/_modal.html:22 tickets/const.py:20 msgid "Close" msgstr "关闭" @@ -3036,7 +3044,7 @@ msgstr "正常" #: terminal/const.py:34 msgid "Offline" -msgstr "" +msgstr "离线" #: terminal/exceptions.py:8 msgid "Bulk create not support" @@ -3046,15 +3054,15 @@ msgstr "不支持批量创建" msgid "Storage is invalid" msgstr "存储无效" -#: terminal/models/session.py:43 +#: terminal/models/session.py:44 msgid "Login from" msgstr "登录来源" -#: terminal/models/session.py:47 +#: terminal/models/session.py:48 msgid "Replay" msgstr "回放" -#: terminal/models/session.py:52 +#: terminal/models/session.py:53 msgid "Date end" msgstr "结束日期" @@ -3201,7 +3209,7 @@ msgstr "文档类型" #: terminal/serializers/storage.py:185 msgid "Ignore Certificate Verification" -msgstr "" +msgstr "忽略证书认证" #: terminal/serializers/terminal.py:66 terminal/serializers/terminal.py:74 msgid "Not found" @@ -3291,15 +3299,15 @@ msgstr "申请资产" msgid "Apply for application" msgstr "申请应用" -#: tickets/const.py:16 tickets/const.py:23 +#: tickets/const.py:17 tickets/const.py:24 msgid "Open" msgstr "打开" -#: tickets/const.py:17 +#: tickets/const.py:18 msgid "Approve" msgstr "同意" -#: tickets/const.py:24 +#: tickets/const.py:25 msgid "Closed" msgstr "关闭" @@ -3414,6 +3422,30 @@ msgstr "工单申请信息" msgid "Ticket approved info" msgstr "工单批准信息" +#: tickets/handler/command_confirm.py:23 +msgid "Applied run user" +msgstr "申请运行的用户" + +#: tickets/handler/command_confirm.py:24 +msgid "Applied run asset" +msgstr "申请运行的资产" + +#: tickets/handler/command_confirm.py:25 +msgid "Applied run system user" +msgstr "申请运行的系统用户" + +#: tickets/handler/command_confirm.py:26 +msgid "Applied run command" +msgstr "申请运行的命令" + +#: tickets/handler/command_confirm.py:27 +msgid "Applied from session" +msgstr "申请来自会话" + +#: tickets/handler/command_confirm.py:28 +msgid "Applied from command filter rules" +msgstr "申请来自命令过滤规则" + #: tickets/handler/login_asset_confirm.py:16 msgid "Applied login user" msgstr "申请登录的用户" @@ -3551,6 +3583,30 @@ msgstr "批准的资产" msgid "No `Asset` are found under Organization `{}`" msgstr "在组织 `{}` 下没有发现 `资产`" +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:12 +msgid "Run user" +msgstr "运行的用户" + +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:13 +msgid "Run asset" +msgstr "运行的资产" + +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:15 +msgid "Run system user" +msgstr "运行的系统用户" + +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:17 +msgid "Run command" +msgstr "运行的命令" + +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:18 +msgid "From session" +msgstr "来自会话" + +#: tickets/serializers/ticket/meta/ticket_type/command_confirm.py:20 +msgid "From cmd filter rule" +msgstr "来自命令过滤规则" + #: tickets/serializers/ticket/meta/ticket_type/common.py:11 msgid "Created by ticket ({}-{})" msgstr "通过工单创建 ({}-{})" From 4a9e83ba151ce3f4afbec055042e8732961a4463 Mon Sep 17 00:00:00 2001 From: Bai <bugatti_it@163.com> Date: Tue, 27 Apr 2021 17:48:07 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=A4=8D=E6=A0=B8=E9=80=BB=E8=BE=91;=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=BD=E4=BB=A4=E5=A4=8D=E6=A0=B8=E5=B7=A5=E5=8D=95?= =?UTF-8?q?;=205?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/models/cmd_filter.py | 1 + apps/tickets/handler/command_confirm.py | 2 ++ .../serializers/ticket/meta/ticket_type/command_confirm.py | 1 + 3 files changed, 4 insertions(+) diff --git a/apps/assets/models/cmd_filter.py b/apps/assets/models/cmd_filter.py index 72547da19..1ef14bad0 100644 --- a/apps/assets/models/cmd_filter.py +++ b/apps/assets/models/cmd_filter.py @@ -117,6 +117,7 @@ class CommandFilterRule(OrgModelMixin): 'apply_run_command': run_command, 'apply_from_session_id': str(session.id), 'apply_from_cmd_filter_rule_id': str(cmd_filter_rule.id), + 'apply_from_cmd_filter_id': str(cmd_filter_rule.filter.id) }, 'org_id': org_id, } diff --git a/apps/tickets/handler/command_confirm.py b/apps/tickets/handler/command_confirm.py index 0e1fd0d6a..2d66db2d8 100644 --- a/apps/tickets/handler/command_confirm.py +++ b/apps/tickets/handler/command_confirm.py @@ -12,6 +12,7 @@ class Handler(BaseHandler): apply_run_command = self.ticket.meta.get('apply_run_command') apply_from_session_id = self.ticket.meta.get('apply_from_session_id') apply_from_cmd_filter_rule_id = self.ticket.meta.get('apply_from_cmd_filter_rule_id') + apply_from_cmd_filter_id = self.ticket.meta.get('apply_from_cmd_filter_id') applied_body = '''{}: {}, {}: {}, @@ -26,5 +27,6 @@ class Handler(BaseHandler): _("Applied run command"), apply_run_command, _("Applied from session"), apply_from_session_id, _("Applied from command filter rules"), apply_from_cmd_filter_rule_id, + _("Applied from command filter"), apply_from_cmd_filter_id, ) return applied_body diff --git a/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py index 4dbdc08fc..eb631fe98 100644 --- a/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py +++ b/apps/tickets/serializers/ticket/meta/ticket_type/command_confirm.py @@ -19,6 +19,7 @@ class ApplySerializer(serializers.Serializer): apply_from_cmd_filter_rule_id = serializers.UUIDField( required=False, label=_('From cmd filter rule') ) + apply_from_cmd_filter_id = serializers.UUIDField(required=False, label=_('From cmd filter')) class CommandConfirmSerializer(ApplySerializer):