Unverified Commit 52d52861 authored by 老广's avatar 老广 Committed by GitHub

Merge pull request #2893 from jumpserver/dev

Dev
parents 9ca4a8c9 0809916b
...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _ ...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django import forms from django import forms
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from assets.models import SystemUser, Protocol from assets.models import SystemUser
from ..models import RemoteApp from ..models import RemoteApp
from .. import const from .. import const
...@@ -88,9 +88,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): ...@@ -88,9 +88,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
# 过滤RDP资产和系统用户 # 过滤RDP资产和系统用户
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
field_asset = self.fields['asset'] field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.filter( field_asset.queryset = field_asset.queryset.has_protocol('rdp')
protocols__name=Protocol.PROTOCOL_RDP
)
field_system_user = self.fields['system_user'] field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter( field_system_user.queryset = field_system_user.queryset.filter(
protocol=SystemUser.PROTOCOL_RDP protocol=SystemUser.PROTOCOL_RDP
......
...@@ -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, 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().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,32 +6,32 @@ from django.utils.translation import gettext_lazy as _ ...@@ -6,32 +6,32 @@ 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, Node
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = [ __all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
'ProtocolForm'
] ]
class ProtocolForm(forms.ModelForm): class ProtocolForm(forms.Form):
class Meta: name = forms.ChoiceField(
model = Protocol choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh',
fields = ['name', 'port'] widget=forms.Select(attrs={'class': 'form-control protocol-name'})
widgets = { )
'name': forms.Select(attrs={ port = forms.IntegerField(
'class': 'form-control protocol-name' max_value=65534, min_value=1, label=_("Port"), initial=22,
}), widget=forms.TextInput(attrs={'class': 'form-control protocol-port'})
'port': forms.TextInput(attrs={ )
'class': 'form-control protocol-port'
}),
}
class AssetCreateForm(OrgModelForm): class AssetCreateForm(OrgModelForm):
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
......
# -*- 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')},
),
]
...@@ -3,14 +3,6 @@ ...@@ -3,14 +3,6 @@
from django.db import migrations from django.db import migrations
def migrate_assets_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
assets = asset_model.objects.using(db_alias).all()
for asset in assets:
asset.protocols.create(name=asset.protocol, port=asset.port)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
...@@ -18,5 +10,4 @@ class Migration(migrations.Migration): ...@@ -18,5 +10,4 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RunPython(migrate_assets_protocol),
] ]
# 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',
),
]
# Generated by Django 2.1.7 on 2019-07-05 05:48
from django.db import migrations
from django.db.models import F
from django.db.models import CharField, Value as V
from django.db.models.functions import Concat
def migrate_assets_protocol(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
db_alias = schema_editor.connection.alias
assets = asset_model.objects.using(db_alias).all().annotate(
protocols_new=Concat(
'protocol', V('/'), 'port',
output_field=CharField(),
),
)
assets.update(protocols=F('protocols_new'))
class Migration(migrations.Migration):
dependencies = [
('assets', '0033_auto_20190624_2108'),
]
operations = [
migrations.RemoveField(
model_name='asset',
name='protocols',
),
migrations.AddField(
model_name='asset',
name='protocols',
field=CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
),
migrations.RunPython(migrate_assets_protocol),
migrations.DeleteModel(name='Protocol'),
]
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 .authbook import *
from .utils import * from .utils import *
from .authbook import * from .authbook import *
...@@ -6,18 +6,16 @@ import uuid ...@@ -6,18 +6,16 @@ import uuid
import logging import logging
import random import random
from functools import reduce from functools import reduce
from collections import defaultdict from collections import OrderedDict
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 .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', 'ProtocolsMixin']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -47,14 +45,12 @@ class AssetQuerySet(models.QuerySet): ...@@ -47,14 +45,12 @@ class AssetQuerySet(models.QuerySet):
def valid(self): def valid(self):
return self.active() return self.active()
def has_protocol(self, name):
class AssetManager(OrgManager): return self.filter(protocols__contains=name)
def get_queryset(self):
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
return queryset
class Protocol(models.Model): class ProtocolsMixin:
protocols = ''
PROTOCOL_SSH = 'ssh' PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp' PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet' PROTOCOL_TELNET = 'telnet'
...@@ -65,19 +61,42 @@ class Protocol(models.Model): ...@@ -65,19 +61,42 @@ class Protocol(models.Model):
(PROTOCOL_TELNET, 'telnet (beta)'), (PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'), (PROTOCOL_VNC, 'vnc'),
) )
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
id = models.UUIDField(default=uuid.uuid4, primary_key=True) @property
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, def protocols_as_list(self):
default=PROTOCOL_SSH, verbose_name=_("Name")) if not self.protocols:
port = models.IntegerField(default=22, verbose_name=_("Port"), return []
validators=PORT_VALIDATORS) return self.protocols.split(' ')
def __str__(self): @property
return "{}/{}".format(self.name, self.port) def protocols_as_dict(self):
d = OrderedDict()
protocols = self.protocols_as_list
for i in protocols:
if '/' not in i:
continue
name, port = i.split('/')[:2]
if not all([name, port]):
continue
d[name] = int(port)
return d
@property
def protocols_as_json(self):
return [
{"name": name, "port": port}
for name, port in self.protocols_as_dict.items()
]
class Asset(OrgModelMixin): def has_protocol(self, name):
return name in self.protocols_as_dict
@property
def ssh_port(self):
return self.protocols_as_dict.get("ssh", 22)
class Asset(ProtocolsMixin, OrgModelMixin):
# Important # Important
PLATFORM_CHOICES = ( PLATFORM_CHOICES = (
('Linux', 'Linux'), ('Linux', 'Linux'),
...@@ -92,12 +111,12 @@ class Asset(OrgModelMixin): ...@@ -92,12 +111,12 @@ class Asset(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH, protocol = models.CharField(max_length=128, default=ProtocolsMixin.PROTOCOL_SSH,
choices=Protocol.PROTOCOL_CHOICES, choices=ProtocolsMixin.PROTOCOL_CHOICES,
verbose_name=_('Protocol')) verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocol")) protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
...@@ -133,14 +152,8 @@ class Asset(OrgModelMixin): ...@@ -133,14 +152,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)
...@@ -150,41 +163,9 @@ class Asset(OrgModelMixin): ...@@ -150,41 +163,9 @@ class Asset(OrgModelMixin):
warning = '' warning = ''
if not self.is_active: if not self.is_active:
warning += ' inactive' warning += ' inactive'
else: if warning:
return True, '' return False, warning
return False, warning return True, warning
@property
def protocols_name(self):
names = []
for protocol in self.protocols.all():
names.append(protocol.name)
return names
def has_protocol(self, name):
return name in self.protocols_name
def get_protocol_by_name(self, name):
for i in self.protocols.all():
if i.name.lower() == name.lower():
return i
return None
@property
def protocol_ssh(self):
return self.get_protocol_by_name("ssh")
@property
def protocol_rdp(self):
return self.get_protocol_by_name("rdp")
@property
def ssh_port(self):
if self.protocol_ssh:
port = self.protocol_ssh.port
else:
port = 22
return port
def is_windows(self): def is_windows(self):
if self.platform in ("Windows", "Windows2016"): if self.platform in ("Windows", "Windows2016"):
...@@ -215,20 +196,6 @@ class Asset(OrgModelMixin): ...@@ -215,20 +196,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 +217,18 @@ class Asset(OrgModelMixin): ...@@ -250,15 +217,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:
...@@ -303,10 +273,7 @@ class Asset(OrgModelMixin): ...@@ -303,10 +273,7 @@ class Asset(OrgModelMixin):
'id': self.id, 'id': self.id,
'hostname': self.hostname, 'hostname': self.hostname,
'ip': self.ip, 'ip': self.ip,
'protocols': [ 'protocols': self.protocols_as_list,
{"name": p.name, "port": p.port}
for p in self.protocols.all()
],
'platform': self.platform, 'platform': self.platform,
} }
} }
...@@ -321,20 +288,25 @@ class Asset(OrgModelMixin): ...@@ -321,20 +288,25 @@ 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:
asset.save() asset.save()
asset.protocols.create(name="ssh", port=22) asset.protocols = 'ssh/22'
if nodes and len(nodes) > 3: if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3) _nodes = random.sample(nodes, 3)
else: else:
......
...@@ -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):
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from rest_framework import serializers
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, Node, Label
from .system_user import AssetSystemUserSerializer from .base import ConnectivitySerializer
__all__ = [ __all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer', 'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolSerializer', 'ProtocolsField',
] ]
class ProtocolSerializer(serializers.ModelSerializer): class ProtocolField(serializers.RegexField):
class Meta: protocols = '|'.join(dict(Asset.PROTOCOL_CHOICES).keys())
model = Protocol default_error_messages = {
fields = ["name", "port"] 'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
}
regex = r'^(%s)/(\d{1,5})$' % protocols
def __init__(self, *args, **kwargs):
super().__init__(self.regex, **kwargs)
class ProtocolsRelatedField(serializers.RelatedField):
def to_representation(self, value):
return str(value)
def to_internal_value(self, data): def validate_duplicate_protocols(values):
if isinstance(data, dict): errors = []
return data names = []
if '/' not in data:
raise ValidationError("protocol not contain /: {}".format(data)) for value in values:
v = data.split("/") if not value or '/' not in value:
if len(v) != 2: continue
raise ValidationError("protocol format should be name/port: {}".format(data)) name = value.split('/')[0]
name, port = v if name in names:
cleaned_data = {"name": name, "port": port} errors.append(_("Protocol duplicate: {}").format(name))
return cleaned_data names.append(name)
errors.append('')
if any(errors):
raise serializers.ValidationError(errors)
class ProtocolsField(serializers.ListField):
default_validators = [validate_duplicate_protocols]
def __init__(self, *args, **kwargs):
kwargs['child'] = ProtocolField()
kwargs['allow_null'] = True
kwargs['allow_empty'] = True
kwargs['min_length'] = 1
kwargs['max_length'] = 4
super().__init__(*args, **kwargs)
def to_representation(self, value):
if not value:
return []
return value.split(' ')
class AssetSerializer(BulkOrgResourceModelSerializer): class AssetSerializer(BulkOrgResourceModelSerializer):
protocols = ProtocolsRelatedField( protocols = ProtocolsField(label=_('Protocols'), required=False)
many=True, queryset=Protocol.objects.all(), label=_("Protocols") connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
)
""" """
资产的数据结构 资产的数据结构
...@@ -57,7 +76,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -57,7 +76,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,121 +88,41 @@ class AssetSerializer(BulkOrgResourceModelSerializer): ...@@ -69,121 +88,41 @@ 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')),
).select_related('admin_user', 'domain')
return queryset return queryset
@staticmethod def compatible_with_old_protocol(self, validated_data):
def validate_protocols(attr):
protocols_serializer = ProtocolSerializer(data=attr, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols_name = [i.get("name", "ssh") for i in attr]
errors = [{} for i in protocols_name]
for i, name in enumerate(protocols_name):
if name in protocols_name[:i]:
errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
if any(errors):
raise ValidationError(errors)
return attr
def create(self, validated_data):
protocols_data = validated_data.pop("protocols", []) protocols_data = validated_data.pop("protocols", [])
# 兼容老的api # 兼容老的api
protocol = validated_data.get("protocol") name = validated_data.get("protocol")
port = validated_data.get("port") port = validated_data.get("port")
if not protocols_data and protocol and port: if not protocols_data and name and port:
protocols_data = [{"name": protocol, "port": port}] protocols_data.insert(0, '/'.join([name, str(port)]))
elif not name and not port and protocols_data:
protocol = protocols_data[0].split('/')
validated_data["protocol"] = protocol[0]
validated_data["port"] = int(protocol[1])
if validated_data:
validated_data["protocols"] = ' '.join(protocols_data)
if not protocol and not port and protocols_data: def create(self, validated_data):
validated_data["protocol"] = protocols_data[0]["name"] self.compatible_with_old_protocol(validated_data)
validated_data["port"] = protocols_data[0]["port"]
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().create(validated_data) instance = super().create(validated_data)
instance.protocols.set(protocols)
return instance return instance
def update(self, instance, validated_data): def update(self, instance, validated_data):
protocols_data = validated_data.pop("protocols", []) self.compatible_with_old_protocol(validated_data)
return super().update(instance, validated_data)
# 兼容老的api
protocol = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and protocol and port:
protocols_data = [{"name": protocol, "port": port}]
if not protocol and not port and protocols_data:
validated_data["protocol"] = protocols_data[0]["name"]
validated_data["port"] = protocols_data[0]["port"]
protocols = None
if protocols_data:
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
protocols_serializer.is_valid(raise_exception=True)
protocols = protocols_serializer.save()
instance = super().update(instance, validated_data)
if protocols:
instance.protocols.all().delete()
instance.protocols.set(protocols)
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):
......
...@@ -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.
...@@ -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 () {
......
...@@ -70,11 +70,7 @@ ...@@ -70,11 +70,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'Protocol' %}</td> <td>{% trans 'Protocol' %}</td>
<td> <td>{{ asset.protocols }}</td>
{% for protocol in asset.protocols.all %}
<b>{{ protocol }}</b>
{% endfor %}
</td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Admin user' %}:</td> <td>{% trans 'Admin user' %}:</td>
......
...@@ -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)
......
This diff is collapsed.
...@@ -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)
......
...@@ -4,3 +4,4 @@ ...@@ -4,3 +4,4 @@
from .backends import * from .backends import *
from .middleware import * from .middleware import *
from .utils import * from .utils import *
from .decorator import *
...@@ -20,7 +20,6 @@ __all__ = [ ...@@ -20,7 +20,6 @@ __all__ = [
class BaseOpenIDAuthorizationBackend(object): class BaseOpenIDAuthorizationBackend(object):
@staticmethod @staticmethod
def user_can_authenticate(user): def user_can_authenticate(user):
""" """
...@@ -40,25 +39,20 @@ class BaseOpenIDAuthorizationBackend(object): ...@@ -40,25 +39,20 @@ class BaseOpenIDAuthorizationBackend(object):
class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
def authenticate(self, request, **kwargs): def authenticate(self, request, **kwargs):
logger.info('Authentication OpenID code backend') logger.info('Authentication OpenID code backend')
code = kwargs.get('code') code = kwargs.get('code')
redirect_uri = kwargs.get('redirect_uri') redirect_uri = kwargs.get('redirect_uri')
if not code or not redirect_uri: if not code or not redirect_uri:
logger.info('Authenticate failed: No code or No redirect uri') logger.info('Authenticate failed: No code or No redirect uri')
return None return None
try: try:
oidt_profile = client.update_or_create_from_code( oidt_profile = client.update_or_create_from_code(
code=code, redirect_uri=redirect_uri code=code, redirect_uri=redirect_uri
) )
except Exception as e: except Exception as e:
logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
return None
else: else:
# Check openid user single logout or not with access_token # Check openid user single logout or not with access_token
request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token
...@@ -68,25 +62,19 @@ class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend): ...@@ -68,25 +62,19 @@ class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend): class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
def authenticate(self, request, username=None, password=None, **kwargs): def authenticate(self, request, username=None, password=None, **kwargs):
logger.info('Authentication OpenID password backend') logger.info('Authentication OpenID password backend')
if not username:
if not settings.AUTH_OPENID:
logger.info('Authenticate failed: AUTH_OPENID is False')
return None
elif not username:
logger.info('Authenticate failed: Not username') logger.info('Authenticate failed: Not username')
return None return None
try: try:
oidt_profile = client.update_or_create_from_password( oidt_profile = client.update_or_create_from_password(
username=username, password=password username=username, password=password
) )
except Exception as e: except Exception as e:
logger.error(e, exc_info=True)
logger.info('Authenticate failed: get oidt_profile: {}'.format(e)) logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
return None
else: else:
user = oidt_profile.user user = oidt_profile.user
logger.info('Authenticate success: user -> {}'.format(user)) logger.info('Authenticate success: user -> {}'.format(user))
......
# coding: utf-8
#
import warnings
import contextlib
import requests
from urllib3.exceptions import InsecureRequestWarning
from django.conf import settings
__all__ = [
'ssl_verification',
]
old_merge_environment_settings = requests.Session.merge_environment_settings
@contextlib.contextmanager
def no_ssl_verification():
"""
https://stackoverflow.com/questions/15445981/
how-do-i-disable-the-security-certificate-check-in-python-requests
"""
opened_adapters = set()
def merge_environment_settings(self, url, proxies, stream, verify, cert):
# Verification happens only once per connection so we need to close
# all the opened adapters once we're done. Otherwise, the effects of
# verify=False persist beyond the end of this context manager.
opened_adapters.add(self.get_adapter(url))
_settings = old_merge_environment_settings(
self, url, proxies, stream, verify, cert
)
_settings['verify'] = False
return _settings
requests.Session.merge_environment_settings = merge_environment_settings
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore', InsecureRequestWarning)
yield
finally:
requests.Session.merge_environment_settings = old_merge_environment_settings
for adapter in opened_adapters:
try:
adapter.close()
except:
pass
def ssl_verification(func):
def wrapper(*args, **kwargs):
if not settings.AUTH_OPENID_IGNORE_SSL_VERIFICATION:
return func(*args, **kwargs)
with no_ssl_verification():
return func(*args, **kwargs)
return wrapper
...@@ -19,24 +19,23 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): ...@@ -19,24 +19,23 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin):
""" """
Check openid user single logout (with access_token) Check openid user single logout (with access_token)
""" """
def process_request(self, request): def process_request(self, request):
# Don't need openid auth if AUTH_OPENID is False # Don't need openid auth if AUTH_OPENID is False
if not settings.AUTH_OPENID: if not settings.AUTH_OPENID:
return return
# Don't need openid auth if no shared session enabled
if not settings.AUTH_OPENID_SHARE_SESSION:
return
# Don't need check single logout if user not authenticated # Don't need check single logout if user not authenticated
if not request.user.is_authenticated: if not request.user.is_authenticated:
return return
elif not request.session[BACKEND_SESSION_KEY].endswith( elif not request.session[BACKEND_SESSION_KEY].endswith(
BACKEND_OPENID_AUTH_CODE): BACKEND_OPENID_AUTH_CODE):
return return
# Check openid user single logout or not with access_token # Check openid user single logout or not with access_token
client = new_client()
try: try:
client.openid_connect_client.userinfo( client = new_client()
token=request.session.get(OIDT_ACCESS_TOKEN) client.get_userinfo(token=request.session.get(OIDT_ACCESS_TOKEN))
)
except Exception as e: except Exception as e:
logout(request) logout(request)
logger.error(e) logger.error(e)
...@@ -24,7 +24,6 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView'] ...@@ -24,7 +24,6 @@ __all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
class OpenIDLoginView(RedirectView): class OpenIDLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
redirect_uri = settings.BASE_SITE_URL + str(settings.LOGIN_COMPLETE_URL) redirect_uri = settings.BASE_SITE_URL + str(settings.LOGIN_COMPLETE_URL)
nonce = Nonce( nonce = Nonce(
...@@ -32,42 +31,36 @@ class OpenIDLoginView(RedirectView): ...@@ -32,42 +31,36 @@ class OpenIDLoginView(RedirectView):
next_path=self.request.GET.get('next') next_path=self.request.GET.get('next')
) )
cache.set(str(nonce.state), nonce, 24*3600) cache.set(str(nonce.state), nonce, 24*3600)
self.request.session['openid_state'] = str(nonce.state) self.request.session['openid_state'] = str(nonce.state)
authorization_url = client.openid_connect_client.\ authorization_url = client.get_authorization_url(
authorization_url( redirect_uri=nonce.redirect_uri,
redirect_uri=nonce.redirect_uri, scope='code', scope='code',
state=str(nonce.state) state=str(nonce.state)
) )
return authorization_url return authorization_url
class OpenIDLoginCompleteView(RedirectView): class OpenIDLoginCompleteView(RedirectView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'error' in request.GET: if 'error' in request.GET:
return HttpResponseServerError(self.request.GET['error']) return HttpResponseServerError(self.request.GET['error'])
if 'code' not in self.request.GET and 'state' not in self.request.GET: if 'code' not in self.request.GET and 'state' not in self.request.GET:
return HttpResponseBadRequest() return HttpResponseBadRequest(content='Code or State is empty')
if self.request.GET['state'] != self.request.session['openid_state']: if self.request.GET['state'] != self.request.session['openid_state']:
return HttpResponseBadRequest() return HttpResponseBadRequest(content='State invalid')
nonce = cache.get(self.request.GET['state']) nonce = cache.get(self.request.GET['state'])
if not nonce: if not nonce:
return HttpResponseBadRequest() return HttpResponseBadRequest(content='State failure')
user = authenticate( user = authenticate(
request=self.request, request=self.request,
code=self.request.GET['code'], code=self.request.GET['code'],
redirect_uri=nonce.redirect_uri redirect_uri=nonce.redirect_uri
) )
cache.delete(str(nonce.state)) cache.delete(str(nonce.state))
if not user: if not user:
return HttpResponseBadRequest() return HttpResponseBadRequest(content='Authenticate user failed')
login(self.request, user) login(self.request, user)
post_openid_login_success.send( post_openid_login_success.send(
......
...@@ -18,19 +18,17 @@ from .signals import post_auth_success, post_auth_failed ...@@ -18,19 +18,17 @@ from .signals import post_auth_success, post_auth_failed
def on_user_logged_out(sender, request, user, **kwargs): def on_user_logged_out(sender, request, user, **kwargs):
if not settings.AUTH_OPENID: if not settings.AUTH_OPENID:
return return
if not settings.AUTH_OPENID_SHARE_SESSION:
return
query = QueryDict('', mutable=True) query = QueryDict('', mutable=True)
query.update({ query.update({
'redirect_uri': settings.BASE_SITE_URL 'redirect_uri': settings.BASE_SITE_URL
}) })
client = new_client() client = new_client()
openid_logout_url = "%s?%s" % ( openid_logout_url = "%s?%s" % (
client.openid_connect_client.get_url( client.get_url_end_session_endpoint(),
name='end_session_endpoint'),
query.urlencode() query.urlencode()
) )
request.COOKIES['next'] = openid_logout_url request.COOKIES['next'] = openid_logout_url
......
...@@ -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
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
#
from werkzeug.local import Local
thread_local = Local()
def _find(attr):
return getattr(thread_local, attr, None)
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.
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