Merge pull request #3358 from jumpserver/dev

Dev
pull/3463/head
BaiJiangJie 2019-10-18 18:52:07 +08:00 committed by GitHub
commit 20f8c12576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 1089 additions and 546 deletions

View File

@ -1,10 +1,8 @@
# coding: utf-8 # coding: utf-8
# #
from rest_framework import generics
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..hands import IsOrgAdmin, IsAppUser from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
@ -16,14 +14,14 @@ __all__ = [
class RemoteAppViewSet(OrgBulkModelViewSet): class RemoteAppViewSet(OrgBulkModelViewSet):
model = RemoteApp
filter_fields = ('name',) filter_fields = ('name',)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer serializer_class = RemoteAppSerializer
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView): class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
queryset = RemoteApp.objects.all() model = RemoteApp
permission_classes = (IsAppUser, ) permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer serializer_class = RemoteAppConnectionInfoSerializer

View File

@ -7,3 +7,4 @@ from .domain import *
from .cmd_filter import * from .cmd_filter import *
from .asset_user import * from .asset_user import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import *

View File

@ -15,11 +15,10 @@
from django.db import transaction from django.db import transaction
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.mixins import CommonApiMixin
from common.utils import get_logger from common.utils import get_logger
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset from ..models import AdminUser, Asset
@ -39,22 +38,21 @@ class AdminUserViewSet(OrgBulkModelViewSet):
""" """
Admin user api set, for add,delete,update,list,retrieve resource Admin user api set, for add,delete,update,list,retrieve resource
""" """
model = AdminUser
filter_fields = ("name", "username") filter_fields = ("name", "username")
search_fields = filter_fields search_fields = filter_fields
queryset = AdminUser.objects.all()
serializer_class = serializers.AdminUserSerializer serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
class AdminUserAuthApi(generics.UpdateAPIView): class AdminUserAuthApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all() model = AdminUser
serializer_class = serializers.AdminUserAuthSerializer serializer_class = serializers.AdminUserAuthSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
class ReplaceNodesAdminUserApi(generics.UpdateAPIView): class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
queryset = AdminUser.objects.all() model = AdminUser
serializer_class = serializers.ReplaceNodeAdminUserSerializer serializer_class = serializers.ReplaceNodeAdminUserSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
@ -79,7 +77,7 @@ class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
""" """
Test asset admin user assets_connectivity Test asset admin user assets_connectivity
""" """
queryset = AdminUser.objects.all() model = AdminUser
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer serializer_class = serializers.TaskIDSerializer

View File

@ -3,14 +3,14 @@
import random import random
from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet 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 .. import serializers
from ..tasks import update_asset_hardware_info_manual, \ from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual test_asset_connectivity_manual
@ -29,10 +29,10 @@ class AssetViewSet(OrgBulkModelViewSet):
""" """
API endpoint that allows Asset to be viewed or edited. API endpoint that allows Asset to be viewed or edited.
""" """
model = Asset
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id") filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
search_fields = ("hostname", "ip") search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores") ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend] extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
@ -57,7 +57,7 @@ class AssetRefreshHardwareApi(generics.RetrieveAPIView):
""" """
Refresh asset hardware info Refresh asset hardware info
""" """
queryset = Asset.objects.all() model = Asset
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
@ -72,7 +72,7 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
""" """
Test asset admin user assets_connectivity Test asset admin user assets_connectivity
""" """
queryset = Asset.objects.all() model = Asset
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer serializer_class = serializers.TaskIDSerializer
@ -84,9 +84,9 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
class AssetGatewayApi(generics.RetrieveAPIView): class AssetGatewayApi(generics.RetrieveAPIView):
queryset = Asset.objects.all()
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer serializer_class = serializers.GatewayWithAuthSerializer
model = Asset
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk') asset_id = kwargs.get('pk')

View File

@ -13,14 +13,15 @@ __all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
class CommandFilterViewSet(OrgBulkModelViewSet): class CommandFilterViewSet(OrgBulkModelViewSet):
model = CommandFilter
filter_fields = ("name",) filter_fields = ("name",)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
queryset = CommandFilter.objects.all()
serializer_class = serializers.CommandFilterSerializer serializer_class = serializers.CommandFilterSerializer
class CommandFilterRuleViewSet(OrgBulkModelViewSet): class CommandFilterRuleViewSet(OrgBulkModelViewSet):
model = CommandFilterRule
filter_fields = ("content",) filter_fields = ("content",)
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)

View File

@ -15,7 +15,7 @@ __all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
class DomainViewSet(OrgBulkModelViewSet): class DomainViewSet(OrgBulkModelViewSet):
queryset = Domain.objects.all() model = Domain
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DomainSerializer serializer_class = serializers.DomainSerializer
@ -26,16 +26,15 @@ class DomainViewSet(OrgBulkModelViewSet):
class GatewayViewSet(OrgBulkModelViewSet): class GatewayViewSet(OrgBulkModelViewSet):
model = Gateway
filter_fields = ("domain__name", "name", "username", "ip", "domain") filter_fields = ("domain__name", "name", "username", "ip", "domain")
search_fields = filter_fields search_fields = filter_fields
queryset = Gateway.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
class GatewayTestConnectionApi(SingleObjectMixin, APIView): class GatewayTestConnectionApi(SingleObjectMixin, APIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
model = Gateway
object = None object = None
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):

View File

@ -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

View File

@ -13,12 +13,10 @@ __all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet): class GatheredUserViewSet(OrgModelViewSet):
queryset = GatheredUser.objects.all() model = GatheredUser
serializer_class = GatheredUserSerializer serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend] extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present'] filter_fields = ['asset', 'username', 'present']
search_fields = ['username', 'asset__ip', 'asset__hostname'] search_fields = ['username', 'asset__ip', 'asset__hostname']

View File

@ -27,6 +27,7 @@ __all__ = ['LabelViewSet']
class LabelViewSet(OrgBulkModelViewSet): class LabelViewSet(OrgBulkModelViewSet):
model = Label
filter_fields = ("name", "value") filter_fields = ("name", "value")
search_fields = filter_fields search_fields = filter_fields
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)

View File

@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from rest_framework import generics from rest_framework import status
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response 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.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer from common.tree import TreeNodeSerializer
from orgs.mixins.api import OrgModelViewSet from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from ..hands import IsOrgAdmin from ..hands import IsOrgAdmin
from ..models import Node 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 from .. import serializers
@ -40,9 +43,9 @@ __all__ = [
class NodeViewSet(OrgModelViewSet): class NodeViewSet(OrgModelViewSet):
model = Node
filter_fields = ('value', 'key', 'id') filter_fields = ('value', 'key', 'id')
search_fields = ('value', ) search_fields = ('value', )
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
@ -59,6 +62,13 @@ class NodeViewSet(OrgModelViewSet):
raise ValidationError({"error": msg}) raise ValidationError({"error": msg})
return super().perform_update(serializer) return super().perform_update(serializer)
def destroy(self, request, *args, **kwargs):
node = self.get_object()
if node.has_children_or_contains_assets():
msg = _("Deletion failed and the node contains children or assets")
return Response(data={'msg': msg}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
class NodeListAsTreeApi(generics.ListAPIView): class NodeListAsTreeApi(generics.ListAPIView):
""" """
@ -72,6 +82,7 @@ class NodeListAsTreeApi(generics.ListAPIView):
} }
] ]
""" """
model = Node
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
@ -80,10 +91,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
queryset = [node.as_tree_node() for node in queryset] queryset = [node.as_tree_node() for node in queryset]
return queryset return queryset
def get_queryset(self):
queryset = Node.objects.all()
return queryset
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
queryset = self.to_tree_queryset(queryset) queryset = self.to_tree_queryset(queryset)
@ -91,7 +98,6 @@ class NodeListAsTreeApi(generics.ListAPIView):
class NodeChildrenApi(generics.ListCreateAPIView): class NodeChildrenApi(generics.ListCreateAPIView):
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer serializer_class = serializers.NodeSerializer
instance = None instance = None
@ -155,6 +161,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
] ]
""" """
model = Node
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
http_method_names = ['get'] http_method_names = ['get']
@ -197,7 +204,7 @@ class NodeAssetsApi(generics.ListAPIView):
class NodeAddChildrenApi(generics.UpdateAPIView): class NodeAddChildrenApi(generics.UpdateAPIView):
queryset = Node.objects.all() model = Node
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeAddChildrenSerializer serializer_class = serializers.NodeAddChildrenSerializer
instance = None instance = None
@ -214,8 +221,8 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
class NodeAddAssetsApi(generics.UpdateAPIView): class NodeAddAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
@ -226,8 +233,8 @@ class NodeAddAssetsApi(generics.UpdateAPIView):
class NodeRemoveAssetsApi(generics.UpdateAPIView): class NodeRemoveAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
@ -242,8 +249,8 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
class NodeReplaceAssetsApi(generics.UpdateAPIView): class NodeReplaceAssetsApi(generics.UpdateAPIView):
model = Node
serializer_class = serializers.NodeAssetsSerializer serializer_class = serializers.NodeAssetsSerializer
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
instance = None instance = None
@ -255,8 +262,8 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
class RefreshNodeHardwareInfoApi(APIView): class RefreshNodeHardwareInfoApi(APIView):
permission_classes = (IsOrgAdmin,)
model = Node model = Node
permission_classes = (IsOrgAdmin,)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk') node_id = kwargs.get('pk')

View File

@ -14,13 +14,13 @@
# limitations under the License. # limitations under the License.
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework.response import Response from rest_framework.response import Response
from common.serializers import CeleryTaskSerializer from common.serializers import CeleryTaskSerializer
from common.utils import get_logger from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..tasks import ( from ..tasks import (
@ -43,22 +43,18 @@ class SystemUserViewSet(OrgBulkModelViewSet):
""" """
System user api set, for add,delete,update,list,retrieve resource System user api set, for add,delete,update,list,retrieve resource
""" """
model = SystemUser
filter_fields = ("name", "username") filter_fields = ("name", "username")
search_fields = filter_fields search_fields = filter_fields
queryset = SystemUser.objects.all()
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def get_queryset(self):
queryset = super().get_queryset().all()
return queryset
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
""" """
Get system user auth info Get system user auth info
""" """
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer serializer_class = serializers.SystemUserAuthSerializer
@ -72,7 +68,7 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
""" """
Get system user with asset auth info Get system user with asset auth info
""" """
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer serializer_class = serializers.SystemUserAuthSerializer
@ -88,7 +84,7 @@ class SystemUserPushApi(generics.RetrieveAPIView):
""" """
Push system user to cluster assets api Push system user to cluster assets api
""" """
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer serializer_class = CeleryTaskSerializer
@ -105,7 +101,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
""" """
Push system user to cluster assets api Push system user to cluster assets api
""" """
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer serializer_class = CeleryTaskSerializer
@ -132,7 +128,7 @@ class SystemUserAssetsListView(generics.ListAPIView):
class SystemUserPushToAssetApi(generics.RetrieveAPIView): class SystemUserPushToAssetApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer serializer_class = serializers.TaskIDSerializer
@ -145,7 +141,7 @@ class SystemUserPushToAssetApi(generics.RetrieveAPIView):
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView): class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
queryset = SystemUser.objects.all() model = SystemUser
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer serializer_class = serializers.TaskIDSerializer

14
apps/assets/const.py Normal file
View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT = _(
'Only Numbers、letters、 chinese and characters ( {} ) are allowed'
).format(" ".join(['.', '_', '@', '-']))
GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN = r"^[\._@\w-]+$"
GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG = \
_("* The contains characters that are not allowed")

View File

@ -53,7 +53,7 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend):
return queryset return queryset
if node is None: if node is None:
return queryset.none() return queryset
query_all = self.is_query_all(request) query_all = self.is_query_all(request)
if query_all: if query_all:
pattern = node.get_all_children_pattern(with_self=True) pattern = node.get_all_children_pattern(with_self=True)

View File

@ -7,6 +7,7 @@ from common.utils import get_logger
from orgs.mixins.forms import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import Asset, Node from ..models import Asset, Node
from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__) logger = get_logger(__file__)
@ -14,10 +15,6 @@ __all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm', 'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
] ]
HELP_TEXTS_ASSET_HOSTNAME = _(
'Only Numbers、letters、 chinese and characters ( {} ) are allowed'
).format(" ".join(['.', '_', '@']))
class ProtocolForm(forms.Form): class ProtocolForm(forms.Form):
name = forms.ChoiceField( name = forms.ChoiceField(
@ -72,7 +69,7 @@ class AssetCreateForm(OrgModelForm):
'nodes': _("Node"), 'nodes': _("Node"),
} }
help_texts = { help_texts = {
'hostname': HELP_TEXTS_ASSET_HOSTNAME, 'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _( 'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,' 'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu' 'If asset is windows or other set any one, more see admin user left menu'
@ -119,7 +116,7 @@ class AssetUpdateForm(OrgModelForm):
'nodes': _("Node"), 'nodes': _("Node"),
} }
help_texts = { help_texts = {
'hostname': HELP_TEXTS_ASSET_HOSTNAME, 'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _( 'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,' 'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu' 'If asset is windows or other set any one, more see admin user left menu'
@ -132,7 +129,7 @@ class AssetUpdateForm(OrgModelForm):
class AssetBulkUpdateForm(OrgModelForm): class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField( assets = forms.ModelMultipleChoiceField(
required=True, required=True,
label=_('Select assets'), queryset=Asset.objects.all(), label=_('Select assets'), queryset=Asset.objects,
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
'class': 'select2', 'class': 'select2',
@ -158,11 +155,18 @@ class AssetBulkUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.set_fields_queryset()
# 重写其他字段为不再required # 重写其他字段为不再required
for name, field in self.fields.items(): for name, field in self.fields.items():
if name != 'assets': if name != 'assets':
field.required = False 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): def save(self, commit=True):
changed_fields = [] changed_fields = []
for field in self._meta.fields: for field in self._meta.fields:

View File

@ -12,7 +12,7 @@ __all__ = ['DomainForm', 'GatewayForm']
class DomainForm(forms.ModelForm): class DomainForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField( assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(), label=_('Asset'), required=False, queryset=Asset.objects, label=_('Asset'), required=False,
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')} attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
) )
@ -23,19 +23,23 @@ class DomainForm(forms.ModelForm):
fields = ['name', 'comment', 'assets'] fields = ['name', 'comment', 'assets']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if kwargs.get('instance', None):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.set_fields_queryset()
# 前端渲染优化, 防止过多资产 def set_fields_queryset(self):
assets_field = self.fields.get('assets') assets_field = self.fields.get('assets')
# 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data: if not self.data:
instance = kwargs.get('instance') # 有instance 代表渲染更新表单, 否则是创建表单
if instance: # 前端渲染优化, 防止过多资产, 设置assets queryset为none
assets_field.queryset = instance.assets.all() if self.instance:
assets_field.initial = self.instance.assets.all()
assets_field.queryset = self.instance.assets.all()
else: else:
assets_field.queryset = Asset.objects.none() assets_field.queryset = Asset.objects.none()
else:
assets_field.queryset = Asset.objects.all()
def save(self, commit=True): def save(self, commit=True):
instance = super().save(commit=commit) instance = super().save(commit=commit)

View File

@ -10,7 +10,7 @@ __all__ = ['LabelForm']
class LabelForm(forms.ModelForm): class LabelForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField( assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.all(), label=_('Asset'), required=False, queryset=Asset.objects.none(), label=_('Asset'), required=False,
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')} attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
) )
@ -21,19 +21,23 @@ class LabelForm(forms.ModelForm):
fields = ['name', 'value', 'assets'] fields = ['name', 'value', 'assets']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if kwargs.get('instance', None):
initial = kwargs.get('initial', {})
initial['assets'] = kwargs['instance'].assets.all()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.set_fields_queryset()
# 前端渲染优化, 防止过多资产 def set_fields_queryset(self):
assets_field = self.fields.get('assets') assets_field = self.fields.get('assets')
# 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data: if not self.data:
instance = kwargs.get('instance') # 有instance 代表渲染更新表单, 否则是创建表单
if instance: # 前端渲染优化, 防止过多资产, 设置assets queryset为none
assets_field.queryset = instance.assets.all() if self.instance:
assets_field.initial = self.instance.assets.all()
assets_field.queryset = self.instance.assets.all()
else: else:
assets_field.queryset = Asset.objects.none() assets_field.queryset = Asset.objects.none()
else:
assets_field.queryset = Asset.objects.all()
def save(self, commit=True): def save(self, commit=True):
label = super().save(commit=commit) label = super().save(commit=commit)

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
from orgs.mixins.forms import OrgModelForm from orgs.mixins.forms import OrgModelForm
from ..models import AdminUser, SystemUser from ..models import AdminUser, SystemUser
from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
@ -98,6 +99,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
}), }),
} }
help_texts = { help_texts = {
'name': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'auto_push': _('Auto push system user to asset'), 'auto_push': _('Auto push system user to asset'),
'priority': _('1-100, High level will be using login asset as default, ' 'priority': _('1-100, High level will be using login asset as default, '
'if user was granted more than 2 system user'), 'if user was granted more than 2 system user'),

View File

@ -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')},
},
),
]

View File

@ -10,3 +10,4 @@ from .authbook import *
from .utils import * from .utils import *
from .authbook import * from .authbook import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import *

View File

@ -7,6 +7,7 @@ from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
@ -57,25 +58,30 @@ class CommandFilterRule(OrgModelMixin):
date_updated = models.DateTimeField(auto_now=True) date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by')) created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
__pattern = None
class Meta: class Meta:
ordering = ('-priority', 'action') ordering = ('-priority', 'action')
verbose_name = _("Command filter rule") verbose_name = _("Command filter rule")
@property @lazyproperty
def _pattern(self): def _pattern(self):
if self.__pattern:
return self.__pattern
if self.type == 'command': if self.type == 'command':
regex = [] regex = []
for cmd in self.content.split('\r\n'): content = self.content.replace('\r\n', '\n')
cmd = cmd.replace(' ', '\s+') for cmd in content.split('\n'):
regex.append(r'\b{0}\b'.format(cmd)) cmd = re.escape(cmd)
self.__pattern = re.compile(r'{}'.format('|'.join(regex))) cmd = cmd.replace('\\ ', '\s+')
if cmd[-1].isalpha():
regex.append(r'\b{0}\b'.format(cmd))
else:
regex.append(r'\b{0}'.format(cmd))
s = r'{}'.format('|'.join(regex))
else: else:
self.__pattern = re.compile(r'{0}'.format(self.content)) s = r'{0}'.format(self.content)
return self.__pattern try:
_pattern = re.compile(s)
except:
_pattern = ''
return _pattern
def match(self, data): def match(self, data):
found = self._pattern.search(data) found = self._pattern.search(data)

View File

@ -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)

View File

@ -324,6 +324,8 @@ class SomeNodesMixin:
ungrouped_value = _('ungrouped') ungrouped_value = _('ungrouped')
empty_key = '-11' empty_key = '-11'
empty_value = _("empty") empty_value = _("empty")
favorite_key = '-12'
favorite_value = _("favorite")
def is_default_node(self): def is_default_node(self):
return self.key == self.default_key return self.key == self.default_key
@ -363,7 +365,7 @@ class SomeNodesMixin:
@classmethod @classmethod
def ungrouped_node(cls): def ungrouped_node(cls):
with tmp_to_org(Organization.system()): with tmp_to_org(Organization.system()):
defaults = {'value': cls.ungrouped_key} defaults = {'value': cls.ungrouped_value}
obj, created = cls.objects.get_or_create( obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.ungrouped_key defaults=defaults, key=cls.ungrouped_key
) )
@ -387,11 +389,21 @@ class SomeNodesMixin:
) )
return obj 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 @classmethod
def initial_some_nodes(cls): def initial_some_nodes(cls):
cls.default_node() cls.default_node()
cls.empty_node() cls.empty_node()
cls.ungrouped_node() cls.ungrouped_node()
cls.favorite_node()
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin): class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
@ -412,11 +424,11 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
def __str__(self): def __str__(self):
return self.value return self.value
def __eq__(self, other): # def __eq__(self, other):
if not other: # if not other:
return False # return False
return self.id == other.id # return self.id == other.id
#
def __gt__(self, other): def __gt__(self, other):
self_key = [int(k) for k in self.key.split(':')] self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')] other_key = [int(k) for k in other.key.split(':')]
@ -470,8 +482,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
tree_node = TreeNode(**data) tree_node = TreeNode(**data)
return tree_node return tree_node
def delete(self, using=None, keep_parents=False): def has_children_or_contains_assets(self):
if self.children or self.get_assets(): if self.children or self.get_assets():
return True
return False
def delete(self, using=None, keep_parents=False):
if self.has_children_or_contains_assets():
return return
return super().delete(using=using, keep_parents=keep_parents) return super().delete(using=using, keep_parents=keep_parents)

View File

@ -120,6 +120,10 @@ class SystemUser(AssetUser):
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
@property
def nodes_amount(self):
return self.nodes.all().count()
@property @property
def login_mode_display(self): def login_mode_display(self):
return self.get_login_mode_display() return self.get_login_mode_display()

View File

@ -10,3 +10,4 @@ from .domain import *
from .cmd_filter import * from .cmd_filter import *
from .asset_user import * from .asset_user import *
from .gathered_user import * from .gathered_user import *
from .favorite_asset import *

View File

@ -45,7 +45,7 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
管理用户更新关联到的集群 管理用户更新关联到的集群
""" """
nodes = serializers.PrimaryKeyRelatedField( nodes = serializers.PrimaryKeyRelatedField(
many=True, queryset=Node.objects.all() many=True, queryset=Node.objects
) )
class Meta: class Meta:

View File

@ -8,6 +8,10 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label from ..models import Asset, Node, Label
from ..const import (
GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN,
GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
)
from .base import ConnectivitySerializer from .base import ConnectivitySerializer
__all__ = [ __all__ = [
@ -94,10 +98,10 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
@staticmethod @staticmethod
def validate_hostname(hostname): def validate_hostname(hostname):
pattern = r"^[\._@\w-]+$" pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN
res = re.match(pattern, hostname) res = re.match(pattern, hostname)
if res is None: if res is None:
msg = _("* The hostname contains characters that are not allowed") msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
return hostname return hostname
@ -136,6 +140,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
class AssetSimpleSerializer(serializers.ModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer):
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
class Meta: class Meta:
model = Asset model = Asset

View File

@ -79,7 +79,7 @@ class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class AssetUserPushSerializer(serializers.Serializer): 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) username = serializers.CharField(max_length=1024)
def create(self, validated_data): def create(self, validated_data):

View File

@ -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']

View File

@ -38,8 +38,10 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
return data return data
class NodeAssetsSerializer(serializers.ModelSerializer): class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) assets = serializers.PrimaryKeyRelatedField(
many=True, queryset=Asset.objects
)
class Meta: class Meta:
model = Node model = Node

View File

@ -1,3 +1,4 @@
import re
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -6,6 +7,10 @@ from common.serializers import AdaptedBulkListSerializer
from common.utils import ssh_pubkey_gen from common.utils import ssh_pubkey_gen
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser from ..models import SystemUser
from ..const import (
GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN,
GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
)
from .base import AuthSerializer, AuthSerializerMixin from .base import AuthSerializer, AuthSerializerMixin
@ -21,18 +26,28 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
fields = [ fields = [
'id', 'name', 'username', 'password', 'public_key', 'private_key', 'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol', 'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
'assets_amount', 'auto_generate_key' 'assets_amount', 'nodes_amount', 'auto_generate_key'
] ]
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True}, 'password': {"write_only": True},
'public_key': {"write_only": True}, 'public_key': {"write_only": True},
'private_key': {"write_only": True}, 'private_key': {"write_only": True},
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')}, 'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'created_by': {'read_only': True},
} }
@staticmethod
def validate_name(name):
pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN
res = re.match(pattern, name)
if res is None:
msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
raise serializers.ValidationError(msg)
return name
def validate_auto_push(self, value): def validate_auto_push(self, value):
login_mode = self.initial_data.get("login_mode") login_mode = self.initial_data.get("login_mode")
protocol = self.initial_data.get("protocol") protocol = self.initial_data.get("protocol")

View File

@ -25,12 +25,20 @@
</div> </div>
</div> </div>
</form> </form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
e.preventDefault();
e.stopPropagation();
$("#asset_list_modal").modal();
}
})
}).on('click', '.field-tag', function() { }).on('click', '.field-tag', function() {
changeField(this); changeField(this);
}).on('click', '#change_all', function () { }).on('click', '#change_all', function () {

View File

@ -22,7 +22,6 @@
<li> <li>
<a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset user list' %} </a> <a href="{% url 'assets:asset-user-list' pk=asset.id %}" class="text-center"><i class="fa fa-bar-chart-o"></i> {% trans 'Asset user list' %} </a>
</li> </li>
{% if user.is_superuser %}
<li class="pull-right"> <li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a> <a class="btn btn-outline btn-default" href="{% url 'assets:asset-update' pk=asset.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li> </li>
@ -31,7 +30,6 @@
<i class="fa fa-trash-o"></i>{% trans 'Delete' %} <i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a> </a>
</li> </li>
{% endif %}
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">

View File

@ -292,7 +292,8 @@ $(document).ready(function(){
format: 'csv', format: 'csv',
params: { params: {
search: search, search: search,
node_id: current_node_id || '' node_id: current_node_id || '',
show_current_asset: getCookie('show_current_asset')
} }
}; };
APIExportData(props); APIExportData(props);

View File

@ -21,19 +21,46 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var treeUrl = "{% url 'api-perms:my-nodes-children-as-tree' %}?&cache_policy=1"; var treeUrl = "{% url 'api-perms:my-nodes-children-as-tree' %}?cache_policy=1";
var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1"; var assetTableUrl = "{% url 'api-perms:my-assets' %}?cache_policy=1";
var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1'; var selectUrl = '{% url "api-perms:my-node-assets" node_id=DEFAULT_PK %}?cache_policy=1&all=1';
var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}?cache_policy=1"; var systemUsersUrl = "{% url 'api-perms:my-asset-system-users' asset_id=DEFAULT_PK %}?cache_policy=1";
var showAssetHref = false; // Need input default true var showAssetHref = false; // Need input default true
var favoriteAssets = [];
var favorBtnTmpl = '<a class="btn btn-xs btn-default btn-favor" data-id="ID"><i class="fa fa-star-o"></i></a>';
var disfavorBtnTmpl = '<a class="btn btn-xs btn-default btn-disfavor" data-id="ID"><i class="fa fa-star"></i></a>';
var actions = { var actions = {
targets: 4, createdCell: function (td, cellData) { targets: 4, createdCell: function (td, cellData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData + var connBtn = '<a href="{% url "luna-view" %}?login_to=' + cellData +
'" class="btn btn-xs btn-primary" target="_blank">{% trans "Connect" %}</a>'; '" class="btn btn-xs btn-primary" target="_blank"><i class="fa fa-terminal"></i></a> ';
$(td).html(conn_btn) var favorBtn = favorBtnTmpl.replace("ID", cellData);
var disfavorBtn = disfavorBtnTmpl.replace("ID", cellData);
var btn = connBtn;
if (favoriteAssets.indexOf(cellData) === -1) {
btn += favorBtn
} else {
btn += disfavorBtn;
}
$(td).html(btn)
}}; }};
$(document).ready(function () { $(document).ready(function () {
initTree(); requestApi({
method: "GET",
url: "{% url 'api-assets:favorite-asset-list' %}",
success: function (data) {
favoriteAssets = data.map(function (i) {
return i.asset;
});
initTree();
},
error: function () {
initTree();
},
flash_message: false
})
}).on('click', '.labels li', function () { }).on('click', '.labels li', function () {
var val = $(this).text(); var val = $(this).text();
$("#user_assets_table_filter input").val(val); $("#user_assets_table_filter input").val(val);
@ -67,22 +94,38 @@ $(document).ready(function () {
}; };
$('#asset_detail_tbody').html(trs) $('#asset_detail_tbody').html(trs)
$('#user_asset_detail_modal').modal(); $('#user_asset_detail_modal').modal();
})
.on('click', '.btn-favor', function () {
var $this = $(this);
var assetId = $(this).data("id");
requestApi({
url: "{% url 'api-assets:favorite-asset-list' %}",
method: "POST",
body: JSON.stringify({asset: assetId}),
flash_message: false,
success: function (data) {
favoriteAssets.push(assetId);
var btn = disfavorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn)
}
});
})
.on('click', '.btn-disfavor', function () {
var $this = $(this);
var assetId = $(this).data("id");
requestApi({
url: "{% url 'api-assets:favorite-asset-list' %}?asset=" + assetId,
method: "DELETE",
flash_message: false,
success: function (data) {
var index = favoriteAssets.indexOf(assetId);
if (index !== '-1'){
favoriteAssets.splice(index, 1);
}
var btn = favorBtnTmpl.replace("ID", assetId);
$this.replaceWith(btn)
}
});
}); });
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;
}
}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -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', api.AssetUserViewSet, 'asset-user')
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info') router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user') 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 = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')

View File

@ -1,14 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import viewsets
from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor
from orgs.mixins.api import OrgModelViewSet
from .models import FTPLog from .models import FTPLog
from .serializers import FTPLogSerializer from .serializers import FTPLogSerializer
class FTPLogViewSet(viewsets.ModelViewSet): class FTPLogViewSet(OrgModelViewSet):
queryset = FTPLog.objects.all() model = FTPLog
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,) permission_classes = (IsOrgAdminOrAppUser | IsOrgAuditor,)

View File

@ -32,6 +32,13 @@ class LDAPAuthorizationBackend(LDAPBackend):
if not username: if not username:
logger.info('Authenticate failed: username is None') logger.info('Authenticate failed: username is None')
return None return None
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
user_model = self.get_user_model()
exist = user_model.objects.filter(username=username).exists()
if not exist:
msg = 'Authentication failed: user ({}) is not in the user list'
logger.info(msg.format(username))
return None
ldap_user = LDAPUser(self, username=username.strip(), request=request) ldap_user = LDAPUser(self, username=username.strip(), request=request)
user = self.authenticate_ldap_user(ldap_user, password) user = self.authenticate_ldap_user(ldap_user, password)
logger.info('Authenticate user: {}'.format(user)) logger.info('Authenticate user: {}'.format(user))

View File

@ -83,8 +83,6 @@ class LogTailApi(generics.RetrieveAPIView):
return Response({"data": data, 'end': end, 'mark': new_mark}) return Response({"data": data, 'end': end, 'mark': new_mark})
class ResourcesIDCacheApi(APIView): class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4()) spm = str(uuid.uuid4())

View File

@ -63,7 +63,6 @@ class IDSpmFilter(filters.BaseFilterBackend):
cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm) cache_key = const.KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key) resources_id = cache.get(cache_key)
if not resources_id or not isinstance(resources_id, list): if not resources_id or not isinstance(resources_id, list):
queryset = queryset.none()
return queryset return queryset
queryset = queryset.filter(id__in=resources_id) queryset = queryset.filter(id__in=resources_id)
return queryset return queryset

View File

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"] __all__ = [
"NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet",
"CommonModelMixin"
]
class NoDeleteQuerySet(models.query.QuerySet): class NoDeleteQuerySet(models.query.QuerySet):
@ -40,3 +43,13 @@ class NoDeleteModelMixin(models.Model):
self.is_discard = True self.is_discard = True
self.discard_time = timezone.now() self.discard_time = timezone.now()
return self.save() 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

View File

@ -36,7 +36,7 @@ def on_request_finished_logging_db_query(sender, **kwargs):
queries = connection.queries queries = connection.queries
counters = defaultdict(Counter) counters = defaultdict(Counter)
for query in queries: for query in queries:
if not query['sql'].startswith('SELECT'): if not query['sql'] or not query['sql'].startswith('SELECT'):
continue continue
tables = pattern.findall(query['sql']) tables = pattern.findall(query['sql'])
table_name = ''.join(tables) table_name = ''.join(tables)

View File

@ -51,6 +51,8 @@ class TreeNode:
result = True result = True
elif self.pId != other.pId: elif self.pId != other.pId:
result = self.pId > other.pId result = self.pId > other.pId
elif str(self.id).startswith('-') and not str(other.id).startswith('-'):
result = False
else: else:
result = self.name > other.name result = self.name > other.name
return result return result

View File

@ -378,6 +378,7 @@ defaults = {
'AUTH_LDAP_SYNC_IS_PERIODIC': False, 'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None, 'AUTH_LDAP_SYNC_INTERVAL': None,
'AUTH_LDAP_SYNC_CRONTAB': None, 'AUTH_LDAP_SYNC_CRONTAB': None,
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080, 'HTTP_LISTEN_PORT': 8080,
'WS_LISTEN_PORT': 8070, 'WS_LISTEN_PORT': 8070,

View File

@ -429,6 +429,7 @@ AUTH_LDAP_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_SEARCH_PAGED_SIZE
AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC AUTH_LDAP_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_SYNC_IS_PERIODIC
AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
AUTH_LDAP_SERVER_URI = 'ldap://localhost:389' AUTH_LDAP_SERVER_URI = 'ldap://localhost:389'
AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org' AUTH_LDAP_BIND_DN = 'cn=admin,dc=jumpserver,dc=org'

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,8 @@
<script> <script>
var scheme = document.location.protocol === "https:" ? "wss" : "ws"; var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? ":" + document.location.port : ""; var port = document.location.port ? ":" + document.location.port : "";
var url = "/ws/ops/tasks/" + "{{ task_id }}" + "/log/"; var taskId = "{{ task_id }}";
var url = "/ws/ops/tasks/log/";
var wsURL = scheme + "://" + document.location.hostname + port + url; var wsURL = scheme + "://" + document.location.hostname + port + url;
var failOverPort = "{{ ws_port }}"; var failOverPort = "{{ ws_port }}";
var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url; var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
@ -46,6 +47,10 @@
var data = JSON.parse(e.data); var data = JSON.parse(e.data);
term.write(data.message); term.write(data.message);
}; };
ws.onopen = function() {
var msg = {"task": taskId};
ws.send(JSON.stringify(msg))
};
ws.onerror = function (e) { ws.onerror = function (e) {
ws = new WebSocket(failOverWsURL); ws = new WebSocket(failOverWsURL);
ws.onmessage = function(e) { ws.onmessage = function(e) {

View File

@ -140,6 +140,7 @@
} }
function initTree() { function initTree() {
$('#assetTree').html("{% trans 'Loading' %}" + '..');
if (systemUserId) { if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId url = treeUrl + '&system_user=' + systemUserId
} else { } else {
@ -236,6 +237,7 @@
} }
var term = null; var term = null;
var ws = null;
function initResultTerminal() { function initResultTerminal() {
term = new Terminal({ term = new Terminal({
@ -253,7 +255,29 @@
term.open(document.getElementById('term')); term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n"; var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
fit(term); fit(term);
term.write(msg) term.write(msg);
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? ":" + document.location.port : "";
var url = "/ws/ops/tasks/log/";
var wsURL = scheme + "://" + document.location.hostname + port + url;
var failOverPort = "{{ ws_port }}";
var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
ws = new WebSocket(wsURL);
ws.onerror = function (e) {
ws = new WebSocket(failOverWsURL);
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
term.write(data.message);
};
ws.onerror = function (e) {
term.write("Connect websocket server error")
}
};
ws.onmessage = function(e) {
var data = JSON.parse(e.data);
term.write(data.message);
};
} }
function wrapperError(msg) { function wrapperError(msg) {
@ -288,35 +312,12 @@
run_as: run_as, run_as: run_as,
command: command command: command
}; };
var mark = '';
var log_url = null;
var end = false;
var error = false;
var int = null;
var interval = 200;
function writeExecutionOutput() { function writeExecutionOutput(taskId) {
if (!end) { var msg = "{% trans 'Pending' %} ";
$.ajax({ term.write(msg);
url: log_url + '?mark=' + mark, msg = JSON.stringify({task: taskId});
method: "GET", ws.send(msg);
contentType: "application/json; charset=utf-8"
}).done(function (data, textStatue, jqXHR) {
if (jqXHR.status === 203) {
error = true;
term.write('.');
interval = 500;
}
if (jqXHR.status === 200) {
term.write(data.data);
mark = data.mark;
if (data.end) {
end = true;
window.clearInterval(int)
}
}
})
}
} }
requestApi({ requestApi({
@ -325,12 +326,8 @@
method: 'POST', method: 'POST',
flash_message: false, flash_message: false,
success: function (resp) { success: function (resp) {
var msg = "{% trans 'Pending' %}"; {#log_url = resp.log_url;#}
term.write(msg + "...\r\n"); writeExecutionOutput(resp.id)
log_url = resp.log_url;
int = setInterval(function () {
writeExecutionOutput()
}, interval);
} }
}); });
return false; return false;
@ -339,6 +336,8 @@
var editor; var editor;
$(document).ready(function () { $(document).ready(function () {
systemUserId = $('#system-users-select').val(); systemUserId = $('#system-users-select').val();
$(".select2").select2({ $(".select2").select2({
dropdownAutoWidth: true, dropdownAutoWidth: true,
}).on('select2:select', function (evt) { }).on('select2:select', function (evt) {

View File

@ -5,5 +5,5 @@ from .. import ws
app_name = 'ops' app_name = 'ops'
urlpatterns = [ urlpatterns = [
path('ws/ops/tasks/<uuid:task_id>/log/', ws.CeleryLogWebsocket, name='task-log-ws'), path('ws/ops/tasks/log/', ws.CeleryLogWebsocket, name='task-log-ws'),
] ]

View File

@ -79,7 +79,8 @@ class CommandExecutionStartView(PermissionsMixin, TemplateView):
'app': _('Ops'), 'app': _('Ops'),
'action': _('Command execution'), 'action': _('Command execution'),
'form': self.get_form(), 'form': self.get_form(),
'system_users': system_users 'system_users': system_users,
'ws_port': settings.CONFIG.WS_LISTEN_PORT
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -1,41 +1,58 @@
import time import time
import os
import threading import threading
import json
from celery.result import AsyncResult
from .celery.utils import get_celery_task_log_path from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer from channels.generic.websocket import JsonWebsocketConsumer
class CeleryLogWebsocket(JsonWebsocketConsumer): class CeleryLogWebsocket(JsonWebsocketConsumer):
task = ''
task_log_f = None
disconnected = False disconnected = False
def connect(self): def connect(self):
task_id = self.scope['url_route']['kwargs']['task_id']
log_path = get_celery_task_log_path(task_id)
try:
self.task_log_f = open(log_path)
except OSError:
self.send({'message': "Task {} log not found".format(task_id)})
self.disconnect(None)
return
self.accept() self.accept()
self.send_log_to_client()
def receive(self, text_data=None, bytes_data=None, **kwargs):
data = json.loads(text_data)
task_id = data.get("task")
if task_id:
self.handle_task(task_id)
def handle_task(self, task_id):
log_path = get_celery_task_log_path(task_id)
def func():
task_log_f = None
while not self.disconnected:
if not os.path.exists(log_path):
self.send_json({'message': '.', 'task': task_id})
time.sleep(0.5)
continue
self.send_json({'message': '\r\n'})
try:
task_log_f = open(log_path)
break
except OSError:
return
while not self.disconnected:
data = task_log_f.readline()
if data:
data = data.replace('\n', '\r\n')
self.send_json({'message': data, 'task': task_id})
if data.startswith('Task') and data.find('succeeded'):
break
time.sleep(0.2)
task_log_f.close()
thread = threading.Thread(target=func)
thread.start()
def disconnect(self, close_code): def disconnect(self, close_code):
self.disconnected = True self.disconnected = True
if self.task_log_f and not self.task_log_f.closed:
self.task_log_f.close()
self.close() self.close()
def send_log_to_client(self):
def func():
while not self.disconnected:
data = self.task_log_f.read(4096)
if data:
data = data.replace('\n', '\r\n')
self.send_json({'message': data})
time.sleep(0.2)
thread = threading.Thread(target=func)
thread.start()

View File

@ -5,12 +5,12 @@ from rest_framework.viewsets import ModelViewSet
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from common.mixins import CommonApiMixin 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 from ..models import Organization
__all__ = [ __all__ = [
'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet', 'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet',
'OrgBulkModelViewSet', 'OrgBulkModelViewSet', 'OrgQuerySetMixin',
] ]
@ -22,7 +22,15 @@ class RootOrgViewMixin:
class OrgQuerySetMixin: class OrgQuerySetMixin:
def get_queryset(self): 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'): if hasattr(self, 'swagger_fake_view'):
return queryset[:1] return queryset[:1]
if hasattr(self, 'action') and self.action == 'list' and \ if hasattr(self, 'action') and self.action == 'list' and \

View File

@ -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

View File

@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError
from common.utils import get_logger from common.utils import get_logger
from ..utils import ( from ..utils import (
set_current_org, get_current_org, current_org, set_current_org, get_current_org, current_org,
get_org_filters
) )
from ..models import Organization from ..models import Organization
@ -19,42 +20,38 @@ __all__ = [
] ]
class OrgQuerySet(models.QuerySet):
pass
class OrgManager(models.Manager): class OrgManager(models.Manager):
def get_queryset(self): def get_queryset(self):
queryset = super(OrgManager, self).get_queryset() queryset = super().get_queryset()
kwargs = {} kwargs = get_org_filters()
if kwargs:
_current_org = get_current_org() return queryset.filter(**kwargs)
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)
return queryset 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): def set_current_org(self, org):
if isinstance(org, str): if isinstance(org, str):
org = Organization.get_instance(org) org = Organization.get_instance(org)
set_current_org(org) set_current_org(org)
return self 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): class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, blank=True, default='', org_id = models.CharField(max_length=36, blank=True, default='',
@ -65,9 +62,12 @@ class OrgModelMixin(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
org = get_current_org() 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 self.org_id = org.id
elif org is not None and org.is_default(): elif org.is_default():
self.org_id = '' self.org_id = ''
return super().save(*args, **kwargs) return super().save(*args, **kwargs)

View File

@ -1,9 +1,13 @@
import re
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label from assets.models import Asset, Domain, AdminUser, SystemUser, Label
from assets.const import (
GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN,
GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
)
from perms.models import AssetPermission from perms.models import AssetPermission
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from .utils import set_current_org, get_current_org from .utils import set_current_org, get_current_org
@ -18,6 +22,15 @@ class OrgSerializer(ModelSerializer):
fields = '__all__' fields = '__all__'
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
@staticmethod
def validate_name(name):
pattern = GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN
res = re.match(pattern, name)
if res is None:
msg = GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
raise serializers.ValidationError(msg)
return name
class OrgReadSerializer(ModelSerializer): class OrgReadSerializer(ModelSerializer):
admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True) admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import traceback
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from contextlib import contextmanager from contextlib import contextmanager
@ -82,4 +83,31 @@ def tmp_to_org(org):
set_current_org(ori_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) current_org = LocalProxy(get_current_org)

View File

@ -1,14 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils import timezone
from django.db.models import Q from django.db.models import Q
from rest_framework.views import Response from rest_framework.views import Response
from django.shortcuts import get_object_or_404 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 common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import AssetPermission from ..models import AssetPermission
from ..hands import ( from ..hands import (
@ -24,15 +23,21 @@ __all__ = [
] ]
class AssetPermissionViewSet(viewsets.ModelViewSet): class AssetPermissionViewSet(OrgModelViewSet):
""" """
资产授权列表的增删改查api 资产授权列表的增删改查api
""" """
queryset = AssetPermission.objects.all() model = AssetPermission
serializer_class = serializers.AssetPermissionCreateUpdateSerializer serializer_class = serializers.AssetPermissionCreateUpdateSerializer
filter_fields = ['name'] filter_fields = ['name']
permission_classes = (IsOrgAdmin,) 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): def get_serializer_class(self):
if self.action in ("list", 'retrieve') and \ if self.action in ("list", 'retrieve') and \
self.request.query_params.get("display"): self.request.query_params.get("display"):
@ -68,14 +73,17 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
node_id = self.request.query_params.get('node_id') node_id = self.request.query_params.get('node_id')
node_name = self.request.query_params.get('node') node_name = self.request.query_params.get('node')
if node_id: if node_id:
node = get_object_or_none(Node, pk=node_id) _nodes = Node.objects.filter(pk=node_id)
elif node_name: elif node_name:
node = get_object_or_none(Node, name=node_name) _nodes = Node.objects.filter(value=node_name)
else: else:
return queryset return queryset
if not node: if not _nodes:
return queryset.none() return queryset.none()
nodes = node.get_ancestors(with_self=True)
nodes = set()
for node in _nodes:
nodes |= set(node.get_ancestors(with_self=True))
queryset = queryset.filter(nodes__in=nodes) queryset = queryset.filter(nodes__in=nodes)
return queryset return queryset
@ -160,19 +168,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
queryset = queryset.distinct() queryset = queryset.distinct()
return queryset return queryset
def get_queryset(self):
return self.queryset.all().prefetch_related(
"nodes", "assets", "users", "user_groups", "system_users"
)
class AssetPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
""" """
将用户从授权中移除Detail页面会调用 将用户从授权中移除Detail页面会调用
""" """
model = AssetPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -187,10 +190,10 @@ class AssetPermissionRemoveUserApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
class AssetPermissionAddUserApi(RetrieveUpdateAPIView): class AssetPermissionAddUserApi(generics.RetrieveUpdateAPIView):
model = AssetPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateUserSerializer serializer_class = serializers.AssetPermissionUpdateUserSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -205,13 +208,13 @@ class AssetPermissionAddUserApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView): class AssetPermissionRemoveAssetApi(generics.RetrieveUpdateAPIView):
""" """
将用户从授权中移除Detail页面会调用 将用户从授权中移除Detail页面会调用
""" """
model = AssetPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -226,10 +229,10 @@ class AssetPermissionRemoveAssetApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
class AssetPermissionAddAssetApi(RetrieveUpdateAPIView): class AssetPermissionAddAssetApi(generics.RetrieveUpdateAPIView):
model = AssetPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionUpdateAssetSerializer serializer_class = serializers.AssetPermissionUpdateAssetSerializer
queryset = AssetPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -244,7 +247,7 @@ class AssetPermissionAddAssetApi(RetrieveUpdateAPIView):
return Response({"error": serializer.errors}) return Response({"error": serializer.errors})
class AssetPermissionAssetsApi(ListAPIView): class AssetPermissionAssetsApi(generics.ListAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetPermissionAssetsSerializer serializer_class = serializers.AssetPermissionAssetsSerializer
filter_fields = ("hostname", "ip") filter_fields = ("hostname", "ip")

View File

@ -4,7 +4,7 @@
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.utils import get_logger 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 from ..hands import User, UserGroup
@ -17,15 +17,24 @@ __all__ = [
class UserPermissionMixin: class UserPermissionMixin:
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
current_org = None
obj = None obj = None
def initial(self, *args, **kwargs): def initial(self, *args, **kwargs):
super().initial(*args, *kwargs) super().initial(*args, *kwargs)
self.current_org = get_current_org()
set_to_root_org()
self.obj = self.get_obj() self.obj = self.get_obj()
def get(self, request, *args, **kwargs): # def dispatch(self, request, *args, **kwargs):
set_to_root_org() # """不能这么做,校验权限时拿不到组织了"""
return super().get(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): def get_obj(self):
user_id = self.kwargs.get('pk', '') user_id = self.kwargs.get('pk', '')
@ -40,6 +49,13 @@ class UserPermissionMixin:
self.permission_classes = (IsValidUser,) self.permission_classes = (IsValidUser,)
return super().get_permissions() 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: class UserGroupPermissionMixin:
obj = None obj = None

View File

@ -1,10 +1,11 @@
# coding: utf-8 # coding: utf-8
# #
from rest_framework import viewsets, generics
from rest_framework.views import Response from rest_framework.views import Response
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgModelViewSet
from orgs.mixins import generics
from ..models import RemoteAppPermission from ..models import RemoteAppPermission
from ..serializers import ( from ..serializers import (
RemoteAppPermissionSerializer, RemoteAppPermissionSerializer,
@ -20,18 +21,18 @@ __all__ = [
] ]
class RemoteAppPermissionViewSet(viewsets.ModelViewSet): class RemoteAppPermissionViewSet(OrgModelViewSet):
model = RemoteAppPermission
filter_fields = ('name', ) filter_fields = ('name', )
search_fields = filter_fields search_fields = filter_fields
queryset = RemoteAppPermission.objects.all()
serializer_class = RemoteAppPermissionSerializer serializer_class = RemoteAppPermissionSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
model = RemoteAppPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer serializer_class = RemoteAppPermissionUpdateUserSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -46,9 +47,9 @@ class RemoteAppPermissionAddUserApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
model = RemoteAppPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateUserSerializer serializer_class = RemoteAppPermissionUpdateUserSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -63,9 +64,9 @@ class RemoteAppPermissionRemoveUserApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
model = RemoteAppPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()
@ -80,9 +81,9 @@ class RemoteAppPermissionAddRemoteAppApi(generics.RetrieveUpdateAPIView):
class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView): class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
model = RemoteAppPermission
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer serializer_class = RemoteAppPermissionUpdateRemoteAppSerializer
queryset = RemoteAppPermission.objects.all()
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
perm = self.get_object() perm = self.get_object()

View File

@ -34,7 +34,7 @@ class UserNodeTreeMixin:
for node in nodes: for node in nodes:
assets_amount = self.tree.valid_assets_amount(node.key) 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 continue
node.assets_amount = assets_amount node.assets_amount = assets_amount
data = ParserNode.parse_node_to_tree_node(node) data = ParserNode.parse_node_to_tree_node(node)

View File

@ -3,12 +3,10 @@
import uuid import uuid
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response 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.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer from common.tree import TreeNodeSerializer
from orgs.mixins import generics
from ..utils import ( from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root, RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node, parse_remote_app_to_tree_node,
@ -25,7 +23,7 @@ __all__ = [
] ]
class UserGrantedRemoteAppsApi(ListAPIView): class UserGrantedRemoteAppsApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = RemoteAppSerializer serializer_class = RemoteAppSerializer
filter_fields = ['name', 'id'] filter_fields = ['name', 'id']
@ -68,7 +66,7 @@ class UserGrantedRemoteAppsAsTreeApi(UserGrantedRemoteAppsApi):
return super().get_serializer(data, many=True) return super().get_serializer(data, many=True)
class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, ListAPIView): class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.RemoteAppSystemUserSerializer serializer_class = serializers.RemoteAppSystemUserSerializer
only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields
@ -110,7 +108,7 @@ class ValidateUserRemoteAppPermissionApi(APIView):
# RemoteApp permission # RemoteApp permission
class UserGroupGrantedRemoteAppsApi(ListAPIView): class UserGroupGrantedRemoteAppsApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser, ) permission_classes = (IsOrgAdminOrAppUser, )
serializer_class = RemoteAppSerializer serializer_class = RemoteAppSerializer

View File

@ -2,10 +2,15 @@
# #
from users.models import User, UserGroup 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 assets.serializers import NodeSerializer
from applications.serializers import RemoteAppSerializer from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp from applications.models import RemoteApp
__all__ = [
'User', 'UserGroup',
'Asset', 'SystemUser', 'Node', 'Label', 'FavoriteAsset',
'NodeSerializer', 'RemoteAppSerializer',
'RemoteApp'
]

View File

@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet):
return self.filter(is_active=False) return self.filter(is_active=False)
def invalid(self): def invalid(self):
now = timezone.now now = timezone.now()
q = ( q = (
Q(is_active=False) | Q(is_active=False) |
Q(date_start__gt=now) | Q(date_start__gt=now) |

View File

@ -13,7 +13,7 @@ from common.utils import get_logger, timeit, lazyproperty
from common.tree import TreeNode from common.tree import TreeNode
from assets.utils import TreeService from assets.utils import TreeService
from ..models import AssetPermission from ..models import AssetPermission
from ..hands import Node, Asset, SystemUser from ..hands import Node, Asset, SystemUser, User, FavoriteAsset
logger = get_logger(__file__) logger = get_logger(__file__)
@ -293,6 +293,20 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
parent=user_tree.root, 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): def set_user_tree_to_local(self, user_tree):
self._user_tree = user_tree self._user_tree = user_tree
self._user_tree_filter_id = self._filter_id 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.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(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_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree) self.set_user_tree_to_local(user_tree)
return user_tree return user_tree

View File

@ -101,9 +101,11 @@ class LDAPUserListApi(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
if hasattr(self, 'swagger_fake_view'): if hasattr(self, 'swagger_fake_view'):
return [] return []
util = LDAPUtil() q = self.request.query_params.get('search')
try: try:
users = util.search_user_items() util = LDAPUtil()
extra_filter = util.construct_extra_filter(util.SEARCH_FIELD_ALL, q)
users = util.search_user_items(extra_filter)
except Exception as e: except Exception as e:
users = [] users = []
logger.error(e) logger.error(e)
@ -112,20 +114,6 @@ class LDAPUserListApi(generics.ListAPIView):
user['id'] = user['username'] user['id'] = user['username']
return users return users
def filter_queryset(self, queryset):
search = self.request.query_params.get('search')
if not search:
return queryset
search = search.lower()
queryset = [
q for q in queryset
if
search in q['username'].lower()
or search in q['name'].lower()
or search in q['email'].lower()
]
return queryset
def sort_queryset(self, queryset): def sort_queryset(self, queryset):
order_by = self.request.query_params.get('order') order_by = self.request.query_params.get('order')
if not order_by: if not order_by:
@ -139,7 +127,7 @@ class LDAPUserListApi(generics.ListAPIView):
return queryset return queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) queryset = self.get_queryset()
queryset = self.sort_queryset(queryset) queryset = self.sort_queryset(queryset)
page = self.paginate_queryset(queryset) page = self.paginate_queryset(queryset)
if page is not None: if page is not None:

View File

@ -22,6 +22,9 @@ class LDAPOUGroupException(Exception):
class LDAPUtil: class LDAPUtil:
_conn = None _conn = None
SEARCH_FIELD_ALL = 'all'
SEARCH_FIELD_USERNAME = 'username'
def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None, def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None,
password=None, use_ssl=None, search_ougroup=None, password=None, use_ssl=None, search_ougroup=None,
search_filter=None, attr_map=None, auth_ldap=None): search_filter=None, attr_map=None, auth_ldap=None):
@ -81,9 +84,13 @@ class LDAPUtil:
user_item[attr] = value user_item[attr] = value
return user_item return user_item
def _search_user_items_ou(self, search_ou, cookie=None): def _search_user_items_ou(self, search_ou, extra_filter=None, cookie=None):
search_filter = self.search_filter % {"user": "*"}
if extra_filter:
search_filter = '(&{}{})'.format(search_filter, extra_filter)
ok = self.connection.search( ok = self.connection.search(
search_ou, self.search_filter % ({"user": "*"}), search_ou, search_filter,
attributes=list(self.attr_map.values()), attributes=list(self.attr_map.values()),
paged_size=self.paged_size, paged_cookie=cookie paged_size=self.paged_size, paged_cookie=cookie
) )
@ -108,24 +115,43 @@ class LDAPUtil:
cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie'] cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
return cookie return cookie
def search_user_items(self): def search_user_items(self, extra_filter=None):
user_items = [] user_items = []
logger.info("Search user items") logger.info("Search user items")
for search_ou in str(self.search_ougroup).split("|"): for search_ou in str(self.search_ougroup).split("|"):
logger.info("Search user search ou: {}".format(search_ou)) logger.info("Search user search ou: {}".format(search_ou))
_user_items = self._search_user_items_ou(search_ou) _user_items = self._search_user_items_ou(search_ou, extra_filter=extra_filter)
user_items.extend(_user_items) user_items.extend(_user_items)
while self._cookie(): while self._cookie():
logger.info("Page Search user search ou: {}".format(search_ou)) logger.info("Page Search user search ou: {}".format(search_ou))
_user_items = self._search_user_items_ou(search_ou, self._cookie()) _user_items = self._search_user_items_ou(search_ou, extra_filter, self._cookie())
user_items.extend(_user_items) user_items.extend(_user_items)
logger.info("Search user items end") logger.info("Search user items end")
return user_items return user_items
def construct_extra_filter(self, field, q):
if not q:
return None
extra_filter = ''
if field == self.SEARCH_FIELD_ALL:
for attr in self.attr_map.values():
extra_filter += '({}={})'.format(attr, q)
extra_filter = '(|{})'.format(extra_filter)
return extra_filter
if field == self.SEARCH_FIELD_USERNAME and isinstance(q, list):
attr = self.attr_map.get('username')
for username in q:
extra_filter += '({}={})'.format(attr, username)
extra_filter = '(|{})'.format(extra_filter)
return extra_filter
def search_filter_user_items(self, username_list): def search_filter_user_items(self, username_list):
user_items = self.search_user_items() extra_filter = self.construct_extra_filter(
if username_list: self.SEARCH_FIELD_USERNAME, username_list
user_items = [u for u in user_items if u['username'] in username_list] )
user_items = self.search_user_items(extra_filter)
return user_items return user_items
@staticmethod @staticmethod

File diff suppressed because one or more lines are too long

View File

@ -267,7 +267,7 @@ function requestApi(props) {
$.ajax({ $.ajax({
url: props.url, url: props.url,
type: props.method || "PATCH", type: props.method || "PATCH",
data: props.body, data: props.body || props.data,
contentType: props.content_type || "application/json; charset=utf-8", contentType: props.content_type || "application/json; charset=utf-8",
dataType: props.data_type || "json" dataType: props.data_type || "json"
}).done(function (data, textStatue, jqXHR) { }).done(function (data, textStatue, jqXHR) {
@ -579,6 +579,9 @@ jumpserver.initServerSideDataTable = function (options) {
ajax: { ajax: {
url: options.ajax_url, url: options.ajax_url,
error: function (jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.responseText && jqXHR.responseText.indexOf("%(value)s") !== -1 ) {
return
}
var msg = gettext("Unknown error occur"); var msg = gettext("Unknown error occur");
if (jqXHR.responseJSON) { if (jqXHR.responseJSON) {
if (jqXHR.responseJSON.error) { if (jqXHR.responseJSON.error) {
@ -953,8 +956,13 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
function rootNodeAddDom(ztree, callback) { function rootNodeAddDom(ztree, callback) {
var refreshIcon = "<a id='tree-refresh'><i class='fa fa-refresh'></i></a>"; var refreshIcon = "<a id='tree-refresh'><i class='fa fa-refresh'></i></a>";
var rootNode = ztree.getNodes()[0]; var rootNode = ztree.getNodes()[0];
var $rootNodeRef = $("#" + rootNode.tId + "_a"); if (rootNode) {
$rootNodeRef.after(refreshIcon); var $rootNodeRef = $("#" + rootNode.tId + "_a");
$rootNodeRef.after(refreshIcon);
} else {
$rootNodeRef = $('#' + ztree.setting.treeId);
$rootNodeRef.html(refreshIcon);
}
var refreshIconRef = $('#tree-refresh'); var refreshIconRef = $('#tree-refresh');
refreshIconRef.bind('click', function () { refreshIconRef.bind('click', function () {
ztree.destroy(); ztree.destroy();

File diff suppressed because one or more lines are too long

View File

@ -24,10 +24,10 @@ logger = get_logger(__name__)
class SessionViewSet(OrgBulkModelViewSet): class SessionViewSet(OrgBulkModelViewSet):
queryset = Session.objects.all() model = Session
serializer_class = serializers.SessionSerializer serializer_class = serializers.SessionSerializer
permission_classes = (IsOrgAdminOrAppUser, ) permission_classes = (IsOrgAdminOrAppUser, )
filter_fields = [ filterset_fields = [
"user", "asset", "system_user", "remote_addr", "user", "asset", "system_user", "remote_addr",
"protocol", "terminal", "is_finished", "protocol", "terminal", "is_finished",
] ]

View File

@ -6,6 +6,7 @@ from common import utils
TYPE_ENGINE_MAPPING = { TYPE_ENGINE_MAPPING = {
'elasticsearch': 'terminal.backends.command.es', 'elasticsearch': 'terminal.backends.command.es',
'es': 'terminal.backends.command.es',
} }

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from datetime import datetime
from jms_storage.es import ESStorage from jms_storage.es import ESStorage
from .base import CommandBase from .base import CommandBase
from .models import AbstractSessionCommand from .models import AbstractSessionCommand
@ -14,6 +15,13 @@ class CommandStore(ESStorage, CommandBase):
user=None, asset=None, system_user=None, user=None, asset=None, system_user=None,
input=None, session=None): input=None, session=None):
if date_from is not None:
if isinstance(date_from, float):
date_from = datetime.fromtimestamp(date_from)
if date_to is not None:
if isinstance(date_to, float):
date_to = datetime.fromtimestamp(date_to)
data = super().filter(date_from=date_from, date_to=date_to, data = super().filter(date_from=date_from, date_to=date_to,
user=user, asset=asset, system_user=system_user, user=user, asset=asset, system_user=system_user,
input=input, session=session) input=input, session=session)

View File

@ -164,11 +164,14 @@ $(document).ready(function () {
detailRows.push(tr.attr('id')); detailRows.push(tr.attr('id'));
} }
} }
}) });
function format(d) { function format(d) {
var output = $("<pre style='border: none; background: none'></pre>"); var output = $("<pre style='border: none; background: none'></pre>");
output.append('$ ', d.input);
output.append('\r\n\r\n');
output.append(d.output); output.append(d.output);
return output return output
} }
@ -187,6 +190,17 @@ function initTable() {
$(td).addClass("toggle"); $(td).addClass("toggle");
$(td).html("<i class='fa fa-angle-right'></i>"); $(td).html("<i class='fa fa-angle-right'></i>");
}}, }},
{targets: 1, createdCell: function (td, cellData) {
var data = htmlEscape(cellData);
var interHtml = $("<span></span>");
if (data.length > 40) {
interHtml.attr('title', data);
data = data.slice(0, 40);
data += ' ...';
}
interHtml.html(data);
$(td).html(interHtml);
}},
{targets: 5, createdCell: function (td, cellData) { {targets: 5, createdCell: function (td, cellData) {
var data = '<a href="{% url "terminal:session-detail" pk=DEFAULT_PK %}">{% trans "Goto" %}</a>' var data = '<a href="{% url "terminal:session-detail" pk=DEFAULT_PK %}">{% trans "Goto" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData); .replace('{{ DEFAULT_PK }}', cellData);
@ -200,7 +214,7 @@ function initTable() {
toggle: true, toggle: true,
ajax_url: commandListUrl, ajax_url: commandListUrl,
columns: [ columns: [
{data: "id"}, {data: "input", orderable: false}, {data: "user", orderable: false}, {data: "id"}, {data: "input", orderable: false, width: "40%"}, {data: "user", orderable: false},
{data: "asset", orderable: false}, {data: "system_user", orderable: false}, {data: "asset", orderable: false}, {data: "system_user", orderable: false},
{data: "session", orderable: false}, {data: "timestamp", width: "160px", orderable: false}, {data: "session", orderable: false}, {data: "timestamp", width: "160px", orderable: false},
], ],

View File

@ -54,8 +54,12 @@
{% for command in object_list %} {% for command in object_list %}
<tr> <tr>
<td>{{ forloop.counter }}</td> <td>{{ forloop.counter }}</td>
<td>{{ command.input }}</td> <td>{{ command.input | truncatechars:40 }}</td>
<td><pre style="border: none;background: none">{{ command.output }}</pre></td> <td><pre style="border: none;background: none">
$ {{ command.input }}
{{ command.output }}
</pre></td>
<td>{{ command.timestamp|ts_to_date}}</td> <td>{{ command.timestamp|ts_to_date}}</td>
</tr> </tr>
{% empty %} {% empty %}

View File

@ -72,6 +72,7 @@
<li><a class="search-item" data-value="asset">{% trans 'Asset' %}</a></li> <li><a class="search-item" data-value="asset">{% trans 'Asset' %}</a></li>
<li><a class="search-item" data-value="system_user">{% trans 'System user' %}</a></li> <li><a class="search-item" data-value="system_user">{% trans 'System user' %}</a></li>
<li><a class="search-item" data-value="remote_addr">{% trans 'Remote addr' %}</a></li> <li><a class="search-item" data-value="remote_addr">{% trans 'Remote addr' %}</a></li>
<li><a class="search-item" data-value="protocol">{% trans 'Protocol' %}</a></li>
{# <li><a class="search-item" data-value="protocol">{% trans 'Protocol' %}</a></li>#} {# <li><a class="search-item" data-value="protocol">{% trans 'Protocol' %}</a></li>#}
</ul> </ul>
{% endblock %} {% endblock %}

View File

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import generics
from ..serializers import ( from ..serializers import (
UserGroupSerializer, UserGroupSerializer,
UserGroupListSerializer, UserGroupListSerializer,
@ -10,6 +8,7 @@ from ..serializers import (
) )
from ..models import UserGroup from ..models import UserGroup
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from common.permissions import IsOrgAdmin from common.permissions import IsOrgAdmin
@ -17,9 +16,9 @@ __all__ = ['UserGroupViewSet', 'UserGroupUpdateUserApi']
class UserGroupViewSet(OrgBulkModelViewSet): class UserGroupViewSet(OrgBulkModelViewSet):
model = UserGroup
filter_fields = ("name",) filter_fields = ("name",)
search_fields = filter_fields search_fields = filter_fields
queryset = UserGroup.objects.all()
serializer_class = UserGroupSerializer serializer_class = UserGroupSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
@ -31,6 +30,6 @@ class UserGroupViewSet(OrgBulkModelViewSet):
class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView): class UserGroupUpdateUserApi(generics.RetrieveUpdateAPIView):
queryset = UserGroup.objects.all() model = UserGroup
serializer_class = UserGroupUpdateMemberSerializer serializer_class = UserGroupUpdateMemberSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)

View File

@ -17,7 +17,7 @@ from common.permissions import (
from common.mixins import CommonApiMixin from common.mixins import CommonApiMixin
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import current_org from orgs.utils import current_org
from .. import serializers from .. import serializers, utils
from ..models import User from ..models import User
from ..signals import post_user_create 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') filter_fields = ('username', 'email', 'name', 'id')
search_fields = filter_fields search_fields = filter_fields
queryset = User.objects.exclude(role=User.ROLE_APP)
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsOrgAdmin, CanUpdateDeleteUser) permission_classes = (IsOrgAdmin, CanUpdateDeleteUser)
def get_queryset(self):
return super().get_queryset().prefetch_related('groups')
def send_created_signal(self, users): def send_created_signal(self, users):
if not isinstance(users, list): if not isinstance(users, list):
users = [users] users = [users]
@ -51,11 +59,6 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet):
current_org.users.add(*users) current_org.users.add(*users)
self.send_created_signal(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): def get_permissions(self):
if self.action in ["retrieve", "list"]: if self.action in ["retrieve", "list"]:
self.permission_classes = (IsOrgAdminOrAppUser,) self.permission_classes = (IsOrgAdminOrAppUser,)
@ -79,9 +82,8 @@ class UserViewSet(CommonApiMixin, BulkModelViewSet):
return super().perform_bulk_update(serializer) return super().perform_bulk_update(serializer)
class UserChangePasswordApi(generics.RetrieveUpdateAPIView): class UserChangePasswordApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
queryset = User.objects.all()
serializer_class = serializers.ChangeUserPasswordSerializer serializer_class = serializers.ChangeUserPasswordSerializer
def perform_update(self, serializer): def perform_update(self, serializer):
@ -90,13 +92,12 @@ class UserChangePasswordApi(generics.RetrieveUpdateAPIView):
user.save() user.save()
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): class UserUpdateGroupApi(UserQuerysetMixin, generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserUpdateGroupSerializer serializer_class = serializers.UserUpdateGroupSerializer
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
class UserResetPasswordApi(generics.UpdateAPIView): class UserResetPasswordApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@ -111,8 +112,7 @@ class UserResetPasswordApi(generics.UpdateAPIView):
send_reset_password_mail(user) send_reset_password_mail(user)
class UserResetPKApi(generics.UpdateAPIView): class UserResetPKApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
@ -125,8 +125,7 @@ class UserResetPKApi(generics.UpdateAPIView):
# 废弃 # 废弃
class UserUpdatePKApi(generics.UpdateAPIView): class UserUpdatePKApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserPKUpdateSerializer serializer_class = serializers.UserPKUpdateSerializer
permission_classes = (IsCurrentUserOrReadOnly,) permission_classes = (IsCurrentUserOrReadOnly,)
@ -136,8 +135,7 @@ class UserUpdatePKApi(generics.UpdateAPIView):
user.save() user.save()
class UserUnblockPKApi(generics.UpdateAPIView): class UserUnblockPKApi(UserQuerysetMixin, generics.UpdateAPIView):
queryset = User.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}" key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
@ -165,8 +163,7 @@ class UserProfileApi(generics.RetrieveAPIView):
return super().retrieve(request, *args, **kwargs) return super().retrieve(request, *args, **kwargs)
class UserResetOTPApi(generics.RetrieveAPIView): class UserResetOTPApi(UserQuerysetMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
permission_classes = (IsOrgAdmin,) permission_classes = (IsOrgAdmin,)
serializer_class = serializers.ResetOTPSerializer serializer_class = serializers.ResetOTPSerializer

View File

@ -5,9 +5,8 @@ from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from orgs.mixins.forms import OrgModelForm from orgs.mixins.forms import OrgModelForm
from orgs.utils import current_org
from .models import User, UserGroup 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): class UserCheckPasswordForm(forms.Form):
@ -267,15 +266,23 @@ class UserBulkUpdateForm(OrgModelForm):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
required=True, required=True,
label=_('Select users'), label=_('Select users'),
queryset=User.objects.all(), queryset=User.objects.none(),
widget=forms.SelectMultiple( widget=forms.SelectMultiple(
attrs={ attrs={
'class': 'select2', 'class': 'users-select2',
'data-placeholder': _('Select users') '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: class Meta:
model = User model = User
fields = ['users', 'groups', 'date_expired'] fields = ['users', 'groups', 'date_expired']
@ -320,25 +327,19 @@ class UserGroupForm(OrgModelForm):
) )
def __init__(self, **kwargs): 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) super().__init__(**kwargs)
if 'initial' not in kwargs: self.set_fields_queryset()
return
def set_fields_queryset(self):
users_field = self.fields.get('users') users_field = self.fields.get('users')
if instance: if self.instance:
users_field.queryset = instance.users.all() users_field.initial = self.instance.users.all()
users_field.queryset = self.instance.users.all()
else: else:
users_field.queryset = User.objects.none() users_field.queryset = User.objects.none()
def save(self, commit=True): def save(self, commit=True):
group = super().save(commit=commit) raise Exception("Save by restful api")
users = self.cleaned_data['users']
group.users.set(users)
return group
class Meta: class Meta:
model = UserGroup model = UserGroup

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import copy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
@ -12,6 +11,7 @@ from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser from common.permissions import CanUpdateDeleteUser
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import User, UserGroup from ..models import User, UserGroup
from .. import utils
__all__ = [ __all__ = [
@ -118,7 +118,9 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
class UserUpdateGroupSerializer(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: class Meta:
model = User model = User
@ -127,7 +129,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
class UserGroupSerializer(BulkOrgResourceModelSerializer): class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField( users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects.all(), label=_('User') required=False, many=True, queryset=User.objects, label=_('User')
) )
class Meta: class Meta:
@ -141,6 +143,14 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
'created_by': {'label': _('Created by'), 'read_only': True} '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): def validate_users(self, users):
for user in users: for user in users:
if user.is_super_auditor: if user.is_super_auditor:
@ -154,12 +164,20 @@ class UserGroupListSerializer(UserGroupSerializer):
class UserGroupUpdateMemberSerializer(serializers.ModelSerializer): class UserGroupUpdateMemberSerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all()) users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects)
class Meta: class Meta:
model = UserGroup model = UserGroup
fields = ['id', 'users'] 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 ChangeUserPasswordSerializer(serializers.ModelSerializer):

View File

@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
<div class="col-lg-3" style="padding-left: 0px"> <div class="col-lg-3" style="padding-left: 0" id="split-left">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0"> <div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager "> <div class="file-manager ">
@ -11,7 +11,12 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-9 animated fadeInRight"> <div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="tree-toggle">
<div class="btn btn-sm btn-primary tree-toggle-btn" onclick="toggle()">
<i class="fa fa-angle-left fa-x" id="toggle-icon"></i>
</div>
</div>
<div class="mail-box-header"> <div class="mail-box-header">
{# <div class="btn-group" style="float: right">#} {# <div class="btn-group" style="float: right">#}
{# <button data-toggle="dropdown" class="btn btn-default btn-sm labels dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#} {# <button data-toggle="dropdown" class="btn btn-default btn-sm labels dropdown-toggle">{% trans 'Label' %} <span class="caret"></span></button>#}
@ -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 () { $(document).ready(function () {
{#loadLabels()#} {#loadLabels()#}
}).on('click', '.labels-menu li', function () { }).on('click', '.labels-menu li', function () {

View File

@ -31,6 +31,7 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
usersSelect2Init('.users-select2')
}).on('click', '.field-tag', function() { }).on('click', '.field-tag', function() {
changeField(this); changeField(this);
}).on('click', '#change_all', function () { }).on('click', '#change_all', function () {

View File

@ -28,7 +28,7 @@
{% bootstrap_field form.comment layout="horizontal" %} {% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group"> <div class="form-group">
<div class="col-sm-4 col-sm-offset-2"> <div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Cancel' %}</button> <button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Confirm' %}</button> <button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Confirm' %}</button>
</div> </div>
</div> </div>

View File

@ -34,7 +34,7 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var assetTableUrl = "{% url 'api-perms:user-group-assets' pk=object.id %}?cache_policy=1"; var assetTableUrl = "{% url 'api-perms:user-group-assets' pk=object.id %}?cache_policy=1";
var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?&cache_policy=1&all=1'; var selectUrl = '{% url "api-perms:user-group-node-assets" pk=object.id node_id=DEFAULT_PK %}?cache_policy=1&all=1';
var treeUrl = "{% url 'api-perms:user-group-nodes-children-as-tree' pk=object.id %}?cache_policy=1"; var treeUrl = "{% url 'api-perms:user-group-nodes-children-as-tree' pk=object.id %}?cache_policy=1";
var systemUsersUrl = "{% url 'api-perms:user-group-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}?cache_policy=1"; var systemUsersUrl = "{% url 'api-perms:user-group-asset-system-users' pk=object.id asset_id=DEFAULT_PK %}?cache_policy=1";
var showAssetHref = true; // Need input default true var showAssetHref = true; // Need input default true

View File

@ -325,3 +325,7 @@ def construct_user_email(username, email):
email = '{}@{}'.format(username, settings.EMAIL_SUFFIX) email = '{}@{}'.format(username, settings.EMAIL_SUFFIX)
return email return email
def get_current_org_members(exclude=()):
from orgs.utils import current_org
return current_org.get_org_members(exclude=exclude)

View File

@ -72,13 +72,18 @@ REDIS_PORT: 6379
# RADIUS_PORT: 1812 # RADIUS_PORT: 1812
# RADIUS_SECRET: # RADIUS_SECRET:
# LDAP/AD 设置定时同步参数 # LDAP/AD settings
# 定时同步用户
# 启用/禁用 # 启用/禁用
# AUTH_LDAP_SYNC_IS_PERIODIC: True # AUTH_LDAP_SYNC_IS_PERIODIC: True
# 单位: 时 # 单位: 时
# AUTH_LDAP_SYNC_INTERVAL: 12 # AUTH_LDAP_SYNC_INTERVAL: 12
# Crontab 表达式 # Crontab 表达式
# AUTH_LDAP_SYNC_CRONTAB: * 6 * * * # AUTH_LDAP_SYNC_CRONTAB: * 6 * * *
#
# LDAP 用户登录时仅允许在用户列表中的用户执行 LDAP Server 认证
# AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS: False
# OTP settings # OTP settings
# OTP/MFA 配置 # OTP/MFA 配置

3
jms
View File

@ -445,8 +445,7 @@ def stop_service(srv, sig=15):
def stop_daemon_service(): def stop_daemon_service():
pid = get_pid('jms') pid = get_pid('jms')
logging.info("Daemon pid is: {}".format(pid)) if pid and check_pid(pid):
if pid:
os.kill(pid, 15) os.kill(pid, 15)

View File

@ -0,0 +1,31 @@
#!/usr/bin/python
#
import os
import sys
import django
if os.path.exists('../apps'):
sys.path.insert(0, '../apps')
elif os.path.exists('./apps'):
sys.path.insert(0, './apps')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup()
from assets.models import *
from orgs.utils import Organization
Organization.root().change_to()
ns = Node.objects.all()
for i in ns:
try:
pkey = i.parent.key
except:
pkey = ''
if i.parent_key != pkey and not i.key.isdigit():
print("Node parent not found: {} -> {}".format(i.key, i.parent_key))