Unverified Commit 58875d9a authored by 老广's avatar 老广 Committed by GitHub

Bugfix (#2831)

* [Update] 修改小问题

* [Update] 添加重传guacamole的脚本

* [Update] 添加debug

* [Update] 优化可连接性

* [Update] 修改connectivity

* [Update] 更改查看认证需要的MFA时间间隔

* [Update] 修改表结构

* [Update] 修改users public_key等字段

* [Update] 修改用户表结构

* [Update] 修改assets users api

* [Update] 修改org mixin

* [Update] 解决连接windows资产出现幽灵会话的问题

* [Update] 优化树结构

* [Update] 修改Permission

* Stash

* [Update] 修改serializer

* [Update] 修改用户有权限的资产

* [Update] 修改upgrouped_node key的获取(解决操作日志中出现coco/gua的问题)

* [Update] 修改一些bug

* [Update] Debug cache

* [Bugfix] 修复用户页面不走cache的bug

* ipython

* [Update] 修改action

* [Bugfix] 修改校验系统用户资产动作权限的API逻辑

* [Update] 去掉原来批量的view

* [Bugfix] 会话/命令列表中获取用户列表排除app用户

* [Update] 修改用户授权资产API返回的queryset

* [Update] 修正migrations

* [Bugfix] 解决进入授权详情页的资产管理页面bug

* [Update] 修改Minxs

* [Update] 修改migrations

* [Update] 资产授权Model模块添加导入

* [Update] 优化命令记录列表

* [Update] 修改command列表

* [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug (#2874)

* [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug

* [Update] 如果用户授权节点为空,返回时添加空节点

* [Update] 修改command导出和搜索

* [Update] 修改session

* [Update] 修改Permission响应层缓存key

* [Update] 准备优化 asset user

* [Update] 修改去掉一些print

* [Bugfix] 修复initDataTable表格搜索栏位置错乱的问题,显示不友好问题 (#2880)

* [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则 (#2877)

* [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则

* [Bugfix]修复小问题

* [Update] 优化创建用户和更新用户密码的校验

* [Update] 优化用户表单校验password逻辑

* [Update] 小问题

* [Update] 修改command搜索

* [Update] 修改user group serialzier

* [Update] 优化资产

* [Update] 优化节点

* [Update] 优化用户组列表用户显示问题 (#2882)

* [Update] 解决select_for_update的错误

* [update] 修改Node无法被删除的bug

* [Update] 添加翻译

* [update] 修改资产导出的permssions

* [Bugfix] 修复删除节点bug (#2883)

* [update] 修改一些性能问题
parents 6273e6be 782bad91
...@@ -20,6 +20,7 @@ from common.mixins import IDInCacheFilterMixin, ApiMessageMixin ...@@ -20,6 +20,7 @@ from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
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 import OrgBulkModelViewSet
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node from ..models import Asset, AdminUser, Node
from .. import serializers from .. import serializers
...@@ -36,7 +37,7 @@ __all__ = [ ...@@ -36,7 +37,7 @@ __all__ = [
] ]
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet): class AssetViewSet(LabelFilter, ApiMessageMixin, OrgBulkModelViewSet):
""" """
API endpoint that allows Asset to be viewed or edited. API endpoint that allows Asset to be viewed or edited.
""" """
...@@ -100,11 +101,6 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModel ...@@ -100,11 +101,6 @@ class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModel
queryset = self.filter_admin_user_id(queryset) queryset = self.filter_admin_user_id(queryset)
return queryset return queryset
def get_queryset(self):
queryset = super().get_queryset().distinct()
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView): class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
""" """
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import time
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets, status, generics from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
...@@ -10,7 +8,7 @@ from rest_framework import filters ...@@ -10,7 +8,7 @@ from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from common.permissions import IsOrgAdminOrAppUser from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from common.mixins import IDInCacheFilterMixin from common.mixins import IDInCacheFilterMixin
from ..backends import AssetUserManager from ..backends import AssetUserManager
...@@ -57,7 +55,7 @@ class AssetUserSearchBackend(filters.BaseFilterBackend): ...@@ -57,7 +55,7 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer serializer_class = serializers.AssetUserSerializer
permission_classes = (IsOrgAdminOrAppUser, ) permission_classes = [IsOrgAdminOrAppUser]
http_method_names = ['get', 'post'] http_method_names = ['get', 'post']
filter_fields = [ filter_fields = [
"id", "ip", "hostname", "username", "asset_id", "node_id", "id", "ip", "hostname", "username", "asset_id", "node_id",
...@@ -78,7 +76,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -78,7 +76,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
system_user_id = self.request.GET.get("system_user_id") system_user_id = self.request.GET.get("system_user_id")
kwargs = {} kwargs = {}
assets = [] assets = None
manager = AssetUserManager() manager = AssetUserManager()
if system_user_id: if system_user_id:
...@@ -92,7 +90,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -92,7 +90,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
manager.prefer('admin_user') manager.prefer('admin_user')
if asset_id: if asset_id:
asset = get_object_or_none(Asset, pk=asset_id) asset = get_object_or_404(Asset, id=asset_id)
assets = [asset] assets = [asset]
elif node_id: elif node_id:
node = get_object_or_404(Node, id=node_id) node = get_object_or_404(Node, id=node_id)
...@@ -100,7 +98,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -100,7 +98,7 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
if username: if username:
kwargs['username'] = username kwargs['username'] = username
if assets: if assets is not None:
kwargs['assets'] = assets kwargs['assets'] = assets
queryset = manager.filter(**kwargs) queryset = manager.filter(**kwargs)
...@@ -110,23 +108,14 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): ...@@ -110,23 +108,14 @@ class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
class AssetUserExportViewSet(AssetUserViewSet): class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer serializer_class = serializers.AssetUserExportSerializer
http_method_names = ['get'] http_method_names = ['get']
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
def list(self, request, *args, **kwargs):
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
return super().list(request, *args, **kwargs)
class AssetUserAuthInfoApi(generics.RetrieveAPIView): class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
instance = self.get_object() instance = self.get_object()
serializer = self.get_serializer(instance) serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK status_code = status.HTTP_200_OK
...@@ -135,15 +124,14 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -135,15 +124,14 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
return Response(serializer.data, status=status_code) return Response(serializer.data, status=status_code)
def get_object(self): def get_object(self):
username = self.request.GET.get('username') query_params = self.request.query_params
asset_id = self.request.GET.get('asset_id') username = query_params.get('username')
prefer = self.request.GET.get("prefer") asset_id = query_params.get('asset_id')
prefer = query_params.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id) asset = get_object_or_none(Asset, pk=asset_id)
try: try:
manger = AssetUserManager() manger = AssetUserManager()
if prefer: instance = manger.get(username, asset, prefer=prefer)
manger.prefer(prefer)
instance = manger.get(username, asset)
except Exception as e: except Exception as e:
logger.error(e, exc_info=True) logger.error(e, exc_info=True)
return None return None
...@@ -156,13 +144,15 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -156,13 +144,15 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
Test asset users connective Test asset users connective
""" """
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.TaskIDSerializer
def get_asset_users(self): def get_asset_users(self):
username = self.request.GET.get('username') username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id') asset_id = self.request.GET.get('asset_id')
prefer = self.request.GET.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id) asset = get_object_or_none(Asset, pk=asset_id)
manager = AssetUserManager() manager = AssetUserManager()
asset_users = manager.filter(username=username, assets=[asset]) asset_users = manager.filter(username=username, assets=[asset], prefer=prefer)
return asset_users return asset_users
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
......
...@@ -26,6 +26,7 @@ from ..hands import IsOrgAdmin ...@@ -26,6 +26,7 @@ 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
from ..utils import NodeUtil
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -79,12 +80,10 @@ class NodeListAsTreeApi(generics.ListAPIView): ...@@ -79,12 +80,10 @@ class NodeListAsTreeApi(generics.ListAPIView):
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
def get_queryset(self): def get_queryset(self):
queryset = [node.as_tree_node() for node in Node.objects.all()] queryset = Node.objects.all()
return queryset util = NodeUtil()
nodes = util.get_nodes_by_queryset(queryset)
def filter_queryset(self, queryset): queryset = [node.as_tree_node() for node in nodes]
if self.request.query_params.get('refresh', '0') == '1':
queryset = self.refresh_nodes(queryset)
return queryset return queryset
@staticmethod @staticmethod
...@@ -113,16 +112,16 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -113,16 +112,16 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
is_root = False is_root = False
def get_queryset(self): def get_queryset(self):
self.check_need_refresh_nodes()
node_key = self.request.query_params.get('key') node_key = self.request.query_params.get('key')
if node_key: util = NodeUtil()
self.node = Node.objects.get(key=node_key) # 是否包含自己
queryset = self.node.get_children(with_self=False) with_self = False
else: if not node_key:
self.is_root = True node_key = Node.root().key
self.node = Node.root() with_self = True
queryset = list(self.node.get_children(with_self=True)) self.node = util.get_node_by_key(node_key)
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key) queryset = self.node.get_children(with_self=with_self)
queryset.extend(list(nodes_invalid))
queryset = [node.as_tree_node() for node in queryset] queryset = [node.as_tree_node() for node in queryset]
queryset = sorted(queryset) queryset = sorted(queryset)
return queryset return queryset
...@@ -131,21 +130,20 @@ class NodeChildrenAsTreeApi(generics.ListAPIView): ...@@ -131,21 +130,20 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
include_assets = self.request.query_params.get('assets', '0') == '1' include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets: if not include_assets:
return queryset return queryset
assets = self.node.get_assets() assets = self.node.get_assets().prefetch_related("protocols").only(
"id", "hostname", "ip", 'platform', "os", "org_id",
)
for asset in assets: for asset in assets:
queryset.append(asset.as_tree_node(self.node)) queryset.append(asset.as_tree_node(self.node))
return queryset return queryset
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
queryset = self.filter_assets(queryset) queryset = self.filter_assets(queryset)
queryset = self.filter_refresh_nodes(queryset)
return queryset return queryset
def filter_refresh_nodes(self, queryset): def check_need_refresh_nodes(self):
if self.request.query_params.get('refresh', '0') == '1': if self.request.query_params.get('refresh', '0') == '1':
Node.expire_nodes_assets_amount() Node.refresh_nodes()
Node.expire_nodes_full_value()
return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
......
...@@ -22,6 +22,7 @@ from rest_framework.pagination import LimitOffsetPagination ...@@ -22,6 +22,7 @@ from rest_framework.pagination import LimitOffsetPagination
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 common.mixins import IDInCacheFilterMixin from common.mixins import IDInCacheFilterMixin
from orgs.mixins import OrgBulkModelViewSet
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
from .. import serializers from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \ from ..tasks import push_system_user_to_assets_manual, \
...@@ -39,7 +40,7 @@ __all__ = [ ...@@ -39,7 +40,7 @@ __all__ = [
] ]
class SystemUserViewSet(IDInCacheFilterMixin, BulkModelViewSet): 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
""" """
......
...@@ -14,6 +14,11 @@ class AssetUserBackend(BaseBackend): ...@@ -14,6 +14,11 @@ class AssetUserBackend(BaseBackend):
@classmethod @classmethod
def filter(cls, username=None, assets=None, **kwargs): def filter(cls, username=None, assets=None, **kwargs):
queryset = cls.model.objects.all() queryset = cls.model.objects.all()
prefer_id = kwargs.get('prefer_id')
if prefer_id:
queryset = queryset.filter(id=prefer_id)
instances = cls.construct_authbook_objects(queryset, assets)
return instances
if username: if username:
queryset = queryset.filter(username=username) queryset = queryset.filter(username=username)
if assets: if assets:
......
...@@ -7,11 +7,13 @@ from abc import abstractmethod ...@@ -7,11 +7,13 @@ from abc import abstractmethod
class BaseBackend: class BaseBackend:
@classmethod @classmethod
@abstractmethod @abstractmethod
def filter(cls, username=None, assets=None, latest=True): def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
""" """
:param username: 用户名 :param username: 用户名
:param assets: <Asset>对象 :param assets: <Asset>对象
:param latest: 是否是最新记录 :param latest: 是否是最新记录
:param prefer: 优先使用
:param prefer_id: 使用id
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>) :return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
""" """
pass pass
......
...@@ -7,7 +7,7 @@ from .base import BaseBackend ...@@ -7,7 +7,7 @@ from .base import BaseBackend
class AuthBookBackend(BaseBackend): class AuthBookBackend(BaseBackend):
@classmethod @classmethod
def filter(cls, username=None, assets=None, latest=True): def filter(cls, username=None, assets=None, latest=True, **kwargs):
queryset = AuthBook.objects.all() queryset = AuthBook.objects.all()
if username is not None: if username is not None:
queryset = queryset.filter(username=username) queryset = queryset.filter(username=username)
......
...@@ -30,21 +30,22 @@ class AssetUserManager: ...@@ -30,21 +30,22 @@ class AssetUserManager:
) )
_prefer = "system_user" _prefer = "system_user"
_using = None
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
def filter(self, username=None, assets=None, latest=True): if assets is not None and not assets:
if self._using: return AssetUserQuerySet([])
backend = dict(self.backends).get(self._using)
if not backend: if prefer:
return self.none() self._prefer = prefer
instances = backend.filter(username=username, assets=assets, latest=latest)
return AssetUserQuerySet(instances)
instances_map = {} instances_map = {}
instances = [] instances = []
for name, backend in self.backends: for name, backend in self.backends:
if name != "db" and self._prefer != name:
continue
_instances = backend.filter( _instances = backend.filter(
username=username, assets=assets, latest=latest username=username, assets=assets, latest=latest,
prefer=self._prefer, prefer_id=prefer_id,
) )
instances_map[name] = _instances instances_map[name] = _instances
...@@ -61,12 +62,12 @@ class AssetUserManager: ...@@ -61,12 +62,12 @@ class AssetUserManager:
else: else:
ordering.extend(["admin_user", "system_user"]) ordering.extend(["admin_user", "system_user"])
# 根据prefer决定优先使用系统用户或管理用户谁的 # 根据prefer决定优先使用系统用户或管理用户谁的
ordering_instances = [instances_map.get(i) for i in ordering] ordering_instances = [instances_map.get(i, []) for i in ordering]
instances = self._merge_instances(*ordering_instances) instances = self._merge_instances(*ordering_instances)
return AssetUserQuerySet(instances) return AssetUserQuerySet(instances)
def get(self, username, asset): def get(self, username, asset, **kwargs):
instances = self.filter(username, assets=[asset]) instances = self.filter(username, assets=[asset], **kwargs)
if len(instances) == 1: if len(instances) == 1:
return instances[0] return instances[0]
elif len(instances) == 0: elif len(instances) == 0:
...@@ -92,10 +93,6 @@ class AssetUserManager: ...@@ -92,10 +93,6 @@ class AssetUserManager:
self._prefer = s self._prefer = s
return self return self
def using(self, s):
self._using = s
return self
@staticmethod @staticmethod
def none(): def none():
return AssetUserQuerySet() return AssetUserQuerySet()
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
UPDATE_ASSETS_HARDWARE_TASKS = [ UPDATE_ASSETS_HARDWARE_TASKS = [
{ {
...@@ -11,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [ ...@@ -11,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
} }
] ]
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
TEST_ADMIN_USER_CONN_TASKS = [ TEST_ADMIN_USER_CONN_TASKS = [
{ {
"name": "ping", "name": "ping",
...@@ -49,7 +48,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [ ...@@ -49,7 +48,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
} }
] ]
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}'
TEST_ASSET_USER_CONN_TASKS = [ TEST_ASSET_USER_CONN_TASKS = [
{ {
"name": "ping", "name": "ping",
...@@ -74,5 +72,10 @@ TASK_OPTIONS = { ...@@ -74,5 +72,10 @@ TASK_OPTIONS = {
} }
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(CONN_UNREACHABLE, _("Unreachable")),
(CONN_REACHABLE, _('Reachable')),
(CONN_UNKNOWN, _("Unknown")),
)
...@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _ ...@@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from ..models import Asset, Protocol from ..models import Asset, Protocol, Node
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -33,6 +33,12 @@ class ProtocolForm(forms.ModelForm): ...@@ -33,6 +33,12 @@ class ProtocolForm(forms.ModelForm):
class AssetCreateForm(OrgModelForm): class AssetCreateForm(OrgModelForm):
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.data:
nodes_field = self.fields['nodes']
nodes_field._queryset = Node.get_queryset()
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django import forms from django import forms
from django.core.exceptions import ValidationError
import re
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from ..models import CommandFilter, CommandFilterRule from ..models import CommandFilter, CommandFilterRule
...@@ -15,6 +17,8 @@ class CommandFilterForm(OrgModelForm): ...@@ -15,6 +17,8 @@ class CommandFilterForm(OrgModelForm):
class CommandFilterRuleForm(OrgModelForm): class CommandFilterRuleForm(OrgModelForm):
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
class Meta: class Meta:
model = CommandFilterRule model = CommandFilterRule
fields = [ fields = [
...@@ -25,3 +29,11 @@ class CommandFilterRuleForm(OrgModelForm): ...@@ -25,3 +29,11 @@ class CommandFilterRuleForm(OrgModelForm):
'placeholder': 'eg:\r\nreboot\r\nrm -rf' 'placeholder': 'eg:\r\nreboot\r\nrm -rf'
}), }),
} }
def clean_content(self):
content = self.cleaned_data.get("content")
if self.invalid_pattern.search(content):
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
msg = _("Content should not be contain: {}").format(invalid_char)
raise ValidationError(msg)
return content
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-05 10:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='adminuser',
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
),
migrations.AlterModelOptions(
name='asset',
options={'verbose_name': 'Asset'},
),
migrations.AlterModelOptions(
name='assetgroup',
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
),
migrations.AlterModelOptions(
name='cluster',
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
),
migrations.AlterModelOptions(
name='systemuser',
options={'ordering': ['name'], 'verbose_name': 'System user'},
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-09 15:31
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0002_auto_20180105_1807'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='cluster',
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-25 04:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0003_auto_20180109_2331'),
]
operations = [
migrations.AlterField(
model_name='assetgroup',
name='created_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-26 08:37
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0004_auto_20180125_1218'),
]
operations = [
migrations.CreateModel(
name='Label',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('value', models.CharField(max_length=128, verbose_name='Value')),
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
],
options={
'db_table': 'assets_label',
},
),
migrations.AlterUniqueTogether(
name='label',
unique_together=set([('name', 'value')]),
),
migrations.AddField(
model_name='asset',
name='labels',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-30 07:02
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0005_auto_20180126_1637'),
]
operations = [
migrations.RemoveField(
model_name='asset',
name='cabinet_no',
),
migrations.RemoveField(
model_name='asset',
name='cabinet_pos',
),
migrations.RemoveField(
model_name='asset',
name='env',
),
migrations.RemoveField(
model_name='asset',
name='remote_card_ip',
),
migrations.RemoveField(
model_name='asset',
name='status',
),
migrations.RemoveField(
model_name='asset',
name='type',
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-02-25 10:15
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0006_auto_20180130_1502'),
]
operations = [
migrations.CreateModel(
name='Node',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
('child_mark', models.IntegerField(default=0)),
('date_create', models.DateTimeField(auto_now_add=True)),
],
),
migrations.RemoveField(
model_name='asset',
name='cluster',
),
migrations.RemoveField(
model_name='asset',
name='groups',
),
migrations.RemoveField(
model_name='systemuser',
name='cluster',
),
migrations.AlterField(
model_name='asset',
name='admin_user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',
name='nodes',
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-06 10:04
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0007_auto_20180225_1815'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='systemuser',
name='created_by',
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=128, verbose_name='Username'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 04:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0008_auto_20180306_1804'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 09:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0009_auto_20180307_1212'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-26 01:57
from __future__ import unicode_literals
import assets.models.utils
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0010_auto_20180307_1749'),
]
operations = [
migrations.CreateModel(
name='Domain',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('comment', models.TextField(blank=True, verbose_name='Comment')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
],
),
migrations.CreateModel(
name='Gateway',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('username', models.CharField(max_length=128, verbose_name='Username')),
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
('date_created', models.DateTimeField(auto_now_add=True)),
('date_updated', models.DateTimeField(auto_now=True)),
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
('port', models.IntegerField(default=22, verbose_name='Port')),
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-04 05:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0011_auto_20180326_0957'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='domain',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-11 03:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0012_auto_20180404_1302'),
]
operations = [
migrations.AddField(
model_name='systemuser',
name='assets',
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
),
migrations.AlterField(
model_name='systemuser',
name='sudo',
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-27 04:45
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0013_auto_20180411_1135'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-10 04:35
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0014_auto_20180427_1245'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-11 04:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0015_auto_20180510_1235'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-07-02 06:15
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
def migrate_win_to_ssh_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
class Migration(migrations.Migration):
dependencies = [
('assets', '0016_auto_20180511_1203'),
]
operations = [
migrations.AddField(
model_name='asset',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
),
migrations.AddField(
model_name='systemuser',
name='login_mode',
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
),
migrations.AlterField(
model_name='adminuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='asset',
name='platform',
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
),
migrations.AlterField(
model_name='gateway',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='systemuser',
name='username',
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
),
migrations.RunPython(migrate_win_to_ssh_protocol),
]
# Generated by Django 2.0.7 on 2018-08-07 03:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0017_auto_20180702_1415'),
]
operations = [
migrations.AddField(
model_name='adminuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='asset',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='domain',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='gateway',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='label',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='node',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AddField(
model_name='systemuser',
name='org_id',
field=models.CharField(blank=True, default=None, max_length=36, null=True),
),
migrations.AlterField(
model_name='adminuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='asset',
name='hostname',
field=models.CharField(max_length=128, verbose_name='Hostname'),
),
migrations.AlterField(
model_name='gateway',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterField(
model_name='systemuser',
name='name',
field=models.CharField(max_length=128, verbose_name='Name'),
),
migrations.AlterUniqueTogether(
name='adminuser',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='asset',
unique_together={('org_id', 'hostname')},
),
migrations.AlterUniqueTogether(
name='gateway',
unique_together={('name', 'org_id')},
),
migrations.AlterUniqueTogether(
name='systemuser',
unique_together={('name', 'org_id')},
),
]
# Generated by Django 2.0.7 on 2018-08-16 05:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0018_auto_20180807_1116'),
]
operations = [
migrations.AddField(
model_name='asset',
name='cpu_vcpus',
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value', 'org_id')},
),
]
# Generated by Django 2.1.7 on 2019-06-24 13:08
import assets.models.utils
import common.fields.model
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0031_auto_20190621_1332'),
]
operations = [
migrations.AlterField(
model_name='adminuser',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='adminuser',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='adminuser',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='authbook',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='authbook',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='authbook',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='gateway',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='gateway',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='gateway',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
migrations.AlterField(
model_name='systemuser',
name='_password',
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
),
migrations.AlterField(
model_name='systemuser',
name='_private_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
),
migrations.AlterField(
model_name='systemuser',
name='_public_key',
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
),
]
# Generated by Django 2.1.7 on 2019-06-24 13:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0032_auto_20190624_2108'),
]
operations = [
migrations.RenameField(
model_name='adminuser',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='adminuser',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='authbook',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='authbook',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='gateway',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='gateway',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='systemuser',
old_name='_private_key',
new_name='private_key',
),
migrations.RenameField(
model_name='systemuser',
old_name='_public_key',
new_name='public_key',
),
migrations.RenameField(
model_name='adminuser',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='authbook',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='gateway',
old_name='_password',
new_name='password',
),
migrations.RenameField(
model_name='systemuser',
old_name='_password',
new_name='password',
),
]
from .user import * from .asset import *
from .label import Label from .label import Label
from .user import *
from .cluster import * from .cluster import *
from .group import * from .group import *
from .domain import * from .domain import *
from .node import * from .node import *
from .asset import *
from .cmd_filter import * from .cmd_filter import *
from .utils import *
from .authbook import * from .authbook import *
from .utils import *
...@@ -6,15 +6,13 @@ import uuid ...@@ -6,15 +6,13 @@ import uuid
import logging import logging
import random import random
from functools import reduce from functools import reduce
from collections import defaultdict
from django.db import models from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from .utils import Connectivity
from orgs.mixins import OrgModelMixin, OrgManager from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset', 'Protocol'] __all__ = ['Asset', 'Protocol']
...@@ -48,12 +46,6 @@ class AssetQuerySet(models.QuerySet): ...@@ -48,12 +46,6 @@ class AssetQuerySet(models.QuerySet):
return self.active() return self.active()
class AssetManager(OrgManager):
def get_queryset(self):
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
return queryset
class Protocol(models.Model): class Protocol(models.Model):
PROTOCOL_SSH = 'ssh' PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp' PROTOCOL_RDP = 'rdp'
...@@ -133,14 +125,8 @@ class Asset(OrgModelMixin): ...@@ -133,14 +125,8 @@ class Asset(OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = AssetManager.from_queryset(AssetQuerySet)() objects = OrgManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}' _connectivity = None
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
def __str__(self): def __str__(self):
return '{0.hostname}({0.ip})'.format(self) return '{0.hostname}({0.ip})'.format(self)
...@@ -215,20 +201,6 @@ class Asset(OrgModelMixin): ...@@ -215,20 +201,6 @@ class Asset(OrgModelMixin):
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes)) nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes return nodes
@classmethod
def get_queryset_by_fullname_list(cls, fullname_list):
org_fullname_map = defaultdict(list)
for fullname in fullname_list:
hostname, org = cls.split_fullname(fullname)
org_fullname_map[org].append(hostname)
filter_arg = Q()
for org, hosts in org_fullname_map.items():
if org.is_real():
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
else:
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
return Asset.objects.filter(filter_arg)
@property @property
def cpu_info(self): def cpu_info(self):
info = "" info = ""
...@@ -250,15 +222,18 @@ class Asset(OrgModelMixin): ...@@ -250,15 +222,18 @@ class Asset(OrgModelMixin):
@property @property
def connectivity(self): def connectivity(self):
if self._connectivity:
return self._connectivity
if not self.admin_user: if not self.admin_user:
return self.UNKNOWN return Connectivity.unknown()
return self.admin_user.get_connectivity_of(self) connectivity = self.admin_user.get_asset_connectivity(self)
return connectivity
@connectivity.setter @connectivity.setter
def connectivity(self, value): def connectivity(self, value):
if not self.admin_user: if not self.admin_user:
return return
self.admin_user.set_connectivity_of(self, value) self.admin_user.set_asset_connectivity(self, value)
def get_auth_info(self): def get_auth_info(self):
if not self.admin_user: if not self.admin_user:
...@@ -321,15 +296,20 @@ class Asset(OrgModelMixin): ...@@ -321,15 +296,20 @@ class Asset(OrgModelMixin):
@classmethod @classmethod
def generate_fake(cls, count=100): def generate_fake(cls, count=100):
from random import seed, choice from random import seed, choice
import forgery_py
from django.db import IntegrityError from django.db import IntegrityError
from .node import Node from .node import Node
from orgs.utils import get_current_org
from orgs.models import Organization
org = get_current_org()
if not org or not org.is_real():
Organization.default().change_to()
nodes = list(Node.objects.all()) nodes = list(Node.objects.all())
seed() seed()
for i in range(count): for i in range(count):
ip = [str(i) for i in random.sample(range(255), 4)] ip = [str(i) for i in random.sample(range(255), 4)]
asset = cls(ip='.'.join(ip), asset = cls(ip='.'.join(ip),
hostname=forgery_py.internet.user_name(True), hostname='.'.join(ip),
admin_user=choice(AdminUser.objects.all()), admin_user=choice(AdminUser.objects.all()),
created_by='Fake') created_by='Fake')
try: try:
......
...@@ -3,12 +3,9 @@ ...@@ -3,12 +3,9 @@
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from orgs.mixins import OrgManager from orgs.mixins import OrgManager
from .base import AssetUser from .base import AssetUser
from ..const import ASSET_USER_CONN_CACHE_KEY
__all__ = ['AuthBook'] __all__ = ['AuthBook']
...@@ -32,6 +29,7 @@ class AuthBook(AssetUser): ...@@ -32,6 +29,7 @@ class AuthBook(AssetUser):
backend = "db" backend = "db"
# 用于system user和admin_user的动态设置 # 用于system user和admin_user的动态设置
_connectivity = None _connectivity = None
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
class Meta: class Meta:
verbose_name = _('AuthBook') verbose_name = _('AuthBook')
...@@ -65,20 +63,15 @@ class AuthBook(AssetUser): ...@@ -65,20 +63,15 @@ class AuthBook(AssetUser):
self._set_version() self._set_version()
self._set_latest() self._set_latest()
@property def get_related_assets(self):
def _conn_cache_key(self): return [self.asset]
return ASSET_USER_CONN_CACHE_KEY.format(self.id)
def generate_id_with_asset(self, asset):
return self.id
@property @property
def connectivity(self): def connectivity(self):
if self._connectivity: return self.get_asset_connectivity(self.asset)
return self._connectivity
value = cache.get(self._conn_cache_key, self.UNKNOWN)
return value
@connectivity.setter
def connectivity(self, value):
cache.set(self._conn_cache_key, value, 3600)
@property @property
def keyword(self): def keyword(self):
......
This diff is collapsed.
This diff is collapsed.
...@@ -4,13 +4,11 @@ ...@@ -4,13 +4,11 @@
import logging import logging
from django.core.cache import cache
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import get_signer from common.utils import get_signer
from ..const import SYSTEM_USER_CONN_CACHE_KEY
from .base import AssetUser from .base import AssetUser
...@@ -31,7 +29,7 @@ class AdminUser(AssetUser): ...@@ -31,7 +29,7 @@ class AdminUser(AssetUser):
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4) become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
become_user = models.CharField(default='root', max_length=64) become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128) _become_pass = models.CharField(default='', max_length=128)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}' CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user" _prefer = "admin_user"
def __str__(self): def __str__(self):
...@@ -61,31 +59,6 @@ class AdminUser(AssetUser): ...@@ -61,31 +59,6 @@ class AdminUser(AssetUser):
info = None info = None
return info return info
def get_related_assets(self):
assets = self.assets.all()
return assets
@property
def assets_amount(self):
return self.get_related_assets().count()
@property
def connectivity(self):
from .asset import Asset
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
data = {
'unreachable': [],
'reachable': [],
}
for asset_id, hostname in assets:
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
value = cache.get(key, Asset.UNKNOWN)
if value == Asset.REACHABLE:
data['reachable'].append(hostname)
elif value == Asset.UNREACHABLE:
data['unreachable'].append(hostname)
return data
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('name', 'org_id')] unique_together = [('name', 'org_id')]
...@@ -141,9 +114,6 @@ class SystemUser(AssetUser): ...@@ -141,9 +114,6 @@ class SystemUser(AssetUser):
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode')) login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True) cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
...@@ -157,49 +127,6 @@ class SystemUser(AssetUser): ...@@ -157,49 +127,6 @@ class SystemUser(AssetUser):
'auto_push': self.auto_push, 'auto_push': self.auto_push,
} }
def get_related_assets(self):
assets = set(self.assets.all())
return assets
@property
def connectivity(self):
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
value = cache.get(cache_key, None)
if not value or 'unreachable' not in value:
return {'unreachable': [], 'reachable': []}
else:
return value
@connectivity.setter
def connectivity(self, value):
data = self.connectivity
unreachable = data['unreachable']
reachable = data['reachable']
assets = {asset.hostname: asset for asset in self.assets.all()}
for host in value.get('dark', {}).keys():
if host not in unreachable:
unreachable.append(host)
if host in reachable:
reachable.remove(host)
self.set_connectivity_of(assets.get(host), self.UNREACHABLE)
for host in value.get('contacted'):
if host not in reachable:
reachable.append(host)
if host in unreachable:
unreachable.remove(host)
self.set_connectivity_of(assets.get(host), self.REACHABLE)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600)
@property
def assets_unreachable(self):
return self.connectivity.get('unreachable')
@property
def assets_reachable(self):
return self.connectivity.get('reachable')
@property @property
def login_mode_display(self): def login_mode_display(self):
return self.get_login_mode_display() return self.get_login_mode_display()
...@@ -210,12 +137,6 @@ class SystemUser(AssetUser): ...@@ -210,12 +137,6 @@ class SystemUser(AssetUser):
else: else:
return False return False
def set_cache(self):
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
def expire_cache(self):
cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
@property @property
def cmd_filter_rules(self): def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule from .cmd_filter import CommandFilterRule
...@@ -233,18 +154,6 @@ class SystemUser(AssetUser): ...@@ -233,18 +154,6 @@ class SystemUser(AssetUser):
return False, matched_cmd return False, matched_cmd
return True, None return True, None
@classmethod
def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
if cached:
return cached
try:
system_user = cls.objects.get(id=sid)
system_user.set_cache()
return system_user
except cls.DoesNotExist:
return None
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
unique_together = [('name', 'org_id')] unique_together = [('name', 'org_id')]
......
...@@ -2,11 +2,17 @@ ...@@ -2,11 +2,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils import timezone
from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from common.utils import validate_ssh_private_key from common.utils import validate_ssh_private_key
__all__ = ['init_model', 'generate_fake'] __all__ = [
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
]
def init_model(): def init_model():
...@@ -31,5 +37,72 @@ def private_key_validator(value): ...@@ -31,5 +37,72 @@ def private_key_validator(value):
) )
if __name__ == '__main__': class Connectivity:
pass UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
status = UNKNOWN
datetime = timezone.now()
def __init__(self, status, datetime):
self.status = status
self.datetime = datetime
def display(self):
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
def is_reachable(self):
return self.status == self.REACHABLE
def is_unreachable(self):
return self.status == self.UNREACHABLE
def is_unknown(self):
return self.status == self.UNKNOWN
@classmethod
def unreachable(cls):
return cls(cls.UNREACHABLE, timezone.now())
@classmethod
def reachable(cls):
return cls(cls.REACHABLE, timezone.now())
@classmethod
def unknown(cls):
return cls(cls.UNKNOWN, timezone.now())
@classmethod
def set(cls, key, value, ttl=0):
cache.set(key, value, ttl)
@classmethod
def get(cls, key):
value = cache.get(key, cls.unknown())
if not isinstance(value, cls):
value = cls.unknown()
return value
@classmethod
def set_unreachable(cls, key, ttl=0):
cls.set(key, cls.unreachable(), ttl)
@classmethod
def set_reachable(cls, key, ttl=0):
cls.set(key, cls.reachable(), ttl)
def __eq__(self, other):
return self.status == other.status
def __gt__(self, other):
return self.status > other.status
def __lt__(self, other):
return not self.__gt__(other)
def __str__(self):
return self.display()
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.core.cache import cache
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
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from .base import AuthSerializer from .base import AuthSerializer
...@@ -17,54 +15,27 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer): ...@@ -17,54 +15,27 @@ class AdminUserSerializer(BulkOrgResourceModelSerializer):
""" """
管理用户 管理用户
""" """
password = serializers.CharField(
required=False, write_only=True, label=_('Password')
)
unreachable_amount = serializers.SerializerMethodField(label=_('Unreachable'))
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
class Meta: class Meta:
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
model = AdminUser model = AdminUser
fields = [ fields = [
'id', 'name', 'username', 'assets_amount', 'id', 'name', 'username', 'password', 'private_key', 'public_key',
'reachable_amount', 'unreachable_amount', 'password', 'comment', 'comment', 'connectivity_amount', 'assets_amount',
'date_created', 'date_updated', 'become', 'become_method', 'date_created', 'date_updated', 'created_by',
'become_user', 'created_by',
] ]
extra_kwargs = { extra_kwargs = {
'date_created': {'label': _('Date created')}, 'password': {"write_only": True},
'date_updated': {'label': _('Date updated')}, 'private_key': {"write_only": True},
'become': {'read_only': True}, 'become_method': {'read_only': True}, 'public_key': {"write_only": True},
'become_user': {'read_only': True}, 'created_by': {'read_only': True} 'date_created': {'read_only': True},
'date_updated': {'read_only': True},
'created_by': {'read_only': True},
'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
} }
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
return [f for f in fields if not f.startswith('_')]
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('dark'))
else:
return 0
@staticmethod
def get_reachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('contacted'))
else:
return 0
@staticmethod
def get_assets_amount(obj):
return obj.assets_amount
class AdminUserAuthSerializer(AuthSerializer): class AdminUserAuthSerializer(AuthSerializer):
......
...@@ -2,17 +2,17 @@ ...@@ -2,17 +2,17 @@
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework.validators import ValidationError from rest_framework.validators import ValidationError
from django.db.models import Prefetch
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Protocol from ..models import Asset, Protocol, Node, Label
from .system_user import AssetSystemUserSerializer from .base import ConnectivitySerializer
__all__ = [ __all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer', 'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolSerializer', 'ProtocolSerializer', 'ProtocolsRelatedField',
] ]
...@@ -43,6 +43,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -43,6 +43,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
protocols = ProtocolsRelatedField( protocols = ProtocolsRelatedField(
many=True, queryset=Protocol.objects.all(), label=_("Protocols") many=True, queryset=Protocol.objects.all(), label=_("Protocols")
) )
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
""" """
资产的数据结构 资产的数据结构
...@@ -57,7 +58,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -57,7 +58,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created', 'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity' 'hardware_info', 'connectivity',
] ]
read_only_fields = ( read_only_fields = (
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
...@@ -69,15 +70,17 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -69,15 +70,17 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'protocol': {'write_only': True}, 'protocol': {'write_only': True},
'port': {'write_only': True}, 'port': {'write_only': True},
'hardware_info': {'label': _('Hardware info')}, 'hardware_info': {'label': _('Hardware info')},
'connectivity': {'label': _('Connectivity')},
'org_name': {'label': _('Org name')} 'org_name': {'label': _('Org name')}
} }
@classmethod @classmethod
def setup_eager_loading(cls, queryset): def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('labels', 'nodes')\ queryset = queryset.prefetch_related(
.select_related('admin_user') Prefetch('nodes', queryset=Node.objects.all().only('id')),
Prefetch('labels', queryset=Label.objects.all().only('id')),
'protocols'
).select_related('admin_user', 'domain')
return queryset return queryset
@staticmethod @staticmethod
...@@ -138,54 +141,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -138,54 +141,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
return instance return instance
# class AssetAsNodeSerializer(serializers.ModelSerializer):
# protocols = ProtocolSerializer(many=True)
#
# class Meta:
# model = Asset
# fields = ['id', 'hostname', 'ip', 'platform', 'protocols']
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构
"""
protocols = ProtocolsRelatedField(
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
)
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField()
# nodes = NodeTMPSerializer(many=True, read_only=True)
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "protocol", "port", "protocols",
"system_users_granted", "is_active", "system_users_join", "os",
'domain', "platform", "comment", "org_id", "org_name",
)
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
# class MyAssetGrantedSerializer(AssetGrantedSerializer):
# """
# 普通用户获取授权的资产定义的数据结构
# """
# protocols = ProtocolSerializer(many=True)
#
# class Meta:
# model = Asset
# fields = (
# "id", "hostname", "system_users_granted",
# "is_active", "system_users_join", "org_name",
# "os", "platform", "comment", "org_id", "protocols"
# )
class AssetSimpleSerializer(serializers.ModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import serializers from rest_framework import serializers
from ..models import AuthBook, Asset
from ..backends import AssetUserManager
from common.utils import validate_ssh_private_key from common.utils import validate_ssh_private_key
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import AuthBook, Asset
from ..backends import AssetUserManager
from .base import ConnectivitySerializer
__all__ = [ __all__ = [
...@@ -26,20 +27,8 @@ class BasicAssetSerializer(serializers.ModelSerializer): ...@@ -26,20 +27,8 @@ class BasicAssetSerializer(serializers.ModelSerializer):
class AssetUserSerializer(BulkOrgResourceModelSerializer): class AssetUserSerializer(BulkOrgResourceModelSerializer):
hostname = serializers.CharField(read_only=True, label=_("Hostname")) hostname = serializers.CharField(read_only=True, label=_("Hostname"))
ip = serializers.CharField(read_only=True, label=_("IP")) ip = serializers.CharField(read_only=True, label=_("IP"))
connectivity = serializers.CharField(read_only=True, label=_("Connectivity")) connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True, write_only=True,
required=False, label=_('Password')
)
public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, label=_('Public key')
)
private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
required=False, label=_('Private key')
)
backend = serializers.CharField(read_only=True, label=_("Backend")) backend = serializers.CharField(read_only=True, label=_("Backend"))
class Meta: class Meta:
...@@ -56,6 +45,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer): ...@@ -56,6 +45,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer):
] ]
extra_kwargs = { extra_kwargs = {
'username': {'required': True}, 'username': {'required': True},
'password': {'write_only': True},
'private_key': {'write_only': True},
'public_key': {'write_only': True},
} }
def validate_private_key(self, key): def validate_private_key(self, key):
...@@ -66,17 +58,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer): ...@@ -66,17 +58,9 @@ class AssetUserSerializer(BulkOrgResourceModelSerializer):
return key return key
def create(self, validated_data): def create(self, validated_data):
kwargs = { if not validated_data.get("name") and validated_data.get("username"):
'name': validated_data.get('username'), validated_data["name"] = validated_data["username"]
'username': validated_data.get('username'), instance = AssetUserManager.create(**validated_data)
'asset': validated_data.get('asset'),
'comment': validated_data.get('comment', ''),
'org_id': validated_data.get('org_id', ''),
'password': validated_data.get('password'),
'public_key': validated_data.get('public_key'),
'private_key': validated_data.get('private_key')
}
instance = AssetUserManager.create(**kwargs)
return instance return instance
......
...@@ -24,3 +24,8 @@ class AuthSerializer(serializers.ModelSerializer): ...@@ -24,3 +24,8 @@ class AuthSerializer(serializers.ModelSerializer):
self.instance.set_auth(password=password, private_key=private_key, self.instance.set_auth(password=password, private_key=private_key,
public_key=public_key) public_key=public_key)
return self.instance return self.instance
class ConnectivitySerializer(serializers.Serializer):
status = serializers.IntegerField()
datetime = serializers.DateTimeField()
\ No newline at end of file
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import re
from rest_framework import serializers from rest_framework import serializers
from common.fields import ChoiceDisplayField from common.fields import ChoiceDisplayField
...@@ -20,8 +21,16 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer): ...@@ -20,8 +21,16 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer):
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
serializer_choice_field = ChoiceDisplayField serializer_choice_field = ChoiceDisplayField
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
class Meta: class Meta:
model = CommandFilterRule model = CommandFilterRule
fields = '__all__' fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
def validate_content(self, content):
if self.invalid_pattern.search(content):
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
msg = _("Content should not be contain: {}").format(invalid_char)
raise serializers.ValidationError(msg)
return content
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext as _
from orgs.mixins import BulkOrgResourceModelSerializer from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import Asset, Node from ..models import Asset, Node
...@@ -25,11 +26,11 @@ class NodeSerializer(BulkOrgResourceModelSerializer): ...@@ -25,11 +26,11 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
def validate_value(self, data): def validate_value(self, data):
instance = self.instance if self.instance else Node.root() instance = self.instance if self.instance else Node.root()
children = instance.parent.get_children().exclude(key=instance.key) children = instance.parent.get_children()
values = [child.value for child in children] children_values = [node.value for node in children if node != instance]
if data in values: if data in children_values:
raise serializers.ValidationError( raise serializers.ValidationError(
'The same level node name cannot be the same' _('The same level node name cannot be the same')
) )
return data return data
......
...@@ -12,53 +12,31 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer): ...@@ -12,53 +12,31 @@ class SystemUserSerializer(BulkOrgResourceModelSerializer):
""" """
系统用户 系统用户
""" """
password = serializers.CharField(
required=False, write_only=True, label=_('Password')
)
unreachable_amount = serializers.SerializerMethodField(
label=_('Unreachable')
)
unreachable_assets = serializers.SerializerMethodField(
label=_('Unreachable assets')
)
reachable_assets = serializers.SerializerMethodField(
label=_('Reachable assets')
)
reachable_amount = serializers.SerializerMethodField(label=_('Reachable'))
assets_amount = serializers.SerializerMethodField(label=_('Asset'))
class Meta: class Meta:
model = SystemUser model = SystemUser
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'name', 'username', 'login_mode', 'login_mode_display', 'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode_display', 'priority', 'protocol', 'auto_push', 'login_mode', 'login_mode_display', 'priority', 'protocol',
'password', 'assets_amount', 'reachable_amount', 'reachable_assets', 'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment', 'nodes',
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo', 'assets_amount', 'connectivity_amount'
'shell', 'comment', 'nodes', 'assets'
] ]
extra_kwargs = { extra_kwargs = {
'password': {"write_only": True},
'public_key': {"write_only": True},
'private_key': {"write_only": True},
'assets_amount': {'label': _('Asset')},
'connectivity_amount': {'label': _('Connectivity')},
'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 @classmethod
def get_unreachable_assets(obj): def setup_eager_loading(cls, queryset):
return obj.assets_unreachable """ Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('cmd_filters', 'nodes')
@staticmethod return queryset
def get_reachable_assets(obj):
return obj.assets_reachable
def get_unreachable_amount(self, obj):
return len(self.get_unreachable_assets(obj))
def get_reachable_amount(self, obj):
return len(self.get_reachable_assets(obj))
@staticmethod
def get_assets_amount(obj):
return len(obj.get_related_assets())
class SystemUserAuthSerializer(AuthSerializer): class SystemUserAuthSerializer(AuthSerializer):
...@@ -74,23 +52,6 @@ class SystemUserAuthSerializer(AuthSerializer): ...@@ -74,23 +52,6 @@ class SystemUserAuthSerializer(AuthSerializer):
] ]
class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少
"""
actions = serializers.SerializerMethodField()
class Meta:
model = SystemUser
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode', 'actions',
)
@staticmethod
def get_actions(obj):
return [action.name for action in obj.actions]
class SystemUserSimpleSerializer(serializers.ModelSerializer): class SystemUserSimpleSerializer(serializers.ModelSerializer):
""" """
......
...@@ -27,11 +27,6 @@ def test_asset_conn_on_created(asset): ...@@ -27,11 +27,6 @@ def test_asset_conn_on_created(asset):
test_asset_connectivity_util.delay([asset]) test_asset_connectivity_util.delay([asset])
def set_asset_root_node(asset):
logger.debug("Set asset default node: {}".format(Node.root()))
asset.nodes.add(Node.root())
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
@on_transaction_commit @on_transaction_commit
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
......
...@@ -15,7 +15,8 @@ from ops.celery.decorator import ( ...@@ -15,7 +15,8 @@ from ops.celery.decorator import (
register_as_period_task, after_app_shutdown_clean_periodic register_as_period_task, after_app_shutdown_clean_periodic
) )
from .models import SystemUser, AdminUser, Asset from .models import SystemUser, AdminUser
from .models.utils import Connectivity
from . import const from . import const
...@@ -207,8 +208,7 @@ def test_asset_connectivity_util(assets, task_name=None): ...@@ -207,8 +208,7 @@ def test_asset_connectivity_util(assets, task_name=None):
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True, pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by, created_by=created_by,
) )
result = task.run() raw, summary = task.run()
summary = result[1]
success = summary.get('success', False) success = summary.get('success', False)
contacted = summary.get('contacted', {}) contacted = summary.get('contacted', {})
dark = summary.get('dark', {}) dark = summary.get('dark', {})
...@@ -218,13 +218,12 @@ def test_asset_connectivity_util(assets, task_name=None): ...@@ -218,13 +218,12 @@ def test_asset_connectivity_util(assets, task_name=None):
results_summary['dark'].update(dark) results_summary['dark'].update(dark)
for asset in assets: for asset in assets:
if asset.hostname in results_summary.get('dark', {}): if asset.hostname in results_summary.get('dark', {}).keys():
asset.connectivity = asset.UNREACHABLE asset.connectivity = Connectivity.unreachable()
elif asset.hostname in results_summary.get('contacted', []): elif asset.hostname in results_summary.get('contacted', {}).keys():
asset.connectivity = asset.REACHABLE asset.connectivity = Connectivity.reachable()
else: else:
asset.connectivity = asset.UNKNOWN asset.connectivity = Connectivity.unknown()
return results_summary return results_summary
...@@ -286,10 +285,6 @@ def test_admin_user_connectivity_manual(admin_user): ...@@ -286,10 +285,6 @@ def test_admin_user_connectivity_manual(admin_user):
## System user connective ## ## System user connective ##
@shared_task
def set_system_user_connectivity_info(system_user, summary):
system_user.connectivity = summary
@shared_task @shared_task
def test_system_user_connectivity_util(system_user, assets, task_name): def test_system_user_connectivity_util(system_user, assets, task_name):
...@@ -336,8 +331,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name): ...@@ -336,8 +331,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
pattern='all', options=const.TASK_OPTIONS, pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id, run_as=system_user.username, created_by=system_user.org_id,
) )
result = task.run() raw, summary = task.run()
summary = result[1]
success = summary.get('success', False) success = summary.get('success', False)
contacted = summary.get('contacted', {}) contacted = summary.get('contacted', {})
dark = summary.get('dark', {}) dark = summary.get('dark', {})
...@@ -346,7 +340,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name): ...@@ -346,7 +340,7 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
results_summary['contacted'].update(contacted) results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark) results_summary['dark'].update(dark)
set_system_user_connectivity_info(system_user, results_summary) system_user.set_connectivity(results_summary)
return results_summary return results_summary
...@@ -567,23 +561,12 @@ def get_test_asset_user_connectivity_tasks(asset): ...@@ -567,23 +561,12 @@ def get_test_asset_user_connectivity_tasks(asset):
return tasks return tasks
@shared_task
def set_asset_user_connectivity_info(asset_user, result):
summary = result[1]
if summary.get('contacted'):
connectivity = 1
elif summary.get("dark"):
connectivity = 0
else:
connectivity = 3
asset_user.connectivity = connectivity
@shared_task @shared_task
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False): def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
""" """
:param asset_user: <AuthBook>对象 :param asset_user: <AuthBook>对象
:param task_name: :param task_name:
:param run_as_admin:
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
...@@ -593,6 +576,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False) ...@@ -593,6 +576,7 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False)
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset) tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks: if not tasks:
logger.debug("No tasks ")
return return
args = (task_name,) args = (task_name,)
...@@ -606,8 +590,8 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False) ...@@ -606,8 +590,8 @@ def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False)
else: else:
kwargs["run_as"] = asset_user.username kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs) task, created = update_or_create_ansible_task(*args, **kwargs)
result = task.run() raw, summary = task.run()
set_asset_user_connectivity_info(asset_user, result) asset_user.set_connectivity(summary)
@shared_task @shared_task
......
...@@ -67,6 +67,7 @@ function initTable2() { ...@@ -67,6 +67,7 @@ function initTable2() {
columns: [ columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" } {data: "id"}, {data: "hostname" }, {data: "ip" }
], ],
lengthMenu: [[10, 25, 50], [10, 25, 50]],
pageLength: 10 pageLength: 10
}; };
asset_table2 = jumpserver.initServerSideDataTable(options); asset_table2 = jumpserver.initServerSideDataTable(options);
......
...@@ -32,7 +32,9 @@ var assetUserListUrl = "{% url "api-assets:asset-user-list" %}"; ...@@ -32,7 +32,9 @@ var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
var assetUserTable; var assetUserTable;
var needPush = false; var needPush = false;
var prefer = null; var prefer = null;
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}"; var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
var testDatetime = "{% trans 'Test datetime: ' %}";
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
function initAssetUserTable() { function initAssetUserTable() {
var options = { var options = {
...@@ -41,19 +43,25 @@ function initAssetUserTable() { ...@@ -41,19 +43,25 @@ function initAssetUserTable() {
columnDefs: [ columnDefs: [
{ {
targets: 5, createdCell: function (td, cellData) { targets: 5, createdCell: function (td, cellData) {
if (cellData == 1) { var innerHtml = "";
$(td).html('<i class="fa fa-circle text-navy"></i>') if (cellData.status == 1) {
} else if (cellData == 0) { innerHtml = '<i class="fa fa-circle text-navy"></i>'
$(td).html('<i class="fa fa-circle text-danger"></i>') } else if (cellData.status == 0) {
innerHtml = '<i class="fa fa-circle text-danger"></i>'
} else { } else {
$(td).html('<i class="fa fa-circle text-warning"></i>') innerHtml = '<i class="fa fa-circle text-warning"></i>'
} }
var date = new Date(cellData.datetime);
var dateManual = date.toLocaleString();
var dataContent = testDatetime + dateManual;
innerHtml = "<a data-toggle='popover' data-content='" + dataContent + "'" + 'data-placement="auto bottom"' + ">" + innerHtml + "</a>";
$(td).html(innerHtml);
} }
}, },
{ {
targets: 6, createdCell: function (td, cellData) { targets: 6, createdCell: function (td, cellData) {
var date = new Date(cellData); var data = formatDateAsCN(cellData);
$(td).html(date.toLocaleString()); $(td).html(data);
}, },
}, },
{ {
...@@ -84,8 +92,8 @@ function initAssetUserTable() { ...@@ -84,8 +92,8 @@ function initAssetUserTable() {
ajax_url: assetUserListUrl, ajax_url: assetUserListUrl,
columns: [ columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"}, {data: "id"}, {data: "hostname"}, {data: "ip"},
{data: "username", orderable: false}, {data: "version", orderable: false}, {data: "username"}, {data: "version", orderable: false},
{data: "connectivity", orderable: false}, {data: "connectivity"},
{data: "date_created", orderable: false}, {data: "date_created", orderable: false},
{data: "asset", orderable: false} {data: "asset", orderable: false}
], ],
...@@ -102,7 +110,7 @@ $(document).ready(function(){ ...@@ -102,7 +110,7 @@ $(document).ready(function(){
authUsername = $(this).data('user'); authUsername = $(this).data('user');
var now = new Date(); var now = new Date();
var nowTime = now.getTime() / 1000; var nowTime = now.getTime() / 1000;
if (nowTime - lastMFATime > 60*10 ) { if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
mfaFor = "viewAuth"; mfaFor = "viewAuth";
$("#mfa_auth_confirm").modal("show"); $("#mfa_auth_confirm").modal("show");
} else { } else {
......
This diff is collapsed.
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-9" style="padding-left: 0;"> <div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins"> <div class="ibox float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span> <span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-3" style="padding-left: 0;padding-right: 0"> <div class="col-sm-4" style="padding-left: 0;padding-right: 0">
<div class="panel panel-primary"> <div class="panel panel-primary">
<div class="panel-heading"> <div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %} <i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
...@@ -81,21 +81,6 @@ $(document).ready(function () { ...@@ -81,21 +81,6 @@ $(document).ready(function () {
prefer = "admin_user"; prefer = "admin_user";
initAssetUserTable(); initAssetUserTable();
}) })
.on('click', '.btn-test-asset', function () {
var asset_id = $(this).data('uid');
var the_url = "{% url 'api-assets:asset-alive-test' pk=DEFAULT_PK %}".replace('{{ DEFAULT_PK }}', asset_id);
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
.on('click', '.btn-test-connective', function () { .on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}"; var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
var success = function (data) { var success = function (data) {
...@@ -110,17 +95,5 @@ $(document).ready(function () { ...@@ -110,17 +95,5 @@ $(document).ready(function () {
flash_message: false flash_message: false
}); });
}) })
.on('click', '.btn-update-asset-user-auth', function() {
asset_id = $(this).data('aid');
hostname = $(this).data('hostname');
username = '{{ admin_user.username }}';
$("#asset_user_auth_update_modal").modal();
})
.on("click", ".btn-view-auth", function (evt) {
asset_id = $(this).data("aid") ;
host = $(this).data("hostname");
username = "{{ admin_user.username }}";
$("#asset_user_auth_view").modal();
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -11,27 +11,27 @@ ...@@ -11,27 +11,27 @@
{% endblock %} {% endblock %}
{% block table_search %} {% block table_search %}
<div class="" style="float: right"> <div class="" style="float: right">
<div class=" btn-group"> <div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button> <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <li>
<a class=" btn_export" tabindex="0"> <a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span> <span>{% trans "Export" %}</span>
</a> </a>
</li> </li>
<li> <li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0"> <a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span> <span>{% trans "Import" %}</span>
</a> </a>
</li> </li>
<li> <li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0"> <a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span> <span>{% trans "Update" %}</span>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
...@@ -75,27 +75,29 @@ function initTable() { ...@@ -75,27 +75,29 @@ function initTable() {
}}, }},
{targets: 4, createdCell: function (td, cellData) { {targets: 4, createdCell: function (td, cellData) {
var innerHtml = ""; var innerHtml = "";
if (cellData !== 0) { var data = cellData.reachable;
innerHtml = "<span class='text-navy'>" + cellData + "</span>"; if (data !== 0) {
innerHtml = "<span class='text-navy'>" + data + "</span>";
} else { } else {
innerHtml = "<span>" + cellData + "</span>"; innerHtml = "<span>" + data + "</span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>'); $(td).html(innerHtml)
}}, }},
{targets: 5, createdCell: function (td, cellData) { {targets: 5, createdCell: function (td, cellData) {
var data = cellData.unreachable;
var innerHtml = ""; var innerHtml = "";
if (cellData !== 0) { if (data !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>"; innerHtml = "<span class='text-danger'>" + data + "</span>";
} else { } else {
innerHtml = "<span>" + cellData + "</span>"; innerHtml = "<span>" + data + "</span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
}}, }},
{targets: 6, createdCell: function (td, cellData, rowData) { {targets: 6, createdCell: function (td, cellData, rowData) {
var val = 0; var val = 0;
var innerHtml = ""; var innerHtml = "";
var total = rowData.assets_amount; var total = rowData.assets_amount;
var reachable = rowData.reachable_amount; var reachable = cellData.reachable;
if (total !== 0) { if (total !== 0) {
val = reachable/total * 100; val = reachable/total * 100;
} }
...@@ -114,15 +116,18 @@ function initTable() { ...@@ -114,15 +116,18 @@ function initTable() {
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}}], }}],
ajax_url: '{% url "api-assets:admin-user-list" %}', ajax_url: '{% url "api-assets:admin-user-list" %}',
columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, columns: [
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}] {data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" },
{data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"},
{data: "comment"}, {data: "id"}
]
}; };
admin_user_table = jumpserver.initServerSideDataTable(options); admin_user_table = jumpserver.initServerSideDataTable(options);
return admin_user_table return admin_user_table
} }
$(document).ready(function(){ $(document).ready(function(){
initTable() initTable();
}) })
.on('click', '.btn_admin_user_delete', function () { .on('click', '.btn_admin_user_delete', function () {
......
...@@ -169,40 +169,6 @@ $(document).ready(function () { ...@@ -169,40 +169,6 @@ $(document).ready(function () {
initAssetUserTable(); initAssetUserTable();
}) })
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
.on('click', '.btn-remove-from-node', function() { .on('click', '.btn-remove-from-node', function() {
var $this = $(this); var $this = $(this);
var $tr = $this.closest('tr'); var $tr = $this.closest('tr');
...@@ -230,6 +196,23 @@ $(document).ready(function () { ...@@ -230,6 +196,23 @@ $(document).ready(function () {
}); });
updateSystemUserNode(nodes); updateSystemUserNode(nodes);
}) })
.on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
.on('click', '.btn-push-auth', function () { .on('click', '.btn-push-auth', function () {
var $this = $(this); var $this = $(this);
var asset_id = $this.data('asset'); var asset_id = $this.data('asset');
...@@ -250,6 +233,23 @@ $(document).ready(function () { ...@@ -250,6 +233,23 @@ $(document).ready(function () {
error: error error: error
}) })
}) })
.on('click', '.btn-test-connective', function () {
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
var error = function (data) {
alert(data)
};
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
error: error,
method: 'GET',
success: success
});
})
</script> </script>
......
...@@ -15,27 +15,27 @@ ...@@ -15,27 +15,27 @@
{% block table_search %} {% block table_search %}
<div class="" style="float: right"> <div class="" style="float: right">
<div class=" btn-group"> <div class=" btn-group">
<button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button> <button data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">CSV <span class="caret"></span></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <li>
<a class=" btn_export" tabindex="0"> <a class=" btn_export" tabindex="0">
<span>{% trans "Export" %}</span> <span>{% trans "Export" %}</span>
</a> </a>
</li> </li>
<li> <li>
<a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0"> <a class=" btn_import" data-toggle="modal" data-target="#import_modal" tabindex="0">
<span>{% trans "Import" %}</span> <span>{% trans "Import" %}</span>
</a> </a>
</li> </li>
<li> <li>
<a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0"> <a class=" btn_update" data-toggle="modal" data-target="#update_modal" tabindex="0">
<span>{% trans "Update" %}</span> <span>{% trans "Update" %}</span>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block table_container %} {% block table_container %}
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
<thead> <thead>
<tr> <tr>
<th class="text-center"> <th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" > <input type="checkbox" id="check_all" class="ipt_check_all">
</th> </th>
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</th> <th class="text-center">{% trans 'Username' %}</th>
...@@ -80,28 +80,30 @@ function initTable() { ...@@ -80,28 +80,30 @@ function initTable() {
}}, }},
{targets: 6, createdCell: function (td, cellData) { {targets: 6, createdCell: function (td, cellData) {
var innerHtml = ""; var innerHtml = "";
if (cellData !== 0) { var data = cellData.reachable;
innerHtml = "<span class='text-navy'>" + cellData + "</span>"; if (data !== 0) {
innerHtml = "<span class='text-navy'>" + data + "</span>";
} else { } else {
innerHtml = "<span>" + cellData + "</span>"; innerHtml = "<span>" + data + "</span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>'); $(td).html(innerHtml)
}}, }},
{targets: 7, createdCell: function (td, cellData) { {targets: 7, createdCell: function (td, cellData) {
var data = cellData.unreachable;
var innerHtml = ""; var innerHtml = "";
if (cellData !== 0) { if (data !== 0) {
innerHtml = "<span class='text-danger'>" + cellData + "</span>"; innerHtml = "<span class='text-danger'>" + data + "</span>";
} else { } else {
innerHtml = "<span>" + cellData + "</span>"; innerHtml = "<span>" + data + "</span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + data + '">' + innerHtml + '</span>');
}}, }},
{targets: 8, createdCell: function (td, cellData, rowData) { {targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0; var val = 0;
var innerHtml = ""; var innerHtml = "";
var total = rowData.assets_amount; var total = rowData.assets_amount;
var reachable = rowData.reachable_amount; var reachable = cellData.reachable;
if (total !== 0) { if (total && total !== 0) {
val = reachable/total * 100; val = reachable/total * 100;
} }
...@@ -112,20 +114,20 @@ function initTable() { ...@@ -112,20 +114,20 @@ function initTable() {
innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>"; innerHtml = "<span class='text-danger'>" + num.toFixed(1) + "% </span>";
} }
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>'); $(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
}}, }},
{targets: 10, createdCell: function (td, cellData, rowData) { {targets: 10, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData); var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn) $(td).html(update_btn + del_btn)
}}], }},
],
ajax_url: '{% url "api-assets:system-user-list" %}', ajax_url: '{% url "api-assets:system-user-list" %}',
columns: [ columns: [
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" }, {data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "login_mode_display"}, {data: "assets_amount" },
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" } {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "connectivity_amount"}, {data: "comment" }, {data: "id" }
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
system_user_table = jumpserver.initServerSideDataTable(options); system_user_table = jumpserver.initServerSideDataTable(options);
return system_user_table return system_user_table
} }
......
...@@ -9,8 +9,6 @@ urlpatterns = [ ...@@ -9,8 +9,6 @@ urlpatterns = [
path('', views.AssetListView.as_view(), name='asset-index'), path('', views.AssetListView.as_view(), name='asset-index'),
path('asset/', views.AssetListView.as_view(), name='asset-list'), path('asset/', views.AssetListView.as_view(), name='asset-list'),
path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'), path('asset/create/', views.AssetCreateView.as_view(), name='asset-create'),
path('asset/export/', views.AssetExportView.as_view(), name='asset-export'),
path('asset/import/', views.BulkImportAssetView.as_view(), name='asset-import'),
path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'), path('asset/<uuid:pk>/', views.AssetDetailView.as_view(), name='asset-detail'),
path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'), path('asset/<uuid:pk>/update/', views.AssetUpdateView.as_view(), name='asset-update'),
path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'), path('asset/<uuid:pk>/delete/', views.AssetDeleteView.as_view(), name='asset-delete'),
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
import os import time
import paramiko from django.db.models import Prefetch
from paramiko.ssh_exception import SSHException
from common.utils import get_object_or_none from common.utils import get_object_or_none, get_logger
from .models import Asset, SystemUser, Label from common.struct import Stack
from .models import SystemUser, Label, Node, Asset
def get_assets_by_id_list(id_list): logger = get_logger(__file__)
return Asset.objects.filter(id__in=id_list).filter(is_active=True)
def get_system_users_by_id_list(id_list):
return SystemUser.objects.filter(id__in=id_list)
def get_assets_by_fullname_list(hostname_list):
return Asset.get_queryset_by_fullname_list(hostname_list)
def get_system_user_by_name(name): def get_system_user_by_name(name):
...@@ -49,3 +40,166 @@ class LabelFilter: ...@@ -49,3 +40,166 @@ class LabelFilter:
for kwargs in conditions: for kwargs in conditions:
queryset = queryset.filter(**kwargs) queryset = queryset.filter(**kwargs)
return queryset return queryset
class NodeUtil:
def __init__(self, with_assets_amount=False, debug=False):
self.stack = Stack()
self._nodes = {}
self.with_assets_amount = with_assets_amount
self._debug = debug
self.init()
@staticmethod
def sorted_by(node):
return [int(i) for i in node.key.split(':')]
def get_queryset(self):
all_nodes = Node.objects.all()
if self.with_assets_amount:
all_nodes = all_nodes.prefetch_related(
Prefetch('assets', queryset=Asset.objects.all().only('id'))
)
all_nodes = list(all_nodes)
for node in all_nodes:
node._assets = set(node.assets.all())
return all_nodes
def get_all_nodes(self):
all_nodes = sorted(self.get_queryset(), key=self.sorted_by)
guarder = Node(key='', value='Guarder')
guarder._assets = []
all_nodes.append(guarder)
return all_nodes
def push_to_stack(self, node):
# 入栈之前检查
# 如果栈是空的,证明是一颗树的根部
if self.stack.is_empty():
node._full_value = node.value
node._parents = []
else:
# 如果不是根节点,
# 该节点的祖先应该是父节点的祖先加上父节点
# 该节点的名字是父节点的名字+自己的名字
node._parents = [self.stack.top] + self.stack.top._parents
node._full_value = ' / '.join(
[self.stack.top._full_value, node.value]
)
node._children = []
node._all_children = []
self.debug("入栈: {}".format(node.key))
self.stack.push(node)
# 出栈
def pop_from_stack(self):
_node = self.stack.pop()
self.debug("出栈: {} 栈顶: {}".format(_node.key, self.stack.top.key if self.stack.top else None))
self._nodes[_node.key] = _node
if not self.stack.top:
return
if self.with_assets_amount:
self.stack.top._assets.update(_node._assets)
_node._assets_amount = len(_node._assets)
delattr(_node, '_assets')
self.stack.top._children.append(_node)
self.stack.top._all_children.extend([_node] + _node._children)
def init(self):
all_nodes = self.get_all_nodes()
for node in all_nodes:
self.debug("准备: {} 栈顶: {}".format(node.key, self.stack.top.key if self.stack.top else None))
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while self.stack.top and not self.stack.top.is_children(node):
self.pop_from_stack()
self.push_to_stack(node)
# 出栈最后一个
self.debug("剩余: {}".format(', '.join([n.key for n in self.stack])))
def get_nodes_by_queryset(self, queryset):
nodes = []
for n in queryset:
node = self.get_node_by_key(n.key)
if not node:
continue
nodes.append(node)
return nodes
def get_node_by_key(self, key):
return self._nodes.get(key)
def debug(self, msg):
self._debug and logger.debug(msg)
def set_assets_amount(self):
for node in self._nodes.values():
node.assets_amount = node._assets_amount
def set_full_value(self):
for node in self._nodes.values():
node.full_value = node._full_value
@property
def nodes(self):
return list(self._nodes.values())
# 使用给定节点生成一颗树
# 找到他们的祖先节点
# 可选找到他们的子孙节点
def get_family(self, nodes, with_children=False):
tree_nodes = set()
for n in nodes:
node = self.get_node_by_key(n.key)
if not node:
continue
tree_nodes.update(node._parents)
tree_nodes.add(node)
if with_children:
tree_nodes.update(node._children)
return list(tree_nodes)
def get_nodes_parents(self, nodes, with_self=True):
parents = set()
for n in nodes:
node = self.get_node_by_key(n.key)
parents.update(set(node._parents))
if with_self:
parents.add(node)
return parents
def test_node_tree():
tree = NodeUtil()
for node in tree._nodes.values():
print("Check {}".format(node.key))
children_wanted = node.get_all_children().count()
children = len(node._children)
if children != children_wanted:
print("{} children not equal: {} != {}".format(node.key, children, children_wanted))
assets_amount_wanted = node.get_all_assets().count()
if node._assets_amount != assets_amount_wanted:
print("{} assets amount not equal: {} != {}".format(
node.key, node._assets_amount, assets_amount_wanted)
)
full_value_wanted = node.full_value
if node._full_value != full_value_wanted:
print("{} full value not equal: {} != {}".format(
node.key, node._full_value, full_value_wanted)
)
parents_wanted = node.get_ancestor().count()
parents = len(node._parents)
if parents != parents_wanted:
print("{} parents count not equal: {} != {}".format(
node.key, parents, parents_wanted)
)
...@@ -81,7 +81,7 @@ class AdminUserDetailView(PermissionsMixin, DetailView): ...@@ -81,7 +81,7 @@ class AdminUserDetailView(PermissionsMixin, DetailView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Admin user detail'), 'action': _('Admin user detail'),
'nodes': Node.objects.all() 'nodes': Node.get_queryset(),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -106,8 +106,6 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView): ...@@ -106,8 +106,6 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Admin user detail'), 'action': _('Admin user detail'),
"total_amount": len(self.queryset),
'unreachable_amount': len([asset for asset in self.queryset if asset.connectivity is False])
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
......
...@@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain ...@@ -37,7 +37,7 @@ from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
__all__ = [ __all__ = [
'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView', 'AssetListView', 'AssetCreateView', 'AssetUpdateView', 'AssetUserListView',
'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView', 'UserAssetListView', 'AssetBulkUpdateView', 'AssetDetailView',
'AssetDeleteView', 'AssetExportView', 'BulkImportAssetView', 'AssetDeleteView',
] ]
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -220,6 +220,11 @@ class AssetDetailView(PermissionsMixin, DetailView): ...@@ -220,6 +220,11 @@ class AssetDetailView(PermissionsMixin, DetailView):
template_name = 'assets/asset_detail.html' template_name = 'assets/asset_detail.html'
permission_classes = [IsValidUser] permission_classes = [IsValidUser]
def get_queryset(self):
return super().get_queryset().prefetch_related(
"nodes", "labels", "protocols"
).select_related('admin_user', 'domain')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = Node.objects.exclude(assets=self.object) nodes_remain = Node.objects.exclude(assets=self.object)
context = { context = {
...@@ -229,150 +234,3 @@ class AssetDetailView(PermissionsMixin, DetailView): ...@@ -229,150 +234,3 @@ class AssetDetailView(PermissionsMixin, DetailView):
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
@method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request):
spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
assets_id = cache.get(spm, assets_id_default)
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created', 'org_id'
]
]
filename = 'assets-{}.csv'.format(
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S')
)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
response.write(codecs.BOM_UTF8)
assets = Asset.objects.filter(id__in=assets_id)
writer = csv.writer(response, dialect='excel', quoting=csv.QUOTE_MINIMAL)
header = [field.verbose_name for field in fields]
writer.writerow(header)
for asset in assets:
data = [getattr(asset, field.name) for field in fields]
writer.writerow(data)
return response
def post(self, request, *args, **kwargs):
try:
assets_id = json.loads(request.body).get('assets_id', [])
node_id = json.loads(request.body).get('node_id', None)
except ValueError:
return HttpResponse('Json object not valid', status=400)
if not assets_id:
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
assets = node.get_all_assets()
for asset in assets:
assets_id.append(asset.id)
spm = uuid.uuid4().hex
cache.set(spm, assets_id, 300)
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm
permission_classes = [IsOrgAdmin]
def form_valid(self, form):
node_id = self.request.GET.get("node_id")
node = get_object_or_none(Node, id=node_id) if node_id else Node.root()
f = form.cleaned_data['file']
det_result = chardet.detect(f.read())
f.seek(0) # reset file seek index
file_data = f.read().decode(det_result['encoding']).strip(codecs.BOM_UTF8.decode())
csv_file = StringIO(file_data)
reader = csv.reader(csv_file)
csv_data = [row for row in reader]
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created'
]
]
header_ = csv_data[0]
mapping_reverse = {field.verbose_name: field.name for field in fields}
attr = [mapping_reverse.get(n, None) for n in header_]
if None in attr:
data = {'valid': False,
'msg': 'Must be same format as '
'template or export file'}
return self.render_json_response(data)
created, updated, failed = [], [], []
assets = []
for row in csv_data[1:]:
if set(row) == {''}:
continue
asset_dict_raw = dict(zip(attr, row))
asset_dict = dict()
for k, v in asset_dict_raw.items():
v = v.strip()
if k == 'is_active':
v = False if v in ['False', 0, 'false'] else True
elif k == 'admin_user':
v = get_object_or_none(AdminUser, name=v)
elif k in ['port', 'cpu_count', 'cpu_cores']:
try:
v = int(v)
except ValueError:
v = ''
elif k == 'domain':
v = get_object_or_none(Domain, name=v)
elif k == 'platform':
v = v.lower().capitalize()
if v != '':
asset_dict[k] = v
asset = None
asset_id = asset_dict.pop('id', None)
if asset_id:
asset = get_object_or_none(Asset, id=asset_id)
if not asset:
try:
if len(Asset.objects.filter(hostname=asset_dict.get('hostname'))):
raise Exception(_('already exists'))
with transaction.atomic():
asset = Asset.objects.create(**asset_dict)
if node:
asset.nodes.set([node])
created.append(asset_dict['hostname'])
assets.append(asset)
except Exception as e:
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
else:
for k, v in asset_dict.items():
if v != '':
setattr(asset, k, v)
try:
asset.save()
updated.append(asset_dict['hostname'])
except Exception as e:
failed.append('%s: %s' % (asset_dict['hostname'], str(e)))
data = {
'created': created,
'created_info': 'Created {}'.format(len(created)),
'updated': updated,
'updated_info': 'Updated {}'.format(len(updated)),
'failed': failed,
'failed_info': 'Failed {}'.format(len(failed)),
'valid': True,
'msg': 'Created: {}. Updated: {}, Error: {}'.format(
len(created), len(updated), len(failed))
}
return self.render_json_response(data)
...@@ -74,10 +74,11 @@ class SystemUserDetailView(PermissionsMixin, DetailView): ...@@ -74,10 +74,11 @@ class SystemUserDetailView(PermissionsMixin, DetailView):
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
cmd_filters_remain = CommandFilter.objects.exclude(system_users=self.object)
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('System user detail'), 'action': _('System user detail'),
'cmd_filters_remain': CommandFilter.objects.exclude(system_users=self.object) 'cmd_filters_remain': cmd_filters_remain,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -92,12 +93,15 @@ class SystemUserDeleteView(PermissionsMixin, DeleteView): ...@@ -92,12 +93,15 @@ class SystemUserDeleteView(PermissionsMixin, DeleteView):
class SystemUserAssetView(PermissionsMixin, DetailView): class SystemUserAssetView(PermissionsMixin, DetailView):
model = SystemUser model = SystemUser
template_name = 'assets/system_user_asset.html' template_name = 'assets/system_user_assets.html'
context_object_name = 'system_user' context_object_name = 'system_user'
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True) from ..utils import NodeUtil
nodes_remain = Node.objects.exclude(systemuser=self.object)
util = NodeUtil()
nodes_remain = util.get_nodes_by_queryset(nodes_remain)
context = { context = {
'app': _('assets'), 'app': _('assets'),
'action': _('System user asset'), 'action': _('System user asset'),
......
...@@ -194,7 +194,7 @@ class UserOtpVerifyApi(CreateAPIView): ...@@ -194,7 +194,7 @@ class UserOtpVerifyApi(CreateAPIView):
code = serializer.validated_data["code"] code = serializer.validated_data["code"]
if request.user.check_otp(code): if request.user.check_otp(code):
request.session["OTP_LAST_VERIFY_TIME"] = int(time.time()) request.session["MFA_VERIFY_TIME"] = int(time.time())
return Response({"ok": "1"}) return Response({"ok": "1"})
else: else:
return Response({"error": "Code not valid"}, status=400) return Response({"error": "Code not valid"}, status=400)
......
...@@ -6,12 +6,16 @@ from django.dispatch import receiver ...@@ -6,12 +6,16 @@ from django.dispatch import receiver
from django.db.backends.signals import connection_created from django.db.backends.signals import connection_created
@receiver(connection_created, dispatch_uid="my_unique_identifier") @receiver(connection_created)
def on_db_connection_ready(sender, **kwargs): def on_db_connection_ready(sender, **kwargs):
from .signals import django_ready from .signals import django_ready
if 'migrate' not in sys.argv: if 'migrate' not in sys.argv:
django_ready.send(CommonConfig) django_ready.send(CommonConfig)
connection_created.disconnect(on_db_connection_ready)
class CommonConfig(AppConfig): class CommonConfig(AppConfig):
name = 'common' name = 'common'
def ready(self):
from . import signals_handlers
...@@ -124,10 +124,27 @@ class EncryptTextField(EncryptMixin, models.TextField): ...@@ -124,10 +124,27 @@ class EncryptTextField(EncryptMixin, models.TextField):
class EncryptCharField(EncryptMixin, models.CharField): class EncryptCharField(EncryptMixin, models.CharField):
@staticmethod
def change_max_length(kwargs):
kwargs.setdefault('max_length', 1024)
max_length = kwargs.get('max_length')
if max_length < 129:
max_length = 128
max_length = max_length * 2
kwargs['max_length'] = max_length
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 2048 self.change_max_length(kwargs)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
max_length = kwargs.pop('max_length')
if max_length > 255:
max_length = max_length // 2
kwargs['max_length'] = max_length
return name, path, args, kwargs
class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField): class EncryptJsonDictTextField(EncryptMixin, JsonDictTextField):
pass pass
......
# -*- coding: utf-8 -*-
#
from rest_framework import filters
from rest_framework.fields import DateTimeField
from rest_framework.serializers import ValidationError
import logging
__all__ = ["DatetimeRangeFilter"]
class DatetimeRangeFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
if not hasattr(view, 'date_range_filter_fields'):
return queryset
try:
fields = dict(view.date_range_filter_fields)
except ValueError:
msg = "View {} datetime_filter_fields set is error".format(view.name)
logging.error(msg)
return queryset
kwargs = {}
for attr, date_range_keyword in fields.items():
if len(date_range_keyword) != 2:
continue
for i, v in enumerate(date_range_keyword):
value = request.query_params.get(v)
if not value:
continue
try:
field = DateTimeField()
value = field.to_internal_value(value)
if i == 0:
lookup = "__gte"
else:
lookup = "__lte"
kwargs[attr+lookup] = value
except ValidationError as e:
print(e)
continue
if kwargs:
queryset = queryset.filter(**kwargs)
return queryset
# -*- coding: utf-8 -*-
#
from werkzeug.local import Local
thread_local = Local()
def _find(attr):
return getattr(thread_local, attr, None)
# -*- coding: utf-8 -*-
#
from .models import *
from .serializers import *
from .api import *
from .views import *
# -*- coding: utf-8 -*-
#
from django.http import JsonResponse
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from ..const import KEY_CACHE_RESOURCES_ID
__all__ = [
"JSONResponseMixin", "IDInCacheFilterMixin", "IDExportFilterMixin",
"IDInFilterMixin", "ApiMessageMixin"
]
class JSONResponseMixin(object):
"""JSON mixin"""
@staticmethod
def render_json_response(context):
return JsonResponse(context)
class IDInFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super(IDInFilterMixin, self).filter_queryset(queryset)
id_list = self.request.query_params.get('id__in')
if id_list:
import json
try:
ids = json.loads(id_list)
except Exception as e:
return queryset
if isinstance(ids, list):
queryset = queryset.filter(id__in=ids)
return queryset
class IDInCacheFilterMixin(object):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
spm = self.request.query_params.get('spm')
if not spm:
return queryset
cache_key = KEY_CACHE_RESOURCES_ID.format(spm)
resources_id = cache.get(cache_key)
if resources_id and isinstance(resources_id, list):
queryset = queryset.filter(id__in=resources_id)
return queryset
class IDExportFilterMixin(object):
def filter_queryset(self, queryset):
# 下载导入模版
if self.request.query_params.get('template') == 'import':
return []
else:
return super(IDExportFilterMixin, self).filter_queryset(queryset)
class ApiMessageMixin:
success_message = _("%(name)s was %(action)s successfully")
_action_map = {"create": _("create"), "update": _("update")}
def get_success_message(self, cleaned_data):
if not isinstance(cleaned_data, dict):
return ''
data = {k: v for k, v in cleaned_data.items()}
action = getattr(self, "action", "create")
data["action"] = self._action_map.get(action)
try:
message = self.success_message % data
except:
message = ''
return message
def dispatch(self, request, *args, **kwargs):
resp = super().dispatch(request, *args, **kwargs)
if request.method.lower() in ("get", "delete", "patch"):
return resp
if resp.status_code >= 400:
return resp
message = self.get_success_message(resp.data)
if message:
messages.success(request, message)
return resp
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
__all__ = ["NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet"]
class NoDeleteQuerySet(models.query.QuerySet):
def delete(self):
return self.update(is_discard=True, discard_time=timezone.now())
class NoDeleteManager(models.Manager):
def get_all(self):
return NoDeleteQuerySet(self.model, using=self._db)
def get_queryset(self):
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=False)
def get_deleted(self):
return NoDeleteQuerySet(self.model, using=self._db).filter(is_discard=True)
class NoDeleteModelMixin(models.Model):
is_discard = models.BooleanField(verbose_name=_("is discard"), default=False)
discard_time = models.DateTimeField(verbose_name=_("discard time"), null=True, blank=True)
objects = NoDeleteManager()
class Meta:
abstract = True
def delete(self):
self.is_discard = True
self.discard_time = timezone.now()
return self.save()
# -*- coding: utf-8 -*-
#
# coding: utf-8
from django.utils import timezone
__all__ = ["DatetimeSearchMixin"]
class DatetimeSearchMixin:
date_format = '%Y-%m-%d'
date_from = date_to = None
def get_date_range(self):
date_from_s = self.request.GET.get('date_from')
date_to_s = self.request.GET.get('date_to')
if date_from_s:
date_from = timezone.datetime.strptime(date_from_s, self.date_format)
tz = timezone.get_current_timezone()
self.date_from = tz.localize(date_from)
else:
self.date_from = timezone.now() - timezone.timedelta(7)
if date_to_s:
date_to = timezone.datetime.strptime(
date_to_s + ' 23:59:59', self.date_format + ' %H:%M:%S'
)
self.date_to = date_to.replace(
tzinfo=timezone.get_current_timezone()
)
else:
self.date_to = timezone.now()
def get(self, request, *args, **kwargs):
self.get_date_range()
return super().get(request, *args, **kwargs)
...@@ -135,3 +135,24 @@ class PermissionsMixin(UserPassesTestMixin): ...@@ -135,3 +135,24 @@ class PermissionsMixin(UserPassesTestMixin):
if not permission_class().has_permission(self.request, self): if not permission_class().has_permission(self.request, self):
return False return False
return True return True
class NeedMFAVerify(permissions.BasePermission):
def has_permission(self, request, view):
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
if time.time() - mfa_verify_time < settings.SECURITY_MFA_VERIFY_TTL:
return True
return False
class CanUpdateSuperUser(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'OPTIONS']:
return True
if str(request.user.id) == str(obj.id):
return False
if request.user.is_superuser:
return True
if hasattr(obj, 'is_superuser') and obj.is_superuser:
return False
return True
...@@ -57,10 +57,15 @@ class JMSCSVRender(BaseRenderer): ...@@ -57,10 +57,15 @@ class JMSCSVRender(BaseRenderer):
request = renderer_context['request'] request = renderer_context['request']
template = request.query_params.get('template', 'export') template = request.query_params.get('template', 'export')
view = renderer_context['view'] view = renderer_context['view']
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
if isinstance(data, dict) and data.get("count"):
data = data["results"]
if template == 'import': if template == 'import':
data = [data[0]] if data else data data = [data[0]] if data else data
data = json.loads(json.dumps(data, cls=encoders.JSONEncoder))
try: try:
serializer = view.get_serializer() serializer = view.get_serializer()
self.set_response_disposition(serializer, renderer_context) self.set_response_disposition(serializer, renderer_context)
......
# -*- coding: utf-8 -*-
#
import re
from collections import defaultdict
from django.conf import settings
from django.dispatch import receiver
from django.core.signals import request_finished
from django.db import connection
from common.utils import get_logger
from .local import thread_local
pattern = re.compile(r'FROM `(\w+)`')
# logger = logging.getLogger('jmsdb')
logger = get_logger(__name__)
class Counter:
def __init__(self):
self.counter = 0
self.time = 0
def __gt__(self, other):
return self.counter > other.counter
def __lt__(self, other):
return self.counter < other.counter
def __eq__(self, other):
return self.counter == other.counter
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'):
continue
tables = pattern.findall(query['sql'])
table_name = ''.join(tables)
time = query['time']
counters[table_name].counter += 1
counters[table_name].time += float(time)
counters['total'].counter += 1
counters['total'].time += float(time)
counters = sorted(counters.items(), key=lambda x: x[1])
for name, counter in counters:
logger.debug("Query {:3} times using {:.2f}s {}".format(
counter.counter, counter.time, name)
)
@receiver(request_finished)
def on_request_finished_release_local(sender, **kwargs):
thread_local.__release_local__()
if settings.DEBUG:
request_finished.connect(on_request_finished_logging_db_query)
# -*- coding: utf-8 -*-
#
class Stack(list):
def is_empty(self):
return len(self) == 0
@property
def top(self):
if self.is_empty():
return None
return self[-1]
@property
def bottom(self):
if self.is_empty():
return None
return self[0]
def size(self):
return len(self)
def push(self, item):
self.append(item)
...@@ -112,7 +112,6 @@ def to_dict(data): ...@@ -112,7 +112,6 @@ def to_dict(data):
@register.filter @register.filter
def sort(data): def sort(data):
print(data)
return sorted(data) return sorted(data)
......
from django.test import TestCase from django.test import TestCase
# Create your tests here. # Create your tests here.
from .utils import random_string, get_signer
def test_signer_len():
signer = get_signer()
results = {}
for i in range(1, 4096):
s = random_string(i)
encs = signer.sign(s)
results[i] = (len(encs)/len(s))
results = sorted(results.items(), key=lambda x: x[1], reverse=True)
print(results)
This diff is collapsed.
...@@ -51,7 +51,7 @@ class Signer(metaclass=Singleton): ...@@ -51,7 +51,7 @@ class Signer(metaclass=Singleton):
try: try:
return s.loads(value) return s.loads(value)
except BadSignature: except BadSignature:
return {} return None
def sign_t(self, value, expires_in=3600): def sign_t(self, value, expires_in=3600):
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in) s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
...@@ -62,7 +62,7 @@ class Signer(metaclass=Singleton): ...@@ -62,7 +62,7 @@ class Signer(metaclass=Singleton):
try: try:
return s.loads(value) return s.loads(value)
except (BadSignature, SignatureExpired): except (BadSignature, SignatureExpired):
return {} return None
def ssh_key_string_to_obj(text, password=None): def ssh_key_string_to_obj(text, password=None):
......
...@@ -375,8 +375,8 @@ defaults = { ...@@ -375,8 +375,8 @@ defaults = {
'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080, 'HTTP_LISTEN_PORT': 8080,
'LOGIN_LOG_KEEP_DAYS': 90, 'LOGIN_LOG_KEEP_DAYS': 90,
'ASSETS_PERM_CACHE_TIME': 3600, 'ASSETS_PERM_CACHE_TIME': 3600*24,
'SECURITY_MFA_VERIFY_TTL': 3600,
} }
......
...@@ -17,6 +17,7 @@ def jumpserver_processor(request): ...@@ -17,6 +17,7 @@ def jumpserver_processor(request):
'VERSION': settings.VERSION, 'VERSION': settings.VERSION,
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019', 'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2019',
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION, 'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
'SECURITY_MFA_VERIFY_TTL': settings.SECURITY_MFA_VERIFY_TTL,
} }
return context return context
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
# #
from .ansible.inventory import BaseInventory from .ansible.inventory import BaseInventory
from assets.utils import get_assets_by_id_list, get_system_user_by_id
from common.utils import get_logger from common.utils import get_logger
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment