diff --git a/Dockerfile b/Dockerfile index f1a8e17c4..769209673 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,12 @@ COPY --from=stage-build /opt/jumpserver/release/jumpserver /opt/jumpserver RUN mkdir -p /root/.ssh/ \ && echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config +RUN mkdir -p /opt/jumpserver/oracle/ +ADD https://f2c-north-rel.oss-cn-qingdao.aliyuncs.com/2.0/north/jumpserver/instantclient-basiclite-linux.x64-21.1.0.0.0.tar /opt/jumpserver/oracle/ +RUN tar xvf /opt/jumpserver/oracle/instantclient-basiclite-linux.x64-21.1.0.0.0.tar -C /opt/jumpserver/oracle/ +RUN sh -c "echo /opt/jumpserver/oracle/instantclient_21_1 > /etc/ld.so.conf.d/oracle-instantclient.conf" +RUN ldconfig + RUN echo > config.yml VOLUME /opt/jumpserver/data VOLUME /opt/jumpserver/logs diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 6c95a73c8..3193467c3 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -2,74 +2,57 @@ # from django_filters import rest_framework as filters -from django.db.models import F, Value, CharField -from django.db.models.functions import Concat -from django.http import Http404 +from django.db.models import F, Q from common.drf.filters import BaseFilterSet -from common.drf.api import JMSModelViewSet -from common.utils import unique -from perms.models import ApplicationPermission +from common.drf.api import JMSBulkModelViewSet +from ..models import Account from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin, NeedMFAVerify from .. import serializers class AccountFilterSet(BaseFilterSet): username = filters.CharFilter(field_name='username') - app = filters.CharFilter(field_name='applications', lookup_expr='exact') - app_name = filters.CharFilter(field_name='app_name', lookup_expr='exact') + type = filters.CharFilter(field_name='type', lookup_expr='exact') + category = filters.CharFilter(field_name='category', lookup_expr='exact') + app_display = filters.CharFilter(field_name='app_display', lookup_expr='exact') class Meta: - model = ApplicationPermission - fields = ['type', 'category'] + model = Account + fields = ['app', 'systemuser'] + + @property + def qs(self): + qs = super().qs + qs = self.filter_username(qs) + return qs + + def filter_username(self, qs): + username = self.get_query_param('username') + if not username: + return qs + qs = qs.filter(Q(username=username) | Q(systemuser__username=username)).distinct() + return qs -class ApplicationAccountViewSet(JMSModelViewSet): - permission_classes = (IsOrgAdmin, ) - search_fields = ['username', 'app_name'] +class ApplicationAccountViewSet(JMSBulkModelViewSet): + model = Account + search_fields = ['username', 'app_display'] filterset_class = AccountFilterSet - filterset_fields = ['username', 'app_name', 'type', 'category'] - serializer_class = serializers.ApplicationAccountSerializer - http_method_names = ['get', 'put', 'patch', 'options'] + filterset_fields = ['username', 'app_display', 'type', 'category', 'app'] + serializer_class = serializers.AppAccountSerializer + permission_classes = (IsOrgAdmin,) def get_queryset(self): - queryset = ApplicationPermission.objects\ - .exclude(system_users__isnull=True) \ - .exclude(applications__isnull=True) \ - .annotate(uid=Concat( - 'applications', Value('_'), 'system_users', output_field=CharField() - )) \ - .annotate(systemuser=F('system_users')) \ - .annotate(systemuser_display=F('system_users__name')) \ - .annotate(username=F('system_users__username')) \ - .annotate(password=F('system_users__password')) \ - .annotate(app=F('applications')) \ - .annotate(app_name=F("applications__name")) \ - .values('username', 'password', 'systemuser', 'systemuser_display', - 'app', 'app_name', 'category', 'type', 'uid', 'org_id') - return queryset - - def get_object(self): - obj = self.get_queryset().filter( - uid=self.kwargs['pk'] - ).first() - if not obj: - raise Http404() - return obj - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - queryset_list = unique(queryset, key=lambda x: (x['app'], x['systemuser'])) - return queryset_list - - @staticmethod - def filter_spm_queryset(resource_ids, queryset): - queryset = queryset.filter(uid__in=resource_ids) + queryset = Account.objects.all() \ + .annotate(type=F('app__type')) \ + .annotate(app_display=F('app__name')) \ + .annotate(systemuser_display=F('systemuser__name')) \ + .annotate(category=F('app__category')) return queryset class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): - serializer_class = serializers.ApplicationAccountSecretSerializer + serializer_class = serializers.AppAccountSecretSerializer permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify] http_method_names = ['get', 'options'] - diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 1d933bedc..1090e0095 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -23,9 +23,8 @@ class ApplicationViewSet(OrgBulkModelViewSet): search_fields = ('name', 'type', 'category') permission_classes = (IsOrgAdminOrAppUser,) serializer_classes = { - 'default': serializers.ApplicationSerializer, - 'get_tree': TreeNodeSerializer, - 'suggestion': serializers.MiniApplicationSerializer + 'default': serializers.AppSerializer, + 'get_tree': TreeNodeSerializer } @action(methods=['GET'], detail=False, url_path='tree') diff --git a/apps/applications/migrations/0010_appaccount_historicalappaccount.py b/apps/applications/migrations/0010_appaccount_historicalappaccount.py new file mode 100644 index 000000000..fc2cf2ab9 --- /dev/null +++ b/apps/applications/migrations/0010_appaccount_historicalappaccount.py @@ -0,0 +1,76 @@ +# Generated by Django 3.1.12 on 2021-08-26 09:07 + +import assets.models.base +import common.fields.model +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0076_delete_assetuser'), + ('applications', '0009_applicationuser'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalAccount', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), + ('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(blank=True, editable=False, verbose_name='Date created')), + ('date_updated', models.DateTimeField(blank=True, editable=False, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('version', models.IntegerField(default=1, verbose_name='Version')), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('app', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='applications.application', verbose_name='Database')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('systemuser', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='assets.systemuser', verbose_name='System user')), + ], + options={ + 'verbose_name': 'historical Account', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='Account', + fields=[ + ('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('username', models.CharField(blank=True, db_index=True, max_length=128, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')), + ('password', common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('private_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key')), + ('public_key', common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('version', models.IntegerField(default=1, verbose_name='Version')), + ('app', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='applications.application', verbose_name='Database')), + ('systemuser', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assets.systemuser', verbose_name='System user')), + ], + options={ + 'verbose_name': 'Account', + 'unique_together': {('username', 'app', 'systemuser')}, + }, + bases=(models.Model, assets.models.base.AuthMixin), + ), + ] diff --git a/apps/applications/migrations/0011_auto_20210826_1759.py b/apps/applications/migrations/0011_auto_20210826_1759.py new file mode 100644 index 000000000..937102c07 --- /dev/null +++ b/apps/applications/migrations/0011_auto_20210826_1759.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1.12 on 2021-08-26 09:59 + +from django.db import migrations, transaction +from django.db.models import F + + +def migrate_app_account(apps, schema_editor): + db_alias = schema_editor.connection.alias + app_perm_model = apps.get_model("perms", "ApplicationPermission") + app_account_model = apps.get_model("applications", 'Account') + + queryset = app_perm_model.objects \ + .exclude(system_users__isnull=True) \ + .exclude(applications__isnull=True) \ + .annotate(systemuser=F('system_users')) \ + .annotate(app=F('applications')) \ + .values('app', 'systemuser', 'org_id') + + accounts = [] + for p in queryset: + if not p['app']: + continue + account = app_account_model( + app_id=p['app'], systemuser_id=p['systemuser'], + version=1, org_id=p['org_id'] + ) + accounts.append(account) + + app_account_model.objects.using(db_alias).bulk_create(accounts, ignore_conflicts=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('applications', '0010_appaccount_historicalappaccount'), + ] + + operations = [ + migrations.RunPython(migrate_app_account) + ] diff --git a/apps/applications/models/__init__.py b/apps/applications/models/__init__.py index a12310aa4..4bd20e32f 100644 --- a/apps/applications/models/__init__.py +++ b/apps/applications/models/__init__.py @@ -1 +1,2 @@ from .application import * +from .account import * diff --git a/apps/applications/models/account.py b/apps/applications/models/account.py index e69de29bb..ff514590a 100644 --- a/apps/applications/models/account.py +++ b/apps/applications/models/account.py @@ -0,0 +1,88 @@ +from django.db import models +from simple_history.models import HistoricalRecords +from django.utils.translation import ugettext_lazy as _ + +from common.utils import lazyproperty +from assets.models.base import BaseUser + + +class Account(BaseUser): + app = models.ForeignKey('applications.Application', on_delete=models.CASCADE, null=True, verbose_name=_('Database')) + systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) + version = models.IntegerField(default=1, verbose_name=_('Version')) + history = HistoricalRecords() + + auth_attrs = ['username', 'password', 'private_key', 'public_key'] + + class Meta: + verbose_name = _('Account') + unique_together = [('username', 'app', 'systemuser')] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.auth_snapshot = {} + + def get_or_systemuser_attr(self, attr): + val = getattr(self, attr, None) + if val: + return val + if self.systemuser: + return getattr(self.systemuser, attr, '') + return '' + + def load_auth(self): + for attr in self.auth_attrs: + value = self.get_or_systemuser_attr(attr) + self.auth_snapshot[attr] = [getattr(self, attr), value] + setattr(self, attr, value) + + def unload_auth(self): + if not self.systemuser: + return + + for attr, values in self.auth_snapshot.items(): + origin_value, loaded_value = values + current_value = getattr(self, attr, '') + if current_value == loaded_value: + setattr(self, attr, origin_value) + + def save(self, *args, **kwargs): + self.unload_auth() + instance = super().save(*args, **kwargs) + self.load_auth() + return instance + + @lazyproperty + def category(self): + return self.app.category + + @lazyproperty + def type(self): + return self.app.type + + @lazyproperty + def app_display(self): + return self.systemuser.name + + @property + def username_display(self): + return self.get_or_systemuser_attr('username') or '' + + @lazyproperty + def systemuser_display(self): + if not self.systemuser: + return '' + return str(self.systemuser) + + @property + def smart_name(self): + username = self.username_display + + if self.app: + app = str(self.app) + else: + app = '*' + return '{}@{}'.format(username, app) + + def __str__(self): + return self.smart_name diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 8310e9f75..79cbdd3bc 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -4,20 +4,23 @@ from rest_framework import serializers from django.utils.translation import ugettext_lazy as _ -from orgs.models import Organization from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from assets.serializers.base import AuthSerializerMixin from common.drf.serializers import MethodSerializer -from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping +from .attrs import ( + category_serializer_classes_mapping, + type_serializer_classes_mapping +) from .. import models from .. import const __all__ = [ - 'ApplicationSerializer', 'ApplicationSerializerMixin', 'MiniApplicationSerializer', - 'ApplicationAccountSerializer', 'ApplicationAccountSecretSerializer' + 'AppSerializer', 'AppSerializerMixin', + 'AppAccountSerializer', 'AppAccountSecretSerializer' ] -class ApplicationSerializerMixin(serializers.Serializer): +class AppSerializerMixin(serializers.Serializer): attrs = MethodSerializer() def get_attrs_serializer(self): @@ -45,8 +48,14 @@ class ApplicationSerializerMixin(serializers.Serializer): serializer = serializer_class return serializer + def create(self, validated_data): + return super().create(validated_data) -class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSerializer): + def update(self, instance, validated_data): + return super().update(instance, validated_data) + + +class AppSerializer(AppSerializerMixin, BulkOrgResourceModelSerializer): category_display = serializers.ReadOnlyField(source='get_category_display', label=_('Category display')) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) @@ -69,48 +78,54 @@ class ApplicationSerializer(ApplicationSerializerMixin, BulkOrgResourceModelSeri return _attrs -class ApplicationAccountSerializer(serializers.Serializer): - id = serializers.ReadOnlyField(label=_("Id"), source='uid') - username = serializers.ReadOnlyField(label=_("Username")) - password = serializers.CharField(write_only=True, label=_("Password")) - systemuser = serializers.ReadOnlyField(label=_('System user')) - systemuser_display = serializers.ReadOnlyField(label=_("System user display")) - app = serializers.ReadOnlyField(label=_('App')) - app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True) +class AppAccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) category_display = serializers.SerializerMethodField(label=_('Category display')) type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) type_display = serializers.SerializerMethodField(label=_('Type display')) - uid = serializers.ReadOnlyField(label=_("Union id")) - org_id = serializers.ReadOnlyField(label=_("Organization")) - org_name = serializers.SerializerMethodField(label=_("Org name")) category_mapper = dict(const.AppCategory.choices) type_mapper = dict(const.AppType.choices) - def create(self, validated_data): - pass - - def update(self, instance, validated_data): - pass + class Meta: + model = models.Account + fields_mini = ['id', 'username', 'version'] + fields_write_only = ['password', 'private_key'] + fields_fk = ['systemuser', 'systemuser_display', 'app', 'app_display'] + fields = fields_mini + fields_fk + fields_write_only + [ + 'type', 'type_display', 'category', 'category_display', + ] + extra_kwargs = { + 'username': {'default': '', 'required': False}, + 'password': {'write_only': True}, + 'app_display': {'label': _('Application display')} + } + use_model_bulk_create = True + model_bulk_create_kwargs = { + 'ignore_conflicts': True + } def get_category_display(self, obj): - return self.category_mapper.get(obj['category']) + return self.category_mapper.get(obj.category) def get_type_display(self, obj): - return self.type_mapper.get(obj['type']) + return self.type_mapper.get(obj.type) - @staticmethod - def get_org_name(obj): - org = Organization.get_instance(obj['org_id']) - return org.name + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related('systemuser', 'app') + return queryset + + def to_representation(self, instance): + instance.load_auth() + return super().to_representation(instance) -class ApplicationAccountSecretSerializer(ApplicationAccountSerializer): - password = serializers.CharField(write_only=False, label=_("Password")) - - -class MiniApplicationSerializer(serializers.ModelSerializer): - class Meta: - model = models.Application - fields = ApplicationSerializer.Meta.fields_mini +class AppAccountSecretSerializer(AppAccountSerializer): + class Meta(AppAccountSerializer.Meta): + extra_kwargs = { + 'password': {'write_only': False}, + 'private_key': {'write_only': False}, + 'public_key': {'write_only': False}, + } diff --git a/apps/assets/api/system_user_relation.py b/apps/assets/api/system_user_relation.py index cd7b64a68..374d45cc2 100644 --- a/apps/assets/api/system_user_relation.py +++ b/apps/assets/api/system_user_relation.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # from collections import defaultdict -from django.db.models import F, Value +from django.db.models import F, Value, Model from django.db.models.signals import m2m_changed from django.db.models.functions import Concat @@ -13,13 +13,15 @@ from .. import models, serializers __all__ = [ 'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet', - 'SystemUserUserRelationViewSet', + 'SystemUserUserRelationViewSet', 'BaseRelationViewSet', ] logger = get_logger(__name__) class RelationMixin: + model: Model + def get_queryset(self): queryset = self.model.objects.all() if not current_org.is_root(): diff --git a/apps/assets/models/authbook.py b/apps/assets/models/authbook.py index 3153608cd..c4e39bcd4 100644 --- a/apps/assets/models/authbook.py +++ b/apps/assets/models/authbook.py @@ -16,7 +16,6 @@ class AuthBook(BaseUser, AbsConnectivity): systemuser = models.ForeignKey('assets.SystemUser', on_delete=models.CASCADE, null=True, verbose_name=_("System user")) version = models.IntegerField(default=1, verbose_name=_('Version')) history = HistoricalRecords() - _systemuser_display = '' auth_attrs = ['username', 'password', 'private_key', 'public_key'] @@ -64,8 +63,6 @@ class AuthBook(BaseUser, AbsConnectivity): @lazyproperty def systemuser_display(self): - if self._systemuser_display: - return self._systemuser_display if not self.systemuser: return '' return str(self.systemuser) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index ee802e311..3677144c2 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -73,6 +73,10 @@ class ProtocolMixin: def can_perm_to_asset(self): return self.protocol in self.ASSET_CATEGORY_PROTOCOLS + @property + def is_asset_protocol(self): + return self.protocol in self.ASSET_CATEGORY_PROTOCOLS + class AuthMixin: username_same_with_user: bool diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index c5b9c2064..69848ce20 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -14,7 +14,7 @@ __all__ = [ 'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer', 'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer', 'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer', - 'SystemUserTempAuthSerializer', + 'SystemUserTempAuthSerializer', 'RelationMixin', ] @@ -31,12 +31,12 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields_mini = ['id', 'name', 'username'] fields_write_only = ['password', 'public_key', 'private_key'] fields_small = fields_mini + fields_write_only + [ - 'type', 'type_display', 'protocol', 'login_mode', 'login_mode_display', - 'priority', 'sudo', 'shell', 'sftp_root', 'token', 'ssh_key_fingerprint', - 'home', 'system_groups', 'ad_domain', + 'token', 'ssh_key_fingerprint', + 'type', 'type_display', 'protocol', 'is_asset_protocol', + 'login_mode', 'login_mode_display', 'priority', + 'sudo', 'shell', 'sftp_root', 'home', 'system_groups', 'ad_domain', 'username_same_with_user', 'auto_push', 'auto_generate_key', - 'date_created', 'date_updated', - 'comment', 'created_by', + 'date_created', 'date_updated', 'comment', 'created_by', ] fields_m2m = ['cmd_filters', 'assets_amount'] fields = fields_small + fields_m2m @@ -53,6 +53,7 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, 'ad_domain': {'required': False, 'allow_blank': True, 'label': _('Ad domain')}, + 'is_asset_protocol': {'label': _('Is asset protocol')} } def validate_auto_push(self, value): diff --git a/apps/common/management/commands/services/services/base.py b/apps/common/management/commands/services/services/base.py index 5063fb92e..0fb6cb1b3 100644 --- a/apps/common/management/commands/services/services/base.py +++ b/apps/common/management/commands/services/services/base.py @@ -160,7 +160,7 @@ class BaseService(object): if self.process: try: self.process.wait(1) # 不wait,子进程可能无法回收 - except subprocess.TimeoutExpired: + except: pass if self.is_running: diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 3059ebf7a..378b949db 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index d3d14eb9d..dc47472c0 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -35,12 +35,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:54 -#: assets/models/user.py:203 +#: assets/models/user.py:207 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:54 -#: assets/models/user.py:203 +#: assets/models/user.py:207 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -51,8 +51,6 @@ msgstr "优先级可选范围为 1-100 (数值越小越优先)" msgid "Active" msgstr "激活中" -# msgid "Date created" -# msgstr "创建日期" #: acls/models/base.py:32 applications/models/application.py:179 #: assets/models/asset.py:144 assets/models/asset.py:220 #: assets/models/base.py:180 assets/models/cluster.py:29 @@ -62,7 +60,7 @@ msgstr "激活中" #: orgs/models.py:27 perms/models/base.py:53 settings/models.py:34 #: terminal/models/storage.py:26 terminal/models/terminal.py:114 #: tickets/models/ticket.py:71 users/models/group.py:16 -#: users/models/user.py:590 xpack/plugins/change_auth_plan/models.py:88 +#: users/models/user.py:590 xpack/plugins/change_auth_plan/models/base.py:41 #: xpack/plugins/cloud/models.py:35 xpack/plugins/cloud/models.py:113 #: xpack/plugins/gathered_user/models.py:26 msgid "Comment" @@ -114,20 +112,16 @@ msgstr "用户" msgid "Login confirm" msgstr "登录复核" -#: acls/models/login_asset_acl.py:21 -msgid "System User" -msgstr "系统用户" - #: acls/models/login_asset_acl.py:22 #: applications/serializers/attrs/application_category/remote_app.py:37 #: assets/models/asset.py:357 assets/models/authbook.py:15 -#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:200 +#: assets/models/gathered_user.py:14 assets/serializers/system_user.py:201 #: audits/models.py:38 perms/models/asset_permission.py:99 #: templates/index.html:82 terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 #: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:70 -#: xpack/plugins/change_auth_plan/models.py:315 +#: xpack/plugins/change_auth_plan/models/asset.py:195 #: xpack/plugins/cloud/models.py:217 msgid "Asset" msgstr "资产" @@ -173,7 +167,6 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: acls/serializers/login_asset_acl.py:17 #: acls/serializers/login_asset_acl.py:51 -#: applications/serializers/application.py:74 #: applications/serializers/attrs/application_type/chrome.py:20 #: applications/serializers/attrs/application_type/custom.py:21 #: applications/serializers/attrs/application_type/mysql_workbench.py:30 @@ -182,8 +175,8 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: audits/models.py:105 authentication/forms.py:15 authentication/forms.py:17 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:555 #: users/templates/users/_select_user_modal.html:14 -#: xpack/plugins/change_auth_plan/models.py:51 -#: xpack/plugins/change_auth_plan/models.py:311 +#: xpack/plugins/change_auth_plan/models/asset.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:191 #: xpack/plugins/cloud/serializers.py:67 msgid "Username" msgstr "用户名" @@ -212,7 +205,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:184 -#: assets/models/domain.py:63 assets/models/user.py:204 +#: assets/models/domain.py:63 assets/models/user.py:208 #: terminal/serializers/session.py:30 terminal/serializers/storage.py:69 msgid "Protocol" msgstr "协议" @@ -234,9 +227,11 @@ msgstr "所有复核人都不属于组织 `{}`" msgid "My applications" msgstr "我的应用" -#: applications/const.py:8 +#: applications/const.py:8 applications/models/account.py:10 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:26 +#: xpack/plugins/change_auth_plan/models/app.py:32 +#: xpack/plugins/change_auth_plan/models/app.py:139 msgid "Database" msgstr "数据库" @@ -248,25 +243,44 @@ msgstr "远程应用" msgid "Custom" msgstr "自定义" +#: applications/models/account.py:11 assets/models/authbook.py:16 +#: assets/models/user.py:281 audits/models.py:39 +#: perms/models/application_permission.py:31 +#: perms/models/asset_permission.py:101 templates/_nav.html:45 +#: terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 +#: users/templates/users/_granted_assets.html:27 +#: users/templates/users/user_asset_permission.html:42 +#: users/templates/users/user_asset_permission.html:76 +#: users/templates/users/user_asset_permission.html:159 +#: users/templates/users/user_database_app_permission.html:40 +#: users/templates/users/user_database_app_permission.html:67 +#: xpack/plugins/change_auth_plan/models/app.py:36 +msgid "System user" +msgstr "系统用户" + #: applications/models/application.py:50 templates/_nav.html:60 msgid "Applications" msgstr "应用管理" #: applications/models/application.py:168 -#: applications/serializers/application.py:80 assets/models/label.py:21 +#: applications/serializers/application.py:82 assets/models/label.py:21 #: perms/models/application_permission.py:20 #: perms/serializers/application/user_permission.py:33 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20 +#: xpack/plugins/change_auth_plan/models/app.py:25 msgid "Category" msgstr "类别" #: applications/models/application.py:171 -#: applications/serializers/application.py:82 assets/models/cmd_filter.py:53 -#: assets/models/user.py:202 perms/models/application_permission.py:23 +#: applications/serializers/application.py:84 assets/models/cmd_filter.py:53 +#: assets/models/user.py:206 perms/models/application_permission.py:23 #: perms/serializers/application/user_permission.py:34 #: terminal/models/storage.py:55 terminal/models/storage.py:116 #: tickets/models/flow.py:50 tickets/models/ticket.py:48 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:27 +#: xpack/plugins/change_auth_plan/models/app.py:28 +#: xpack/plugins/change_auth_plan/models/app.py:148 msgid "Type" msgstr "类型" @@ -279,15 +293,15 @@ msgstr "网域" msgid "Attrs" msgstr "" -#: applications/serializers/application.py:50 -#: applications/serializers/application.py:81 assets/serializers/label.py:13 +#: applications/serializers/application.py:59 +#: applications/serializers/application.py:83 assets/serializers/label.py:13 #: perms/serializers/application/permission.py:18 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24 msgid "Category display" msgstr "类别名称" -#: applications/serializers/application.py:51 -#: applications/serializers/application.py:83 +#: applications/serializers/application.py:60 +#: applications/serializers/application.py:85 #: assets/serializers/system_user.py:26 audits/serializers.py:29 #: perms/serializers/application/permission.py:19 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31 @@ -300,46 +314,6 @@ msgstr "类型名称" msgid "Id" msgstr "" -#: applications/serializers/application.py:75 -#: applications/serializers/application.py:110 -#: applications/serializers/attrs/application_type/chrome.py:23 -#: applications/serializers/attrs/application_type/custom.py:25 -#: applications/serializers/attrs/application_type/mysql_workbench.py:34 -#: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:177 audits/signals_handler.py:63 -#: authentication/forms.py:22 -#: authentication/templates/authentication/login.html:164 -#: settings/serializers/auth/ldap.py:44 users/forms/profile.py:21 -#: users/templates/users/user_otp_check_password.html:13 -#: users/templates/users/user_password_update.html:43 -#: users/templates/users/user_password_verify.html:18 -#: xpack/plugins/change_auth_plan/models.py:72 -#: xpack/plugins/change_auth_plan/models.py:207 -#: xpack/plugins/change_auth_plan/models.py:318 -#: xpack/plugins/cloud/serializers.py:69 -msgid "Password" -msgstr "密码" - -#: applications/serializers/application.py:76 assets/models/authbook.py:16 -#: assets/models/user.py:277 audits/models.py:39 -#: perms/models/application_permission.py:31 -#: perms/models/asset_permission.py:101 templates/_nav.html:45 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 -#: users/templates/users/_granted_assets.html:27 -#: users/templates/users/user_asset_permission.html:42 -#: users/templates/users/user_asset_permission.html:76 -#: users/templates/users/user_asset_permission.html:159 -#: users/templates/users/user_database_app_permission.html:40 -#: users/templates/users/user_database_app_permission.html:67 -msgid "System user" -msgstr "系统用户" - -#: applications/serializers/application.py:77 assets/serializers/account.py:31 -#: assets/serializers/account.py:52 -msgid "System user display" -msgstr "系统用户名称" - #: applications/serializers/application.py:78 msgid "App" msgstr "应用" @@ -348,21 +322,6 @@ msgstr "应用" msgid "Application name" msgstr "应用名称" -#: applications/serializers/application.py:84 -msgid "Union id" -msgstr "联合ID" - -#: applications/serializers/application.py:85 orgs/mixins/models.py:46 -#: orgs/mixins/serializers.py:25 orgs/models.py:37 orgs/models.py:432 -#: orgs/serializers.py:106 tickets/serializers/ticket/ticket.py:77 -msgid "Organization" -msgstr "组织" - -#: applications/serializers/application.py:86 assets/serializers/asset.py:98 -#: assets/serializers/system_user.py:217 orgs/mixins/serializers.py:26 -msgid "Org name" -msgstr "组织名称" - #: applications/serializers/attrs/application_category/cloud.py:9 #: assets/models/cluster.py:40 msgid "Cluster" @@ -400,6 +359,24 @@ msgstr "该字段是必填项。" msgid "Target URL" msgstr "目标URL" +#: applications/serializers/attrs/application_type/chrome.py:23 +#: applications/serializers/attrs/application_type/custom.py:25 +#: applications/serializers/attrs/application_type/mysql_workbench.py:34 +#: applications/serializers/attrs/application_type/vmware_client.py:30 +#: assets/models/base.py:177 audits/signals_handler.py:63 +#: authentication/forms.py:22 +#: authentication/templates/authentication/login.html:164 +#: settings/serializers/settings.py:95 users/forms/profile.py:21 +#: users/templates/users/user_otp_check_password.html:13 +#: users/templates/users/user_password_update.html:43 +#: users/templates/users/user_password_verify.html:18 +#: xpack/plugins/change_auth_plan/models/base.py:39 +#: xpack/plugins/change_auth_plan/models/base.py:114 +#: xpack/plugins/change_auth_plan/models/base.py:182 +#: xpack/plugins/cloud/serializers.py:69 +msgid "Password" +msgstr "密码" + #: applications/serializers/attrs/application_type/custom.py:13 msgid "Operating parameter" msgstr "运行参数" @@ -452,9 +429,9 @@ msgstr "系统平台" msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:189 assets/models/user.py:194 +#: assets/models/asset.py:189 assets/models/user.py:198 #: perms/models/asset_permission.py:100 -#: xpack/plugins/change_auth_plan/models.py:60 +#: xpack/plugins/change_auth_plan/models/asset.py:44 #: xpack/plugins/gathered_user/models.py:24 msgid "Nodes" msgstr "节点" @@ -466,7 +443,7 @@ msgid "Is active" msgstr "激活" #: assets/models/asset.py:193 assets/models/cluster.py:19 -#: assets/models/user.py:191 assets/models/user.py:326 templates/_nav.html:44 +#: assets/models/user.py:195 assets/models/user.py:330 templates/_nav.html:44 msgid "Admin user" msgstr "特权用户" @@ -543,13 +520,12 @@ msgstr "标签管理" #: assets/models/cmd_filter.py:67 assets/models/group.py:21 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:25 #: orgs/models.py:437 perms/models/base.py:51 users/models/user.py:598 -#: users/serializers/group.py:33 xpack/plugins/change_auth_plan/models.py:92 +#: users/serializers/group.py:33 +#: xpack/plugins/change_auth_plan/models/base.py:45 #: xpack/plugins/cloud/models.py:119 xpack/plugins/gathered_user/models.py:30 msgid "Created by" msgstr "创建者" -# msgid "Created by" -# msgstr "创建者" #: assets/models/asset.py:219 assets/models/base.py:181 #: assets/models/cluster.py:26 assets/models/domain.py:27 #: assets/models/gathered_user.py:19 assets/models/group.py:22 @@ -589,15 +565,15 @@ msgstr "可连接性" msgid "Date verified" msgstr "校验日期" -#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models.py:82 -#: xpack/plugins/change_auth_plan/models.py:214 -#: xpack/plugins/change_auth_plan/models.py:325 +#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:54 +#: xpack/plugins/change_auth_plan/models/asset.py:126 +#: xpack/plugins/change_auth_plan/models/asset.py:202 msgid "SSH private key" msgstr "SSH密钥" -#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models.py:85 -#: xpack/plugins/change_auth_plan/models.py:210 -#: xpack/plugins/change_auth_plan/models.py:321 +#: assets/models/base.py:179 xpack/plugins/change_auth_plan/models/asset.py:57 +#: xpack/plugins/change_auth_plan/models/asset.py:122 +#: xpack/plugins/change_auth_plan/models/asset.py:198 msgid "SSH public key" msgstr "SSH公钥" @@ -649,7 +625,7 @@ msgstr "系统" msgid "Default Cluster" msgstr "默认Cluster" -#: assets/models/cmd_filter.py:33 assets/models/user.py:209 +#: assets/models/cmd_filter.py:33 assets/models/user.py:213 msgid "Command filter" msgstr "命令过滤器" @@ -751,7 +727,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:199 +#: assets/models/node.py:559 assets/serializers/system_user.py:200 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -759,65 +735,65 @@ msgstr "ssh私钥" msgid "Node" msgstr "节点" -#: assets/models/user.py:185 +#: assets/models/user.py:189 msgid "Automatic managed" msgstr "托管密码" -#: assets/models/user.py:186 +#: assets/models/user.py:190 msgid "Manually input" msgstr "手动输入" -#: assets/models/user.py:190 +#: assets/models/user.py:194 msgid "Common user" msgstr "普通用户" -#: assets/models/user.py:193 +#: assets/models/user.py:197 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:196 assets/serializers/domain.py:28 -#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:56 +#: assets/models/user.py:200 assets/serializers/domain.py:28 +#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models/asset.py:40 msgid "Assets" msgstr "资产" -#: assets/models/user.py:200 templates/_nav.html:17 +#: assets/models/user.py:204 templates/_nav.html:17 #: users/views/profile/pubkey.py:37 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:201 +#: assets/models/user.py:205 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:205 +#: assets/models/user.py:209 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:206 +#: assets/models/user.py:210 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:207 +#: assets/models/user.py:211 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:208 +#: assets/models/user.py:212 msgid "Login mode" msgstr "认证方式" -#: assets/models/user.py:210 +#: assets/models/user.py:214 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:211 authentication/models.py:94 +#: assets/models/user.py:215 authentication/models.py:94 msgid "Token" msgstr "" -#: assets/models/user.py:212 +#: assets/models/user.py:216 msgid "Home" msgstr "家目录" -#: assets/models/user.py:213 +#: assets/models/user.py:217 msgid "System groups" msgstr "用户组" @@ -826,6 +802,10 @@ msgstr "用户组" msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" +#: assets/serializers/account.py:31 assets/serializers/account.py:52 +msgid "System user display" +msgstr "系统用户名称" + #: assets/serializers/asset.py:20 msgid "Protocol format should {}/{}" msgstr "协议格式 {}/{}" @@ -846,6 +826,11 @@ msgstr "节点名称" msgid "Hardware info" msgstr "硬件信息" +#: assets/serializers/asset.py:98 assets/serializers/system_user.py:218 +#: orgs/mixins/serializers.py:26 +msgid "Org name" +msgstr "用户名" + #: assets/serializers/asset.py:99 msgid "Admin user display" msgstr "特权用户名称" @@ -890,7 +875,7 @@ msgstr "密钥指纹" msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:201 +#: assets/serializers/system_user.py:53 assets/serializers/system_user.py:202 msgid "Login mode display" msgstr "认证方式名称" @@ -898,27 +883,31 @@ msgstr "认证方式名称" msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:95 +#: assets/serializers/system_user.py:56 +msgid "Is asset protocol" +msgstr "" + +#: assets/serializers/system_user.py:96 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:109 +#: assets/serializers/system_user.py:110 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:123 +#: assets/serializers/system_user.py:124 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:148 +#: assets/serializers/system_user.py:149 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" -#: assets/serializers/system_user.py:216 +#: assets/serializers/system_user.py:217 msgid "System user name" msgstr "系统用户名称" -#: assets/serializers/system_user.py:226 +#: assets/serializers/system_user.py:227 msgid "Asset hostname" msgstr "资产主机名" @@ -1092,8 +1081,8 @@ msgstr "成功" #: terminal/models/session.py:52 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:53 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:45 -#: xpack/plugins/change_auth_plan/models.py:194 -#: xpack/plugins/change_auth_plan/models.py:340 +#: xpack/plugins/change_auth_plan/models/base.py:105 +#: xpack/plugins/change_auth_plan/models/base.py:189 #: xpack/plugins/gathered_user/models.py:76 msgid "Date start" msgstr "开始日期" @@ -2123,7 +2112,7 @@ msgid "Regularly perform" msgstr "定期执行" #: ops/mixin.py:106 ops/mixin.py:147 -#: xpack/plugins/change_auth_plan/serializers.py:60 +#: xpack/plugins/change_auth_plan/serializers/base.py:42 msgid "Periodic perform" msgstr "定时执行" @@ -2202,8 +2191,8 @@ msgstr "开始时间" msgid "End time" msgstr "完成时间" -#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models.py:197 -#: xpack/plugins/change_auth_plan/models.py:343 +#: ops/models/adhoc.py:246 xpack/plugins/change_auth_plan/models/base.py:108 +#: xpack/plugins/change_auth_plan/models/base.py:190 #: xpack/plugins/gathered_user/models.py:79 msgid "Time" msgstr "时间" @@ -2285,6 +2274,12 @@ msgstr "当前组织 ({}) 不能被删除" msgid "The organization have resource ({}) cannot be deleted" msgstr "组织存在资源 ({}) 不能被删除" +#: orgs/mixins/models.py:46 orgs/mixins/serializers.py:25 orgs/models.py:37 +#: orgs/models.py:432 orgs/serializers.py:106 +#: tickets/serializers/ticket/ticket.py:77 +msgid "Organization" +msgstr "组织审计员" + #: orgs/models.py:17 users/const.py:12 msgid "Organization administrator" msgstr "组织管理员" @@ -2935,6 +2930,7 @@ msgstr "仅管理员" msgid "Global MFA auth" msgstr "全局启用 MFA 认证" + #: settings/serializers/security.py:33 msgid "Limit the number of login failures" msgstr "限制登录失败次数" @@ -2947,6 +2943,7 @@ msgstr "禁止登录时间间隔" msgid "" "Unit: minute, If the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." + msgstr "单位:分, 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" #: settings/serializers/security.py:45 @@ -4311,7 +4308,7 @@ msgstr "工单受理人" #: tickets/models/ticket.py:45 msgid "Title" -msgstr "标题" +msgstr "" #: tickets/models/ticket.py:53 msgid "State" @@ -4624,8 +4621,9 @@ msgstr "两次密码不一致" msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:22 xpack/plugins/change_auth_plan/models.py:65 -#: xpack/plugins/change_auth_plan/serializers.py:33 +#: users/serializers/user.py:22 +#: xpack/plugins/change_auth_plan/models/base.py:32 +#: xpack/plugins/change_auth_plan/serializers/base.py:24 msgid "Password strategy" msgstr "密码策略" @@ -5263,84 +5261,111 @@ msgstr "* 新密码不能是最近 {} 次的密码" msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" +#: xpack/plugins/change_auth_plan/api/asset.py:83 +#: xpack/plugins/change_auth_plan/api/asset.py:141 +msgid "The parameter 'action' must be [{}]" +msgstr "" + #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:100 -#: xpack/plugins/change_auth_plan/models.py:201 +#: xpack/plugins/change_auth_plan/models/asset.py:63 +#: xpack/plugins/change_auth_plan/models/asset.py:119 msgid "Change auth plan" msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models.py:40 -msgid "Custom password" -msgstr "自定义密码" +#: xpack/plugins/change_auth_plan/models/app.py:41 +#: xpack/plugins/change_auth_plan/models/app.py:90 +msgid "Database Change auth plan" +msgstr "改密计划" -#: xpack/plugins/change_auth_plan/models.py:41 -msgid "All assets use the same random password" -msgstr "所有资产使用相同的随机密码" +#: xpack/plugins/change_auth_plan/models/app.py:94 +#: xpack/plugins/change_auth_plan/models/app.py:146 +msgid "Database Change auth plan execution" +msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:42 -msgid "All assets use different random password" -msgstr "所有资产使用不同的随机密码" +#: xpack/plugins/change_auth_plan/models/app.py:142 +msgid "SystemUser" +msgstr "系统用户" -#: xpack/plugins/change_auth_plan/models.py:46 +#: xpack/plugins/change_auth_plan/models/app.py:151 +msgid "Database Change auth plan task" +msgstr "改密计划任务" + +#: xpack/plugins/change_auth_plan/models/asset.py:30 msgid "Append SSH KEY" msgstr "追加新密钥" -#: xpack/plugins/change_auth_plan/models.py:47 +#: xpack/plugins/change_auth_plan/models/asset.py:31 msgid "Empty and append SSH KEY" msgstr "清空所有密钥再追加新密钥" -#: xpack/plugins/change_auth_plan/models.py:48 +#: xpack/plugins/change_auth_plan/models/asset.py:32 msgid "Empty current user and append SSH KEY" msgstr "清空当前账号密钥再追加新密钥" -#: xpack/plugins/change_auth_plan/models.py:69 -msgid "Password rules" -msgstr "密码规则" - -#: xpack/plugins/change_auth_plan/models.py:78 -#: xpack/plugins/change_auth_plan/serializers.py:35 +#: xpack/plugins/change_auth_plan/models/asset.py:50 +#: xpack/plugins/change_auth_plan/serializers/asset.py:34 msgid "SSH Key strategy" msgstr "SSH Key 策略" -#: xpack/plugins/change_auth_plan/models.py:189 -msgid "Manual trigger" -msgstr "手动触发" - -#: xpack/plugins/change_auth_plan/models.py:190 -msgid "Timing trigger" -msgstr "定时触发" - -#: xpack/plugins/change_auth_plan/models.py:204 -msgid "Change auth plan snapshot" -msgstr "改密计划快照" - -#: xpack/plugins/change_auth_plan/models.py:218 -#: xpack/plugins/change_auth_plan/serializers.py:166 -msgid "Trigger mode" -msgstr "触发模式" - -#: xpack/plugins/change_auth_plan/models.py:223 -#: xpack/plugins/change_auth_plan/models.py:329 +#: xpack/plugins/change_auth_plan/models/asset.py:130 +#: xpack/plugins/change_auth_plan/models/asset.py:206 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:302 +#: xpack/plugins/change_auth_plan/models/asset.py:213 +msgid "Change auth plan task" +msgstr "改密计划任务" + +#: xpack/plugins/change_auth_plan/models/base.py:24 +msgid "Custom password" +msgstr "自定义密码" + +#: xpack/plugins/change_auth_plan/models/base.py:25 +msgid "All assets use the same random password" +msgstr "使用相同的随机密码" + +#: xpack/plugins/change_auth_plan/models/base.py:26 +msgid "All assets use different random password" +msgstr "使用不同的随机密码" + +#: xpack/plugins/change_auth_plan/models/base.py:36 +msgid "Password rules" +msgstr "密码规则" + +#: xpack/plugins/change_auth_plan/models/base.py:100 +msgid "Manual trigger" +msgstr "手动触发" + +#: xpack/plugins/change_auth_plan/models/base.py:101 +msgid "Timing trigger" +msgstr "定时触发" + +#: xpack/plugins/change_auth_plan/models/base.py:111 +msgid "Change auth plan snapshot" +msgstr "改密计划快照" + +#: xpack/plugins/change_auth_plan/models/base.py:118 +#: xpack/plugins/change_auth_plan/serializers/base.py:70 +msgid "Trigger mode" +msgstr "触发模式" + +#: xpack/plugins/change_auth_plan/models/base.py:173 msgid "Ready" msgstr "准备" -#: xpack/plugins/change_auth_plan/models.py:303 +#: xpack/plugins/change_auth_plan/models/base.py:174 msgid "Preflight check" msgstr "改密前的校验" -#: xpack/plugins/change_auth_plan/models.py:304 +#: xpack/plugins/change_auth_plan/models/base.py:175 msgid "Change auth" msgstr "执行改密" -#: xpack/plugins/change_auth_plan/models.py:305 +#: xpack/plugins/change_auth_plan/models/base.py:176 msgid "Verify auth" msgstr "验证密码/密钥" -#: xpack/plugins/change_auth_plan/models.py:306 +#: xpack/plugins/change_auth_plan/models/base.py:177 msgid "Keep auth" msgstr "保存密码/密钥" @@ -5348,55 +5373,51 @@ msgstr "保存密码/密钥" msgid "Step" msgstr "步骤" -#: xpack/plugins/change_auth_plan/models.py:350 -msgid "Change auth plan task" -msgstr "改密计划任务" - -#: xpack/plugins/change_auth_plan/serializers.py:29 +#: xpack/plugins/change_auth_plan/serializers/asset.py:31 msgid "Change Password" msgstr "修改密码" -#: xpack/plugins/change_auth_plan/serializers.py:30 +#: xpack/plugins/change_auth_plan/serializers/asset.py:32 msgid "Change SSH Key" msgstr "修改密钥" -#: xpack/plugins/change_auth_plan/serializers.py:61 -msgid "Run times" -msgstr "执行次数" - -#: xpack/plugins/change_auth_plan/serializers.py:79 +#: xpack/plugins/change_auth_plan/serializers/asset.py:65 msgid "Require password strategy perform setting" msgstr "需要密码策略执行设置" -#: xpack/plugins/change_auth_plan/serializers.py:82 +#: xpack/plugins/change_auth_plan/serializers/asset.py:68 msgid "Require password perform setting" msgstr "需要密码执行设置" -#: xpack/plugins/change_auth_plan/serializers.py:85 +#: xpack/plugins/change_auth_plan/serializers/asset.py:71 msgid "Require password rule perform setting" msgstr "需要密码规则执行设置" -#: xpack/plugins/change_auth_plan/serializers.py:97 -msgid "* Please enter the correct password length" -msgstr "* 请输入正确的密码长度" - -#: xpack/plugins/change_auth_plan/serializers.py:100 -msgid "* Password length range 6-30 bits" -msgstr "* 密码长度范围 6-30 位" - -#: xpack/plugins/change_auth_plan/serializers.py:118 +#: xpack/plugins/change_auth_plan/serializers/asset.py:87 msgid "Require ssh key strategy or ssh key perform setting" msgstr "需要ssh密钥策略或ssh密钥执行设置" -#: xpack/plugins/change_auth_plan/utils.py:485 +#: xpack/plugins/change_auth_plan/serializers/base.py:43 +msgid "Run times" +msgstr "执行次数" + +#: xpack/plugins/change_auth_plan/serializers/base.py:54 +msgid "* Please enter the correct password length" +msgstr "* 请输入正确的密码长度" + +#: xpack/plugins/change_auth_plan/serializers/base.py:57 +msgid "* Password length range 6-30 bits" +msgstr "* 密码长度范围 6-30 位" + +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:248 msgid "Invalid/incorrect password" msgstr "无效/错误 密码" -#: xpack/plugins/change_auth_plan/utils.py:487 +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:250 msgid "Failed to connect to the host" msgstr "连接主机失败" -#: xpack/plugins/change_auth_plan/utils.py:489 +#: xpack/plugins/change_auth_plan/task_handlers/base/handler.py:252 msgid "Data could not be sent to remote" msgstr "无法将数据发送到远程" @@ -5492,10 +5513,6 @@ msgstr "云服务商" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:82 xpack/plugins/cloud/serializers.py:207 -msgid "Account" -msgstr "账户" - #: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers.py:179 msgid "Regions" msgstr "地域" @@ -5816,135 +5833,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "Approval level" -#~ msgstr "同意" - -#~ msgid "Login challenge enabled" -#~ msgstr "登录页面开启CHALLENGE输入框" - -#~ msgid "Login captcha enabled" -#~ msgstr "登录页面开启验证码" - -#~ msgid "Insecure command level" -#~ msgstr "不安全命令等级" - -#~ msgid "Encrypt password" -#~ msgstr "密码加密" - -#~ msgid "Create User" -#~ msgstr "创建用户" - -#~ msgid "Apply attr to use" -#~ msgstr "申请可用属性" - -#~ msgid "User login only in users" -#~ msgstr "仅在用户列表中用户认证" - -#~ msgid "Approved applications" -#~ msgstr "批准的应用" - -#~ msgid "Approved system users" -#~ msgstr "批准的系统用户" - -#~ msgid "Approved date expired" -#~ msgstr "批准的失效日期" - -#~ msgid "Applied IP group" -#~ msgstr "申请的IP组" - -#~ msgid "Approved assets" -#~ msgstr "批准的资产" - -#~ msgid "Approved actions" -#~ msgstr "批准的动作" - -#~ msgid "Ticket action" -#~ msgstr "工单动作" - -#~ msgid "Ticket processor" -#~ msgstr "工单处理人" - -#~ msgid "Processor display" -#~ msgstr "处理人名称" - -#~ msgid "Application group" -#~ msgstr "应用组" - -#~ msgid "System user group" -#~ msgstr "系统用户组" - -#~ msgid "Permission name" -#~ msgstr "授权名称" - -#~ msgid "Approve applications" -#~ msgstr "批准的应用" - -#~ msgid "No `Application` are found under Organization `{}`" -#~ msgstr "在组织 `{}` 下没有发现 `应用`" - -#~ msgid "No `SystemUser` are found under Organization `{}`" -#~ msgstr "在组织 `{}` 下没有发现 `系统用户`" - -#~ msgid "IP group" -#~ msgstr "IP组" - -#~ msgid "Hostname group" -#~ msgstr "主机名组" - -#~ msgid "Approve assets" -#~ msgstr "批准的资产" - -#~ msgid "No `Asset` are found under Organization `{}`" -#~ msgstr "在组织 `{}` 下没有发现 `资产`" - -#~ msgid "Action display" -#~ msgstr "动作名称" - -#~ msgid "None of the assignees belong to Organization `{}` admins" -#~ msgstr "所有受理人都不属于组织 `{}` 下的管理员" - -#~ msgid "* Please enter custom password" -#~ msgstr "* 请输入自定义密码" - -#~ msgid "FeiShu Error, Please contact your system administrator" -#~ msgstr "飞书错误,请联系系统管理员" - -#~ msgid "Category(Display)" -#~ msgstr "类别 (显示名称)" - -#~ msgid "Type(Dispaly)" -#~ msgstr "类型 (显示名称)" - -#~ msgid "Users name" -#~ msgstr "用户名" - -#~ msgid "User groups name" -#~ msgstr "用户组名称" - -#~ msgid "Assets name" -#~ msgstr "资产名称" - -#~ msgid "System users name" -#~ msgstr "系统用户名称" - -#~ msgid "Admin user MFA auth" -#~ msgstr "所有管理员启用 MFA" - -#~ msgid "Admin user enable MFA" -#~ msgstr "强制管理员启用 MFA" - -#~ msgid "Password update" -#~ msgstr "密码更新" - -#~ msgid "All user enable MFA" -#~ msgstr "强制所有用户启用 MFA" - -#~ msgid "Application category" -#~ msgstr "应用类别" - -#~ msgid "Application type" -#~ msgstr "应用类型" - -#~ msgid "Trigger" -#~ msgstr "触发" diff --git a/apps/perms/api/application/user_group_permission.py b/apps/perms/api/application/user_group_permission.py index 32d116b2f..34c3eefaa 100644 --- a/apps/perms/api/application/user_group_permission.py +++ b/apps/perms/api/application/user_group_permission.py @@ -19,8 +19,8 @@ class UserGroupGrantedApplicationsApi(CommonApiMixin, ListAPIView): 获取用户组直接授权的应用 """ permission_classes = (IsOrgAdminOrAppUser,) - serializer_class = serializers.ApplicationGrantedSerializer - only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields + serializer_class = serializers.AppGrantedSerializer + only_fields = serializers.AppGrantedSerializer.Meta.only_fields filterset_fields = ['id', 'name', 'category', 'type', 'comment'] search_fields = ['name', 'comment'] diff --git a/apps/perms/api/application/user_permission/user_permission_applications.py b/apps/perms/api/application/user_permission/user_permission_applications.py index a88217e27..be5fc6745 100644 --- a/apps/perms/api/application/user_permission/user_permission_applications.py +++ b/apps/perms/api/application/user_permission/user_permission_applications.py @@ -24,8 +24,8 @@ __all__ = [ class AllGrantedApplicationsMixin(CommonApiMixin, ListAPIView): - only_fields = serializers.ApplicationGrantedSerializer.Meta.only_fields - serializer_class = serializers.ApplicationGrantedSerializer + only_fields = serializers.AppGrantedSerializer.Meta.only_fields + serializer_class = serializers.AppGrantedSerializer filterset_fields = { 'id': ['exact'], 'name': ['exact'], diff --git a/apps/perms/serializers/application/user_permission.py b/apps/perms/serializers/application/user_permission.py index 5c722852b..63b681d5a 100644 --- a/apps/perms/serializers/application/user_permission.py +++ b/apps/perms/serializers/application/user_permission.py @@ -6,10 +6,10 @@ from django.utils.translation import ugettext_lazy as _ from assets.models import SystemUser from applications.models import Application -from applications.serializers import ApplicationSerializerMixin +from applications.serializers import AppSerializerMixin __all__ = [ - 'ApplicationGrantedSerializer', 'ApplicationSystemUserSerializer' + 'AppGrantedSerializer', 'ApplicationSystemUserSerializer' ] @@ -26,7 +26,7 @@ class ApplicationSystemUserSerializer(serializers.ModelSerializer): read_only_fields = fields -class ApplicationGrantedSerializer(ApplicationSerializerMixin, serializers.ModelSerializer): +class AppGrantedSerializer(AppSerializerMixin, serializers.ModelSerializer): """ 被授权应用的数据结构 """ diff --git a/apps/perms/signals_handler/__init__.py b/apps/perms/signals_handler/__init__.py index e0b84afea..68a531887 100644 --- a/apps/perms/signals_handler/__init__.py +++ b/apps/perms/signals_handler/__init__.py @@ -1,2 +1,3 @@ -from . import common +from . import asset_permission +from . import app_permission from . import refresh_perms diff --git a/apps/perms/signals_handler/app_permission.py b/apps/perms/signals_handler/app_permission.py new file mode 100644 index 000000000..84ee58807 --- /dev/null +++ b/apps/perms/signals_handler/app_permission.py @@ -0,0 +1,104 @@ +import itertools + +from django.db.models.signals import m2m_changed +from django.dispatch import receiver + +from users.models import User, UserGroup +from assets.models import SystemUser +from applications.models import Application +from common.utils import get_logger +from common.exceptions import M2MReverseNotAllowed +from common.decorator import on_transaction_commit +from common.const.signals import POST_ADD +from perms.models import ApplicationPermission +from applications.models import Account as AppAccount + + +logger = get_logger(__file__) + + +@receiver(m2m_changed, sender=ApplicationPermission.applications.through) +@on_transaction_commit +def on_app_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs): + if reverse: + raise M2MReverseNotAllowed + if action != POST_ADD: + return + + logger.debug("Application permission applications change signal received") + system_users = instance.system_users.all() + set_remote_app_asset_system_users_if_need(instance, system_users=system_users) + + apps = Application.objects.filter(pk__in=pk_set) + set_app_accounts(apps, system_users) + + +def set_app_accounts(apps, system_users): + for app, system_user in itertools.product(apps, system_users): + AppAccount.objects.get_or_create( + defaults={'app': app, 'systemuser': system_user}, + app=app, systemuser=system_user + ) + + +def set_remote_app_asset_system_users_if_need(instance: ApplicationPermission, system_users=None, + users=None, groups=None): + if not instance.category_remote_app: + return + + attrs = instance.applications.all().values_list('attrs', flat=True) + asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] + if not asset_ids: + return + + system_users = system_users or instance.system_users.all() + for system_user in system_users: + system_user.assets.add(*asset_ids) + + if system_user.username_same_with_user: + users = users or instance.users.all() + groups = groups or instance.user_groups.all() + system_user.groups.add(*groups) + system_user.users.add(*users) + + +@receiver(m2m_changed, sender=ApplicationPermission.system_users.through) +@on_transaction_commit +def on_app_permission_system_users_changed(sender, instance, action, reverse, pk_set, **kwargs): + if reverse: + raise M2MReverseNotAllowed + if action != POST_ADD: + return + + logger.debug("Application permission system_users change signal received") + system_users = SystemUser.objects.filter(pk__in=pk_set) + + set_remote_app_asset_system_users_if_need(instance, system_users=system_users) + apps = instance.applications.all() + set_app_accounts(apps, system_users) + + +@receiver(m2m_changed, sender=ApplicationPermission.users.through) +@on_transaction_commit +def on_app_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs): + if reverse: + raise M2MReverseNotAllowed + if action != POST_ADD: + return + + logger.debug("Application permission users change signal received") + users = User.objects.filter(pk__in=pk_set) + set_remote_app_asset_system_users_if_need(instance, users=users) + + +@receiver(m2m_changed, sender=ApplicationPermission.user_groups.through) +@on_transaction_commit +def on_app_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs): + if reverse: + raise M2MReverseNotAllowed + if action != POST_ADD: + return + + logger.debug("Application permission user groups change signal received") + groups = UserGroup.objects.filter(pk__in=pk_set) + set_remote_app_asset_system_users_if_need(instance, groups=groups) diff --git a/apps/perms/signals_handler/common.py b/apps/perms/signals_handler/asset_permission.py similarity index 54% rename from apps/perms/signals_handler/common.py rename to apps/perms/signals_handler/asset_permission.py index 7399346db..0b2c1aeee 100644 --- a/apps/perms/signals_handler/common.py +++ b/apps/perms/signals_handler/asset_permission.py @@ -3,19 +3,20 @@ from django.db.models.signals import m2m_changed from django.dispatch import receiver -from users.models import User, UserGroup +from users.models import User from assets.models import SystemUser -from applications.models import Application from common.utils import get_logger +from common.decorator import on_transaction_commit from common.exceptions import M2MReverseNotAllowed from common.const.signals import POST_ADD -from perms.models import AssetPermission, ApplicationPermission +from perms.models import AssetPermission logger = get_logger(__file__) @receiver(m2m_changed, sender=User.groups.through) +@on_transaction_commit def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs): """ UserGroup 增加 User 时,增加的 User 需要与 UserGroup 关联的动态系统用户相关联 @@ -39,12 +40,13 @@ def on_user_groups_change(sender, instance, action, reverse, pk_set, **kwargs): @receiver(m2m_changed, sender=AssetPermission.nodes.through) +@on_transaction_commit def on_permission_nodes_changed(instance, action, reverse, pk_set, model, **kwargs): if reverse: raise M2MReverseNotAllowed - if action != POST_ADD: return + logger.debug("Asset permission nodes change signal received") nodes = model.objects.filter(pk__in=pk_set) system_users = instance.system_users.all() @@ -55,12 +57,13 @@ def on_permission_nodes_changed(instance, action, reverse, pk_set, model, **kwar @receiver(m2m_changed, sender=AssetPermission.assets.through) +@on_transaction_commit def on_permission_assets_changed(instance, action, reverse, pk_set, model, **kwargs): if reverse: raise M2MReverseNotAllowed - if action != POST_ADD: return + logger.debug("Asset permission assets change signal received") assets = model.objects.filter(pk__in=pk_set) @@ -71,33 +74,38 @@ def on_permission_assets_changed(instance, action, reverse, pk_set, model, **kwa @receiver(m2m_changed, sender=AssetPermission.system_users.through) +@on_transaction_commit def on_asset_permission_system_users_changed(instance, action, reverse, **kwargs): if reverse: raise M2MReverseNotAllowed - if action != POST_ADD: return + logger.debug("Asset permission system_users change signal received") system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) assets = instance.assets.all().values_list('id', flat=True) nodes = instance.nodes.all().values_list('id', flat=True) - users = instance.users.all().values_list('id', flat=True) - groups = instance.user_groups.all().values_list('id', flat=True) + for system_user in system_users: system_user.nodes.add(*tuple(nodes)) system_user.assets.add(*tuple(assets)) + + # 动态系统用户,需要关联用户和用户组了 if system_user.username_same_with_user: + users = instance.users.all().values_list('id', flat=True) + groups = instance.user_groups.all().values_list('id', flat=True) system_user.groups.add(*tuple(groups)) system_user.users.add(*tuple(users)) @receiver(m2m_changed, sender=AssetPermission.users.through) +@on_transaction_commit def on_asset_permission_users_changed(instance, action, reverse, pk_set, model, **kwargs): if reverse: raise M2MReverseNotAllowed - if action != POST_ADD: return + logger.debug("Asset permission users change signal received") users = model.objects.filter(pk__in=pk_set) system_users = instance.system_users.all() @@ -109,13 +117,13 @@ def on_asset_permission_users_changed(instance, action, reverse, pk_set, model, @receiver(m2m_changed, sender=AssetPermission.user_groups.through) -def on_asset_permission_user_groups_changed(instance, action, pk_set, model, - reverse, **kwargs): +@on_transaction_commit +def on_asset_permission_user_groups_changed(instance, action, pk_set, model, reverse, **kwargs): if reverse: raise M2MReverseNotAllowed - if action != POST_ADD: return + logger.debug("Asset permission user groups change signal received") groups = model.objects.filter(pk__in=pk_set) system_users = instance.system_users.all() @@ -126,87 +134,6 @@ def on_asset_permission_user_groups_changed(instance, action, pk_set, model, system_user.groups.add(*tuple(groups)) -@receiver(m2m_changed, sender=ApplicationPermission.system_users.through) -def on_application_permission_system_users_changed(sender, instance: ApplicationPermission, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if not instance.category_remote_app: - return - if action != POST_ADD: - return - - system_users = SystemUser.objects.filter(pk__in=pk_set) - logger.debug("Application permission system_users change signal received") - attrs = instance.applications.all().values_list('attrs', flat=True) - - asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] - if not asset_ids: - return - - for system_user in system_users: - system_user.assets.add(*asset_ids) - if system_user.username_same_with_user: - user_ids = instance.users.all().values_list('id', flat=True) - group_ids = instance.user_groups.all().values_list('id', flat=True) - system_user.groups.add(*group_ids) - system_user.users.add(*user_ids) -@receiver(m2m_changed, sender=ApplicationPermission.users.through) -def on_application_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if not instance.category_remote_app: - return - - if action != POST_ADD: - return - - logger.debug("Application permission users change signal received") - user_ids = User.objects.filter(pk__in=pk_set).values_list('id', flat=True) - system_users = instance.system_users.all() - - for system_user in system_users: - if system_user.username_same_with_user: - system_user.users.add(*user_ids) - - -@receiver(m2m_changed, sender=ApplicationPermission.user_groups.through) -def on_application_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - if not instance.category_remote_app: - return - if action != POST_ADD: - return - - logger.debug("Application permission user groups change signal received") - group_ids = UserGroup.objects.filter(pk__in=pk_set).values_list('id', flat=True) - system_users = instance.system_users.all() - - for system_user in system_users: - if system_user.username_same_with_user: - system_user.groups.add(*group_ids) - - -@receiver(m2m_changed, sender=ApplicationPermission.applications.through) -def on_application_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs): - if reverse: - raise M2MReverseNotAllowed - - if not instance.category_remote_app: - return - - if action != POST_ADD: - return - - attrs = Application.objects.filter(id__in=pk_set).values_list('attrs', flat=True) - asset_ids = [attr['asset'] for attr in attrs if attr.get('asset')] - if not asset_ids: - return - - system_users = instance.system_users.all() - - for system_user in system_users: - system_user.assets.add(*asset_ids) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 6684636c6..c809a7ad4 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -115,3 +115,6 @@ azure-mgmt-subscription==1.0.0 qingcloud-sdk==1.2.12 django-simple-history==3.0.0 google-cloud-compute==0.5.0 +PyMySQL==1.0.2 +cx-Oracle==8.2.1 +psycopg2-binary==2.9.1