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
#
from rest_framework import generics
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
@ -16,14 +14,14 @@ __all__ = [
class RemoteAppViewSet(OrgBulkModelViewSet):
model = RemoteApp
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
queryset = RemoteApp.objects.all()
model = RemoteApp
permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer

View File

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

View File

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

View File

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

View File

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

View File

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

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):
queryset = GatheredUser.objects.all()
model = GatheredUser
serializer_class = GatheredUserSerializer
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present']
search_fields = ['username', 'asset__ip', 'asset__hostname']

View File

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

View File

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

View File

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

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
if node is None:
return queryset.none()
return queryset
query_all = self.is_query_all(request)
if query_all:
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 ..models import Asset, Node
from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__)
@ -14,10 +15,6 @@ __all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
]
HELP_TEXTS_ASSET_HOSTNAME = _(
'Only Numbers、letters、 chinese and characters ( {} ) are allowed'
).format(" ".join(['.', '_', '@']))
class ProtocolForm(forms.Form):
name = forms.ChoiceField(
@ -72,7 +69,7 @@ class AssetCreateForm(OrgModelForm):
'nodes': _("Node"),
}
help_texts = {
'hostname': HELP_TEXTS_ASSET_HOSTNAME,
'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'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'
@ -119,7 +116,7 @@ class AssetUpdateForm(OrgModelForm):
'nodes': _("Node"),
}
help_texts = {
'hostname': HELP_TEXTS_ASSET_HOSTNAME,
'hostname': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'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'
@ -132,7 +129,7 @@ class AssetUpdateForm(OrgModelForm):
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True,
label=_('Select assets'), queryset=Asset.objects.all(),
label=_('Select assets'), queryset=Asset.objects,
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
@ -158,11 +155,18 @@ class AssetBulkUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
# 重写其他字段为不再required
for name, field in self.fields.items():
if name != 'assets':
field.required = False
def set_fields_queryset(self):
assets_field = self.fields['assets']
if hasattr(self, 'data'):
assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:

View File

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

View File

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

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 orgs.mixins.forms import OrgModelForm
from ..models import AdminUser, SystemUser
from ..const import GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__)
__all__ = [
@ -98,6 +99,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
}),
}
help_texts = {
'name': GENERAL_LIMIT_SPECIAL_CHARACTERS_HELP_TEXT,
'auto_push': _('Auto push system user to asset'),
'priority': _('1-100, High level will be using login asset as default, '
'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 .authbook 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.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin
@ -57,25 +58,30 @@ class CommandFilterRule(OrgModelMixin):
date_updated = models.DateTimeField(auto_now=True)
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
__pattern = None
class Meta:
ordering = ('-priority', 'action')
verbose_name = _("Command filter rule")
@property
@lazyproperty
def _pattern(self):
if self.__pattern:
return self.__pattern
if self.type == 'command':
regex = []
for cmd in self.content.split('\r\n'):
cmd = cmd.replace(' ', '\s+')
regex.append(r'\b{0}\b'.format(cmd))
self.__pattern = re.compile(r'{}'.format('|'.join(regex)))
content = self.content.replace('\r\n', '\n')
for cmd in content.split('\n'):
cmd = re.escape(cmd)
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:
self.__pattern = re.compile(r'{0}'.format(self.content))
return self.__pattern
s = r'{0}'.format(self.content)
try:
_pattern = re.compile(s)
except:
_pattern = ''
return _pattern
def match(self, 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')
empty_key = '-11'
empty_value = _("empty")
favorite_key = '-12'
favorite_value = _("favorite")
def is_default_node(self):
return self.key == self.default_key
@ -363,7 +365,7 @@ class SomeNodesMixin:
@classmethod
def ungrouped_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.ungrouped_key}
defaults = {'value': cls.ungrouped_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.ungrouped_key
)
@ -387,11 +389,21 @@ class SomeNodesMixin:
)
return obj
@classmethod
def favorite_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.favorite_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.favorite_key
)
return obj
@classmethod
def initial_some_nodes(cls):
cls.default_node()
cls.empty_node()
cls.ungrouped_node()
cls.favorite_node()
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
@ -412,11 +424,11 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
def __str__(self):
return self.value
def __eq__(self, other):
if not other:
return False
return self.id == other.id
# def __eq__(self, other):
# if not other:
# return False
# return self.id == other.id
#
def __gt__(self, other):
self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')]
@ -470,8 +482,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
tree_node = TreeNode(**data)
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():
return True
return False
def delete(self, using=None, keep_parents=False):
if self.has_children_or_contains_assets():
return
return super().delete(using=using, keep_parents=keep_parents)

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class AssetUserPushSerializer(serializers.Serializer):
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset"))
username = serializers.CharField(max_length=1024)
def create(self, validated_data):

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
class NodeAssetsSerializer(serializers.ModelSerializer):
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
assets = serializers.PrimaryKeyRelatedField(
many=True, queryset=Asset.objects
)
class Meta:
model = Node

View File

@ -1,3 +1,4 @@
import re
from rest_framework import serializers
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 orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser
from ..const import (
GENERAL_LIMIT_SPECIAL_CHARACTERS_PATTERN,
GENERAL_LIMIT_SPECIAL_CHARACTERS_ERROR_MSG
)
from .base import AuthSerializer, AuthSerializerMixin
@ -21,18 +26,28 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
fields = [
'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'assets_amount', 'auto_generate_key'
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
'assets_amount', 'nodes_amount', 'auto_generate_key'
]
extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')},
'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):
login_mode = self.initial_data.get("login_mode")
protocol = self.initial_data.get("protocol")

View File

@ -25,12 +25,20 @@
</div>
</div>
</form>
{% include 'assets/_asset_list_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.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() {
changeField(this);
}).on('click', '#change_all', function () {

View File

@ -22,7 +22,6 @@
<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>
</li>
{% if user.is_superuser %}
<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>
</li>
@ -31,7 +30,6 @@
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
{% endif %}
</ul>
</div>
<div class="tab-content">

View File

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

View File

@ -21,19 +21,46 @@
{% block custom_foot_js %}
<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 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 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 = {
targets: 4, createdCell: function (td, cellData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +
'" class="btn btn-xs btn-primary" target="_blank">{% trans "Connect" %}</a>';
$(td).html(conn_btn)
var connBtn = '<a href="{% url "luna-view" %}?login_to=' + cellData +
'" class="btn btn-xs btn-primary" target="_blank"><i class="fa fa-terminal"></i></a> ';
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 () {
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 () {
var val = $(this).text();
$("#user_assets_table_filter input").val(val);
@ -67,22 +94,38 @@ $(document).ready(function () {
};
$('#asset_detail_tbody').html(trs)
$('#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>
{% 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-info', api.AssetUserExportViewSet, 'asset-user-info')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')

View File

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

View File

@ -32,6 +32,13 @@ class LDAPAuthorizationBackend(LDAPBackend):
if not username:
logger.info('Authenticate failed: username is 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)
user = self.authenticate_ldap_user(ldap_user, password)
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})
class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4())

View File

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

View File

@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
#
import uuid
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"]
__all__ = [
"NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet",
"CommonModelMixin"
]
class NoDeleteQuerySet(models.query.QuerySet):
@ -40,3 +43,13 @@ class NoDeleteModelMixin(models.Model):
self.is_discard = True
self.discard_time = timezone.now()
return self.save()
class CommonModelMixin(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_('Date updated'))
class Meta:
abstract = True

View File

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

View File

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

View File

@ -378,6 +378,7 @@ defaults = {
'AUTH_LDAP_SYNC_IS_PERIODIC': False,
'AUTH_LDAP_SYNC_INTERVAL': None,
'AUTH_LDAP_SYNC_CRONTAB': None,
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080,
'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_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
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_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>
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
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 failOverPort = "{{ ws_port }}";
var failOverWsURL = scheme + "://" + document.location.hostname + ':' + failOverPort + url;
@ -46,6 +47,10 @@
var data = JSON.parse(e.data);
term.write(data.message);
};
ws.onopen = function() {
var msg = {"task": taskId};
ws.send(JSON.stringify(msg))
};
ws.onerror = function (e) {
ws = new WebSocket(failOverWsURL);
ws.onmessage = function(e) {

View File

@ -140,6 +140,7 @@
}
function initTree() {
$('#assetTree').html("{% trans 'Loading' %}" + '..');
if (systemUserId) {
url = treeUrl + '&system_user=' + systemUserId
} else {
@ -236,6 +237,7 @@
}
var term = null;
var ws = null;
function initResultTerminal() {
term = new Terminal({
@ -253,7 +255,29 @@
term.open(document.getElementById('term'));
var msg = "{% trans 'Select the left asset, select the running system user, execute command in batch' %}" + "\r\n";
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) {
@ -288,35 +312,12 @@
run_as: run_as,
command: command
};
var mark = '';
var log_url = null;
var end = false;
var error = false;
var int = null;
var interval = 200;
function writeExecutionOutput() {
if (!end) {
$.ajax({
url: log_url + '?mark=' + mark,
method: "GET",
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)
}
}
})
}
function writeExecutionOutput(taskId) {
var msg = "{% trans 'Pending' %} ";
term.write(msg);
msg = JSON.stringify({task: taskId});
ws.send(msg);
}
requestApi({
@ -325,12 +326,8 @@
method: 'POST',
flash_message: false,
success: function (resp) {
var msg = "{% trans 'Pending' %}";
term.write(msg + "...\r\n");
log_url = resp.log_url;
int = setInterval(function () {
writeExecutionOutput()
}, interval);
{#log_url = resp.log_url;#}
writeExecutionOutput(resp.id)
}
});
return false;
@ -339,6 +336,8 @@
var editor;
$(document).ready(function () {
systemUserId = $('#system-users-select').val();
$(".select2").select2({
dropdownAutoWidth: true,
}).on('select2:select', function (evt) {
@ -365,4 +364,4 @@
execute()
})
</script>
{% endblock %}
{% endblock %}

View File

@ -5,5 +5,5 @@ from .. import ws
app_name = 'ops'
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'),
'action': _('Command execution'),
'form': self.get_form(),
'system_users': system_users
'system_users': system_users,
'ws_port': settings.CONFIG.WS_LISTEN_PORT
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@ -1,41 +1,58 @@
import time
import os
import threading
import json
from celery.result import AsyncResult
from .celery.utils import get_celery_task_log_path
from channels.generic.websocket import JsonWebsocketConsumer
class CeleryLogWebsocket(JsonWebsocketConsumer):
task = ''
task_log_f = None
disconnected = False
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.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):
self.disconnected = True
if self.task_log_f and not self.task_log_f.closed:
self.task_log_f.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 common.mixins import CommonApiMixin
from ..utils import set_to_root_org
from ..utils import set_to_root_org, filter_org_queryset
from ..models import Organization
__all__ = [
'RootOrgViewMixin', 'OrgMembershipModelViewSetMixin', 'OrgModelViewSet',
'OrgBulkModelViewSet',
'OrgBulkModelViewSet', 'OrgQuerySetMixin',
]
@ -22,7 +22,15 @@ class RootOrgViewMixin:
class OrgQuerySetMixin:
def get_queryset(self):
queryset = super().get_queryset().all()
if hasattr(self, 'model'):
queryset = self.model.objects.all()
else:
assert self.queryset is None, (
"'%s' should not include a `queryset` attribute"
% self.__class__.__name__
)
queryset = super().get_queryset()
if hasattr(self, 'swagger_fake_view'):
return queryset[:1]
if hasattr(self, 'action') and self.action == 'list' and \

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 ..utils import (
set_current_org, get_current_org, current_org,
get_org_filters
)
from ..models import Organization
@ -19,42 +20,38 @@ __all__ = [
]
class OrgQuerySet(models.QuerySet):
pass
class OrgManager(models.Manager):
def get_queryset(self):
queryset = super(OrgManager, self).get_queryset()
kwargs = {}
_current_org = get_current_org()
if _current_org is None:
kwargs['id'] = None
elif _current_org.is_real():
kwargs['org_id'] = _current_org.id
elif _current_org.is_default():
queryset = queryset.filter(org_id="")
#
# lines = traceback.format_stack()
# print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
# for line in lines[-10:-1]:
# print(line)
# print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
queryset = queryset.filter(**kwargs)
queryset = super().get_queryset()
kwargs = get_org_filters()
if kwargs:
return queryset.filter(**kwargs)
return queryset
def all(self):
if not current_org:
msg = 'You can `objects.set_current_org(org).all()` then run it'
return self
else:
return super(OrgManager, self).all()
def set_current_org(self, org):
if isinstance(org, str):
org = Organization.get_instance(org)
set_current_org(org)
return self
def all(self):
# print("Call all: {}".format(current_org))
#
# lines = traceback.format_stack()
# print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
# for line in lines[-10:-1]:
# print(line)
# print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
if not current_org:
msg = 'You can `objects.set_current_org(org).all()` then run it'
return self
else:
return super().all()
class OrgModelMixin(models.Model):
org_id = models.CharField(max_length=36, blank=True, default='',
@ -65,9 +62,12 @@ class OrgModelMixin(models.Model):
def save(self, *args, **kwargs):
org = get_current_org()
if org is not None and (org.is_real() or org.is_system()):
if org is None:
return super().save(*args, **kwargs)
if org.is_real() or org.is_system():
self.org_id = org.id
elif org is not None and org.is_default():
elif org.is_default():
self.org_id = ''
return super().save(*args, **kwargs)

View File

@ -1,9 +1,13 @@
import re
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from users.models import User, UserGroup
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 common.serializers import AdaptedBulkListSerializer
from .utils import set_current_org, get_current_org
@ -18,6 +22,15 @@ class OrgSerializer(ModelSerializer):
fields = '__all__'
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):
admins = serializers.SlugRelatedField(slug_field='name', many=True, read_only=True)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
import traceback
from werkzeug.local import LocalProxy
from contextlib import contextmanager
@ -82,4 +83,31 @@ def tmp_to_org(org):
set_current_org(ori_org)
def get_org_filters():
kwargs = {}
_current_org = get_current_org()
if _current_org is None:
return kwargs
if _current_org.is_real():
kwargs['org_id'] = _current_org.id
elif _current_org.is_default():
kwargs["org_id"] = ''
return kwargs
def filter_org_queryset(queryset):
kwargs = get_org_filters()
#
# lines = traceback.format_stack()
# print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
# for line in lines[-10:-1]:
# print(line)
# print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
queryset = queryset.filter(**kwargs)
return queryset
current_org = LocalProxy(get_current_org)

View File

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

View File

@ -4,7 +4,7 @@
from rest_framework.generics import get_object_or_404
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.utils import get_logger
from orgs.utils import set_to_root_org
from orgs.utils import set_to_root_org, get_current_org, set_current_org, tmp_to_root_org
from ..hands import User, UserGroup
@ -17,15 +17,24 @@ __all__ = [
class UserPermissionMixin:
permission_classes = (IsOrgAdminOrAppUser,)
current_org = None
obj = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
self.current_org = get_current_org()
set_to_root_org()
self.obj = self.get_obj()
def get(self, request, *args, **kwargs):
set_to_root_org()
return super().get(request, *args, **kwargs)
# def dispatch(self, request, *args, **kwargs):
# """不能这么做,校验权限时拿不到组织了"""
# with tmp_to_root_org():
# return super().dispatch(request, *args, **kwargs)
# def get(self, request, *args, **kwargs):
# """有的api重写了get方法"""
# with tmp_to_root_org():
# return super().get(request, *args, **kwargs)
def get_obj(self):
user_id = self.kwargs.get('pk', '')
@ -40,6 +49,13 @@ class UserPermissionMixin:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
org = getattr(self, 'current_org', None)
if org:
set_current_org(org)
return response
class UserGroupPermissionMixin:
obj = None

View File

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

View File

@ -34,7 +34,7 @@ class UserNodeTreeMixin:
for node in nodes:
assets_amount = self.tree.valid_assets_amount(node.key)
if assets_amount == 0 and node.key != Node.empty_key:
if assets_amount == 0 and not node.key.startswith('-'):
continue
node.assets_amount = assets_amount
data = ParserNode.parse_node_to_tree_node(node)

View File

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

View File

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

View File

@ -29,7 +29,7 @@ class BasePermissionQuerySet(models.QuerySet):
return self.filter(is_active=False)
def invalid(self):
now = timezone.now
now = timezone.now()
q = (
Q(is_active=False) |
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 assets.utils import TreeService
from ..models import AssetPermission
from ..hands import Node, Asset, SystemUser
from ..hands import Node, Asset, SystemUser, User, FavoriteAsset
logger = get_logger(__file__)
@ -293,6 +293,20 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
parent=user_tree.root,
)
def add_favorite_node_if_need(self, user_tree):
if not isinstance(self.object, User):
return
node_key = Node.favorite_key
node_value = Node.favorite_value
user_tree.create_node(
identifier=node_key, tag=node_value,
parent=user_tree.root,
)
assets_id = FavoriteAsset.get_user_favorite_assets_id(self.object)
all_valid_assets = user_tree.all_valid_assets(user_tree.root)
valid_assets_id = set(assets_id) & all_valid_assets
user_tree.set_assets(node_key, valid_assets_id)
def set_user_tree_to_local(self, user_tree):
self._user_tree = user_tree
self._user_tree_filter_id = self._filter_id
@ -323,6 +337,7 @@ class AssetPermissionUtilV2(AssetPermissionUtilCacheMixin):
self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self.add_favorite_node_if_need(user_tree)
self.set_user_tree_to_cache_if_need(user_tree)
self.set_user_tree_to_local(user_tree)
return user_tree

View File

@ -101,9 +101,11 @@ class LDAPUserListApi(generics.ListAPIView):
def get_queryset(self):
if hasattr(self, 'swagger_fake_view'):
return []
util = LDAPUtil()
q = self.request.query_params.get('search')
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:
users = []
logger.error(e)
@ -112,20 +114,6 @@ class LDAPUserListApi(generics.ListAPIView):
user['id'] = user['username']
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):
order_by = self.request.query_params.get('order')
if not order_by:
@ -139,7 +127,7 @@ class LDAPUserListApi(generics.ListAPIView):
return queryset
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
queryset = self.get_queryset()
queryset = self.sort_queryset(queryset)
page = self.paginate_queryset(queryset)
if page is not None:

View File

@ -22,6 +22,9 @@ class LDAPOUGroupException(Exception):
class LDAPUtil:
_conn = None
SEARCH_FIELD_ALL = 'all'
SEARCH_FIELD_USERNAME = 'username'
def __init__(self, use_settings_config=True, server_uri=None, bind_dn=None,
password=None, use_ssl=None, search_ougroup=None,
search_filter=None, attr_map=None, auth_ldap=None):
@ -81,9 +84,13 @@ class LDAPUtil:
user_item[attr] = value
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(
search_ou, self.search_filter % ({"user": "*"}),
search_ou, search_filter,
attributes=list(self.attr_map.values()),
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']
return cookie
def search_user_items(self):
def search_user_items(self, extra_filter=None):
user_items = []
logger.info("Search user items")
for search_ou in str(self.search_ougroup).split("|"):
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)
while self._cookie():
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)
logger.info("Search user items end")
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):
user_items = self.search_user_items()
if username_list:
user_items = [u for u in user_items if u['username'] in username_list]
extra_filter = self.construct_extra_filter(
self.SEARCH_FIELD_USERNAME, username_list
)
user_items = self.search_user_items(extra_filter)
return user_items
@staticmethod

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
from datetime import datetime
from jms_storage.es import ESStorage
from .base import CommandBase
from .models import AbstractSessionCommand
@ -14,6 +15,13 @@ class CommandStore(ESStorage, CommandBase):
user=None, asset=None, system_user=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,
user=user, asset=asset, system_user=system_user,
input=input, session=session)

View File

@ -164,11 +164,14 @@ $(document).ready(function () {
detailRows.push(tr.attr('id'));
}
}
})
});
function format(d) {
var output = $("<pre style='border: none; background: none'></pre>");
output.append('$ ', d.input);
output.append('\r\n\r\n');
output.append(d.output);
return output
}
@ -187,6 +190,17 @@ function initTable() {
$(td).addClass("toggle");
$(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) {
var data = '<a href="{% url "terminal:session-detail" pk=DEFAULT_PK %}">{% trans "Goto" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData);
@ -200,7 +214,7 @@ function initTable() {
toggle: true,
ajax_url: commandListUrl,
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: "session", orderable: false}, {data: "timestamp", width: "160px", orderable: false},
],

View File

@ -54,8 +54,12 @@
{% for command in object_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ command.input }}</td>
<td><pre style="border: none;background: none">{{ command.output }}</pre></td>
<td>{{ command.input | truncatechars:40 }}</td>
<td><pre style="border: none;background: none">
$ {{ command.input }}
{{ command.output }}
</pre></td>
<td>{{ command.timestamp|ts_to_date}}</td>
</tr>
{% 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="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="protocol">{% trans 'Protocol' %}</a></li>
{# <li><a class="search-item" data-value="protocol">{% trans 'Protocol' %}</a></li>#}
</ul>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
{% 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-content mailbox-content" style="padding-top: 0">
<div class="file-manager ">
@ -11,7 +11,12 @@
</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="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>#}
@ -174,6 +179,27 @@ function loadLabels() {
}
}
var show = 0;
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
setTimeout(function () {
$(".table").css("width", "100%");
{#assetTable.columns.adjust();#}
}, 500)
}
$(document).ready(function () {
{#loadLabels()#}
}).on('click', '.labels-menu li', function () {

View File

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

View File

@ -28,7 +28,7 @@
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<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>
</div>
</div>

View File

@ -34,7 +34,7 @@
{% block custom_foot_js %}
<script>
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 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

View File

@ -325,3 +325,7 @@ def construct_user_email(username, email):
email = '{}@{}'.format(username, settings.EMAIL_SUFFIX)
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_SECRET:
# LDAP/AD 设置定时同步参数
# LDAP/AD settings
# 定时同步用户
# 启用/禁用
# AUTH_LDAP_SYNC_IS_PERIODIC: True
# 单位: 时
# AUTH_LDAP_SYNC_INTERVAL: 12
# Crontab 表达式
# AUTH_LDAP_SYNC_CRONTAB: * 6 * * *
#
# LDAP 用户登录时仅允许在用户列表中的用户执行 LDAP Server 认证
# AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS: False
# OTP settings
# OTP/MFA 配置

3
jms
View File

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