From f3dc9b886bbf9f59d37bce1998b5805a239d3a05 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Fri, 18 Oct 2019 15:05:45 +0800 Subject: [PATCH] Asset favor (#3352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 拆分filter org * [Update] 修改session支持protocol搜索 * [Bugfix] 修复判断问题 * [Update] 支持收藏资产 * [update] 修改org resource queryset * [Update] 修改form serializer 对应的多对多字段 * [Bugfix] 修复其他组织取消收藏的bug * [Update] 去掉debug信息 * [Update] 修改remote app get queryset * [Update] 修改remote app get queryset * [Update] 修改没有授权时显示情况 * [Bugfix] 修复组织管理员查看用户权限失败问题 * [Update] 优化forms assets queryset设置 --- apps/applications/api/remote_app.py | 8 +- apps/assets/api/__init__.py | 1 + apps/assets/api/admin_user.py | 12 +- apps/assets/api/asset.py | 14 +- apps/assets/api/cmd_filter.py | 3 +- apps/assets/api/domain.py | 5 +- apps/assets/api/favorite_asset.py | 27 ++++ apps/assets/api/gathered_user.py | 4 +- apps/assets/api/label.py | 1 + apps/assets/api/node.py | 26 ++-- apps/assets/api/system_user.py | 20 +-- apps/assets/forms/asset.py | 9 +- apps/assets/forms/domain.py | 20 ++- apps/assets/forms/label.py | 20 ++- apps/assets/migrations/0042_favoriteasset.py | 31 ++++ apps/assets/models/__init__.py | 1 + apps/assets/models/favorite_asset.py | 20 +++ apps/assets/models/node.py | 24 ++- apps/assets/serializers/__init__.py | 1 + apps/assets/serializers/admin_user.py | 2 +- apps/assets/serializers/asset_user.py | 2 +- apps/assets/serializers/favorite_asset.py | 23 +++ apps/assets/serializers/node.py | 6 +- .../templates/assets/asset_bulk_update.html | 8 + .../templates/assets/user_asset_list.html | 80 +++++++--- apps/assets/urls/api_urls.py | 1 + apps/audits/api.py | 8 +- apps/common/api.py | 2 - apps/common/mixins/models.py | 17 ++- apps/common/signals_handlers.py | 2 +- apps/common/tree.py | 2 + apps/locale/zh/LC_MESSAGES/django.mo | Bin 80879 -> 80914 bytes apps/locale/zh/LC_MESSAGES/django.po | 138 +++++++++--------- apps/orgs/mixins/api.py | 14 +- apps/orgs/mixins/generics.py | 41 ++++++ apps/orgs/mixins/models.py | 56 +++---- apps/orgs/utils.py | 28 ++++ apps/perms/api/asset_permission.py | 38 ++--- apps/perms/api/mixin.py | 24 ++- apps/perms/api/remote_app_permission.py | 15 +- apps/perms/api/user_permission/mixin.py | 2 +- apps/perms/api/user_remote_app_permission.py | 10 +- apps/perms/hands.py | 9 +- apps/perms/utils/asset_permission.py | 17 ++- apps/static/js/jumpserver.js | 14 +- apps/terminal/api/session.py | 4 +- .../templates/terminal/session_list.html | 1 + apps/users/api/group.py | 7 +- apps/users/api/user.py | 39 +++-- apps/users/forms.py | 35 ++--- apps/users/serializers/v1.py | 26 +++- .../templates/users/_granted_assets.html | 30 +++- .../templates/users/user_bulk_update.html | 1 + .../users/user_group_granted_asset.html | 2 +- apps/users/utils.py | 4 + 55 files changed, 656 insertions(+), 299 deletions(-) create mode 100644 apps/assets/api/favorite_asset.py create mode 100644 apps/assets/migrations/0042_favoriteasset.py create mode 100644 apps/assets/models/favorite_asset.py create mode 100644 apps/assets/serializers/favorite_asset.py create mode 100644 apps/orgs/mixins/generics.py diff --git a/apps/applications/api/remote_app.py b/apps/applications/api/remote_app.py index 83b1d490a..79beef8fc 100644 --- a/apps/applications/api/remote_app.py +++ b/apps/applications/api/remote_app.py @@ -1,10 +1,8 @@ # coding: utf-8 # - -from rest_framework import generics - from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics from ..hands import IsOrgAdmin, IsAppUser from ..models import RemoteApp from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer @@ -16,14 +14,14 @@ __all__ = [ class RemoteAppViewSet(OrgBulkModelViewSet): + model = RemoteApp filter_fields = ('name',) search_fields = filter_fields permission_classes = (IsOrgAdmin,) - queryset = RemoteApp.objects.all() serializer_class = RemoteAppSerializer class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): - queryset = RemoteApp.objects.all() + model = RemoteApp permission_classes = (IsAppUser, ) serializer_class = RemoteAppConnectionInfoSerializer diff --git a/apps/assets/api/__init__.py b/apps/assets/api/__init__.py index a4404e290..e7126878d 100644 --- a/apps/assets/api/__init__.py +++ b/apps/assets/api/__init__.py @@ -7,3 +7,4 @@ from .domain import * from .cmd_filter import * from .asset_user import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/api/admin_user.py b/apps/assets/api/admin_user.py index fd10e6129..b91f10c65 100644 --- a/apps/assets/api/admin_user.py +++ b/apps/assets/api/admin_user.py @@ -15,11 +15,10 @@ from django.db import transaction from django.shortcuts import get_object_or_404 -from rest_framework import generics from rest_framework.response import Response from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics -from common.mixins import CommonApiMixin from common.utils import get_logger from ..hands import IsOrgAdmin from ..models import AdminUser, Asset @@ -39,22 +38,21 @@ class AdminUserViewSet(OrgBulkModelViewSet): """ Admin user api set, for add,delete,update,list,retrieve resource """ - + model = AdminUser filter_fields = ("name", "username") search_fields = filter_fields - queryset = AdminUser.objects.all() serializer_class = serializers.AdminUserSerializer permission_classes = (IsOrgAdmin,) class AdminUserAuthApi(generics.UpdateAPIView): - queryset = AdminUser.objects.all() + model = AdminUser serializer_class = serializers.AdminUserAuthSerializer permission_classes = (IsOrgAdmin,) class ReplaceNodesAdminUserApi(generics.UpdateAPIView): - queryset = AdminUser.objects.all() + model = AdminUser serializer_class = serializers.ReplaceNodeAdminUserSerializer permission_classes = (IsOrgAdmin,) @@ -79,7 +77,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView): """ Test asset admin user assets_connectivity """ - queryset = AdminUser.objects.all() + model = AdminUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 39341328b..64fdc16dc 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -3,14 +3,14 @@ import random -from rest_framework import generics from rest_framework.response import Response from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from orgs.mixins.api import OrgBulkModelViewSet -from ..models import Asset, AdminUser, Node +from orgs.mixins import generics +from ..models import Asset, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ test_asset_connectivity_manual @@ -29,10 +29,10 @@ class AssetViewSet(OrgBulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ + model = Asset filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id") search_fields = ("hostname", "ip") ordering_fields = ("hostname", "ip", "port", "cpu_cores") - queryset = Asset.objects.all() serializer_class = serializers.AssetSerializer permission_classes = (IsOrgAdminOrAppUser,) extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend] @@ -57,7 +57,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView): """ Refresh asset hardware info """ - queryset = Asset.objects.all() + model = Asset serializer_class = serializers.AssetSerializer permission_classes = (IsOrgAdmin,) @@ -72,7 +72,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): """ Test asset admin user assets_connectivity """ - queryset = Asset.objects.all() + model = Asset permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer @@ -84,9 +84,9 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView): class AssetGatewayApi(generics.RetrieveAPIView): - queryset = Asset.objects.all() permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.GatewayWithAuthSerializer + model = Asset def retrieve(self, request, *args, **kwargs): asset_id = kwargs.get('pk') @@ -98,4 +98,4 @@ class AssetGatewayApi(generics.RetrieveAPIView): serializer = serializers.GatewayWithAuthSerializer(instance=gateway) return Response(serializer.data) else: - return Response({"msg": "Not have gateway"}, status=404) \ No newline at end of file + return Response({"msg": "Not have gateway"}, status=404) diff --git a/apps/assets/api/cmd_filter.py b/apps/assets/api/cmd_filter.py index 846172548..b4ae67c72 100644 --- a/apps/assets/api/cmd_filter.py +++ b/apps/assets/api/cmd_filter.py @@ -13,14 +13,15 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet'] class CommandFilterViewSet(OrgBulkModelViewSet): + model = CommandFilter filter_fields = ("name",) search_fields = filter_fields permission_classes = (IsOrgAdmin,) - queryset = CommandFilter.objects.all() serializer_class = serializers.CommandFilterSerializer class CommandFilterRuleViewSet(OrgBulkModelViewSet): + model = CommandFilterRule filter_fields = ("content",) search_fields = filter_fields permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index 03c4c2307..13be62315 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -15,7 +15,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"] class DomainViewSet(OrgBulkModelViewSet): - queryset = Domain.objects.all() + model = Domain permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.DomainSerializer @@ -26,16 +26,15 @@ class DomainViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet): + model = Gateway filter_fields = ("domain__name", "name", "username", "ip", "domain") search_fields = filter_fields - queryset = Gateway.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.GatewaySerializer class GatewayTestConnectionApi(SingleObjectMixin, APIView): permission_classes = (IsOrgAdmin,) - model = Gateway object = None def post(self, request, *args, **kwargs): diff --git a/apps/assets/api/favorite_asset.py b/apps/assets/api/favorite_asset.py new file mode 100644 index 000000000..174c77330 --- /dev/null +++ b/apps/assets/api/favorite_asset.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +from rest_framework_bulk import BulkModelViewSet + +from common.permissions import IsValidUser +from orgs.utils import tmp_to_root_org +from ..models import FavoriteAsset +from ..serializers import FavoriteAssetSerializer + +__all__ = ['FavoriteAssetViewSet'] + + +class FavoriteAssetViewSet(BulkModelViewSet): + serializer_class = FavoriteAssetSerializer + permission_classes = (IsValidUser,) + filter_fields = ['asset'] + + def dispatch(self, request, *args, **kwargs): + with tmp_to_root_org(): + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + queryset = FavoriteAsset.objects.filter(user=self.request.user) + return queryset + + def allow_bulk_destroy(self, qs, filtered): + return filtered.count() == 1 diff --git a/apps/assets/api/gathered_user.py b/apps/assets/api/gathered_user.py index 038d47208..b9f137648 100644 --- a/apps/assets/api/gathered_user.py +++ b/apps/assets/api/gathered_user.py @@ -13,12 +13,10 @@ __all__ = ['GatheredUserViewSet'] class GatheredUserViewSet(OrgModelViewSet): - queryset = GatheredUser.objects.all() + model = GatheredUser serializer_class = GatheredUserSerializer permission_classes = [IsOrgAdmin] extra_filter_backends = [AssetRelatedByNodeFilterBackend] filter_fields = ['asset', 'username', 'present'] search_fields = ['username', 'asset__ip', 'asset__hostname'] - - diff --git a/apps/assets/api/label.py b/apps/assets/api/label.py index c3b798959..fe298169a 100644 --- a/apps/assets/api/label.py +++ b/apps/assets/api/label.py @@ -27,6 +27,7 @@ __all__ = ['LabelViewSet'] class LabelViewSet(OrgBulkModelViewSet): + model = Label filter_fields = ("name", "value") search_fields = filter_fields permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index a48d5f00d..1451650d2 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from rest_framework import generics, status +from rest_framework import status from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response @@ -23,9 +23,12 @@ from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none from common.tree import TreeNodeSerializer from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from ..hands import IsOrgAdmin from ..models import Node -from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util +from ..tasks import ( + update_assets_hardware_info_util, test_asset_connectivity_util +) from .. import serializers @@ -40,9 +43,9 @@ __all__ = [ class NodeViewSet(OrgModelViewSet): + model = Node filter_fields = ('value', 'key', 'id') search_fields = ('value', ) - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer @@ -79,6 +82,7 @@ class NodeListAsTreeApi(generics.ListAPIView): } ] """ + model = Node permission_classes = (IsOrgAdmin,) serializer_class = TreeNodeSerializer @@ -87,10 +91,6 @@ class NodeListAsTreeApi(generics.ListAPIView): queryset = [node.as_tree_node() for node in queryset] return queryset - def get_queryset(self): - queryset = Node.objects.all() - return queryset - def filter_queryset(self, queryset): queryset = super().filter_queryset(queryset) queryset = self.to_tree_queryset(queryset) @@ -98,7 +98,6 @@ class NodeListAsTreeApi(generics.ListAPIView): class NodeChildrenApi(generics.ListCreateAPIView): - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer instance = None @@ -162,6 +161,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi): ] """ + model = Node serializer_class = TreeNodeSerializer http_method_names = ['get'] @@ -204,7 +204,7 @@ class NodeAssetsApi(generics.ListAPIView): class NodeAddChildrenApi(generics.UpdateAPIView): - queryset = Node.objects.all() + model = Node permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeAddChildrenSerializer instance = None @@ -221,8 +221,8 @@ class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -233,8 +233,8 @@ class NodeAddAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -249,8 +249,8 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeReplaceAssetsApi(generics.UpdateAPIView): + model = Node serializer_class = serializers.NodeAssetsSerializer - queryset = Node.objects.all() permission_classes = (IsOrgAdmin,) instance = None @@ -262,8 +262,8 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView): class RefreshNodeHardwareInfoApi(APIView): - permission_classes = (IsOrgAdmin,) model = Node + permission_classes = (IsOrgAdmin,) def get(self, request, *args, **kwargs): node_id = kwargs.get('pk') diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 9b87ed9aa..5bf38853d 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -14,13 +14,13 @@ # limitations under the License. from django.shortcuts import get_object_or_404 -from rest_framework import generics from rest_framework.response import Response from common.serializers import CeleryTaskSerializer from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics from ..models import SystemUser, Asset from .. import serializers from ..tasks import ( @@ -43,22 +43,18 @@ class SystemUserViewSet(OrgBulkModelViewSet): """ System user api set, for add,delete,update,list,retrieve resource """ + model = SystemUser filter_fields = ("name", "username") search_fields = filter_fields - queryset = SystemUser.objects.all() serializer_class = serializers.SystemUserSerializer permission_classes = (IsOrgAdminOrAppUser,) - def get_queryset(self): - queryset = super().get_queryset().all() - return queryset - class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): """ Get system user auth info """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer @@ -72,7 +68,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): """ Get system user with asset auth info """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer @@ -88,7 +84,7 @@ class SystemUserPushApi(generics.RetrieveAPIView): """ Push system user to cluster assets api """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = CeleryTaskSerializer @@ -105,7 +101,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): """ Push system user to cluster assets api """ - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = CeleryTaskSerializer @@ -132,7 +128,7 @@ class SystemUserAssetsListView(generics.ListAPIView): class SystemUserPushToAssetApi(generics.RetrieveAPIView): - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer @@ -145,7 +141,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView): class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView): - queryset = SystemUser.objects.all() + model = SystemUser permission_classes = (IsOrgAdmin,) serializer_class = serializers.TaskIDSerializer diff --git a/apps/assets/forms/asset.py b/apps/assets/forms/asset.py index d3d64d602..b6e5d3a42 100644 --- a/apps/assets/forms/asset.py +++ b/apps/assets/forms/asset.py @@ -129,7 +129,7 @@ class AssetUpdateForm(OrgModelForm): class AssetBulkUpdateForm(OrgModelForm): assets = forms.ModelMultipleChoiceField( required=True, - label=_('Select assets'), queryset=Asset.objects.all(), + label=_('Select assets'), queryset=Asset.objects, widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -155,11 +155,18 @@ class AssetBulkUpdateForm(OrgModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.set_fields_queryset() + # 重写其他字段为不再required for name, field in self.fields.items(): if name != 'assets': field.required = False + def set_fields_queryset(self): + assets_field = self.fields['assets'] + if hasattr(self, 'data'): + assets_field.queryset = Asset.objects.all() + def save(self, commit=True): changed_fields = [] for field in self._meta.fields: diff --git a/apps/assets/forms/domain.py b/apps/assets/forms/domain.py index 496147a99..71d9f6cfa 100644 --- a/apps/assets/forms/domain.py +++ b/apps/assets/forms/domain.py @@ -12,7 +12,7 @@ __all__ = ['DomainForm', 'GatewayForm'] class DomainForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), label=_('Asset'), required=False, + queryset=Asset.objects, label=_('Asset'), required=False, widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')} ) @@ -23,19 +23,23 @@ class DomainForm(forms.ModelForm): fields = ['name', 'comment', 'assets'] def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + self.set_fields_queryset() - # 前端渲染优化, 防止过多资产 + def set_fields_queryset(self): assets_field = self.fields.get('assets') + + # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 if not self.data: - instance = kwargs.get('instance') - if instance: - assets_field.queryset = instance.assets.all() + # 有instance 代表渲染更新表单, 否则是创建表单 + # 前端渲染优化, 防止过多资产, 设置assets queryset为none + if self.instance: + assets_field.initial = self.instance.assets.all() + assets_field.queryset = self.instance.assets.all() else: assets_field.queryset = Asset.objects.none() + else: + assets_field.queryset = Asset.objects.all() def save(self, commit=True): instance = super().save(commit=commit) diff --git a/apps/assets/forms/label.py b/apps/assets/forms/label.py index 8a5a54e4a..1f2f13987 100644 --- a/apps/assets/forms/label.py +++ b/apps/assets/forms/label.py @@ -10,7 +10,7 @@ __all__ = ['LabelForm'] class LabelForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( - queryset=Asset.objects.all(), label=_('Asset'), required=False, + queryset=Asset.objects.none(), label=_('Asset'), required=False, widget=forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('Select assets')} ) @@ -21,19 +21,23 @@ class LabelForm(forms.ModelForm): fields = ['name', 'value', 'assets'] def __init__(self, *args, **kwargs): - if kwargs.get('instance', None): - initial = kwargs.get('initial', {}) - initial['assets'] = kwargs['instance'].assets.all() super().__init__(*args, **kwargs) + self.set_fields_queryset() - # 前端渲染优化, 防止过多资产 + def set_fields_queryset(self): assets_field = self.fields.get('assets') + + # 没有data代表是渲染表单, 有data代表是提交创建/更新表单 if not self.data: - instance = kwargs.get('instance') - if instance: - assets_field.queryset = instance.assets.all() + # 有instance 代表渲染更新表单, 否则是创建表单 + # 前端渲染优化, 防止过多资产, 设置assets queryset为none + if self.instance: + assets_field.initial = self.instance.assets.all() + assets_field.queryset = self.instance.assets.all() else: assets_field.queryset = Asset.objects.none() + else: + assets_field.queryset = Asset.objects.all() def save(self, commit=True): label = super().save(commit=commit) diff --git a/apps/assets/migrations/0042_favoriteasset.py b/apps/assets/migrations/0042_favoriteasset.py new file mode 100644 index 000000000..3baebee74 --- /dev/null +++ b/apps/assets/migrations/0042_favoriteasset.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2.5 on 2019-10-16 08:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('assets', '0041_gathereduser'), + ] + + operations = [ + migrations.CreateModel( + name='FavoriteAsset', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'asset')}, + }, + ), + ] diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index c69f19bf6..db9c54aed 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -10,3 +10,4 @@ from .authbook import * from .utils import * from .authbook import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/models/favorite_asset.py b/apps/assets/models/favorite_asset.py new file mode 100644 index 000000000..3abc69c8c --- /dev/null +++ b/apps/assets/models/favorite_asset.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +from django.db import models + +from common.mixins.models import CommonModelMixin + + +__all__ = ['FavoriteAsset'] + + +class FavoriteAsset(CommonModelMixin): + user = models.ForeignKey('users.User', on_delete=models.CASCADE) + asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE) + + class Meta: + unique_together = ('user', 'asset') + + @classmethod + def get_user_favorite_assets_id(cls, user): + return cls.objects.filter(user=user).values_list('asset', flat=True) diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 71e1d4a24..dd1f706f2 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -324,6 +324,8 @@ class SomeNodesMixin: ungrouped_value = _('ungrouped') empty_key = '-11' empty_value = _("empty") + favorite_key = '-12' + favorite_value = _("favorite") def is_default_node(self): return self.key == self.default_key @@ -363,7 +365,7 @@ class SomeNodesMixin: @classmethod def ungrouped_node(cls): with tmp_to_org(Organization.system()): - defaults = {'value': cls.ungrouped_key} + defaults = {'value': cls.ungrouped_value} obj, created = cls.objects.get_or_create( defaults=defaults, key=cls.ungrouped_key ) @@ -387,11 +389,21 @@ class SomeNodesMixin: ) return obj + @classmethod + def favorite_node(cls): + with tmp_to_org(Organization.system()): + defaults = {'value': cls.favorite_value} + obj, created = cls.objects.get_or_create( + defaults=defaults, key=cls.favorite_key + ) + return obj + @classmethod def initial_some_nodes(cls): cls.default_node() cls.empty_node() cls.ungrouped_node() + cls.favorite_node() class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin): @@ -412,11 +424,11 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin def __str__(self): return self.value - def __eq__(self, other): - if not other: - return False - return self.id == other.id - + # def __eq__(self, other): + # if not other: + # return False + # return self.id == other.id + # def __gt__(self, other): self_key = [int(k) for k in self.key.split(':')] other_key = [int(k) for k in other.key.split(':')] diff --git a/apps/assets/serializers/__init__.py b/apps/assets/serializers/__init__.py index 9c86ef407..2c3e9fbd4 100644 --- a/apps/assets/serializers/__init__.py +++ b/apps/assets/serializers/__init__.py @@ -10,3 +10,4 @@ from .domain import * from .cmd_filter import * from .asset_user import * from .gathered_user import * +from .favorite_asset import * diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 918f1d9bf..63aac8cc0 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -45,7 +45,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer): 管理用户更新关联到的集群 """ nodes = serializers.PrimaryKeyRelatedField( - many=True, queryset=Node.objects.all() + many=True, queryset=Node.objects ) class Meta: diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index a93d2b2c3..18b5ea982 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -79,7 +79,7 @@ class AssetUserAuthInfoSerializer(serializers.ModelSerializer): class AssetUserPushSerializer(serializers.Serializer): - asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset")) + asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset")) username = serializers.CharField(max_length=1024) def create(self, validated_data): diff --git a/apps/assets/serializers/favorite_asset.py b/apps/assets/serializers/favorite_asset.py new file mode 100644 index 000000000..8429d959e --- /dev/null +++ b/apps/assets/serializers/favorite_asset.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# + +from rest_framework import serializers + +from orgs.utils import tmp_to_root_org +from common.serializers import AdaptedBulkListSerializer +from common.mixins import BulkSerializerMixin +from ..models import FavoriteAsset + + +__all__ = ['FavoriteAssetSerializer'] + + +class FavoriteAssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): + user = serializers.HiddenField( + default=serializers.CurrentUserDefault() + ) + + class Meta: + list_serializer_class = AdaptedBulkListSerializer + model = FavoriteAsset + fields = ['user', 'asset'] diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index 10a7a52d1..79e07df13 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -38,8 +38,10 @@ class NodeSerializer(BulkOrgResourceModelSerializer): return data -class NodeAssetsSerializer(serializers.ModelSerializer): - assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) +class NodeAssetsSerializer(BulkOrgResourceModelSerializer): + assets = serializers.PrimaryKeyRelatedField( + many=True, queryset=Asset.objects + ) class Meta: model = Node diff --git a/apps/assets/templates/assets/asset_bulk_update.html b/apps/assets/templates/assets/asset_bulk_update.html index 2dd0d8660..08df48e91 100644 --- a/apps/assets/templates/assets/asset_bulk_update.html +++ b/apps/assets/templates/assets/asset_bulk_update.html @@ -25,12 +25,20 @@ +{% include 'assets/_asset_list_modal.html' %} {% endblock %} {% block custom_foot_js %} {% endblock %} diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 46ea16483..35651429b 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -22,6 +22,7 @@ router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter') router.register(r'asset-users', api.AssetUserViewSet, 'asset-user') router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info') router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') +router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') diff --git a/apps/audits/api.py b/apps/audits/api.py index 626749b29..4d7165b4b 100644 --- a/apps/audits/api.py +++ b/apps/audits/api.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- # -from rest_framework import viewsets - from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor +from orgs.mixins.api import OrgModelViewSet from .models import FTPLog from .serializers import FTPLogSerializer -class FTPLogViewSet(viewsets.ModelViewSet): - queryset = FTPLog.objects.all() +class FTPLogViewSet(OrgModelViewSet): + model = FTPLog serializer_class = FTPLogSerializer permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) + diff --git a/apps/common/api.py b/apps/common/api.py index 4c5d28254..d69540cfd 100644 --- a/apps/common/api.py +++ b/apps/common/api.py @@ -83,8 +83,6 @@ class LogTailApi(generics.RetrieveAPIView): return Response({"data": data, 'end': end, 'mark': new_mark}) - - class ResourcesIDCacheApi(APIView): def post(self, request, *args, **kwargs): spm = str(uuid.uuid4()) diff --git a/apps/common/mixins/models.py b/apps/common/mixins/models.py index d4af23896..df3d899e0 100644 --- a/apps/common/mixins/models.py +++ b/apps/common/mixins/models.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- # - +import uuid from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"] +__all__ = [ + "NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet", + "CommonModelMixin" +] class NoDeleteQuerySet(models.query.QuerySet): @@ -40,3 +43,13 @@ class NoDeleteModelMixin(models.Model): self.is_discard = True self.discard_time = timezone.now() return self.save() + + +class CommonModelMixin(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) + date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) + date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated')) + + class Meta: + abstract = True diff --git a/apps/common/signals_handlers.py b/apps/common/signals_handlers.py index e7dd728c3..1fbfd536f 100644 --- a/apps/common/signals_handlers.py +++ b/apps/common/signals_handlers.py @@ -36,7 +36,7 @@ def on_request_finished_logging_db_query(sender, **kwargs): queries = connection.queries counters = defaultdict(Counter) for query in queries: - if not query['sql'].startswith('SELECT'): + if not query['sql'] or not query['sql'].startswith('SELECT'): continue tables = pattern.findall(query['sql']) table_name = ''.join(tables) diff --git a/apps/common/tree.py b/apps/common/tree.py index e73b43aa6..a9da1482f 100644 --- a/apps/common/tree.py +++ b/apps/common/tree.py @@ -51,6 +51,8 @@ class TreeNode: result = True elif self.pId != other.pId: result = self.pId > other.pId + elif self.id.startswith('-') and not other.id.startswith('-'): + result = False else: result = self.name > other.name return result diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a4ec9786d1f2a1492a02929530dce638f9f2df02..62707d10805f7b734067ee4bc83b8bed3d839c0e 100644 GIT binary patch delta 17908 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 79c7d4faa..f282f0c22 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-15 17:37+0800\n" +"POT-Creation-Date: 2019-10-17 16:09+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -193,11 +193,11 @@ msgstr "参数" #: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:16 -#: perms/models/base.py:54 +#: common/mixins/models.py:50 ops/templates/ops/adhoc_detail.html:86 +#: orgs/models.py:16 perms/models/base.py:54 #: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/remote_app_permission_detail.html:90 -#: users/models/user.py:414 users/serializers/v1.py:141 +#: users/models/user.py:414 users/serializers/v1.py:143 #: users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 @@ -216,7 +216,8 @@ msgstr "创建者" #: assets/models/label.py:25 assets/templates/assets/admin_user_detail.html:64 #: assets/templates/assets/cmd_filter_detail.html:69 #: assets/templates/assets/domain_detail.html:68 -#: assets/templates/assets/system_user_detail.html:96 ops/models/adhoc.py:45 +#: assets/templates/assets/system_user_detail.html:96 +#: common/mixins/models.py:51 ops/models/adhoc.py:45 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 #: orgs/models.py:17 perms/models/base.py:55 #: perms/templates/perms/asset_permission_detail.html:94 @@ -321,7 +322,6 @@ msgstr "远程应用" #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create_update.html:53 #: xpack/plugins/gathered_user/templates/gathered_user/task_create_update.html:44 #: xpack/plugins/interface/templates/interface/interface.html:72 -#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 #: xpack/plugins/vault/templates/vault/vault_create.html:45 msgid "Reset" msgstr "重置" @@ -524,7 +524,7 @@ msgstr "创建远程应用" #: settings/templates/settings/terminal_setting.html:107 #: terminal/templates/terminal/session_list.html:36 #: terminal/templates/terminal/terminal_list.html:36 -#: users/templates/users/_granted_assets.html:29 +#: users/templates/users/_granted_assets.html:34 #: users/templates/users/user_group_list.html:38 #: users/templates/users/user_list.html:41 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:60 @@ -539,7 +539,6 @@ msgid "Action" msgstr "动作" #: applications/templates/applications/user_remote_app_list.html:52 -#: assets/templates/assets/user_asset_list.html:32 #: perms/models/asset_permission.py:32 msgid "Connect" msgstr "连接" @@ -566,11 +565,11 @@ msgstr "远程应用详情" msgid "My RemoteApp" msgstr "我的远程应用" -#: assets/api/node.py:58 +#: assets/api/node.py:61 msgid "You can't update the root node name" msgstr "不能修改根节点名称" -#: assets/api/node.py:65 +#: assets/api/node.py:68 msgid "Deletion failed and the node contains children or assets" msgstr "删除失败,节点包含子节点或资产" @@ -628,13 +627,13 @@ msgstr "标签" #: assets/forms/asset.py:65 assets/forms/asset.py:112 #: assets/models/asset.py:144 assets/models/domain.py:26 #: assets/models/domain.py:52 assets/templates/assets/asset_detail.html:78 -#: assets/templates/assets/user_asset_list.html:53 +#: assets/templates/assets/user_asset_list.html:80 #: xpack/plugins/orgs/templates/orgs/org_list.html:18 msgid "Domain" msgstr "网域" #: assets/forms/asset.py:69 assets/forms/asset.py:103 assets/forms/asset.py:116 -#: assets/forms/asset.py:152 assets/models/node.py:409 +#: assets/forms/asset.py:152 assets/models/node.py:421 #: assets/templates/assets/asset_create.html:42 #: perms/forms/asset_permission.py:83 perms/forms/asset_permission.py:90 #: perms/templates/perms/asset_permission_list.html:53 @@ -702,7 +701,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/remote_app_permission_user.html:54 -#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:14 +#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13 #: users/models/user.py:371 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:36 @@ -729,7 +728,7 @@ msgstr "密码或密钥密码" #: authentication/forms.py:15 #: authentication/templates/authentication/login.html:68 #: authentication/templates/authentication/new_login.html:95 -#: settings/forms.py:114 users/forms.py:16 users/forms.py:28 +#: settings/forms.py:114 users/forms.py:15 users/forms.py:27 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_update.html:44 @@ -790,10 +789,10 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/templates/assets/asset_detail.html:62 #: assets/templates/assets/asset_list.html:97 #: assets/templates/assets/domain_gateway_list.html:68 -#: assets/templates/assets/user_asset_list.html:49 +#: assets/templates/assets/user_asset_list.html:76 #: audits/templates/audits/login_log_list.html:60 #: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:144 -#: users/templates/users/_granted_assets.html:26 +#: users/templates/users/_granted_assets.html:31 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:73 msgid "IP" @@ -807,10 +806,10 @@ msgstr "IP" #: assets/templates/assets/_asset_user_list.html:19 #: assets/templates/assets/asset_detail.html:58 #: assets/templates/assets/asset_list.html:96 -#: assets/templates/assets/user_asset_list.html:48 +#: assets/templates/assets/user_asset_list.html:75 #: perms/templates/perms/asset_permission_asset.html:57 #: perms/templates/perms/asset_permission_list.html:73 settings/forms.py:143 -#: users/templates/users/_granted_assets.html:25 +#: users/templates/users/_granted_assets.html:30 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:53 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:72 msgid "Hostname" @@ -822,18 +821,19 @@ msgstr "主机名" #: assets/templates/assets/system_user_detail.html:70 #: assets/templates/assets/system_user_list.html:53 #: terminal/templates/terminal/session_list.html:31 +#: terminal/templates/terminal/session_list.html:75 msgid "Protocol" msgstr "协议" #: assets/models/asset.py:142 assets/serializers/asset.py:68 #: assets/templates/assets/asset_create.html:24 -#: assets/templates/assets/user_asset_list.html:50 +#: assets/templates/assets/user_asset_list.html:77 #: perms/serializers/user_permission.py:48 msgid "Protocols" msgstr "协议组" #: assets/models/asset.py:143 assets/templates/assets/asset_detail.html:102 -#: assets/templates/assets/user_asset_list.html:51 +#: assets/templates/assets/user_asset_list.html:78 msgid "Platform" msgstr "系统平台" @@ -940,7 +940,8 @@ msgid "SSH public key" msgstr "ssh公钥" #: assets/models/base.py:35 assets/models/gathered_user.py:21 -#: assets/templates/assets/cmd_filter_detail.html:73 ops/models/adhoc.py:46 +#: assets/templates/assets/cmd_filter_detail.html:73 common/mixins/models.py:52 +#: ops/models/adhoc.py:46 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:109 #: xpack/plugins/gathered_user/templates/gathered_user/gathered_user_list.html:76 msgid "Date updated" @@ -1107,9 +1108,9 @@ msgstr "默认资产组" #: terminal/models.py:156 terminal/templates/terminal/command_list.html:29 #: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/session_list.html:27 -#: terminal/templates/terminal/session_list.html:71 users/forms.py:312 +#: terminal/templates/terminal/session_list.html:71 users/forms.py:319 #: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500 -#: users/serializers/v1.py:130 users/templates/users/user_group_detail.html:78 +#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:36 users/views/user.py:250 #: xpack/plugins/orgs/forms.py:28 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 @@ -1117,7 +1118,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:19 assets/models/node.py:400 +#: assets/models/label.py:19 assets/models/node.py:412 #: assets/templates/assets/label_list.html:15 settings/models.py:30 msgid "Value" msgstr "值" @@ -1138,7 +1139,11 @@ msgstr "未分组" msgid "empty" msgstr "空" -#: assets/models/node.py:399 +#: assets/models/node.py:328 +msgid "favorite" +msgstr "收藏夹" + +#: assets/models/node.py:411 msgid "Key" msgstr "键" @@ -1192,7 +1197,7 @@ msgstr "Shell" msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:52 +#: assets/models/user.py:162 assets/templates/assets/user_asset_list.html:79 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:52 #: audits/templates/audits/ftp_log_list.html:75 #: perms/forms/asset_permission.py:86 perms/forms/remote_app_permission.py:43 @@ -1208,7 +1213,7 @@ msgstr "登录模式" #: terminal/templates/terminal/command_list.html:67 #: terminal/templates/terminal/session_list.html:29 #: terminal/templates/terminal/session_list.html:73 -#: users/templates/users/_granted_assets.html:27 +#: users/templates/users/_granted_assets.html:32 #: xpack/plugins/orgs/templates/orgs/org_list.html:20 msgid "System user" msgstr "系统用户" @@ -1258,7 +1263,7 @@ msgstr "组织名称" msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:67 users/forms.py:263 +#: assets/serializers/asset_user.py:67 users/forms.py:262 #: users/models/user.py:403 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 @@ -1872,6 +1877,7 @@ msgstr "删除选择资产" #: users/templates/users/user_group_list.html:118 #: users/templates/users/user_list.html:254 #: xpack/plugins/interface/templates/interface/interface.html:101 +#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 msgid "Cancel" msgstr "取消" @@ -2257,7 +2263,7 @@ msgstr "Agent" #: audits/models.py:85 audits/templates/audits/login_log_list.html:62 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms.py:175 users/models/user.py:395 +#: users/forms.py:174 users/models/user.py:395 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2480,7 +2486,7 @@ msgid "" "after {} minutes)" msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" -#: authentication/forms.py:66 users/forms.py:22 +#: authentication/forms.py:66 users/forms.py:21 msgid "MFA code" msgstr "MFA 验证码" @@ -2505,7 +2511,7 @@ msgid "Secret" msgstr "密文" #: authentication/templates/authentication/_access_key_modal.html:48 -#: users/templates/users/_granted_assets.html:75 +#: users/templates/users/_granted_assets.html:80 msgid "Show" msgstr "显示" @@ -2713,11 +2719,11 @@ msgstr "" msgid "Encrypt field using Secret Key" msgstr "" -#: common/mixins/models.py:31 +#: common/mixins/models.py:34 msgid "is discard" msgstr "" -#: common/mixins/models.py:32 +#: common/mixins/models.py:35 msgid "discard time" msgstr "" @@ -3105,7 +3111,7 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orgs/mixins/models.py:61 orgs/mixins/serializers.py:26 orgs/models.py:31 +#: orgs/mixins/models.py:58 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3122,7 +3128,7 @@ msgstr "空" #: perms/templates/perms/asset_permission_list.html:71 #: perms/templates/perms/asset_permission_list.html:118 #: perms/templates/perms/remote_app_permission_list.html:16 -#: templates/_nav.html:21 users/forms.py:286 users/models/group.py:26 +#: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26 #: users/models/user.py:379 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:218 #: users/templates/users/user_list.html:38 @@ -3969,7 +3975,7 @@ msgid "Commercial support" msgstr "商业支持" #: templates/_header_bar.html:70 templates/_nav.html:30 -#: templates/_nav_user.html:32 users/forms.py:154 +#: templates/_nav_user.html:32 users/forms.py:153 #: users/templates/users/_user.html:43 #: users/templates/users/first_login.html:39 #: users/templates/users/user_password_update.html:40 @@ -4390,7 +4396,7 @@ msgstr "线程数" msgid "Boot Time" msgstr "运行时间" -#: terminal/models.py:162 terminal/templates/terminal/session_list.html:136 +#: terminal/models.py:162 terminal/templates/terminal/session_list.html:137 msgid "Replay" msgstr "回放" @@ -4456,15 +4462,15 @@ msgstr "终断所选" msgid "Confirm finished" msgstr "确认已完成" -#: terminal/templates/terminal/session_list.html:91 +#: terminal/templates/terminal/session_list.html:92 msgid "Terminate task send, waiting ..." msgstr "终断任务已发送,请等待" -#: terminal/templates/terminal/session_list.html:142 +#: terminal/templates/terminal/session_list.html:143 msgid "Terminate" msgstr "终断" -#: terminal/templates/terminal/session_list.html:173 +#: terminal/templates/terminal/session_list.html:174 msgid "Finish session success" msgstr "标记会话完成成功" @@ -4534,7 +4540,7 @@ msgstr "你可以使用ssh客户端工具连接终端" msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:33 users/models/user.py:383 +#: users/forms.py:32 users/models/user.py:383 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:37 @@ -4542,44 +4548,44 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置" msgid "Role" msgstr "角色" -#: users/forms.py:36 users/forms.py:233 +#: users/forms.py:35 users/forms.py:232 #: users/templates/users/user_update.html:30 msgid "ssh public key" msgstr "ssh公钥" -#: users/forms.py:37 users/forms.py:234 +#: users/forms.py:36 users/forms.py:233 msgid "ssh-rsa AAAA..." msgstr "" -#: users/forms.py:38 +#: users/forms.py:37 msgid "Paste user id_rsa.pub here." msgstr "复制用户公钥到这里" -#: users/forms.py:52 users/templates/users/user_detail.html:226 +#: users/forms.py:51 users/templates/users/user_detail.html:226 msgid "Join user groups" msgstr "添加到用户组" -#: users/forms.py:87 users/forms.py:248 +#: users/forms.py:86 users/forms.py:247 msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:91 users/forms.py:252 users/serializers/v1.py:116 +#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" -#: users/forms.py:104 users/views/login.py:114 users/views/user.py:287 +#: users/forms.py:103 users/views/login.py:114 users/views/user.py:287 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/forms.py:125 +#: users/forms.py:124 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/forms.py:126 +#: users/forms.py:125 msgid "Set password" msgstr "设置密码" -#: users/forms.py:133 xpack/plugins/change_auth_plan/models.py:88 +#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57 @@ -4587,7 +4593,7 @@ msgstr "设置密码" msgid "Password strategy" msgstr "密码策略" -#: users/forms.py:160 +#: users/forms.py:159 msgid "" "When enabled, you will enter the MFA binding process the next time you log " "in. you can also directly bind in \"personal information -> quick " @@ -4596,11 +4602,11 @@ msgstr "" "启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修改->更" "改MFA设置)中直接绑定!" -#: users/forms.py:170 +#: users/forms.py:169 msgid "* Enable MFA authentication to make the account more secure." msgstr "* 启用MFA认证,使账号更加安全。" -#: users/forms.py:180 +#: users/forms.py:179 msgid "" "In order to protect you and your company, please keep your account, password " "and key sensitive information properly. (for example: setting complex " @@ -4609,41 +4615,41 @@ msgstr "" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "设置复杂密码,启用MFA认证)" -#: users/forms.py:187 users/templates/users/first_login.html:48 +#: users/forms.py:186 users/templates/users/first_login.html:48 #: users/templates/users/first_login.html:110 #: users/templates/users/first_login.html:139 msgid "Finish" msgstr "完成" -#: users/forms.py:193 +#: users/forms.py:192 msgid "Old password" msgstr "原来密码" -#: users/forms.py:198 +#: users/forms.py:197 msgid "New password" msgstr "新密码" -#: users/forms.py:203 +#: users/forms.py:202 msgid "Confirm password" msgstr "确认密码" -#: users/forms.py:213 +#: users/forms.py:212 msgid "Old password error" msgstr "原来密码错误" -#: users/forms.py:221 +#: users/forms.py:220 msgid "Password does not match" msgstr "密码不一致" -#: users/forms.py:231 +#: users/forms.py:230 msgid "Automatically configure and download the SSH key" msgstr "自动配置并下载SSH密钥" -#: users/forms.py:235 +#: users/forms.py:234 msgid "Paste your id_rsa.pub here." msgstr "复制你的公钥到这里" -#: users/forms.py:269 users/forms.py:274 users/forms.py:316 +#: users/forms.py:268 users/forms.py:273 users/forms.py:323 #: xpack/plugins/orgs/forms.py:18 msgid "Select users" msgstr "选择用户" @@ -4736,7 +4742,7 @@ msgstr "角色只能为 {}" msgid "Password does not match security rules" msgstr "密码不满足安全规则" -#: users/serializers/v1.py:147 +#: users/serializers/v1.py:157 msgid "Auditors cannot be join in the user group" msgstr "审计员不能被加入到用户组" @@ -5933,7 +5939,7 @@ msgstr "更新同步实例任务" #: xpack/plugins/gathered_user/views.py:21 #: xpack/plugins/gathered_user/views.py:34 #: xpack/plugins/gathered_user/views.py:49 -#: xpack/plugins/gathered_user/views.py:66 +#: xpack/plugins/gathered_user/views.py:69 msgid "Gathered user" msgstr "收集用户" @@ -5968,7 +5974,7 @@ msgstr "创建任务" msgid "Gathered user list" msgstr "收集用户列表" -#: xpack/plugins/gathered_user/views.py:67 +#: xpack/plugins/gathered_user/views.py:70 msgid "Update task" msgstr "更新任务" diff --git a/apps/orgs/mixins/api.py b/apps/orgs/mixins/api.py index 9de7f2c7d..8fb7f30dd 100644 --- a/apps/orgs/mixins/api.py +++ b/apps/orgs/mixins/api.py @@ -5,12 +5,12 @@ from rest_framework.viewsets import ModelViewSet from rest_framework_bulk import BulkModelViewSet from common.mixins import CommonApiMixin -from ..utils import set_to_root_org +from ..utils import set_to_root_org, filter_org_queryset from ..models import Organization __all__ = [ 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', - 'OrgBulkModelViewSet', + 'OrgBulkModelViewSet', 'OrgQuerySetMixin', ] @@ -22,7 +22,15 @@ class RootOrgViewMixin: class OrgQuerySetMixin: def get_queryset(self): - queryset = super().get_queryset().all() + if hasattr(self, 'model'): + queryset = self.model.objects.all() + else: + assert self.queryset is None, ( + "'%s' should not include a `queryset` attribute" + % self.__class__.__name__ + ) + queryset = super().get_queryset() + if hasattr(self, 'swagger_fake_view'): return queryset[:1] if hasattr(self, 'action') and self.action == 'list' and \ diff --git a/apps/orgs/mixins/generics.py b/apps/orgs/mixins/generics.py new file mode 100644 index 000000000..490ee1b87 --- /dev/null +++ b/apps/orgs/mixins/generics.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import generics + +from .api import OrgQuerySetMixin + + +class ListAPIView(OrgQuerySetMixin, generics.ListAPIView): + pass + + +class RetrieveAPIView(OrgQuerySetMixin, generics.RetrieveAPIView): + pass + + +class CreateAPIView(OrgQuerySetMixin, generics.CreateAPIView): + pass + + +class DestroyAPIView(OrgQuerySetMixin, generics.DestroyAPIView): + pass + + +class ListCreateAPIView(OrgQuerySetMixin, generics.ListCreateAPIView): + pass + + +class UpdateAPIView(OrgQuerySetMixin, generics.UpdateAPIView): + pass + + +class RetrieveUpdateAPIView(OrgQuerySetMixin, generics.RetrieveUpdateAPIView): + pass + + +class RetrieveDestroyAPIView(OrgQuerySetMixin, generics.RetrieveDestroyAPIView): + pass + + +class RetrieveUpdateDestroyAPIView(OrgQuerySetMixin, generics.RetrieveUpdateDestroyAPIView): + pass diff --git a/apps/orgs/mixins/models.py b/apps/orgs/mixins/models.py index 672b975dc..4ffa24c2a 100644 --- a/apps/orgs/mixins/models.py +++ b/apps/orgs/mixins/models.py @@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError from common.utils import get_logger from ..utils import ( set_current_org, get_current_org, current_org, + get_org_filters ) from ..models import Organization @@ -19,42 +20,38 @@ __all__ = [ ] +class OrgQuerySet(models.QuerySet): + pass + + class OrgManager(models.Manager): - def get_queryset(self): - queryset = super(OrgManager, self).get_queryset() - kwargs = {} - - _current_org = get_current_org() - if _current_org is None: - kwargs['id'] = None - elif _current_org.is_real(): - kwargs['org_id'] = _current_org.id - elif _current_org.is_default(): - queryset = queryset.filter(org_id="") - # - # lines = traceback.format_stack() - # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") - # for line in lines[-10:-1]: - # print(line) - # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") - - queryset = queryset.filter(**kwargs) + queryset = super().get_queryset() + kwargs = get_org_filters() + if kwargs: + return queryset.filter(**kwargs) return queryset - def all(self): - if not current_org: - msg = 'You can `objects.set_current_org(org).all()` then run it' - return self - else: - return super(OrgManager, self).all() - def set_current_org(self, org): if isinstance(org, str): org = Organization.get_instance(org) set_current_org(org) return self + def all(self): + # print("Call all: {}".format(current_org)) + # + # lines = traceback.format_stack() + # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + # for line in lines[-10:-1]: + # print(line) + # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + if not current_org: + msg = 'You can `objects.set_current_org(org).all()` then run it' + return self + else: + return super().all() + class OrgModelMixin(models.Model): org_id = models.CharField(max_length=36, blank=True, default='', @@ -65,9 +62,12 @@ class OrgModelMixin(models.Model): def save(self, *args, **kwargs): org = get_current_org() - if org is not None and (org.is_real() or org.is_system()): + if org is None: + return super().save(*args, **kwargs) + + if org.is_real() or org.is_system(): self.org_id = org.id - elif org is not None and org.is_default(): + elif org.is_default(): self.org_id = '' return super().save(*args, **kwargs) diff --git a/apps/orgs/utils.py b/apps/orgs/utils.py index 8fca26e35..2a7cfca6b 100644 --- a/apps/orgs/utils.py +++ b/apps/orgs/utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +import traceback from werkzeug.local import LocalProxy from contextlib import contextmanager @@ -82,4 +83,31 @@ def tmp_to_org(org): set_current_org(ori_org) +def get_org_filters(): + kwargs = {} + + _current_org = get_current_org() + if _current_org is None: + return kwargs + + if _current_org.is_real(): + kwargs['org_id'] = _current_org.id + elif _current_org.is_default(): + kwargs["org_id"] = '' + return kwargs + + +def filter_org_queryset(queryset): + kwargs = get_org_filters() + + # + # lines = traceback.format_stack() + # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + # for line in lines[-10:-1]: + # print(line) + # print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + queryset = queryset.filter(**kwargs) + return queryset + + current_org = LocalProxy(get_current_org) diff --git a/apps/perms/api/asset_permission.py b/apps/perms/api/asset_permission.py index 41c64c7f1..68f01d2e3 100644 --- a/apps/perms/api/asset_permission.py +++ b/apps/perms/api/asset_permission.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- # -from django.utils import timezone from django.db.models import Q from rest_framework.views import Response from django.shortcuts import get_object_or_404 -from rest_framework.generics import RetrieveUpdateAPIView, ListAPIView -from rest_framework import viewsets from common.permissions import IsOrgAdmin +from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from common.utils import get_object_or_none from ..models import AssetPermission from ..hands import ( @@ -24,15 +23,21 @@ __all__ = [ ] -class AssetPermissionViewSet(viewsets.ModelViewSet): +class AssetPermissionViewSet(OrgModelViewSet): """ 资产授权列表的增删改查api """ - queryset = AssetPermission.objects.all() + model = AssetPermission serializer_class = serializers.AssetPermissionCreateUpdateSerializer filter_fields = ['name'] permission_classes = (IsOrgAdmin,) + def get_queryset(self): + queryset = super().get_queryset().prefetch_related( + "nodes", "assets", "users", "user_groups", "system_users" + ) + return queryset + def get_serializer_class(self): if self.action in ("list", 'retrieve') and \ self.request.query_params.get("display"): @@ -160,19 +165,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): queryset = queryset.distinct() return queryset - def get_queryset(self): - return self.queryset.all().prefetch_related( - "nodes", "assets", "users", "user_groups", "system_users" - ) - -class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView): +class AssetPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): """ 将用户从授权中移除,Detail页面会调用 """ + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -187,10 +187,10 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAddUserApi(RetrieveUpdateAPIView): +class AssetPermissionAddUserApi(generics.RetrieveUpdateAPIView): + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateUserSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -205,13 +205,13 @@ class AssetPermissionAddUserApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): +class AssetPermissionRemoveAssetApi(generics.RetrieveUpdateAPIView): """ 将用户从授权中移除,Detail页面会调用 """ + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -226,10 +226,10 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): +class AssetPermissionAddAssetApi(generics.RetrieveUpdateAPIView): + model = AssetPermission permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionUpdateAssetSerializer - queryset = AssetPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -244,7 +244,7 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): return Response({"error": serializer.errors}) -class AssetPermissionAssetsApi(ListAPIView): +class AssetPermissionAssetsApi(generics.ListAPIView): permission_classes = (IsOrgAdmin,) serializer_class = serializers.AssetPermissionAssetsSerializer filter_fields = ("hostname", "ip") diff --git a/apps/perms/api/mixin.py b/apps/perms/api/mixin.py index 85db529a0..86e1a8c0a 100644 --- a/apps/perms/api/mixin.py +++ b/apps/perms/api/mixin.py @@ -4,7 +4,7 @@ from rest_framework.generics import get_object_or_404 from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.utils import get_logger -from orgs.utils import set_to_root_org +from orgs.utils import set_to_root_org, get_current_org, set_current_org, tmp_to_root_org from ..hands import User, UserGroup @@ -17,15 +17,24 @@ __all__ = [ class UserPermissionMixin: permission_classes = (IsOrgAdminOrAppUser,) + current_org = None obj = None def initial(self, *args, **kwargs): super().initial(*args, *kwargs) + self.current_org = get_current_org() + set_to_root_org() self.obj = self.get_obj() - def get(self, request, *args, **kwargs): - set_to_root_org() - return super().get(request, *args, **kwargs) + # def dispatch(self, request, *args, **kwargs): + # """不能这么做,校验权限时拿不到组织了""" + # with tmp_to_root_org(): + # return super().dispatch(request, *args, **kwargs) + + # def get(self, request, *args, **kwargs): + # """有的api重写了get方法""" + # with tmp_to_root_org(): + # return super().get(request, *args, **kwargs) def get_obj(self): user_id = self.kwargs.get('pk', '') @@ -40,6 +49,13 @@ class UserPermissionMixin: self.permission_classes = (IsValidUser,) return super().get_permissions() + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + org = getattr(self, 'current_org', None) + if org: + set_current_org(org) + return response + class UserGroupPermissionMixin: obj = None diff --git a/apps/perms/api/remote_app_permission.py b/apps/perms/api/remote_app_permission.py index 12b1ebfb6..6ced7f0ae 100644 --- a/apps/perms/api/remote_app_permission.py +++ b/apps/perms/api/remote_app_permission.py @@ -1,10 +1,11 @@ # coding: utf-8 # -from rest_framework import viewsets, generics from rest_framework.views import Response from common.permissions import IsOrgAdmin +from orgs.mixins.api import OrgModelViewSet +from orgs.mixins import generics from ..models import RemoteAppPermission from ..serializers import ( RemoteAppPermissionSerializer, @@ -20,18 +21,18 @@ __all__ = [ ] -class RemoteAppPermissionViewSet(viewsets.ModelViewSet): +class RemoteAppPermissionViewSet(OrgModelViewSet): + model = RemoteAppPermission filter_fields = ('name', ) search_fields = filter_fields - queryset = RemoteAppPermission.objects.all() serializer_class = RemoteAppPermissionSerializer permission_classes = (IsOrgAdmin,) class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateUserSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -46,9 +47,9 @@ class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateUserSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -63,9 +64,9 @@ class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() @@ -80,9 +81,9 @@ class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): + model = RemoteAppPermission permission_classes = (IsOrgAdmin,) serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer - queryset = RemoteAppPermission.objects.all() def update(self, request, *args, **kwargs): perm = self.get_object() diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 069f08937..4a004e0d1 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -34,7 +34,7 @@ class UserNodeTreeMixin: for node in nodes: assets_amount = self.tree.valid_assets_amount(node.key) - if assets_amount == 0 and node.key != Node.empty_key: + if assets_amount == 0 and not node.key.startswith('-'): continue node.assets_amount = assets_amount data = ParserNode.parse_node_to_tree_node(node) diff --git a/apps/perms/api/user_remote_app_permission.py b/apps/perms/api/user_remote_app_permission.py index 030e760b5..868c92015 100644 --- a/apps/perms/api/user_remote_app_permission.py +++ b/apps/perms/api/user_remote_app_permission.py @@ -3,12 +3,10 @@ import uuid from django.shortcuts import get_object_or_404 from rest_framework.views import APIView, Response -from rest_framework.generics import ( - ListAPIView, get_object_or_404, -) from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.tree import TreeNodeSerializer +from orgs.mixins import generics from ..utils import ( RemoteAppPermissionUtil, construct_remote_apps_tree_root, parse_remote_app_to_tree_node, @@ -25,7 +23,7 @@ __all__ = [ ] -class UserGrantedRemoteAppsApi(ListAPIView): +class UserGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = RemoteAppSerializer filter_fields = ['name', 'id'] @@ -68,7 +66,7 @@ class UserGrantedRemoteAppsAsTreeApi(UserGrantedRemoteAppsApi): return super().get_serializer(data, many=True) -class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, ListAPIView): +class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.RemoteAppSystemUserSerializer only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields @@ -110,7 +108,7 @@ class ValidateUserRemoteAppPermissionApi(APIView): # RemoteApp permission -class UserGroupGrantedRemoteAppsApi(ListAPIView): +class UserGroupGrantedRemoteAppsApi(generics.ListAPIView): permission_classes = (IsOrgAdminOrAppUser, ) serializer_class = RemoteAppSerializer diff --git a/apps/perms/hands.py b/apps/perms/hands.py index aef0f4875..ab9e3f494 100644 --- a/apps/perms/hands.py +++ b/apps/perms/hands.py @@ -2,10 +2,15 @@ # from users.models import User, UserGroup -from assets.models import Asset, SystemUser, Node, Label +from assets.models import Asset, SystemUser, Node, Label, FavoriteAsset from assets.serializers import NodeSerializer from applications.serializers import RemoteAppSerializer from applications.models import RemoteApp - +__all__ = [ + 'User', 'UserGroup', + 'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset', + 'NodeSerializer', 'RemoteAppSerializer', + 'RemoteApp' +] diff --git a/apps/perms/utils/asset_permission.py b/apps/perms/utils/asset_permission.py index bbc64bfa9..6754511d0 100644 --- a/apps/perms/utils/asset_permission.py +++ b/apps/perms/utils/asset_permission.py @@ -13,7 +13,7 @@ from common.utils import get_logger, timeit, lazyproperty from common.tree import TreeNode from assets.utils import TreeService from ..models import AssetPermission -from ..hands import Node, Asset, SystemUser +from ..hands import Node, Asset, SystemUser, User, FavoriteAsset logger = get_logger(__file__) @@ -293,6 +293,20 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): parent=user_tree.root, ) + def add_favorite_node_if_need(self, user_tree): + if not isinstance(self.object, User): + return + node_key = Node.favorite_key + node_value = Node.favorite_value + user_tree.create_node( + identifier=node_key, tag=node_value, + parent=user_tree.root, + ) + assets_id = FavoriteAsset.get_user_favorite_assets_id(self.object) + all_valid_assets = user_tree.all_valid_assets(user_tree.root) + valid_assets_id = set(assets_id) & all_valid_assets + user_tree.set_assets(node_key, valid_assets_id) + def set_user_tree_to_local(self, user_tree): self._user_tree = user_tree self._user_tree_filter_id = self._filter_id @@ -323,6 +337,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin): self.add_single_assets_node_to_user_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree) self.add_empty_node_if_need(user_tree) + self.add_favorite_node_if_need(user_tree) self.set_user_tree_to_cache_if_need(user_tree) self.set_user_tree_to_local(user_tree) return user_tree diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index d028743be..4f55cfb4f 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -267,7 +267,7 @@ function requestApi(props) { $.ajax({ url: props.url, type: props.method || "PATCH", - data: props.body, + data: props.body || props.data, contentType: props.content_type || "application/json; charset=utf-8", dataType: props.data_type || "json" }).done(function (data, textStatue, jqXHR) { @@ -579,6 +579,9 @@ jumpserver.initServerSideDataTable = function (options) { ajax: { url: options.ajax_url, error: function (jqXHR, textStatus, errorThrown) { + if (jqXHR.responseText && jqXHR.responseText.indexOf("%(value)s") !== -1 ) { + return + } var msg = gettext("Unknown error occur"); if (jqXHR.responseJSON) { if (jqXHR.responseJSON.error) { @@ -953,8 +956,13 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul function rootNodeAddDom(ztree, callback) { var refreshIcon = ""; var rootNode = ztree.getNodes()[0]; - var $rootNodeRef = $("#" + rootNode.tId + "_a"); - $rootNodeRef.after(refreshIcon); + if (rootNode) { + var $rootNodeRef = $("#" + rootNode.tId + "_a"); + $rootNodeRef.after(refreshIcon); + } else { + $rootNodeRef = $('#' + ztree.setting.treeId); + $rootNodeRef.html(refreshIcon); + } var refreshIconRef = $('#tree-refresh'); refreshIconRef.bind('click', function () { ztree.destroy(); diff --git a/apps/terminal/api/session.py b/apps/terminal/api/session.py index 7071ddd33..264502959 100644 --- a/apps/terminal/api/session.py +++ b/apps/terminal/api/session.py @@ -24,10 +24,10 @@ logger = get_logger(__name__) class SessionViewSet(OrgBulkModelViewSet): - queryset = Session.objects.all() + model = Session serializer_class = serializers.SessionSerializer permission_classes = (IsOrgAdminOrAppUser, ) - filter_fields = [ + filterset_fields = [ "user", "asset", "system_user", "remote_addr", "protocol", "terminal", "is_finished", ] diff --git a/apps/terminal/templates/terminal/session_list.html b/apps/terminal/templates/terminal/session_list.html index d23d9be02..1393bd241 100644 --- a/apps/terminal/templates/terminal/session_list.html +++ b/apps/terminal/templates/terminal/session_list.html @@ -72,6 +72,7 @@
  • {% trans 'Asset' %}
  • {% trans 'System user' %}
  • {% trans 'Remote addr' %}
  • +
  • {% trans 'Protocol' %}
  • {#
  • {% trans 'Protocol' %}
  • #} {% endblock %} diff --git a/apps/users/api/group.py b/apps/users/api/group.py index e5c68004b..eb00ea220 100644 --- a/apps/users/api/group.py +++ b/apps/users/api/group.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # -from rest_framework import generics - from ..serializers import ( UserGroupSerializer, UserGroupListSerializer, @@ -10,6 +8,7 @@ from ..serializers import ( ) from ..models import UserGroup from orgs.mixins.api import OrgBulkModelViewSet +from orgs.mixins import generics from common.permissions import IsOrgAdmin @@ -17,9 +16,9 @@ __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi'] class UserGroupViewSet(OrgBulkModelViewSet): + model = UserGroup filter_fields = ("name",) search_fields = filter_fields - queryset = UserGroup.objects.all() serializer_class = UserGroupSerializer permission_classes = (IsOrgAdmin,) @@ -31,6 +30,6 @@ class UserGroupViewSet(OrgBulkModelViewSet): class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): - queryset = UserGroup.objects.all() + model = UserGroup serializer_class = UserGroupUpdateMemberSerializer permission_classes = (IsOrgAdmin,) diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 77406c896..2a033915c 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -17,7 +17,7 @@ from common.permissions import ( from common.mixins import CommonApiMixin from common.utils import get_logger from orgs.utils import current_org -from .. import serializers +from .. import serializers, utils from ..models import User from ..signals import post_user_create @@ -30,13 +30,21 @@ __all__ = [ ] -class UserViewSet(CommonApiMixin, BulkModelViewSet): +class UserQuerysetMixin: + def get_queryset(self): + queryset = utils.get_current_org_members() + return queryset + + +class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet): filter_fields = ('username', 'email', 'name', 'id') search_fields = filter_fields - queryset = User.objects.exclude(role=User.ROLE_APP) serializer_class = serializers.UserSerializer permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) + def get_queryset(self): + return super().get_queryset().prefetch_related('groups') + def send_created_signal(self, users): if not isinstance(users, list): users = [users] @@ -51,11 +59,6 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet): current_org.users.add(*users) self.send_created_signal(users) - def get_queryset(self): - queryset = current_org.get_org_members()\ - .prefetch_related('groups') - return queryset - def get_permissions(self): if self.action in ["retrieve", "list"]: self.permission_classes = (IsOrgAdminOrAppUser,) @@ -79,9 +82,8 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet): return super().perform_bulk_update(serializer) -class UserChangePasswordApi(generics.RetrieveUpdateAPIView): +class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView): permission_classes = (IsOrgAdmin,) - queryset = User.objects.all() serializer_class = serializers.ChangeUserPasswordSerializer def perform_update(self, serializer): @@ -90,13 +92,12 @@ class UserChangePasswordApi(generics.RetrieveUpdateAPIView): user.save() -class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): - queryset = User.objects.all() +class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView): serializer_class = serializers.UserUpdateGroupSerializer permission_classes = (IsOrgAdmin,) -class UserResetPasswordApi(generics.UpdateAPIView): +class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView): queryset = User.objects.all() serializer_class = serializers.UserSerializer permission_classes = (IsAuthenticated,) @@ -111,8 +112,7 @@ class UserResetPasswordApi(generics.UpdateAPIView): send_reset_password_mail(user) -class UserResetPKApi(generics.UpdateAPIView): - queryset = User.objects.all() +class UserResetPKApi(UserQuerysetMixin, generics.UpdateAPIView): serializer_class = serializers.UserSerializer permission_classes = (IsAuthenticated,) @@ -125,8 +125,7 @@ class UserResetPKApi(generics.UpdateAPIView): # 废弃 -class UserUpdatePKApi(generics.UpdateAPIView): - queryset = User.objects.all() +class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView): serializer_class = serializers.UserPKUpdateSerializer permission_classes = (IsCurrentUserOrReadOnly,) @@ -136,8 +135,7 @@ class UserUpdatePKApi(generics.UpdateAPIView): user.save() -class UserUnblockPKApi(generics.UpdateAPIView): - queryset = User.objects.all() +class UserUnblockPKApi(UserQuerysetMixin, generics.UpdateAPIView): permission_classes = (IsOrgAdmin,) serializer_class = serializers.UserSerializer key_prefix_limit = "_LOGIN_LIMIT_{}_{}" @@ -165,8 +163,7 @@ class UserProfileApi(generics.RetrieveAPIView): return super().retrieve(request, *args, **kwargs) -class UserResetOTPApi(generics.RetrieveAPIView): - queryset = User.objects.all() +class UserResetOTPApi(UserQuerysetMixin, generics.RetrieveAPIView): permission_classes = (IsOrgAdmin,) serializer_class = serializers.ResetOTPSerializer diff --git a/apps/users/forms.py b/apps/users/forms.py index 3a8228b49..98d7c9e09 100644 --- a/apps/users/forms.py +++ b/apps/users/forms.py @@ -5,9 +5,8 @@ from django.utils.translation import gettext_lazy as _ from common.utils import validate_ssh_public_key from orgs.mixins.forms import OrgModelForm -from orgs.utils import current_org from .models import User, UserGroup -from .utils import check_password_rules +from .utils import check_password_rules, get_current_org_members class UserCheckPasswordForm(forms.Form): @@ -267,15 +266,23 @@ class UserBulkUpdateForm(OrgModelForm): users = forms.ModelMultipleChoiceField( required=True, label=_('Select users'), - queryset=User.objects.all(), + queryset=User.objects.none(), widget=forms.SelectMultiple( attrs={ - 'class': 'select2', + 'class': 'users-select2', 'data-placeholder': _('Select 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.queryset = get_current_org_members() + class Meta: model = User fields = ['users', 'groups', 'date_expired'] @@ -320,25 +327,19 @@ class UserGroupForm(OrgModelForm): ) def __init__(self, **kwargs): - instance = kwargs.get('instance') - if instance: - initial = kwargs.get('initial', {}) - initial.update({'users': instance.users.all()}) - kwargs['initial'] = initial super().__init__(**kwargs) - if 'initial' not in kwargs: - return + self.set_fields_queryset() + + def set_fields_queryset(self): users_field = self.fields.get('users') - if instance: - users_field.queryset = instance.users.all() + if self.instance: + users_field.initial = self.instance.users.all() + users_field.queryset = self.instance.users.all() else: users_field.queryset = User.objects.none() def save(self, commit=True): - group = super().save(commit=commit) - users = self.cleaned_data['users'] - group.users.set(users) - return group + raise Exception("Save by restful api") class Meta: model = UserGroup diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index 59890afea..847afe885 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # -import copy from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers @@ -12,6 +11,7 @@ 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__ = [ @@ -118,7 +118,9 @@ class UserPKUpdateSerializer(serializers.ModelSerializer): class UserUpdateGroupSerializer(serializers.ModelSerializer): - groups = serializers.PrimaryKeyRelatedField(many=True, queryset=UserGroup.objects.all()) + groups = serializers.PrimaryKeyRelatedField( + many=True, queryset=UserGroup.objects + ) class Meta: model = User @@ -127,7 +129,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): class UserGroupSerializer(BulkOrgResourceModelSerializer): users = serializers.PrimaryKeyRelatedField( - required=False, many=True, queryset=User.objects.all(), label=_('User') + required=False, many=True, queryset=User.objects, label=_('User') ) class Meta: @@ -141,6 +143,14 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer): '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: @@ -154,12 +164,20 @@ class UserGroupListSerializer(UserGroupSerializer): class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): - users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all()) + 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): diff --git a/apps/users/templates/users/_granted_assets.html b/apps/users/templates/users/_granted_assets.html index 8e6401a71..ce6bf3cd4 100644 --- a/apps/users/templates/users/_granted_assets.html +++ b/apps/users/templates/users/_granted_assets.html @@ -1,5 +1,5 @@ {% load i18n %} -
    +
    @@ -11,7 +11,12 @@
    -
    +
    +
    +
    + +
    +
    {#
    #} {# #} @@ -174,6 +179,27 @@ function loadLabels() { } } +var show = 0; + +function toggle() { + if (show === 0) { + $("#split-left").hide(500, function () { + $("#split-right").attr("class", "col-lg-12"); + $("#toggle-icon").attr("class", "fa fa-angle-right fa-x"); + show = 1; + }); + } else { + $("#split-right").attr("class", "col-lg-9"); + $("#toggle-icon").attr("class", "fa fa-angle-left fa-x"); + $("#split-left").show(500); + show = 0; + } + setTimeout(function () { + $(".table").css("width", "100%"); + {#assetTable.columns.adjust();#} + }, 500) +} + $(document).ready(function () { {#loadLabels()#} }).on('click', '.labels-menu li', function () { diff --git a/apps/users/templates/users/user_bulk_update.html b/apps/users/templates/users/user_bulk_update.html index d6862fd2d..28b0e8cb7 100644 --- a/apps/users/templates/users/user_bulk_update.html +++ b/apps/users/templates/users/user_bulk_update.html @@ -31,6 +31,7 @@