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 @property
def latest_change_secret_record(self) -> ChangeSecretRecord: def latest_change_secret_record(self) -> ChangeSecretRecord:
return self.account.change_secret_records.filter( return self.account.changesecretrecords.filter(
status=ChangeSecretRecordStatusChoice.pending status=ChangeSecretRecordStatusChoice.pending
).order_by('-date_created').first() ).order_by('-date_created').first()

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import defaultdict
from django.db.models import Count, F, Q from django.db.models import Count, F, Q
from django.http.response import JsonResponse from django.http.response import JsonResponse
from rest_framework.views import APIView from rest_framework.views import APIView
from accounts.models import ( from accounts.models import (
Account, RiskChoice, GatherAccountsAutomation, Account, GatherAccountsAutomation,
PushAccountAutomation, BackupAccountAutomation, PushAccountAutomation, BackupAccountAutomation,
AccountRisk, IntegrationApplication, ChangeSecretAutomation AccountRisk, IntegrationApplication, ChangeSecretAutomation
) )
@ -23,126 +25,109 @@ class PamDashboardApi(APIView):
@staticmethod @staticmethod
def get_type_to_accounts(): def get_type_to_accounts():
result = Account.objects.annotate(type=F('asset__platform__type')). \ result = Account.objects.annotate(type=F('asset__platform__type')) \
values('type').order_by('type').annotate(total=Count(1)) .values('type').order_by('type').annotate(total=Count(1))
all_types_dict = dict(AllTypes.choices()) all_types_dict = dict(AllTypes.choices())
result = [ return [
{ {**i, 'label': all_types_dict.get(i['type'], i['type'])}
**i,
'label': all_types_dict.get(i['type'], i['type'])
}
for i in result 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): def get(self, request, *args, **kwargs):
monday_time = local_monday()
query_params = self.request.query_params query_params = self.request.query_params
_all = query_params.get('all') _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 = {} data = {}
if aggregations: data.update(self.get_account_data(_all, query_params))
account_stats = Account.objects.aggregate(**aggregations) data.update(self.get_account_risk_data(_all, query_params))
for param_key, (agg_key, __) in agg_map.items(): data.update(self.get_automation_counts(_all, query_params))
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()
if _all or query_params.get('total_count_type_to_accounts'): if _all or query_params.get('total_count_type_to_accounts'):
data.update({ data.update({
'total_count_type_to_accounts': self.get_type_to_accounts(), '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) return JsonResponse(data, status=200)

View File

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import mixins
from accounts import serializers from accounts import serializers
from accounts.const import AutomationTypes from accounts.const import AutomationTypes
from accounts.models import PushAccountAutomation from accounts.filters import PushAccountRecordFilterSet
from orgs.mixins.api import OrgBulkModelViewSet from accounts.models import PushAccountAutomation, PushSecretRecord
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
from .base import ( from .base import (
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi, AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
AutomationNodeAddRemoveApi, AutomationExecutionViewSet AutomationNodeAddRemoveApi, AutomationExecutionViewSet
@ -13,6 +15,7 @@ from .base import (
__all__ = [ __all__ = [
'PushAccountAutomationViewSet', 'PushAccountAssetsListApi', 'PushAccountRemoveAssetApi', 'PushAccountAutomationViewSet', 'PushAccountAssetsListApi', 'PushAccountRemoveAssetApi',
'PushAccountAddAssetApi', 'PushAccountNodeAddRemoveApi', 'PushAccountExecutionViewSet', 'PushAccountAddAssetApi', 'PushAccountNodeAddRemoveApi', 'PushAccountExecutionViewSet',
'PushAccountRecordViewSet'
] ]
@ -39,6 +42,22 @@ class PushAccountExecutionViewSet(AutomationExecutionViewSet):
return queryset 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): class PushAccountAssetsListApi(AutomationAssetsListApi):
model = PushAccountAutomation model = PushAccountAutomation

View File

@ -1,13 +1,15 @@
from copy import deepcopy from copy import deepcopy
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from accounts.automations.methods import platform_automation_methods 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 accounts.models import BaseAccountQuerySet
from assets.automations.base.manager import BasePlaybookManager from assets.automations.base.manager import BasePlaybookManager
from assets.const import HostTypes from assets.const import HostTypes
from common.db.utils import safe_db_connection
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
@ -32,6 +34,8 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
'ssh_key_change_strategy', SSHKeyStrategy.set_jms 'ssh_key_change_strategy', SSHKeyStrategy.set_jms
) )
self.account_ids = self.execution.snapshot['accounts'] 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): def gen_account_inventory(self, account, asset, h, path_dir):
raise NotImplementedError raise NotImplementedError
@ -119,3 +123,51 @@ class BaseChangeSecretPushManager(AccountBasePlaybookManager):
inventory_hosts.append(h) inventory_hosts.append(h)
return inventory_hosts 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 import time
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from xlsxwriter import Workbook from xlsxwriter import Workbook
@ -12,7 +11,6 @@ from accounts.const import (
from accounts.models import ChangeSecretRecord from accounts.models import ChangeSecretRecord
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretReportMsg
from accounts.serializers import ChangeSecretRecordBackUpSerializer from accounts.serializers import ChangeSecretRecordBackUpSerializer
from common.db.utils import safe_db_connection
from common.decorators import bulk_create_decorator from common.decorators import bulk_create_decorator
from common.utils import get_logger from common.utils import get_logger
from common.utils.file import encrypt_and_compress_zip_file from common.utils.file import encrypt_and_compress_zip_file
@ -26,11 +24,6 @@ logger = get_logger(__name__)
class ChangeSecretManager(BaseChangeSecretPushManager): class ChangeSecretManager(BaseChangeSecretPushManager):
ansible_account_prefer = '' 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 @classmethod
def method_type(cls): def method_type(cls):
return AutomationTypes.change_secret return AutomationTypes.change_secret
@ -74,54 +67,6 @@ class ChangeSecretManager(BaseChangeSecretPushManager):
) )
return recorder 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): def check_secret(self):
if self.secret_strategy == SecretStrategy.custom \ if self.secret_strategy == SecretStrategy.custom \
and not self.execution.snapshot['secret']: and not self.execution.snapshot['secret']:

View File

@ -1,9 +1,11 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from accounts.const import AutomationTypes from accounts.const import AutomationTypes
from common.decorators import bulk_create_decorator
from common.utils import get_logger from common.utils import get_logger
from common.utils.timezone import local_now_filename from common.utils.timezone import local_now_filename
from ..base.manager import BaseChangeSecretPushManager from ..base.manager import BaseChangeSecretPushManager
from ...models import PushSecretRecord
logger = get_logger(__name__) logger = get_logger(__name__)
@ -17,12 +19,33 @@ class PushAccountManager(BaseChangeSecretPushManager):
return account.secret return account.secret
def gen_account_inventory(self, account, asset, h, path_dir): 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 = self.get_secret(account)
secret_type = account.secret_type secret_type = account.secret_type
new_secret, private_key_path = self.handle_ssh_secret(secret_type, secret, path_dir) 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) h = self.gen_inventory(h, account, new_secret, private_key_path, asset)
return h 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): def print_summary(self):
print('\n\n' + '-' * 80) print('\n\n' + '-' * 80)
plan_execution_end = _('Plan execution end') 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 assets.models import Node
from common.drf.filters import BaseFilterSet from common.drf.filters import BaseFilterSet
from common.utils.timezone import local_zero_hour, local_now 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): class AccountFilterSet(BaseFilterSet):
@ -134,7 +134,7 @@ class GatheredAccountFilterSet(BaseFilterSet):
fields = ["id", "username"] fields = ["id", "username"]
class ChangeSecretRecordFilterSet(BaseFilterSet): class SecretRecordMixin:
asset_name = drf_filters.CharFilter( asset_name = drf_filters.CharFilter(
field_name="asset__name", lookup_expr="icontains" field_name="asset__name", lookup_expr="icontains"
) )
@ -155,6 +155,14 @@ class ChangeSecretRecordFilterSet(BaseFilterSet):
dt = local_now() - timezone.timedelta(days=value) dt = local_now() - timezone.timedelta(days=value)
return queryset.filter(date_finished__gte=dt) return queryset.filter(date_finished__gte=dt)
class ChangeSecretRecordFilterSet(SecretRecordMixin, BaseFilterSet):
class Meta: class Meta:
model = ChangeSecretRecord model = ChangeSecretRecord
fields = ["id", "status", "asset_id", "execution"] 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) 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(): def replace_history_model_with_mixin():
""" """

View File

@ -9,7 +9,7 @@ from common.db import fields
from common.db.models import JMSBaseModel from common.db.models import JMSBaseModel
from .base import AccountBaseAutomation, ChangeSecretMixin from .base import AccountBaseAutomation, ChangeSecretMixin
__all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', ] __all__ = ['ChangeSecretAutomation', 'ChangeSecretRecord', 'BaseSecretRecord']
class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation): class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -30,36 +30,42 @@ class ChangeSecretAutomation(ChangeSecretMixin, AccountBaseAutomation):
return attr_json return attr_json
class ChangeSecretRecord(JMSBaseModel): class BaseSecretRecord(JMSBaseModel):
account = models.ForeignKey( account = models.ForeignKey(
'accounts.Account', on_delete=models.SET_NULL, 'accounts.Account', on_delete=models.SET_NULL,
null=True, related_name='change_secret_records' null=True, related_name='%(class)ss'
) )
asset = models.ForeignKey( asset = models.ForeignKey(
'assets.Asset', on_delete=models.SET_NULL, '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( execution = models.ForeignKey(
'accounts.AutomationExecution', on_delete=models.SET_NULL, '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) 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( status = models.CharField(
max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value max_length=16, verbose_name=_('Status'), default=ChangeSecretRecordStatusChoice.pending.value
) )
error = models.TextField(blank=True, null=True, verbose_name=_('Error')) error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
class Meta: class Meta:
verbose_name = _("Change secret record") abstract = True
def __str__(self): def __str__(self):
return f'{self.account.username}@{self.asset}' return f'{self.account.username}@{self.asset}'
@staticmethod @classmethod
def get_valid_records(): def get_valid_records(cls):
return ChangeSecretRecord.objects.exclude( return cls.objects.exclude(
Q(execution__isnull=True) | Q(asset__isnull=True) | Q(account__isnull=True) 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.const import AutomationTypes
from accounts.models import Account from accounts.models import Account
from .base import AccountBaseAutomation from .base import AccountBaseAutomation, ChangeSecretMixin
from .change_secret import ChangeSecretMixin from .change_secret import BaseSecretRecord
__all__ = ['PushAccountAutomation'] __all__ = ['PushAccountAutomation', 'PushSecretRecord']
class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation): class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
@ -36,3 +36,8 @@ class PushAccountAutomation(ChangeSecretMixin, AccountBaseAutomation):
class Meta: class Meta:
verbose_name = _("Push asset account") 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 accounts.models import PushAccountAutomation
from .change_secret import ( from .change_secret import (
ChangeSecretAutomationSerializer, ChangeSecretUpdateAssetSerializer, ChangeSecretAutomationSerializer, ChangeSecretUpdateAssetSerializer,
ChangeSecretUpdateNodeSerializer ChangeSecretUpdateNodeSerializer, ChangeSecretRecordSerializer
) )
@ -19,6 +19,10 @@ class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
return AutomationTypes.push_account return AutomationTypes.push_account
class PushSecretRecordSerializer(ChangeSecretRecordSerializer):
pass
class PushAccountUpdateAssetSerializer(ChangeSecretUpdateAssetSerializer): class PushAccountUpdateAssetSerializer(ChangeSecretUpdateAssetSerializer):
class Meta: class Meta:
model = PushAccountAutomation 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'gather-account-executions', api.GatherAccountsExecutionViewSet, 'gather-account-execution')
router.register(r'push-account-automations', api.PushAccountAutomationViewSet, 'push-account-automation') 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-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-automations', api.CheckAccountAutomationViewSet, 'check-account-automation')
router.register(r'check-account-executions', api.CheckAccountExecutionViewSet, 'check-account-execution') router.register(r'check-account-executions', api.CheckAccountExecutionViewSet, 'check-account-execution')
router.register(r'account-check-engines', api.CheckAccountEngineViewSet, 'account-check-engine') router.register(r'account-check-engines', api.CheckAccountEngineViewSet, 'account-check-engine')