diff --git a/apps/applications/api/account.py b/apps/applications/api/account.py index 8071125b2..6c95a73c8 100644 --- a/apps/applications/api/account.py +++ b/apps/applications/api/account.py @@ -2,7 +2,6 @@ # from django_filters import rest_framework as filters -from django.conf import settings from django.db.models import F, Value, CharField from django.db.models.functions import Concat from django.http import Http404 @@ -31,11 +30,11 @@ class ApplicationAccountViewSet(JMSModelViewSet): filterset_class = AccountFilterSet filterset_fields = ['username', 'app_name', 'type', 'category'] serializer_class = serializers.ApplicationAccountSerializer - http_method_names = ['get', 'put', 'patch', 'options'] def get_queryset(self): - queryset = ApplicationPermission.objects.exclude(system_users__isnull=True) \ + queryset = ApplicationPermission.objects\ + .exclude(system_users__isnull=True) \ .exclude(applications__isnull=True) \ .annotate(uid=Concat( 'applications', Value('_'), 'system_users', output_field=CharField() @@ -47,7 +46,7 @@ class ApplicationAccountViewSet(JMSModelViewSet): .annotate(app=F('applications')) \ .annotate(app_name=F("applications__name")) \ .values('username', 'password', 'systemuser', 'systemuser_display', - 'app', 'app_name', 'category', 'type', 'uid') + 'app', 'app_name', 'category', 'type', 'uid', 'org_id') return queryset def get_object(self): @@ -63,6 +62,11 @@ class ApplicationAccountViewSet(JMSModelViewSet): 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) + return queryset + class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): serializer_class = serializers.ApplicationAccountSecretSerializer diff --git a/apps/applications/serializers/application.py b/apps/applications/serializers/application.py index 73b4eb7f7..b88fcef21 100644 --- a/apps/applications/serializers/application.py +++ b/apps/applications/serializers/application.py @@ -3,6 +3,8 @@ 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 common.drf.serializers import MethodSerializer from .attrs import category_serializer_classes_mapping, type_serializer_classes_mapping @@ -74,12 +76,14 @@ class ApplicationAccountSerializer(serializers.Serializer): systemuser = serializers.ReadOnlyField(label=_('System user')) systemuser_display = serializers.ReadOnlyField(label=_("System user display")) app = serializers.ReadOnlyField(label=_('App')) - uid = serializers.ReadOnlyField(label=_("Union id")) app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True) 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) @@ -96,6 +100,11 @@ class ApplicationAccountSerializer(serializers.Serializer): def get_type_display(self, obj): return self.type_mapper.get(obj['type']) + @staticmethod + def get_org_name(obj): + org = Organization.get_instance(obj['org_id']) + return org.name + class ApplicationAccountSecretSerializer(ApplicationAccountSerializer): password = serializers.CharField(write_only=False, label=_("Password")) diff --git a/apps/applications/serializers/attrs/application_category/remote_app.py b/apps/applications/serializers/attrs/application_category/remote_app.py index fd5fa54f3..c50fd9364 100644 --- a/apps/applications/serializers/attrs/application_category/remote_app.py +++ b/apps/applications/serializers/attrs/application_category/remote_app.py @@ -14,7 +14,7 @@ logger = get_logger(__file__) __all__ = ['RemoteAppSerializer'] -class AssetCharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): +class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): def to_internal_value(self, data): instance = super().to_internal_value(data) @@ -26,14 +26,14 @@ class AssetCharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): return self.pk_field.to_representation(_id) # 解决删除资产后,远程应用更新页面会显示资产ID的问题 asset = get_object_or_none(Asset, id=_id) - if asset: + if not asset: return None return _id class RemoteAppSerializer(serializers.Serializer): asset_info = serializers.SerializerMethodField() - asset = AssetCharPrimaryKeyRelatedField( + asset = ExistAssetPrimaryKeyRelatedField( queryset=Asset.objects, required=False, label=_("Asset"), allow_null=True ) path = serializers.CharField( diff --git a/apps/assets/api/mixin.py b/apps/assets/api/mixin.py index f7738f3f1..7f485bf29 100644 --- a/apps/assets/api/mixin.py +++ b/apps/assets/api/mixin.py @@ -26,7 +26,7 @@ class SerializeToTreeNodeMixin: 'isParent': True, 'open': node.is_org_root(), 'meta': { - 'node': { + 'data': { "id": node.id, "key": node.key, "value": node.value, @@ -65,7 +65,7 @@ class SerializeToTreeNodeMixin: 'chkDisabled': not asset.is_active, 'meta': { 'type': 'asset', - 'asset': { + 'data': { 'id': asset.id, 'hostname': asset.hostname, 'ip': asset.ip, diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index e86a167c9..47cf53d65 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -200,6 +200,7 @@ class AssetTaskSerializer(AssetsTaskSerializer): ('push_system_user', 'push_system_user'), ('test_system_user', 'test_system_user') ]) + action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True) asset = serializers.PrimaryKeyRelatedField( queryset=Asset.objects, required=False, allow_empty=True, many=False ) diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index 609cb5ab0..c2fd94e3f 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -112,7 +112,10 @@ class IDSpmFilter(filters.BaseFilterBackend): resource_ids = cache.get(cache_key) if resource_ids is None or not isinstance(resource_ids, list): return queryset - queryset = queryset.filter(id__in=resource_ids) + if hasattr(view, 'filter_spm_queryset'): + queryset = view.filter_spm_queryset(resource_ids, queryset) + else: + queryset = queryset.filter(id__in=resource_ids) return queryset diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 1248f2a32..72a39d13c 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -76,10 +76,23 @@ class CommandStore(): self._ensure_index_exists() def _ensure_index_exists(self): + mappings = { + "mappings": { + "properties": { + "session": { + "type": "keyword" + }, + "org_id": { + "type": "keyword" + } + } + } + } + try: - self.es.indices.create(self.index) - except RequestError: - pass + self.es.indices.create(self.index, body=mappings) + except RequestError as e: + logger.exception(e) @staticmethod def make_data(command): diff --git a/apps/users/models/user.py b/apps/users/models/user.py index c6a689c7e..836e0b383 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -209,6 +209,11 @@ class RoleMixin: from orgs.models import ROLE as ORG_ROLE return [str(role.label) for role in self.org_roles if role in ORG_ROLE] + @lazyproperty + def org_roles_value_list(self): + from orgs.models import ROLE as ORG_ROLE + return [str(role.value) for role in self.org_roles if role in ORG_ROLE] + @lazyproperty def org_role_display(self): return ' | '.join(self.org_roles_label_list) diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index 3ba22ac7b..09ab50cc0 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -32,7 +32,7 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): def validate_new_password(self, value): from ..utils import check_password_rules - if not check_password_rules(value, user=self.instance): + if not check_password_rules(value, is_org_admin=self.instance.is_org_admin): msg = _('Password does not match security rules') raise serializers.ValidationError(msg) if self.instance.is_history_password(value): diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index e9010e1af..9298253e4 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -116,6 +116,18 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): raise serializers.ValidationError(msg) return value + @property + def is_org_admin(self): + roles = [] + role = self.initial_data.get('role') + if role: + roles.append(role) + org_roles = self.initial_data.get('org_roles') + if org_roles: + roles.extend(org_roles) + is_org_admin = User.ROLE.ADMIN.value in roles + return is_org_admin + def validate_password(self, password): from ..utils import check_password_rules password_strategy = self.initial_data.get('password_strategy') @@ -125,7 +137,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): if self.instance and not password: # 更新用户, 未设置密码 return - if not check_password_rules(password, user=self.instance): + if not check_password_rules(password, is_org_admin=self.is_org_admin): msg = _('Password does not match security rules') raise serializers.ValidationError(msg) return password diff --git a/apps/users/utils.py b/apps/users/utils.py index 8b77a3fa0..8c55a99e5 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -308,7 +308,7 @@ def get_password_check_rules(user): return check_rules -def check_password_rules(password, user): +def check_password_rules(password, is_org_admin=False): pattern = r"^" if settings.SECURITY_PASSWORD_UPPER_CASE: pattern += '(?=.*[A-Z])' @@ -319,7 +319,7 @@ def check_password_rules(password, user): if settings.SECURITY_PASSWORD_SPECIAL_CHAR: pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?])' pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?]' - if user.is_org_admin: + if is_org_admin: min_length = settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH else: min_length = settings.SECURITY_PASSWORD_MIN_LENGTH diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index 46d09ab7e..793322cc9 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -101,7 +101,7 @@ class UserResetPasswordView(FormView): return self.form_invalid(form) password = form.cleaned_data['new_password'] - is_ok = check_password_rules(password, user) + is_ok = check_password_rules(password, is_org_admin=user.is_org_admin) if not is_ok: error = _('* Your password does not meet the requirements') form.add_error('new_password', error)