feat: 账号模版切换至 (#10396)

Co-authored-by: feng <1304903146@qq.com>
pull/10410/head
fit2bot 2023-05-09 15:29:02 +08:00 committed by GitHub
parent 6b6900cfd4
commit 1933e82587
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 110 additions and 5 deletions

View File

@ -1,4 +1,6 @@
from django_filters import rest_framework as drf_filters from django_filters import rest_framework as drf_filters
from rest_framework.decorators import action
from rest_framework.response import Response
from accounts import serializers from accounts import serializers
from accounts.models import AccountTemplate from accounts.models import AccountTemplate
@ -38,8 +40,20 @@ class AccountTemplateViewSet(OrgBulkModelViewSet):
filterset_class = AccountTemplateFilterSet filterset_class = AccountTemplateFilterSet
search_fields = ('username', 'name') search_fields = ('username', 'name')
serializer_classes = { serializer_classes = {
'default': serializers.AccountTemplateSerializer 'default': serializers.AccountTemplateSerializer,
} }
rbac_perms = {
'su_from_account_templates': 'accounts.view_accounttemplate',
}
@action(methods=['get'], detail=False, url_path='su-from-account-templates')
def su_from_account_templates(self, request, *args, **kwargs):
pk = request.query_params.get('template_id')
template = AccountTemplate.objects.filter(pk=pk).first()
templates = AccountTemplate.get_su_from_account_templates(template)
templates = self.filter_queryset(templates)
serializer = self.get_serializer(templates, many=True)
return Response(data=serializer.data)
class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet): class AccountTemplateSecretsViewSet(RecordViewLogMixin, AccountTemplateViewSet):

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.17 on 2023-05-06 06:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0010_gatheraccountsautomation_is_sync_account'),
]
operations = [
migrations.AddField(
model_name='accounttemplate',
name='su_from',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='su_to', to='accounts.accounttemplate', verbose_name='Su from'),
),
migrations.AlterField(
model_name='changesecretautomation',
name='ssh_key_change_strategy',
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
),
migrations.AlterField(
model_name='pushaccountautomation',
name='ssh_key_change_strategy',
field=models.CharField(choices=[('add', 'Append SSH KEY'), ('set', 'Empty and append SSH KEY'), ('set_jms', 'Replace (Replace only keys pushed by JumpServer) ')], default='add', max_length=16, verbose_name='SSH key change strategy'),
),
]

View File

@ -1,5 +1,5 @@
from django.db import models from django.db import models
from django.db.models import Count from django.db.models import Count, Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
@ -108,6 +108,11 @@ class Account(AbsConnectivity, BaseAccount):
class AccountTemplate(BaseAccount): class AccountTemplate(BaseAccount):
su_from = models.ForeignKey(
'self', related_name='su_to', null=True,
on_delete=models.SET_NULL, verbose_name=_("Su from")
)
class Meta: class Meta:
verbose_name = _('Account template') verbose_name = _('Account template')
unique_together = ( unique_together = (
@ -118,6 +123,21 @@ class AccountTemplate(BaseAccount):
('change_accounttemplatesecret', _('Can change asset account template secret')), ('change_accounttemplatesecret', _('Can change asset account template secret')),
] ]
@classmethod
def get_su_from_account_templates(cls, instance=None):
if not instance:
return cls.objects.all()
return cls.objects.exclude(Q(id=instance.id) | Q(su_from=instance))
def get_su_from_account(self, asset):
su_from = self.su_from
if su_from and asset.platform.su_enabled:
account = asset.accounts.filter(
username=su_from.username,
secret_type=su_from.secret_type
).first()
return account
def __str__(self): def __str__(self):
return self.username return self.username

View File

@ -91,7 +91,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
self._template = template self._template = template
# Set initial data from template # Set initial data from template
ignore_fields = ['id', 'date_created', 'date_updated', 'org_id'] ignore_fields = ['id', 'date_created', 'date_updated', 'su_from', 'org_id']
field_names = [ field_names = [
field.name for field in template._meta.fields field.name for field in template._meta.fields
if field.name not in ignore_fields if field.name not in ignore_fields
@ -151,6 +151,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
template = self._template template = self._template
if template is None: if template is None:
return return
validated_data['source'] = Source.TEMPLATE validated_data['source'] = Source.TEMPLATE
validated_data['source_id'] = str(template.id) validated_data['source_id'] = str(template.id)
@ -238,6 +239,9 @@ class AssetAccountBulkSerializerResultSerializer(serializers.Serializer):
class AssetAccountBulkSerializer( class AssetAccountBulkSerializer(
AccountCreateUpdateSerializerMixin, AuthValidateMixin, serializers.ModelSerializer AccountCreateUpdateSerializerMixin, AuthValidateMixin, serializers.ModelSerializer
): ):
su_from_username = serializers.CharField(
max_length=128, required=False, write_only=True, allow_null=True, label=_("Su from")
)
assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets')) assets = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, many=True, label=_('Assets'))
class Meta: class Meta:
@ -245,7 +249,7 @@ class AssetAccountBulkSerializer(
fields = [ fields = [
'name', 'username', 'secret', 'secret_type', 'name', 'username', 'secret', 'secret_type',
'privileged', 'is_active', 'comment', 'template', 'privileged', 'is_active', 'comment', 'template',
'on_invalid', 'push_now', 'assets', 'on_invalid', 'push_now', 'assets', 'su_from_username'
] ]
extra_kwargs = { extra_kwargs = {
'name': {'required': False}, 'name': {'required': False},
@ -293,8 +297,20 @@ class AssetAccountBulkSerializer(
raise serializers.ValidationError(_('Account already exists')) raise serializers.ValidationError(_('Account already exists'))
return instance, True, 'created' return instance, True, 'created'
def generate_su_from_data(self, validated_data):
template = self._template
asset = validated_data['asset']
su_from = validated_data.get('su_from')
su_from_username = validated_data.pop('su_from_username', None)
if template:
su_from = template.get_su_from_account()
elif su_from_username:
su_from = asset.accounts.filter(username=su_from_username).first()
validated_data['su_from'] = su_from
def perform_create(self, vd, handler): def perform_create(self, vd, handler):
lookup = self.get_filter_lookup(vd) lookup = self.get_filter_lookup(vd)
self.generate_su_from_data(vd)
try: try:
instance, changed, state = handler(vd, lookup) instance, changed, state = handler(vd, lookup)
except IntegrityError: except IntegrityError:

View File

@ -1,7 +1,9 @@
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from accounts.models import AccountTemplate, Account from accounts.models import AccountTemplate, Account
from common.serializers import SecretReadableMixin from common.serializers import SecretReadableMixin
from common.serializers.fields import ObjectRelatedField
from .base import BaseAccountSerializer from .base import BaseAccountSerializer
@ -9,9 +11,14 @@ class AccountTemplateSerializer(BaseAccountSerializer):
is_sync_account = serializers.BooleanField(default=False, write_only=True) is_sync_account = serializers.BooleanField(default=False, write_only=True)
_is_sync_account = False _is_sync_account = False
su_from = ObjectRelatedField(
required=False, queryset=AccountTemplate.objects, allow_null=True,
allow_empty=True, label=_('Su from'), attrs=('id', 'name', 'username')
)
class Meta(BaseAccountSerializer.Meta): class Meta(BaseAccountSerializer.Meta):
model = AccountTemplate model = AccountTemplate
fields = BaseAccountSerializer.Meta.fields + ['is_sync_account'] fields = BaseAccountSerializer.Meta.fields + ['is_sync_account', 'su_from']
def sync_accounts_secret(self, instance, diff): def sync_accounts_secret(self, instance, diff):
if not self._is_sync_account or 'secret' not in diff: if not self._is_sync_account or 'secret' not in diff:

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.17 on 2023-05-06 06:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0116_auto_20230418_1726'),
]
operations = [
migrations.AlterField(
model_name='baseautomation',
name='params',
field=models.JSONField(default=dict, verbose_name='Parameters'),
),
]

View File

@ -109,6 +109,7 @@ class AssetPermissionSerializer(BulkOrgResourceModelSerializer):
if condition in username_secret_type_dict: if condition in username_secret_type_dict:
continue continue
account_data = {key: getattr(template, key) for key in account_attribute} account_data = {key: getattr(template, key) for key in account_attribute}
account_data['su_from'] = template.get_su_from_account(asset)
account_data['name'] = f"{account_data['name']}-{_('Account template')}" account_data['name'] = f"{account_data['name']}-{_('Account template')}"
need_create_accounts.append(Account(**{'asset_id': asset.id, **account_data})) need_create_accounts.append(Account(**{'asset_id': asset.id, **account_data}))
return Account.objects.bulk_create(need_create_accounts) return Account.objects.bulk_create(need_create_accounts)