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

pull/14860/head
ibuler 2025-02-06 10:05:18 +08:00
commit f223960b6a
13 changed files with 283 additions and 195 deletions

View File

@ -187,7 +187,7 @@ class AccountHistoriesSecretAPI(ExtraFilterFieldsMixin, AccountRecordViewLogMixi
@property
def latest_change_secret_record(self) -> ChangeSecretRecord:
return self.account.change_secret_records.filter(
return self.account.changesecretrecords.filter(
status=ChangeSecretRecordStatusChoice.pending
).order_by('-date_created').first()

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import Count, F, Q
from django.http.response import JsonResponse
from rest_framework.views import APIView
from accounts.models import (
Account, RiskChoice, GatherAccountsAutomation,
Account, GatherAccountsAutomation,
PushAccountAutomation, BackupAccountAutomation,
AccountRisk, IntegrationApplication, ChangeSecretAutomation
)
@ -23,126 +25,109 @@ class PamDashboardApi(APIView):
@staticmethod
def get_type_to_accounts():
result = Account.objects.annotate(type=F('asset__platform__type')). \
values('type').order_by('type').annotate(total=Count(1))
result = Account.objects.annotate(type=F('asset__platform__type')) \
.values('type').order_by('type').annotate(total=Count(1))
all_types_dict = dict(AllTypes.choices())
result = [
{
**i,
'label': all_types_dict.get(i['type'], i['type'])
}
return [
{**i, 'label': all_types_dict.get(i['type'], i['type'])}
for i in result
]
return result
@staticmethod
def get_account_risk_data(_all, query_params):
agg_map = {
'total_privileged_accounts': ('long_time_no_login_count', Q(risk='long_time_no_login')),
'total_new_found_accounts': ('new_found_count', Q(risk='new_found')),
'total_group_changed_accounts': ('group_changed_count', Q(risk='group_changed')),
'total_sudo_changed_accounts': ('sudo_changed_count', Q(risk='sudo_changed')),
'total_authorized_keys_changed_accounts': (
'authorized_keys_changed_count', Q(risk='authorized_keys_changed')),
'total_account_deleted_accounts': ('account_deleted_count', Q(risk='account_deleted')),
'total_password_expired_accounts': ('password_expired_count', Q(risk='password_expired')),
'total_long_time_password_accounts': ('long_time_password_count', Q(risk='long_time_password')),
'total_weak_password_accounts': ('weak_password_count', Q(risk='weak_password')),
'total_leaked_password_accounts': ('leaked_password_count', Q(risk='leaked_password')),
'total_repeated_password_accounts': ('repeated_password_count', Q(risk='repeated_password')),
'total_password_error_accounts': ('password_error_count', Q(risk='password_error')),
'total_no_admin_account_accounts': ('no_admin_account_count', Q(risk='no_admin_account')),
}
aggregations = {
agg_key: Count('account_id', distinct=True, filter=agg_filter)
for param_key, (agg_key, agg_filter) in agg_map.items()
if _all or query_params.get(param_key)
}
data = {}
if aggregations:
account_stats = AccountRisk.objects.filter(account__isnull=False).aggregate(**aggregations)
data = {param_key: account_stats.get(agg_key) for param_key, (agg_key, _) in agg_map.items() if
agg_key in account_stats}
return data
@staticmethod
def get_account_data(_all, query_params):
agg_map = {
'total_accounts': ('total_count', Count('id')),
'total_privileged_accounts': ('privileged_count', Count('id', filter=Q(privileged=True))),
'total_connectivity_ok_accounts': ('connectivity_ok_count', Count('id', filter=Q(connectivity='ok'))),
'total_secret_reset_accounts': ('secret_reset_count', Count('id', filter=Q(secret_reset=True))),
'total_unavailable_accounts': ('unavailable_count', Count('id', filter=Q(is_active=False))),
'total_week_add_accounts': ('week_add_count', Count('id', filter=Q(date_created__gte=local_monday()))),
}
aggregations = {
agg_key: agg_expr
for param_key, (agg_key, agg_expr) in agg_map.items()
if _all or query_params.get(param_key)
}
data = {}
account_stats = Account.objects.aggregate(**aggregations)
for param_key, (agg_key, __) in agg_map.items():
if agg_key in account_stats:
data[param_key] = account_stats[agg_key]
if _all or query_params.get('total_ordinary_accounts'):
if 'total_count' in account_stats and 'privileged_count' in account_stats:
data['total_ordinary_accounts'] = \
account_stats['total_count'] - account_stats['privileged_count']
return data
@staticmethod
def get_automation_counts(_all, query_params):
automation_counts = defaultdict(int)
automation_models = {
'total_count_change_secret_automation': ChangeSecretAutomation,
'total_count_gathered_account_automation': GatherAccountsAutomation,
'total_count_push_account_automation': PushAccountAutomation,
'total_count_backup_account_automation': BackupAccountAutomation,
'total_count_risk_account': AccountRisk,
'total_count_integration_application': IntegrationApplication,
}
for param_key, model in automation_models.items():
if _all or query_params.get(param_key):
automation_counts[param_key] = model.objects.count()
return automation_counts
def get(self, request, *args, **kwargs):
monday_time = local_monday()
query_params = self.request.query_params
_all = query_params.get('all')
agg_map = {
'total_accounts': (
'total_count',
Count('id')
),
'total_privileged_accounts': (
'privileged_count',
Count('id', filter=Q(privileged=True))
),
'total_connectivity_ok_accounts': (
'connectivity_ok_count',
Count('id', filter=Q(connectivity='ok'))
),
'total_secret_reset_accounts': (
'secret_reset_count',
Count('id', filter=Q(secret_reset=True))
),
'total_unavailable_accounts': (
'unavailable_count',
Count('id', filter=Q(is_active=False))
),
'total_week_add_accounts': (
'week_add_count',
Count('id', filter=Q(date_created__gte=monday_time))
),
}
aggregations = {}
for param_key, (agg_key, agg_expr) in agg_map.items():
if _all or query_params.get(param_key):
aggregations[agg_key] = agg_expr
data = {}
if aggregations:
account_stats = Account.objects.aggregate(**aggregations)
for param_key, (agg_key, __) in agg_map.items():
if agg_key in account_stats:
data[param_key] = account_stats[agg_key]
if (_all or query_params.get('total_ordinary_accounts')):
if 'total_count' in account_stats and 'privileged_count' in account_stats:
data['total_ordinary_accounts'] = \
account_stats['total_count'] - account_stats['privileged_count']
if _all or query_params.get('total_unmanaged_accounts'):
data['total_unmanaged_accounts'] = Account.get_risks(
risk_type=RiskChoice.new_found).count()
if _all or query_params.get('total_long_time_no_login_accounts'):
data['total_long_time_no_login_accounts'] = Account.get_risks(
risk_type=RiskChoice.long_time_no_login).count()
if _all or query_params.get('total_weak_password_accounts'):
data['total_weak_password_accounts'] = Account.get_risks(
risk_type=RiskChoice.weak_password).count()
if _all or query_params.get('total_long_time_change_password_accounts'):
data['total_long_time_change_password_accounts'] = Account.get_risks(
risk_type=RiskChoice.long_time_password).count()
if _all or query_params.get('total_leaked_password_accounts'):
data['total_leaked_password_accounts'] = Account.get_risks(
risk_type=RiskChoice.leaked_password).count()
if _all or query_params.get('total_repeated_password_accounts'):
data['total_repeated_password_accounts'] = Account.get_risks(
risk_type=RiskChoice.repeated_password).count()
data.update(self.get_account_data(_all, query_params))
data.update(self.get_account_risk_data(_all, query_params))
data.update(self.get_automation_counts(_all, query_params))
if _all or query_params.get('total_count_type_to_accounts'):
data.update({
'total_count_type_to_accounts': self.get_type_to_accounts(),
})
if _all or query_params.get('total_count_change_secret_automation'):
data.update({
'total_count_change_secret_automation': ChangeSecretAutomation.objects.count()
})
if _all or query_params.get('total_count_gathered_account_automation'):
data.update({
'total_count_gathered_account_automation': GatherAccountsAutomation.objects.count()
})
if _all or query_params.get('total_count_push_account_automation'):
data.update({
'total_count_push_account_automation': PushAccountAutomation.objects.count()
})
if _all or query_params.get('total_count_backup_account_automation'):
data.update({
'total_count_backup_account_automation': BackupAccountAutomation.objects.count()
})
if _all or query_params.get('total_count_risk_account'):
data.update({
'total_count_risk_account': AccountRisk.objects.count()
})
if _all or query_params.get('total_count_integration_application'):
data.update({
'total_count_integration_application': IntegrationApplication.objects.count()
})
return JsonResponse(data, status=200)

View File

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
#
from rest_framework import mixins
from accounts import serializers
from accounts.const import AutomationTypes
from accounts.models import PushAccountAutomation
from orgs.mixins.api import OrgBulkModelViewSet
from accounts.filters import PushAccountRecordFilterSet
from accounts.models import PushAccountAutomation, PushSecretRecord
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
@ -13,6 +15,7 @@ from .base import (
__all__ = [
'PushAccountAutomationViewSet', 'PushAccountAssetsListApi', 'PushAccountRemoveAssetApi',
'PushAccountAddAssetApi', 'PushAccountNodeAddRemoveApi', 'PushAccountExecutionViewSet',
'PushAccountRecordViewSet'
]
@ -39,6 +42,22 @@ class PushAccountExecutionViewSet(AutomationExecutionViewSet):
return queryset
class PushAccountRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
filterset_class = PushAccountRecordFilterSet
search_fields = ('asset__address', 'account_username')
ordering_fields = ('date_finished',)
tp = AutomationTypes.push_account
serializer_classes = {
'default': serializers.PushSecretRecordSerializer,
}
def get_queryset(self):
qs = PushSecretRecord.get_valid_records()
return qs.filter(
execution__automation__type=self.tp
)
class PushAccountAssetsListApi(AutomationAssetsListApi):
model = PushAccountAutomation

View File

@ -1,13 +1,15 @@
from copy import deepcopy
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from accounts.automations.methods import platform_automation_methods
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType
from accounts.const import SSHKeyStrategy, SecretStrategy, SecretType, ChangeSecretRecordStatusChoice
from accounts.models import BaseAccountQuerySet
from assets.automations.base.manager import BasePlaybookManager
from assets.const import HostTypes
from common.db.utils import safe_db_connection
from common.utils import get_logger
logger = get_logger(__name__)
@ -32,6 +34,8 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
'ssh_key_change_strategy', SSHKeyStrategy.set_jms
)
self.account_ids = self.execution.snapshot['accounts']
self.record_map = self.execution.snapshot.get('record_map', {}) # 这个是某个失败的记录重试
self.name_recorder_mapper = {} # 做个映射,方便后面处理
def gen_account_inventory(self, account, asset, h, path_dir):
raise NotImplementedError
@ -119,3 +123,51 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
inventory_hosts.append(h)
return inventory_hosts
def on_host_success(self, host, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.date_finished = timezone.now()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = getattr(recorder, 'new_secret', account.secret)
account.date_updated = timezone.now()
with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'date_updated'])
self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append(
{
"asset": str(account.asset),
"username": account.username,
}
)
super().on_host_success(host, result)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
}
)
super().on_host_error(host, error, result)

View File

@ -2,7 +2,6 @@ import os
import time
from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook
@ -12,7 +11,6 @@ from accounts.const import (
from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer
from common.db.utils import safe_db_connection
from common.decorators import bulk_create_decorator
from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file
@ -26,11 +24,6 @@ logger = get_logger(__name__)
class ChangeSecretManager(BaseChangeSecretPushManager):
ansible_account_prefer = ''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.record_map = self.execution.snapshot.get('record_map', {}) # 这个是某个失败的记录重试
self.name_recorder_mapper = {} # 做个映射,方便后面处理
@classmethod
def method_type(cls):
return AutomationTypes.change_secret
@ -74,54 +67,6 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
)
return recorder
def on_host_success(self, host, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.success.value
recorder.date_finished = timezone.now()
account = recorder.account
if not account:
print("Account not found, deleted ?")
return
account.secret = recorder.new_secret
account.date_updated = timezone.now()
with safe_db_connection():
recorder.save(update_fields=['status', 'date_finished'])
account.save(update_fields=['secret', 'date_updated'])
self.summary['ok_accounts'] += 1
self.result['ok_accounts'].append(
{
"asset": str(account.asset),
"username": account.username,
}
)
super().on_host_success(host, result)
def on_host_error(self, host, error, result):
recorder = self.name_recorder_mapper.get(host)
if not recorder:
return
recorder.status = ChangeSecretRecordStatusChoice.failed.value
recorder.date_finished = timezone.now()
recorder.error = error
try:
recorder.save()
except Exception as e:
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
self.summary['fail_accounts'] += 1
self.result['fail_accounts'].append(
{
"asset": str(recorder.asset),
"username": recorder.account.username,
}
)
super().on_host_success(host, result)
def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \
and not self.execution.snapshot['secret']:

View File

@ -1,9 +1,11 @@
from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from common.decorators import bulk_create_decorator
from common.utils import get_logger
from common.utils.timezone import local_now_filename
from ..base.manager import BaseChangeSecretPushManager
from ...models import PushSecretRecord
logger = get_logger(__name__)
@ -17,12 +19,33 @@ class PushAccountManager(BaseChangeSecretPushManager):
return account.secret
def gen_account_inventory(self, account, asset, h, path_dir):
self.get_or_create_record(asset, account, h['name'])
secret = self.get_secret(account)
secret_type = account.secret_type
new_secret, private_key_path = self.handle_ssh_secret(secret_type, secret, path_dir)
h = self.gen_inventory(h, account, new_secret, private_key_path, asset)
return h
def get_or_create_record(self, asset, account, name):
asset_account_id = f'{asset.id}-{account.id}'
if asset_account_id in self.record_map:
record_id = self.record_map[asset_account_id]
recorder = PushSecretRecord.objects.filter(id=record_id).first()
else:
recorder = self.create_record(asset, account)
self.name_recorder_mapper[name] = recorder
return recorder
@bulk_create_decorator(PushSecretRecord)
def create_record(self, asset, account):
recorder = PushSecretRecord(
asset=asset, account=account, execution=self.execution,
comment=f'{account.username}@{asset.address}'
)
return recorder
def print_summary(self):
print('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end')

View File

@ -7,7 +7,7 @@ from django_filters import rest_framework as drf_filters
from assets.models import Node
from common.drf.filters import BaseFilterSet
from common.utils.timezone import local_zero_hour, local_now
from .models import Account, GatheredAccount, ChangeSecretRecord
from .models import Account, GatheredAccount, ChangeSecretRecord, PushSecretRecord
class AccountFilterSet(BaseFilterSet):
@ -134,7 +134,7 @@ class GatheredAccountFilterSet(BaseFilterSet):
fields = ["id", "username"]
class ChangeSecretRecordFilterSet(BaseFilterSet):
class SecretRecordMixin:
asset_name = drf_filters.CharFilter(
field_name="asset__name", lookup_expr="icontains"
)
@ -155,6 +155,14 @@ class ChangeSecretRecordFilterSet(BaseFilterSet):
dt = local_now() - timezone.timedelta(days=value)
return queryset.filter(date_finished__gte=dt)
class ChangeSecretRecordFilterSet(SecretRecordMixin, BaseFilterSet):
class Meta:
model = ChangeSecretRecord
fields = ["id", "status", "asset_id", "execution"]
class PushAccountRecordFilterSet(SecretRecordMixin, BaseFilterSet):
class Meta:
model = PushSecretRecord
fields = ["id", "status", "asset_id", "execution"]

View File

@ -0,0 +1,51 @@
# Generated by Django 4.1.13 on 2025-01-23 07:22
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0011_auto_20241204_1516'),
('accounts', '0028_remove_checkaccountengine_is_active_and_more'),
]
operations = [
migrations.AlterField(
model_name='changesecretrecord',
name='account',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='accounts.account'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='asset',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_%(class)ss', to='assets.asset'),
),
migrations.AlterField(
model_name='changesecretrecord',
name='execution',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='execution_%(class)ss', to='accounts.automationexecution'),
),
migrations.CreateModel(
name='PushSecretRecord',
fields=[
('created_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Created by')),
('updated_by', models.CharField(blank=True, max_length=128, null=True, verbose_name='Updated by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('date_finished', models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Date finished')),
('status', models.CharField(default='pending', max_length=16, verbose_name='Status')),
('error', models.TextField(blank=True, null=True, verbose_name='Error')),
('account', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='accounts.account')),
('asset', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_%(class)ss', to='assets.asset')),
('execution', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='execution_%(class)ss', to='accounts.automationexecution')),
],
options={
'verbose_name': 'Push secret record',
},
),
]

View File

@ -166,17 +166,6 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
return escape(value)
@classmethod
def get_risks(cls, queryset=None, risk_type=None):
query = {
'risks__risk': risk_type
}
if queryset is None:
queryset = cls.objects.all()
return queryset.filter(**query)
def replace_history_model_with_mixin():
"""

View File

@ -9,7 +9,7 @@ from common.db import fields
from common.db.models import JMSBaseModel
from .base import AccountBaseAutomation, ChangeSecretMixin
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ]
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'BaseSecretRecord']
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -30,36 +30,42 @@ class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
return attr_json
class ChangeSecretRecord(JMSBaseModel):
class BaseSecretRecord(JMSBaseModel):
account = models.ForeignKey(
'accounts.Account', on_delete=models.SET_NULL,
null=True, related_name='change_secret_records'
null=True, related_name='%(class)ss'
)
asset = models.ForeignKey(
'assets.Asset', on_delete=models.SET_NULL,
null=True, related_name='asset_change_secret_records'
null=True, related_name='asset_%(class)ss'
)
execution = models.ForeignKey(
'accounts.AutomationExecution', on_delete=models.SET_NULL,
null=True, related_name='execution_change_secret_records',
null=True, related_name='execution_%(class)ss',
)
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'), db_index=True)
ignore_fail = models.BooleanField(default=False, verbose_name=_('Ignore fail'))
status = models.CharField(
max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value
)
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
class Meta:
verbose_name = _("Change secret record")
abstract = True
def __str__(self):
return f'{self.account.username}@{self.asset}'
@staticmethod
def get_valid_records():
return ChangeSecretRecord.objects.exclude(
@classmethod
def get_valid_records(cls):
return cls.objects.exclude(
Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True)
)
class ChangeSecretRecord(BaseSecretRecord):
old_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Old secret'))
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
ignore_fail = models.BooleanField(default=False, verbose_name=_('Ignore fail'))
class Meta:
verbose_name = _("Change secret record")

View File

@ -3,10 +3,10 @@ from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes
from accounts.models import Account
from .base import AccountBaseAutomation
from .change_secret import ChangeSecretMixin
from .base import AccountBaseAutomation, ChangeSecretMixin
from .change_secret import BaseSecretRecord
__all__ = ['PushAccountAutomation']
__all__ = ['PushAccountAutomation', 'PushSecretRecord']
class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -36,3 +36,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
class Meta:
verbose_name = _("Push asset account")
class PushSecretRecord(BaseSecretRecord):
class Meta:
verbose_name = _("Push secret record")

View File

@ -2,7 +2,7 @@ from accounts.const import AutomationTypes
from accounts.models import PushAccountAutomation
from .change_secret import (
ChangeSecretAutomationSerializer, ChangeSecretUpdateAssetSerializer,
ChangeSecretUpdateNodeSerializer
ChangeSecretUpdateNodeSerializer, ChangeSecretRecordSerializer
)
@ -19,6 +19,10 @@ class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
return AutomationTypes.push_account
class PushSecretRecordSerializer(ChangeSecretRecordSerializer):
pass
class PushAccountUpdateAssetSerializer(ChangeSecretUpdateAssetSerializer):
class Meta:
model = PushAccountAutomation

View File

@ -23,6 +23,7 @@ router.register(r'gather-account-automations', api.GatherAccountsAutomationViewS
router.register(r'gather-account-executions', api.GatherAccountsExecutionViewSet, 'gather-account-execution')
router.register(r'push-account-automations', api.PushAccountAutomationViewSet, 'push-account-automation')
router.register(r'push-account-executions', api.PushAccountExecutionViewSet, 'push-account-execution')
router.register(r'push-account-records', api.PushAccountRecordViewSet, 'push-account-record')
router.register(r'check-account-automations', api.CheckAccountAutomationViewSet, 'check-account-automation')
router.register(r'check-account-executions', api.CheckAccountExecutionViewSet, 'check-account-execution')
router.register(r'account-check-engines', api.CheckAccountEngineViewSet, 'account-check-engine')