Merge branch 'v3' of github.com:jumpserver/jumpserver into v3

pull/9115/head
ibuler 2022-11-17 20:49:31 +08:00
commit b1bd57cd76
27 changed files with 158 additions and 141 deletions

View File

@ -53,12 +53,13 @@ class LoginACL(BaseACL):
@staticmethod
def match(user, ip):
acls = LoginACL.filter_acl(user)
if not acls:
acl_qs = LoginACL.filter_acl(user)
if not acl_qs:
return
for acl in acls:
if acl.is_action(LoginACL.ActionChoices.confirm) and not acl.reviewers.exists():
for acl in acl_qs:
if acl.is_action(LoginACL.ActionChoices.confirm) and \
not acl.reviewers.exists():
continue
ip_group = acl.rules.get('ip_group')
time_periods = acl.rules.get('time_period')
@ -79,12 +80,12 @@ class LoginACL(BaseACL):
login_datetime = local_now_display()
data = {
'title': title,
'type': const.TicketType.login_confirm,
'applicant': self.user,
'apply_login_city': login_city,
'apply_login_ip': login_ip,
'apply_login_datetime': login_datetime,
'org_id': Organization.ROOT_ID,
'apply_login_city': login_city,
'apply_login_datetime': login_datetime,
'type': const.TicketType.login_confirm,
}
ticket = ApplyLoginTicket.objects.create(**data)
assignees = self.reviewers.all()

View File

@ -86,12 +86,12 @@ class LoginAssetACL(BaseACL, OrgModelMixin):
title = _('Login asset confirm') + ' ({})'.format(user)
data = {
'title': title,
'type': TicketType.login_asset_confirm,
'org_id': org_id,
'applicant': user,
'apply_login_user': user,
'apply_login_asset': asset,
'apply_login_account': str(account),
'org_id': org_id,
'type': TicketType.login_asset_confirm,
}
ticket = ApplyLoginAssetTicket.objects.create(**data)
ticket.open_by_system(assignees)

View File

@ -7,23 +7,22 @@
2. 程序需要, 用户不需要更改的写到settings中
3. 程序需要, 用户需要更改的写到本config中
"""
import base64
import copy
import errno
import json
import logging
import os
import re
import sys
import types
import errno
import json
import yaml
import copy
import base64
import logging
from importlib import import_module
from urllib.parse import urljoin, urlparse
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
import yaml
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR)
@ -499,6 +498,9 @@ class Config(dict):
'FORGOT_PASSWORD_URL': '',
'HEALTH_CHECK_TOKEN': '',
# Applet 等软件的下载地址
'APPLET_DOWNLOAD_HOST': '',
}
def __init__(self, *args):

View File

@ -1,4 +1,5 @@
import os
from django.urls import reverse_lazy
from .. import const
@ -36,6 +37,9 @@ DEBUG_DEV = CONFIG.DEBUG_DEV
# Absolute url for some case, for example email link
SITE_URL = CONFIG.SITE_URL
# Absolute url for downloading applet
APPLET_DOWNLOAD_HOST = CONFIG.APPLET_DOWNLOAD_HOST
# https://docs.djangoproject.com/en/4.1/ref/settings/
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
@ -313,7 +317,6 @@ PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
GMSSL_ENABLED = CONFIG.GMSSL_ENABLED
GM_HASHER = 'common.hashers.PBKDF2SM3PasswordHasher'
if GMSSL_ENABLED:
@ -329,4 +332,3 @@ if os.environ.get('DEBUG_TOOLBAR', False):
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.profiling.ProfilingPanel',
]

View File

@ -4,7 +4,7 @@
from rest_framework import viewsets
from ..models import AdHoc
from ..serializers import (
AdHocSerializer, AdhocListSerializer,
AdHocSerializer
)
__all__ = [
@ -14,9 +14,4 @@ __all__ = [
class AdHocViewSet(viewsets.ModelViewSet):
queryset = AdHoc.objects.all()
def get_serializer_class(self):
if self.action != 'list':
return AdhocListSerializer
return AdHocSerializer
serializer_class = AdHocSerializer

View File

@ -0,0 +1,27 @@
# Generated by Django 3.2.14 on 2022-11-17 10:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ops', '0031_auto_20221116_2024'),
]
operations = [
migrations.RemoveField(
model_name='job',
name='variables',
),
migrations.AddField(
model_name='job',
name='parameters_define',
field=models.JSONField(default=dict, verbose_name='Parameters define'),
),
migrations.AddField(
model_name='jobexecution',
name='parameters',
field=models.JSONField(default=dict, verbose_name='Parameters'),
),
]

View File

@ -45,7 +45,7 @@ class Job(BaseCreateUpdateModel):
runas = models.CharField(max_length=128, default='root', verbose_name=_('Runas'))
runas_policy = models.CharField(max_length=128, choices=RunasPolicies.choices, default=RunasPolicies.skip,
verbose_name=_('Runas policy'))
variables = models.JSONField(default=dict, verbose_name=_('Variables'))
parameters_define = models.JSONField(default=dict, verbose_name=_('Parameters define'))
comment = models.CharField(max_length=1024, default='', verbose_name=_('Comment'), null=True, blank=True)
@property
@ -55,15 +55,13 @@ class Job(BaseCreateUpdateModel):
def create_execution(self):
return self.executions.create()
def get_variables(self):
return json.loads(self.variables)
class JobExecution(BaseCreateUpdateModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
task_id = models.UUIDField(null=True)
status = models.CharField(max_length=16, verbose_name=_('Status'), default='running')
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name='executions', null=True)
parameters = models.JSONField(default=dict, verbose_name=_('Parameters'))
result = models.JSONField(blank=True, null=True, verbose_name=_('Result'))
summary = models.JSONField(default=dict, verbose_name=_('Summary'))
creator = models.ForeignKey('users.User', verbose_name=_("Creator"), on_delete=models.SET_NULL, null=True)
@ -74,11 +72,12 @@ class JobExecution(BaseCreateUpdateModel):
def get_runner(self):
inv = self.job.inventory
inv.write_to_file(self.inventory_path)
extra_vars = json.loads(self.parameters)
if self.job.type == 'adhoc':
runner = AdHocRunner(
self.inventory_path, self.job.module, module_args=self.job.args,
pattern="all", project_dir=self.private_dir, extra_vars=self.job.get_variables()
pattern="all", project_dir=self.private_dir, extra_vars=extra_vars,
)
elif self.job.type == 'playbook':
runner = PlaybookRunner(

View File

@ -14,15 +14,6 @@ class AdHocSerializer(serializers.ModelSerializer):
row_count = serializers.IntegerField(read_only=True)
size = serializers.IntegerField(read_only=True)
class Meta:
model = AdHoc
fields = ["id", "name", "module", "owner", "row_count", "size", "date_created", "date_updated"]
class AdhocListSerializer(AdHocSerializer):
row_count = serializers.IntegerField(read_only=True)
size = serializers.IntegerField(read_only=True)
class Meta:
model = AdHoc
fields = ["id", "name", "module", "row_count", "size", "args", "owner", "date_created", "date_updated"]

View File

@ -14,7 +14,7 @@ class JobSerializer(serializers.ModelSerializer):
model = Job
fields = [
"id", "name", "instant", "type", "module", "args", "playbook", "assets", "runas_policy", "runas", "owner",
"variables",
"parameters_define",
"timeout",
"chdir",
"comment",
@ -29,5 +29,5 @@ class JobExecutionSerializer(serializers.ModelSerializer):
read_only_fields = ["id", "task_id", "timedelta", "time_cost", 'is_finished', 'date_start', 'date_created',
'is_success', 'task_id', 'short_id']
fields = read_only_fields + [
"job"
"job", "parameters"
]

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.db.fields import BitChoices
@ -32,3 +31,7 @@ class ActionChoices(BitChoices):
def has_perm(cls, action_name, total):
action_value = getattr(cls, action_name)
return action_value & total == action_value
@classmethod
def display(cls, value):
return ', '.join([str(c.label) for c in cls if c.value & value == c.value])

View File

@ -64,17 +64,15 @@ class AssetPermission(OrgModelMixin):
# 特殊的账号: @ALL, @INPUT @USER 默认包含,将来在全局设置中进行控制.
accounts = models.JSONField(default=list, verbose_name=_("Accounts"))
actions = models.IntegerField(default=ActionChoices.connect, verbose_name=_("Actions"))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(
default=timezone.now, db_index=True, verbose_name=_("Date start")
)
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
date_expired = models.DateTimeField(
default=date_expired_default, db_index=True, verbose_name=_('Date expired')
)
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
from_ticket = models.BooleanField(default=False, verbose_name=_('From ticket'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
objects = AssetPermissionManager.from_queryset(AssetPermissionQuerySet)()

View File

@ -45,20 +45,26 @@ class DeployAppletHostManager:
def generate_initial_playbook(self):
site_url = settings.SITE_URL
download_host = settings.APPLET_DOWNLOAD_HOST
bootstrap_token = settings.BOOTSTRAP_TOKEN
host_id = str(self.deployment.host.id)
if not site_url:
site_url = "http://localhost:8080"
if not download_host:
download_host = site_url
options = self.deployment.host.deploy_options
site_url = site_url.rstrip("/")
download_host = download_host.rstrip("/")
def handler(plays):
for play in plays:
play["vars"].update(options)
play["vars"]["DownloadHost"] = site_url + "/download"
play["vars"]["APPLET_DOWNLOAD_HOST"] = download_host
play["vars"]["CORE_HOST"] = site_url
play["vars"]["BOOTSTRAP_TOKEN"] = bootstrap_token
play["vars"]["HOST_ID"] = host_id
play["vars"]["HOST_NAME"] = self.deployment.host.name
return plays
return self._generate_playbook("playbook.yml", handler)

View File

@ -2,7 +2,7 @@
- hosts: all
vars:
DownloadHost: https://demo.jumpserver.org/download
APPLET_DOWNLOAD_HOST: https://demo.jumpserver.org
HOST_NAME: test
HOST_ID: 00000000-0000-0000-0000-000000000000
CORE_HOST: https://demo.jumpserver.org
@ -32,7 +32,7 @@
- name: Download JumpServer Tinker installer (jumpserver)
ansible.windows.win_get_url:
url: "{{ DownloadHost }}/{{ TinkerInstaller }}"
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/{{ TinkerInstaller }}"
dest: "{{ ansible_env.TEMP }}\\{{ TinkerInstaller }}"
- name: Install JumpServer Tinker (jumpserver)
@ -52,7 +52,7 @@
- name: Download python-3.10.8
ansible.windows.win_get_url:
url: "{{ DownloadHost }}/python-3.10.8-amd64.exe"
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/python-3.10.8-amd64.exe"
dest: "{{ ansible_env.TEMP }}\\python-3.10.8-amd64.exe"
- name: Install the python-3.10.8
@ -112,27 +112,27 @@
- name: Download pip packages
ansible.windows.win_get_url:
url: "{{ DownloadHost }}/pip_packages_v0.0.1.zip"
dest: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/pip_packages.zip"
dest: "{{ ansible_env.TEMP }}\\pip_packages.zip"
- name: Unzip pip_packages
community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\pip_packages_v0.0.1.zip"
dest: "{{ ansible_env.TEMP }}"
src: "{{ ansible_env.TEMP }}\\pip_packages.zip"
dest: "{{ ansible_env.TEMP }}\\pip_packages"
- name: Install python requirements offline
ansible.windows.win_shell: >
pip install -r '{{ ansible_env.TEMP }}\pip_packages_v0.0.1\requirements.txt'
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages_v0.0.1'
pip install -r '{{ ansible_env.TEMP }}\pip_packages\requirements.txt'
--no-index --find-links='{{ ansible_env.TEMP }}\pip_packages'
- name: Download chromedriver (chrome)
ansible.windows.win_get_url:
url: "{{ DownloadHost }}/chromedriver_win32.107.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/chromedriver_win32.zip"
dest: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
- name: Unzip chromedriver (chrome)
community.windows.win_unzip:
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.107.zip"
src: "{{ ansible_env.TEMP }}\\chromedriver_win32.zip"
dest: C:\Program Files\JumpServer\drivers
- name: Set chromedriver on the global system path (chrome)
@ -142,7 +142,7 @@
- name: Download chrome msi package (chrome)
ansible.windows.win_get_url:
url: "{{ DownloadHost }}/googlechromestandaloneenterprise64.msi"
url: "{{ APPLET_DOWNLOAD_HOST }}/download/applets/googlechromestandaloneenterprise64.msi"
dest: "{{ ansible_env.TEMP }}\\googlechromestandaloneenterprise64.msi"
- name: Install chrome (chrome)

View File

@ -107,6 +107,9 @@ class AppletHostDeployment(JMSBaseModel):
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
task = models.UUIDField(null=True, verbose_name=_('Task'))
class Meta:
ordering = ('-date_start',)
def start(self, **kwargs):
from ...automations.deploy_applet_host import DeployAppletHostManager
manager = DeployAppletHostManager(self)

View File

@ -97,6 +97,10 @@ class ApplyAssetTicketViewSet(TicketViewSet):
serializer_class = serializers.ApplyAssetSerializer
model = ApplyAssetTicket
filterset_class = filters.ApplyAssetTicketFilter
serializer_classes = {
'open': serializers.ApplyAssetSerializer,
'approve': serializers.ApproveAssetSerializer
}
class ApplyLoginTicketViewSet(TicketViewSet):

View File

@ -14,7 +14,6 @@ class Handler(BaseHandler):
if is_finished:
self._create_asset_permission()
# permission
def _create_asset_permission(self):
org_id = self.ticket.org_id
with tmp_to_org(org_id):
@ -27,6 +26,7 @@ class Handler(BaseHandler):
apply_permission_name = self.ticket.apply_permission_name
apply_actions = self.ticket.apply_actions
apply_accounts = self.ticket.apply_accounts
apply_date_start = self.ticket.apply_date_start
apply_date_expired = self.ticket.apply_date_expired
permission_created_by = '{}:{}'.format(
@ -46,19 +46,20 @@ class Handler(BaseHandler):
)
permission_data = {
'id': self.ticket.id,
'name': apply_permission_name,
'from_ticket': True,
'comment': str(permission_comment),
'created_by': permission_created_by,
'id': self.ticket.id,
'actions': apply_actions,
'accounts': apply_accounts,
'name': apply_permission_name,
'date_start': apply_date_start,
'date_expired': apply_date_expired,
'comment': str(permission_comment),
'created_by': permission_created_by,
}
with tmp_to_org(self.ticket.org_id):
asset_permission = AssetPermission.objects.create(**permission_data)
asset_permission.users.add(self.ticket.applicant)
asset_permission.nodes.set(apply_nodes)
asset_permission.assets.set(apply_assets)
asset_permission.users.add(self.ticket.applicant)
return asset_permission

View File

@ -1,22 +1,6 @@
from django.utils.translation import ugettext as _
from tickets.models import ApplyLoginTicket
from .base import BaseHandler
class Handler(BaseHandler):
ticket: ApplyLoginTicket
def _construct_meta_body_of_open(self):
apply_login_ip = self.ticket.apply_login_ip
apply_login_city = self.ticket.apply_login_city
apply_login_datetime = self.ticket.apply_login_datetime
applied_body = '''
{}: {}
{}: {}
{}: {}
'''.format(
_("Applied login IP"), apply_login_ip,
_("Applied login city"), apply_login_city,
_("Applied login datetime"), apply_login_datetime,
)
return applied_body

View File

@ -18,3 +18,6 @@ class ApplyAssetTicket(Ticket):
apply_actions = models.IntegerField(verbose_name=_('Actions'), default=ActionChoices.all())
apply_date_start = models.DateTimeField(verbose_name=_('Date start'), null=True)
apply_date_expired = models.DateTimeField(verbose_name=_('Date expired'), null=True)
def get_apply_actions_display(self):
return ActionChoices.display(self.apply_actions)

View File

@ -10,8 +10,8 @@ class ApplyCommandTicket(Ticket):
null=True, verbose_name=_('Run user')
)
apply_run_asset = models.CharField(max_length=128, verbose_name=_('Run asset'))
apply_run_account = models.CharField(max_length=128, default='', verbose_name=_('Run account'))
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")

View File

@ -24,7 +24,9 @@ from tickets.handlers import get_ticket_handler
from tickets.errors import AlreadyClosed
from ..flow import TicketFlow
__all__ = ['Ticket', 'TicketStep', 'TicketAssignee', 'SuperTicket', 'SubTicketManager']
__all__ = [
'Ticket', 'TicketStep', 'TicketAssignee', 'SuperTicket', 'SubTicketManager'
]
class TicketStep(CommonModelMixin):
@ -204,11 +206,11 @@ class StatusMixin:
step_info = {
'state': state,
'approval_level': step.level,
'assignees': assignee_ids,
'processor': processor_id,
'approval_level': step.level,
'assignees_display': assignees_display,
'approval_date': str(step.date_updated),
'processor': processor_id,
'processor_display': processor_display
}
process_map.append(step_info)
@ -224,15 +226,15 @@ class StatusMixin:
org_id = self.flow.org_id
flow_rules = self.flow.rules.order_by('level')
for rule in flow_rules:
step = TicketStep.objects.create(ticket=self, level=rule.level)
assignees = rule.get_assignees(org_id=org_id)
assignees = self.exclude_applicant(assignees, self.applicant)
step = TicketStep.objects.create(ticket=self, level=rule.level)
step_assignees = [TicketAssignee(step=step, assignee=user) for user in assignees]
TicketAssignee.objects.bulk_create(step_assignees)
def create_process_steps_by_assignees(self, assignees):
assignees = self.exclude_applicant(assignees, self.applicant)
step = TicketStep.objects.create(ticket=self, level=1)
assignees = self.exclude_applicant(assignees, self.applicant)
ticket_assignees = [TicketAssignee(step=step, assignee=user) for user in assignees]
TicketAssignee.objects.bulk_create(ticket_assignees)
@ -248,14 +250,13 @@ class StatusMixin:
@property
def processor(self):
processor = self.current_step.ticket_assignees \
.exclude(state=StepState.pending) \
.first()
.exclude(state=StepState.pending).first()
return processor.assignee if processor else None
def has_current_assignee(self, assignee):
return self.ticket_steps.filter(
level=self.approval_step,
ticket_assignees__assignee=assignee,
level=self.approval_step
).exists()
def has_all_assignee(self, assignee):
@ -282,19 +283,19 @@ class Ticket(StatusMixin, CommonModelMixin):
)
# 申请人
applicant = models.ForeignKey(
'users.User', related_name='applied_tickets', on_delete=models.SET_NULL,
null=True, verbose_name=_("Applicant")
'users.User', related_name='applied_tickets', null=True,
on_delete=models.SET_NULL, verbose_name=_("Applicant")
)
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
flow = models.ForeignKey(
'TicketFlow', related_name='tickets', on_delete=models.SET_NULL,
null=True, verbose_name=_('TicketFlow')
'TicketFlow', related_name='tickets', null=True,
on_delete=models.SET_NULL, verbose_name=_('TicketFlow')
)
approval_step = models.SmallIntegerField(
default=TicketLevel.one, choices=TicketLevel.choices, verbose_name=_('Approval step')
)
serial_num = models.CharField(_('Serial number'), max_length=128, unique=True, null=True)
comment = models.TextField(default='', blank=True, verbose_name=_('Comment'))
rel_snapshot = models.JSONField(verbose_name=_('Relation snapshot'), default=dict)
serial_num = models.CharField(_('Serial number'), max_length=128, unique=True, null=True)
meta = models.JSONField(encoder=ModelJSONFieldEncoder, default=dict, verbose_name=_("Meta"))
org_id = models.CharField(
max_length=36, blank=True, default='', verbose_name=_('Organization'), db_index=True
@ -324,7 +325,7 @@ class Ticket(StatusMixin, CommonModelMixin):
@classmethod
def get_user_related_tickets(cls, user):
queries = Q(applicant=user) | Q(ticket_steps__ticket_assignees__assignee=user)
tickets = cls.objects.all().filter(queries).distinct()
tickets = cls.objects.filter(queries).distinct()
return tickets
def get_current_ticket_flow_approve(self):
@ -398,15 +399,17 @@ class Ticket(StatusMixin, CommonModelMixin):
value = self.rel_snapshot[name]
elif isinstance(field, related.ManyToManyField):
value = ', '.join(self.rel_snapshot[name])
elif isinstance(value, list):
value = ', '.join(value)
return value
def get_local_snapshot(self):
snapshot = {}
excludes = ['ticket_ptr']
fields = self._meta._forward_fields_map
json_data = json.dumps(model_to_dict(self), cls=ModelJSONFieldEncoder)
data = json.loads(json_data)
snapshot = {}
local_fields = self._meta.local_fields + self._meta.local_many_to_many
excludes = ['ticket_ptr']
item_names = [field.name for field in local_fields if field.name not in excludes]
for name in item_names:
field = fields[name]

View File

@ -8,12 +8,10 @@ __all__ = ['ApplyLoginAssetTicket']
class ApplyLoginAssetTicket(Ticket):
apply_login_user = models.ForeignKey(
'users.User', on_delete=models.SET_NULL, null=True,
verbose_name=_('Login user'),
'users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Login user'),
)
apply_login_asset = models.ForeignKey(
'assets.Asset', on_delete=models.SET_NULL, null=True,
verbose_name=_('Login asset'),
'assets.Asset', on_delete=models.SET_NULL, null=True, verbose_name=_('Login asset'),
)
apply_login_account = models.CharField(
max_length=128, default='', verbose_name=_('Login account')

View File

@ -9,7 +9,7 @@ from tickets.models import ApplyAssetTicket
from .common import BaseApplyAssetSerializer
from .ticket import TicketApplySerializer
__all__ = ['ApplyAssetSerializer']
__all__ = ['ApplyAssetSerializer', 'ApproveAssetSerializer']
asset_or_node_help_text = _("Select at least one asset or node")
@ -22,18 +22,14 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
class Meta(TicketApplySerializer.Meta):
model = ApplyAssetTicket
fields_mini = ['id', 'title']
writeable_fields = [
'id', 'title', 'apply_nodes', 'apply_assets',
'apply_accounts', 'apply_actions', 'org_id', 'comment',
'apply_date_start', 'apply_date_expired'
'apply_nodes', 'apply_assets', 'apply_accounts',
'apply_actions', 'apply_date_start', 'apply_date_expired'
]
fields = TicketApplySerializer.Meta.fields + writeable_fields + ['apply_permission_name', ]
read_only_fields = list(set(fields) - set(writeable_fields))
read_only_fields = TicketApplySerializer.Meta.read_only_fields + ['apply_permission_name', ]
fields = TicketApplySerializer.Meta.fields_small + writeable_fields + read_only_fields
ticket_extra_kwargs = TicketApplySerializer.Meta.extra_kwargs
extra_kwargs = {
'apply_nodes': {'required': False},
'apply_assets': {'required': False},
'apply_accounts': {'required': False},
}
extra_kwargs.update(ticket_extra_kwargs)
@ -48,8 +44,7 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
attrs['type'] = 'apply_asset'
attrs = super().validate(attrs)
if self.is_final_approval and (
not attrs.get('apply_nodes')
and not attrs.get('apply_assets')
not attrs.get('apply_nodes') and not attrs.get('apply_assets')
):
raise serializers.ValidationError({
'apply_nodes': asset_or_node_help_text,
@ -62,3 +57,9 @@ class ApplyAssetSerializer(BaseApplyAssetSerializer, TicketApplySerializer):
def setup_eager_loading(cls, queryset):
queryset = queryset.prefetch_related('apply_nodes', 'apply_assets')
return queryset
class ApproveAssetSerializer(ApplyAssetSerializer):
class Meta(ApplyAssetSerializer.Meta):
read_only_fields = TicketApplySerializer.Meta.fields_small + \
ApplyAssetSerializer.Meta.read_only_fields

View File

@ -9,8 +9,8 @@ __all__ = [
class ApplyCommandConfirmSerializer(TicketApplySerializer):
class Meta:
model = ApplyCommandTicket
fields = TicketApplySerializer.Meta.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'
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'
]
fields = TicketApplySerializer.Meta.fields + writeable_fields

View File

@ -75,10 +75,11 @@ class BaseApplyAssetSerializer(serializers.Serializer):
def create(self, validated_data):
instance = super().create(validated_data)
name = _('Created by ticket ({}-{})').format(instance.title, str(instance.id)[:4])
with tmp_to_org(instance.org_id):
org_id = instance.org_id
with tmp_to_org(org_id):
if not self.permission_model.objects.filter(name=name).exists():
instance.apply_permission_name = name
instance.save()
instance.save(update_fields=['apply_permission_name'])
return instance
raise serializers.ValidationError(_('Permission named `{}` already exists'.format(name)))

View File

@ -9,6 +9,5 @@ __all__ = [
class LoginAssetConfirmSerializer(TicketApplySerializer):
class Meta:
model = ApplyLoginAssetTicket
fields = TicketApplySerializer.Meta.fields + [
'apply_login_user', 'apply_login_asset', 'apply_login_account'
]
writeable_fields = ['apply_login_user', 'apply_login_asset', 'apply_login_account']
fields = TicketApplySerializer.Meta.fields + writeable_fields

View File

@ -7,8 +7,7 @@ __all__ = [
class LoginConfirmSerializer(TicketApplySerializer):
class Meta:
class Meta(TicketApplySerializer.Meta):
model = ApplyLoginTicket
fields = TicketApplySerializer.Meta.fields + [
'apply_login_ip', 'apply_login_city', 'apply_login_datetime'
]
writeable_fields = ['apply_login_ip', 'apply_login_city', 'apply_login_datetime']
fields = TicketApplySerializer.Meta.fields + writeable_fields

View File

@ -22,13 +22,12 @@ class TicketSerializer(OrgResourceModelSerializerMixin):
class Meta:
model = Ticket
fields_mini = ['id', 'title']
fields_small = fields_mini + [
'type', 'status', 'state', 'approval_step', 'comment',
'date_created', 'date_updated', 'org_id', 'rel_snapshot',
'process_map', 'org_name', 'serial_num'
fields_small = fields_mini + ['org_id', 'comment']
read_only_fields = [
'serial_num', 'process_map', 'approval_step', 'type', 'state', 'applicant',
'status', 'date_created', 'date_updated', 'org_name', 'rel_snapshot'
]
fields_fk = ['applicant', ]
fields = fields_small + fields_fk
fields = fields_small + read_only_fields
extra_kwargs = {
'type': {'required': True}
}
@ -72,8 +71,6 @@ class TicketApplySerializer(TicketSerializer):
if self.instance:
return attrs
print("Attrs: ", attrs)
ticket_type = attrs.get('type')
org_id = attrs.get('org_id')
flow = TicketFlow.get_org_related_flows(org_id=org_id) \
@ -81,7 +78,7 @@ class TicketApplySerializer(TicketSerializer):
if flow:
attrs['flow'] = flow
return attrs
else:
error = _('The ticket flow `{}` does not exist'.format(ticket_type))
raise serializers.ValidationError(error)
return attrs