diff --git a/apps/acls/api/command_acl.py b/apps/acls/api/command_acl.py index 717e36930..80163e717 100644 --- a/apps/acls/api/command_acl.py +++ b/apps/acls/api/command_acl.py @@ -1,3 +1,7 @@ +from rest_framework.decorators import action +from rest_framework.response import Response + +from common.utils import reverse from orgs.mixins.api import OrgBulkModelViewSet from .. import models, serializers @@ -16,3 +20,36 @@ class CommandFilterACLViewSet(OrgBulkModelViewSet): filterset_fields = ('name',) search_fields = filterset_fields serializer_class = serializers.CommandFilterACLSerializer + rbac_perms = { + 'command_review': 'tickets.add_superticket' + } + + @action(['POST'], detail=False, url_path='command-review') + def command_review(self, request, *args, **kwargs): + serializer = serializers.CommandReviewSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + data = { + 'run_command': serializer.validated_data['run_command'], + 'session': serializer.session, + 'cmd_filter_acl': serializer.cmd_filter_acl, + 'org_id': serializer.org.id + } + ticket = serializer.cmd_filter_acl.create_command_review_ticket(**data) + + url_review_status = reverse( + view_name='api-tickets:super-ticket-status', kwargs={'pk': str(ticket.id)} + ) + url_ticket_detail = reverse( + view_name='api-tickets:ticket-detail', kwargs={'pk': str(ticket.id)}, + external=True, api_to_ui=True + ) + resp_data = { + 'check_review_status': {'method': 'GET', 'url': url_review_status}, + 'close_review': {'method': 'DELETE', 'url': url_review_status}, + 'ticket_detail_url': url_ticket_detail, + 'reviewers': [ + str(ticket_assignee.assignee) + for ticket_assignee in ticket.current_step.ticket_assignees.all() + ] + } + return Response(resp_data) diff --git a/apps/acls/api/login_asset_check.py b/apps/acls/api/login_asset_check.py index 62babfafb..386f4480c 100644 --- a/apps/acls/api/login_asset_check.py +++ b/apps/acls/api/login_asset_check.py @@ -20,7 +20,7 @@ class LoginAssetCheckAPI(CreateAPIView): return LoginAssetACL.objects.all() def create(self, request, *args, **kwargs): - data = self.check_confirm() + data = self.check_review() return Response(data=data, status=200) @lazyproperty @@ -29,7 +29,7 @@ class LoginAssetCheckAPI(CreateAPIView): serializer.is_valid(raise_exception=True) return serializer - def check_confirm(self): + def check_review(self): with tmp_to_org(self.serializer.asset.org): kwargs = { 'user': self.serializer.user, @@ -39,15 +39,15 @@ class LoginAssetCheckAPI(CreateAPIView): } acl = LoginAssetACL.filter_queryset(**kwargs).valid().first() if acl: - need_confirm = True - response_data = self._get_response_data_of_need_confirm(acl) + need_review = True + response_data = self._get_response_data_of_need_review(acl) else: - need_confirm = False + need_review = False response_data = {} - response_data['need_confirm'] = need_confirm + response_data['need_review'] = need_review return response_data - def _get_response_data_of_need_confirm(self, acl) -> dict: + def _get_response_data_of_need_review(self, acl) -> dict: ticket = LoginAssetACL.create_login_asset_confirm_ticket( user=self.serializer.user, asset=self.serializer.asset, @@ -55,7 +55,7 @@ class LoginAssetCheckAPI(CreateAPIView): assignees=acl.reviewers.all(), org_id=self.serializer.asset.org.id, ) - confirm_status_url = reverse( + review_status_url = reverse( view_name='api-tickets:super-ticket-status', kwargs={'pk': str(ticket.id)} ) @@ -67,8 +67,8 @@ class LoginAssetCheckAPI(CreateAPIView): ticket_detail_url = '{url}?type={type}'.format(url=ticket_detail_url, type=ticket.type) ticket_assignees = ticket.current_step.ticket_assignees.all() data = { - 'check_confirm_status': {'method': 'GET', 'url': confirm_status_url}, - 'close_confirm': {'method': 'DELETE', 'url': confirm_status_url}, + 'check_review_status': {'method': 'GET', 'url': review_status_url}, + 'close_review': {'method': 'DELETE', 'url': review_status_url}, 'ticket_detail_url': ticket_detail_url, 'reviewers': [str(ticket_assignee.assignee) for ticket_assignee in ticket_assignees], 'ticket_id': str(ticket.id) diff --git a/apps/acls/models/command_acl.py b/apps/acls/models/command_acl.py index f10373a02..3db5a738a 100644 --- a/apps/acls/models/command_acl.py +++ b/apps/acls/models/command_acl.py @@ -104,7 +104,7 @@ class CommandFilterACL(UserAssetAccountBaseACL): def __str__(self): return self.name - def create_command_confirm_ticket(self, run_command, session, cmd_filter_rule, org_id): + def create_command_review_ticket(self, run_command, session, cmd_filter_acl, org_id): from tickets.const import TicketType from tickets.models import ApplyCommandTicket data = { @@ -116,8 +116,7 @@ class CommandFilterACL(UserAssetAccountBaseACL): 'apply_run_account': str(session.account), 'apply_run_command': run_command[:4090], '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), + 'apply_from_cmd_filter_acl_id': str(cmd_filter_acl.id), 'org_id': org_id, } ticket = ApplyCommandTicket.objects.create(**data) diff --git a/apps/acls/serializers/command_acl.py b/apps/acls/serializers/command_acl.py index 0accb5f56..07f2e39a7 100644 --- a/apps/acls/serializers/command_acl.py +++ b/apps/acls/serializers/command_acl.py @@ -1,11 +1,16 @@ from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from terminal.models import Session from acls.models import CommandGroup, CommandFilterACL +from common.utils import lazyproperty, get_object_or_none from common.drf.fields import ObjectRelatedField, LabeledChoiceField +from orgs.utils import tmp_to_root_org from orgs.mixins.serializers import BulkOrgResourceModelSerializer from .base import BaseUserAssetAccountACLSerializerMixin as BaseSerializer -__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer"] +__all__ = ["CommandFilterACLSerializer", "CommandGroupSerializer", "CommandReviewSerializer"] class CommandGroupSerializer(BulkOrgResourceModelSerializer): @@ -27,3 +32,35 @@ class CommandFilterACLSerializer(BaseSerializer, BulkOrgResourceModelSerializer) class Meta(BaseSerializer.Meta): model = CommandFilterACL fields = BaseSerializer.Meta.fields + ['command_groups'] + + +class CommandReviewSerializer(serializers.Serializer): + session_id = serializers.UUIDField(required=True, allow_null=False) + cmd_filter_acl_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_acl = None + + def validate_session_id(self, pk): + self.session = self.validate_object(Session, pk) + return pk + + def validate_cmd_filter_acl_id(self, pk): + self.cmd_filter_acl = self.validate_object(CommandFilterACL, pk) + return pk + + @lazyproperty + def org(self): + return self.session.org + + @staticmethod + def validate_object(model, pk): + with tmp_to_root_org(): + obj = get_object_or_none(model, id=pk) + if obj: + return obj + error = '{} Model object does not exist'.format(model.__name__) + raise serializers.ValidationError(error) diff --git a/apps/acls/urls/api_urls.py b/apps/acls/urls/api_urls.py index 0185278d9..b0f5cdf22 100644 --- a/apps/acls/urls/api_urls.py +++ b/apps/acls/urls/api_urls.py @@ -8,8 +8,8 @@ app_name = 'acls' router = BulkRouter() router.register(r'login-acls', api.LoginACLViewSet, 'login-acl') router.register(r'login-asset-acls', api.LoginAssetACLViewSet, 'login-asset-acl') -router.register(r'command-groups', api.CommandGroupViewSet, 'command-group') router.register(r'command-filter-acls', api.CommandFilterACLViewSet, 'command-filter-acl') +router.register(r'command-groups', api.CommandGroupViewSet, 'command-group') urlpatterns = [ path('login-asset/check/', api.LoginAssetCheckAPI.as_view(), name='login-asset-check'), diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index dacbb47f0..912d61bc3 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -104,16 +104,16 @@ class ApplyAssetTicketViewSet(TicketViewSet): class ApplyLoginTicketViewSet(TicketViewSet): model = ApplyLoginTicket filterset_class = filters.ApplyLoginTicketFilter - serializer_class = serializers.LoginConfirmSerializer + serializer_class = serializers.LoginReviewSerializer class ApplyLoginAssetTicketViewSet(TicketViewSet): model = ApplyLoginAssetTicket filterset_class = filters.ApplyLoginAssetTicketFilter - serializer_class = serializers.LoginAssetConfirmSerializer + serializer_class = serializers.LoginAssetReviewSerializer class ApplyCommandTicketViewSet(TicketViewSet): model = ApplyCommandTicket filterset_class = filters.ApplyCommandTicketFilter - serializer_class = serializers.ApplyCommandConfirmSerializer + serializer_class = serializers.ApplyCommandReviewSerializer diff --git a/apps/tickets/migrations/0025_auto_20221206_1820.py b/apps/tickets/migrations/0025_auto_20221206_1820.py new file mode 100644 index 000000000..c82a17ce8 --- /dev/null +++ b/apps/tickets/migrations/0025_auto_20221206_1820.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.14 on 2022-12-06 10:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('acls', '0010_auto_20221205_1122'), + ('tickets', '0024_auto_20221121_1800'), + ] + + operations = [ + migrations.RemoveField( + model_name='applycommandticket', + name='apply_from_cmd_filter', + ), + migrations.RemoveField( + model_name='applycommandticket', + name='apply_from_cmd_filter_rule', + ), + migrations.AddField( + model_name='applycommandticket', + name='apply_from_cmd_filter_acl', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='acls.commandfilteracl', verbose_name='Command filter acl'), + ), + ] diff --git a/apps/tickets/models/ticket/command_confirm.py b/apps/tickets/models/ticket/command_confirm.py index ac70eaadb..632bb1ee9 100644 --- a/apps/tickets/models/ticket/command_confirm.py +++ b/apps/tickets/models/ticket/command_confirm.py @@ -6,21 +6,15 @@ from .general import Ticket class ApplyCommandTicket(Ticket): apply_run_user = models.ForeignKey( - 'users.User', on_delete=models.SET_NULL, - null=True, verbose_name=_('Run user') + 'users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Run user') ) apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset')) apply_run_command = models.CharField(max_length=4096, verbose_name=_('Run command')) apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account')) apply_from_session = models.ForeignKey( - 'terminal.Session', on_delete=models.SET_NULL, - null=True, verbose_name=_("Session") + 'terminal.Session', on_delete=models.SET_NULL, null=True, verbose_name=_("Session") ) - apply_from_cmd_filter = models.ForeignKey( - 'assets.CommandFilter', on_delete=models.SET_NULL, - null=True, verbose_name=_('From cmd filter') - ) - apply_from_cmd_filter_rule = models.ForeignKey( - 'assets.CommandFilterRule', on_delete=models.SET_NULL, - null=True, verbose_name=_('From cmd filter rule') + apply_from_cmd_filter_acl = models.ForeignKey( + 'acls.CommandFilterACL', on_delete=models.SET_NULL, + null=True, verbose_name=_('Command filter acl') ) diff --git a/apps/tickets/serializers/ticket/__init__.py b/apps/tickets/serializers/ticket/__init__.py index 73fc3b122..f780a2a53 100644 --- a/apps/tickets/serializers/ticket/__init__.py +++ b/apps/tickets/serializers/ticket/__init__.py @@ -1,6 +1,6 @@ from .common import * from .ticket import * from .apply_asset import * -from .login_confirm import * -from .command_confirm import * -from .login_asset_confirm import * +from .login_review import * +from .command_review import * +from .login_asset_review import * diff --git a/apps/tickets/serializers/ticket/command_confirm.py b/apps/tickets/serializers/ticket/command_review.py similarity index 65% rename from apps/tickets/serializers/ticket/command_confirm.py rename to apps/tickets/serializers/ticket/command_review.py index 5b3a39b0e..158371b1e 100644 --- a/apps/tickets/serializers/ticket/command_confirm.py +++ b/apps/tickets/serializers/ticket/command_review.py @@ -2,15 +2,15 @@ from tickets.models import ApplyCommandTicket from .ticket import TicketApplySerializer __all__ = [ - 'ApplyCommandConfirmSerializer', + 'ApplyCommandReviewSerializer', ] -class ApplyCommandConfirmSerializer(TicketApplySerializer): +class ApplyCommandReviewSerializer(TicketApplySerializer): class Meta: model = ApplyCommandTicket writeable_fields = [ 'apply_run_user', 'apply_run_asset', 'apply_run_account', 'apply_run_command', - 'apply_from_session', 'apply_from_cmd_filter', 'apply_from_cmd_filter_rule' + 'apply_from_session', 'apply_from_cmd_filter_acl' ] fields = TicketApplySerializer.Meta.fields + writeable_fields diff --git a/apps/tickets/serializers/ticket/login_asset_confirm.py b/apps/tickets/serializers/ticket/login_asset_review.py similarity index 77% rename from apps/tickets/serializers/ticket/login_asset_confirm.py rename to apps/tickets/serializers/ticket/login_asset_review.py index 4d3db5fc6..18140c08b 100644 --- a/apps/tickets/serializers/ticket/login_asset_confirm.py +++ b/apps/tickets/serializers/ticket/login_asset_review.py @@ -2,11 +2,11 @@ from tickets.models import ApplyLoginAssetTicket from .ticket import TicketApplySerializer __all__ = [ - 'LoginAssetConfirmSerializer' + 'LoginAssetReviewSerializer' ] -class LoginAssetConfirmSerializer(TicketApplySerializer): +class LoginAssetReviewSerializer(TicketApplySerializer): class Meta: model = ApplyLoginAssetTicket writeable_fields = ['apply_login_user', 'apply_login_asset', 'apply_login_account'] diff --git a/apps/tickets/serializers/ticket/login_confirm.py b/apps/tickets/serializers/ticket/login_review.py similarity index 80% rename from apps/tickets/serializers/ticket/login_confirm.py rename to apps/tickets/serializers/ticket/login_review.py index 128ac5971..8004735d3 100644 --- a/apps/tickets/serializers/ticket/login_confirm.py +++ b/apps/tickets/serializers/ticket/login_review.py @@ -2,11 +2,11 @@ from tickets.models import ApplyLoginTicket from .ticket import TicketApplySerializer __all__ = [ - 'LoginConfirmSerializer' + 'LoginReviewSerializer' ] -class LoginConfirmSerializer(TicketApplySerializer): +class LoginReviewSerializer(TicketApplySerializer): class Meta(TicketApplySerializer.Meta): model = ApplyLoginTicket writeable_fields = ['apply_login_ip', 'apply_login_city', 'apply_login_datetime']