diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index e6ceb1f66..f349eb9f6 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -68,7 +68,7 @@ class TreeMixin: @classmethod def refresh_node_assets(cls, t=None): - logger.debug("Refresh node tree assets") + logger.debug("Refresh node assets") key = cls.tree_assets_cache_key ttl = cls.tree_cache_time if not t: diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html index 8d8c3f0ba..4c6eb7199 100644 --- a/apps/assets/templates/assets/_asset_list_modal.html +++ b/apps/assets/templates/assets/_asset_list_modal.html @@ -25,7 +25,7 @@
-
+
diff --git a/apps/assets/templates/assets/_node_tree.html b/apps/assets/templates/assets/_node_tree.html index c76d3d685..803c29c13 100644 --- a/apps/assets/templates/assets/_node_tree.html +++ b/apps/assets/templates/assets/_node_tree.html @@ -32,8 +32,7 @@ } - -
+
@@ -306,6 +305,7 @@ function defaultCallback(action) { $(document).ready(function () { + $('.treebox').css('height', window.innerHeight - 180); }) .on('click', '.btn-show-current-asset', function(){ hideRMenu(); @@ -322,4 +322,4 @@ $(document).ready(function () { location.reload(); }) - \ No newline at end of file + diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index be267cc4d..fcc17c87d 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -426,13 +426,15 @@ $(document).ready(function(){ function success(data) { url = setUrlParam(the_url, 'spm', data.spm); requestApi({ - url:url, - method:'DELETE', - success:refreshPage, - flash_message:false, + url: url, + method: 'DELETE', + success: function () { + var msg = "{% trans 'Asset Deleted.' %}"; + swal("{% trans 'Asset Delete' %}", msg, "success"); + refreshPage(); + }, + flash_message: false, }); - var msg = "{% trans 'Asset Deleted.' %}"; - swal("{% trans 'Asset Delete' %}", msg, "success"); } function fail() { var msg = "{% trans 'Asset Deleting failed.' %}"; @@ -440,10 +442,11 @@ $(document).ready(function(){ } requestApi({ url: "{% url 'api-common:resources-cache' %}", - method:'POST', - body:JSON.stringify(data), - success:success, - error:fail + method: 'POST', + body: JSON.stringify(data), + success: success, + error: fail, + flash_message: false }) }) } diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 58ca170c6..f7f35c215 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -395,6 +395,7 @@ defaults = { 'FLOWER_URL': "127.0.0.1:5555", 'DEFAULT_ORG_SHOW_ALL_USERS': True, 'PERIOD_TASK_ENABLED': True, + 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False, } diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py index eecd101e8..4a7fe389c 100644 --- a/apps/perms/signals_handler.py +++ b/apps/perms/signals_handler.py @@ -11,13 +11,6 @@ from .utils.asset_permission import AssetPermissionUtilV2 logger = get_logger(__file__) -permission_m2m_senders = ( - AssetPermission.nodes.through, - AssetPermission.assets.through, - AssetPermission.users.through, - AssetPermission.user_groups.through, -) - @receiver([post_save, post_delete], sender=AssetPermission) @on_transaction_commit diff --git a/apps/settings/api.py b/apps/settings/api.py index 22a295b68..426cdf76d 100644 --- a/apps/settings/api.py +++ b/apps/settings/api.py @@ -12,11 +12,14 @@ from django.conf import settings from django.core.mail import send_mail from django.utils.translation import ugettext_lazy as _ -from .models import Setting -from .utils import LDAPUtil from common.permissions import IsOrgAdmin, IsSuperUser from common.utils import get_logger -from .serializers import MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer +from .models import Setting +from .utils import LDAPUtil +from .serializers import ( + MailTestSerializer, LDAPTestSerializer, LDAPUserSerializer, + PublicSettingSerializer, +) logger = get_logger(__file__) @@ -245,3 +248,19 @@ class CommandStorageDeleteAPI(APIView): storage_name = str(request.data.get('name')) Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name) return Response({"msg": _('Delete succeed')}, status=200) + + +class PublicSettingApi(generics.RetrieveAPIView): + permission_classes = () + serializer_class = PublicSettingSerializer + + def get_object(self): + c = settings.CONFIG + instance = { + "data": { + "WINDOWS_SKIP_ALL_MANUAL_PASSWORD": c.WINDOWS_SKIP_ALL_MANUAL_PASSWORD + } + } + return instance + + diff --git a/apps/settings/serializers.py b/apps/settings/serializers.py index eb8a61679..f29b514d7 100644 --- a/apps/settings/serializers.py +++ b/apps/settings/serializers.py @@ -28,3 +28,6 @@ class LDAPUserSerializer(serializers.Serializer): email = serializers.CharField() existing = serializers.BooleanField(read_only=True) + +class PublicSettingSerializer(serializers.Serializer): + data = serializers.DictField(read_only=True) diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index bc2e4731f..026598206 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -15,4 +15,5 @@ urlpatterns = [ path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'), path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'), path('terminal/command-storage/delete/', api.CommandStorageDeleteAPI.as_view(), name='command-storage-delete'), + path('public/', api.PublicSettingApi.as_view(), name='public-setting'), ] diff --git a/apps/users/forms.py b/apps/users/forms.py index 98d7c9e09..649f66ab9 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -2,6 +2,7 @@ from django import forms from django.utils.translation import gettext_lazy as _ +from django.conf import settings from common.utils import validate_ssh_public_key from orgs.mixins.forms import OrgModelForm @@ -21,6 +22,20 @@ class UserCheckOtpCodeForm(forms.Form): otp_code = forms.CharField(label=_('MFA code'), max_length=6) +def get_source_choices(): + choices_all = dict(User.SOURCE_CHOICES) + choices = [ + (User.SOURCE_LOCAL, choices_all[User.SOURCE_LOCAL]), + ] + if settings.AUTH_LDAP: + choices.append((User.SOURCE_LDAP, choices_all[User.SOURCE_LDAP])) + if settings.AUTH_OPENID: + choices.append((User.SOURCE_OPENID, choices_all[User.SOURCE_OPENID])) + if settings.AUTH_RADIUS: + choices.append((User.SOURCE_RADIUS, choices_all[User.SOURCE_RADIUS])) + return choices + + class UserCreateUpdateFormMixin(OrgModelForm): role_choices = ((i, n) for i, n in User.ROLE_CHOICES if i != User.ROLE_APP) password = forms.CharField( @@ -31,6 +46,10 @@ class UserCreateUpdateFormMixin(OrgModelForm): choices=role_choices, required=True, initial=User.ROLE_USER, label=_("Role") ) + source = forms.ChoiceField( + choices=get_source_choices, required=True, + initial=User.SOURCE_LOCAL, label=_("Source") + ) public_key = forms.CharField( label=_('ssh public key'), max_length=5000, required=False, widget=forms.Textarea(attrs={'placeholder': _('ssh-rsa AAAA...')}), @@ -41,7 +60,8 @@ class UserCreateUpdateFormMixin(OrgModelForm): model = User fields = [ 'username', 'name', 'email', 'groups', 'wechat', - 'phone', 'role', 'date_expired', 'comment', 'otp_level' + 'source', 'phone', 'role', 'date_expired', + 'comment', 'otp_level' ] widgets = { 'otp_level': forms.RadioSelect(), diff --git a/apps/users/serializers/__init__.py b/apps/users/serializers/__init__.py index 94ef71f28..78a695e51 100644 --- a/apps/users/serializers/__init__.py +++ b/apps/users/serializers/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- # -from .v1 import * \ No newline at end of file +from .user import * +from .group import * diff --git a/apps/users/serializers/group.py b/apps/users/serializers/group.py new file mode 100644 index 000000000..d27ddc19a --- /dev/null +++ b/apps/users/serializers/group.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from common.fields import StringManyToManyField +from common.serializers import AdaptedBulkListSerializer +from orgs.mixins.serializers import BulkOrgResourceModelSerializer +from ..models import User, UserGroup +from .. import utils + + +__all__ = [ + 'UserGroupSerializer', 'UserGroupListSerializer', + 'UserGroupUpdateMemberSerializer' +] + + +class UserGroupSerializer(BulkOrgResourceModelSerializer): + users = serializers.PrimaryKeyRelatedField( + required=False, many=True, queryset=User.objects, label=_('User') + ) + + class Meta: + model = UserGroup + list_serializer_class = AdaptedBulkListSerializer + fields = [ + 'id', 'name', 'users', 'comment', 'date_created', + 'created_by', + ] + extra_kwargs = { + 'created_by': {'label': _('Created by'), 'read_only': True} + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_fields_queryset() + + def set_fields_queryset(self): + users_field = self.fields['users'] + users_field.child_relation.queryset = utils.get_current_org_members() + + def validate_users(self, users): + for user in users: + if user.is_super_auditor: + msg = _('Auditors cannot be join in the user group') + raise serializers.ValidationError(msg) + return users + + +class UserGroupListSerializer(UserGroupSerializer): + users = StringManyToManyField(many=True, read_only=True) + + +class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): + users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects) + + class Meta: + model = UserGroup + fields = ['id', 'users'] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_fields_queryset() + + def set_fields_queryset(self): + users_field = self.fields['users'] + users_field.child_relation.queryset = utils.get_current_org_members() + diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/user.py similarity index 69% rename from apps/users/serializers/v1.py rename to apps/users/serializers/user.py index 847afe885..57e2f43fa 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/user.py @@ -6,19 +6,14 @@ from rest_framework import serializers from common.utils import validate_ssh_public_key from common.mixins import BulkSerializerMixin -from common.fields import StringManyToManyField from common.serializers import AdaptedBulkListSerializer from common.permissions import CanUpdateDeleteUser -from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import User, UserGroup -from .. import utils __all__ = [ 'UserSerializer', 'UserPKUpdateSerializer', 'UserUpdateGroupSerializer', - 'UserGroupSerializer', 'UserGroupListSerializer', - 'UserGroupUpdateMemberSerializer', 'ChangeUserPasswordSerializer', - 'ResetOTPSerializer', + 'ChangeUserPasswordSerializer', 'ResetOTPSerializer', ] @@ -49,7 +44,6 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): 'is_valid': {'label': _('Is valid')}, 'is_expired': {'label': _('Is expired')}, 'avatar_url': {'label': _('Avatar url')}, - 'source': {'read_only': True}, 'created_by': {'read_only': True, 'allow_blank': True}, 'can_update': {'read_only': True}, 'can_delete': {'read_only': True}, @@ -127,58 +121,6 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): fields = ['id', 'groups'] -class UserGroupSerializer(BulkOrgResourceModelSerializer): - users = serializers.PrimaryKeyRelatedField( - required=False, many=True, queryset=User.objects, label=_('User') - ) - - class Meta: - model = UserGroup - list_serializer_class = AdaptedBulkListSerializer - fields = [ - 'id', 'name', 'users', 'comment', 'date_created', - 'created_by', - ] - extra_kwargs = { - 'created_by': {'label': _('Created by'), 'read_only': True} - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members() - - def validate_users(self, users): - for user in users: - if user.is_super_auditor: - msg = _('Auditors cannot be join in the user group') - raise serializers.ValidationError(msg) - return users - - -class UserGroupListSerializer(UserGroupSerializer): - users = StringManyToManyField(many=True, read_only=True) - - -class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): - users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects) - - class Meta: - model = UserGroup - fields = ['id', 'users'] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_fields_queryset() - - def set_fields_queryset(self): - users_field = self.fields['users'] - users_field.child_relation.queryset = utils.get_current_org_members() - - class ChangeUserPasswordSerializer(serializers.ModelSerializer): class Meta: diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 4c6afc663..a33d9eec9 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -2,11 +2,11 @@ # from django.dispatch import receiver -# from django.db.models.signals import post_save +from django.db.models.signals import post_save, m2m_changed from common.utils import get_logger from .signals import post_user_create -# from .models import User +from .models import User logger = get_logger(__file__) @@ -28,3 +28,14 @@ def on_user_create(sender, user=None, **kwargs): logger.info(" - Sending welcome mail ...".format(user.name)) if user.email: send_user_created_mail(user) + + +@receiver(m2m_changed, sender=User.groups.through) +def on_user_groups_change(sender, instance=None, action='', **kwargs): + """ + 资产节点发生变化时,刷新节点 + """ + if action.startswith('post'): + logger.debug("User group member change signal recv: {}".format(instance)) + from perms.utils import AssetPermissionUtilV2 + AssetPermissionUtilV2.expire_all_user_tree_cache() diff --git a/apps/users/templates/users/_user.html b/apps/users/templates/users/_user.html index 192dbfb70..ea0f76854 100644 --- a/apps/users/templates/users/_user.html +++ b/apps/users/templates/users/_user.html @@ -21,6 +21,7 @@

{% trans 'Auth' %}

{% block password %}{% endblock %} {% bootstrap_field form.otp_level layout="horizontal" %} + {% bootstrap_field form.source layout="horizontal" %}

{% trans 'Security and Role' %}