Merge pull request #6664 from jumpserver/dev

v2.13.0 rc3
This commit is contained in:
Jiangjie.Bai 2021-08-17 20:37:03 +08:00 committed by GitHub
commit af827f3626
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 66 additions and 19 deletions

View File

@ -2,7 +2,6 @@
# #
from django_filters import rest_framework as filters 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 import F, Value, CharField
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.http import Http404 from django.http import Http404
@ -31,11 +30,11 @@ class ApplicationAccountViewSet(JMSModelViewSet):
filterset_class = AccountFilterSet filterset_class = AccountFilterSet
filterset_fields = ['username', 'app_name', 'type', 'category'] filterset_fields = ['username', 'app_name', 'type', 'category']
serializer_class = serializers.ApplicationAccountSerializer serializer_class = serializers.ApplicationAccountSerializer
http_method_names = ['get', 'put', 'patch', 'options'] http_method_names = ['get', 'put', 'patch', 'options']
def get_queryset(self): def get_queryset(self):
queryset = ApplicationPermission.objects.exclude(system_users__isnull=True) \ queryset = ApplicationPermission.objects\
.exclude(system_users__isnull=True) \
.exclude(applications__isnull=True) \ .exclude(applications__isnull=True) \
.annotate(uid=Concat( .annotate(uid=Concat(
'applications', Value('_'), 'system_users', output_field=CharField() 'applications', Value('_'), 'system_users', output_field=CharField()
@ -47,7 +46,7 @@ class ApplicationAccountViewSet(JMSModelViewSet):
.annotate(app=F('applications')) \ .annotate(app=F('applications')) \
.annotate(app_name=F("applications__name")) \ .annotate(app_name=F("applications__name")) \
.values('username', 'password', 'systemuser', 'systemuser_display', .values('username', 'password', 'systemuser', 'systemuser_display',
'app', 'app_name', 'category', 'type', 'uid') 'app', 'app_name', 'category', 'type', 'uid', 'org_id')
return queryset return queryset
def get_object(self): def get_object(self):
@ -63,6 +62,11 @@ class ApplicationAccountViewSet(JMSModelViewSet):
queryset_list = unique(queryset, key=lambda x: (x['app'], x['systemuser'])) queryset_list = unique(queryset, key=lambda x: (x['app'], x['systemuser']))
return queryset_list return queryset_list
@staticmethod
def filter_spm_queryset(resource_ids, queryset):
queryset = queryset.filter(uid__in=resource_ids)
return queryset
class ApplicationAccountSecretViewSet(ApplicationAccountViewSet): class ApplicationAccountSecretViewSet(ApplicationAccountViewSet):
serializer_class = serializers.ApplicationAccountSecretSerializer serializer_class = serializers.ApplicationAccountSecretSerializer

View File

@ -3,6 +3,8 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.models import Organization
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.drf.serializers import MethodSerializer 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
@ -74,12 +76,14 @@ class ApplicationAccountSerializer(serializers.Serializer):
systemuser = serializers.ReadOnlyField(label=_('System user')) systemuser = serializers.ReadOnlyField(label=_('System user'))
systemuser_display = serializers.ReadOnlyField(label=_("System user display")) systemuser_display = serializers.ReadOnlyField(label=_("System user display"))
app = serializers.ReadOnlyField(label=_('App')) app = serializers.ReadOnlyField(label=_('App'))
uid = serializers.ReadOnlyField(label=_("Union id"))
app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True) app_name = serializers.ReadOnlyField(label=_("Application name"), read_only=True)
category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True) category = serializers.ChoiceField(label=_('Category'), choices=const.AppCategory.choices, read_only=True)
category_display = serializers.SerializerMethodField(label=_('Category display')) category_display = serializers.SerializerMethodField(label=_('Category display'))
type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True) type = serializers.ChoiceField(label=_('Type'), choices=const.AppType.choices, read_only=True)
type_display = serializers.SerializerMethodField(label=_('Type display')) 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) category_mapper = dict(const.AppCategory.choices)
type_mapper = dict(const.AppType.choices) type_mapper = dict(const.AppType.choices)
@ -96,6 +100,11 @@ class ApplicationAccountSerializer(serializers.Serializer):
def get_type_display(self, obj): 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
class ApplicationAccountSecretSerializer(ApplicationAccountSerializer): class ApplicationAccountSecretSerializer(ApplicationAccountSerializer):
password = serializers.CharField(write_only=False, label=_("Password")) password = serializers.CharField(write_only=False, label=_("Password"))

View File

@ -14,7 +14,7 @@ logger = get_logger(__file__)
__all__ = ['RemoteAppSerializer'] __all__ = ['RemoteAppSerializer']
class AssetCharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField): class ExistAssetPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def to_internal_value(self, data): def to_internal_value(self, data):
instance = super().to_internal_value(data) instance = super().to_internal_value(data)
@ -26,14 +26,14 @@ class AssetCharPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
return self.pk_field.to_representation(_id) return self.pk_field.to_representation(_id)
# 解决删除资产后远程应用更新页面会显示资产ID的问题 # 解决删除资产后远程应用更新页面会显示资产ID的问题
asset = get_object_or_none(Asset, id=_id) asset = get_object_or_none(Asset, id=_id)
if asset: if not asset:
return None return None
return _id return _id
class RemoteAppSerializer(serializers.Serializer): class RemoteAppSerializer(serializers.Serializer):
asset_info = serializers.SerializerMethodField() asset_info = serializers.SerializerMethodField()
asset = AssetCharPrimaryKeyRelatedField( asset = ExistAssetPrimaryKeyRelatedField(
queryset=Asset.objects, required=False, label=_("Asset"), allow_null=True queryset=Asset.objects, required=False, label=_("Asset"), allow_null=True
) )
path = serializers.CharField( path = serializers.CharField(

View File

@ -26,7 +26,7 @@ class SerializeToTreeNodeMixin:
'isParent': True, 'isParent': True,
'open': node.is_org_root(), 'open': node.is_org_root(),
'meta': { 'meta': {
'node': { 'data': {
"id": node.id, "id": node.id,
"key": node.key, "key": node.key,
"value": node.value, "value": node.value,
@ -65,7 +65,7 @@ class SerializeToTreeNodeMixin:
'chkDisabled': not asset.is_active, 'chkDisabled': not asset.is_active,
'meta': { 'meta': {
'type': 'asset', 'type': 'asset',
'asset': { 'data': {
'id': asset.id, 'id': asset.id,
'hostname': asset.hostname, 'hostname': asset.hostname,
'ip': asset.ip, 'ip': asset.ip,

View File

@ -200,6 +200,7 @@ class AssetTaskSerializer(AssetsTaskSerializer):
('push_system_user', 'push_system_user'), ('push_system_user', 'push_system_user'),
('test_system_user', 'test_system_user') ('test_system_user', 'test_system_user')
]) ])
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
asset = serializers.PrimaryKeyRelatedField( asset = serializers.PrimaryKeyRelatedField(
queryset=Asset.objects, required=False, allow_empty=True, many=False queryset=Asset.objects, required=False, allow_empty=True, many=False
) )

View File

@ -112,7 +112,10 @@ class IDSpmFilter(filters.BaseFilterBackend):
resource_ids = cache.get(cache_key) resource_ids = cache.get(cache_key)
if resource_ids is None or not isinstance(resource_ids, list): if resource_ids is None or not isinstance(resource_ids, list):
return queryset 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 return queryset

View File

@ -76,10 +76,23 @@ class CommandStore():
self._ensure_index_exists() self._ensure_index_exists()
def _ensure_index_exists(self): def _ensure_index_exists(self):
mappings = {
"mappings": {
"properties": {
"session": {
"type": "keyword"
},
"org_id": {
"type": "keyword"
}
}
}
}
try: try:
self.es.indices.create(self.index) self.es.indices.create(self.index, body=mappings)
except RequestError: except RequestError as e:
pass logger.exception(e)
@staticmethod @staticmethod
def make_data(command): def make_data(command):

View File

@ -209,6 +209,11 @@ class RoleMixin:
from orgs.models import ROLE as ORG_ROLE from orgs.models import ROLE as ORG_ROLE
return [str(role.label) for role in self.org_roles if role in 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 @lazyproperty
def org_role_display(self): def org_role_display(self):
return ' | '.join(self.org_roles_label_list) return ' | '.join(self.org_roles_label_list)

View File

@ -32,7 +32,7 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer):
def validate_new_password(self, value): def validate_new_password(self, value):
from ..utils import check_password_rules 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') msg = _('Password does not match security rules')
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
if self.instance.is_history_password(value): if self.instance.is_history_password(value):

View File

@ -116,6 +116,18 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return value 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): def validate_password(self, password):
from ..utils import check_password_rules from ..utils import check_password_rules
password_strategy = self.initial_data.get('password_strategy') password_strategy = self.initial_data.get('password_strategy')
@ -125,7 +137,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
if self.instance and not password: if self.instance and not password:
# 更新用户, 未设置密码 # 更新用户, 未设置密码
return 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') msg = _('Password does not match security rules')
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return password return password

View File

@ -308,7 +308,7 @@ def get_password_check_rules(user):
return check_rules return check_rules
def check_password_rules(password, user): def check_password_rules(password, is_org_admin=False):
pattern = r"^" pattern = r"^"
if settings.SECURITY_PASSWORD_UPPER_CASE: if settings.SECURITY_PASSWORD_UPPER_CASE:
pattern += '(?=.*[A-Z])' pattern += '(?=.*[A-Z])'
@ -319,7 +319,7 @@ def check_password_rules(password, user):
if settings.SECURITY_PASSWORD_SPECIAL_CHAR: if settings.SECURITY_PASSWORD_SPECIAL_CHAR:
pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?])' pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?])'
pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?]' pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?]'
if user.is_org_admin: if is_org_admin:
min_length = settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH min_length = settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH
else: else:
min_length = settings.SECURITY_PASSWORD_MIN_LENGTH min_length = settings.SECURITY_PASSWORD_MIN_LENGTH

View File

@ -101,7 +101,7 @@ class UserResetPasswordView(FormView):
return self.form_invalid(form) return self.form_invalid(form)
password = form.cleaned_data['new_password'] 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: if not is_ok:
error = _('* Your password does not meet the requirements') error = _('* Your password does not meet the requirements')
form.add_error('new_password', error) form.add_error('new_password', error)