mirror of https://github.com/jumpserver/jumpserver
commit
1c54e5acd8
26
README.md
26
README.md
|
@ -1,26 +1,26 @@
|
|||
# Jumpserver 多云环境下更好用的堡垒机
|
||||
# JumpServer 多云环境下更好用的堡垒机
|
||||
|
||||
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
|
||||
[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
|
||||
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
|
||||
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/)
|
||||
|
||||
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 机制的运维安全审计系统。
|
||||
JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 机制的运维安全审计系统。
|
||||
|
||||
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
|
||||
JumpServer 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
|
||||
|
||||
Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
|
||||
JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
|
||||
|
||||
改变世界,从一点点开始。
|
||||
|
||||
注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 Jumpserver 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
|
||||
注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
|
||||
|
||||
## 核心功能列表
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td rowspan="7">身份认证<br>Authentication</td>
|
||||
<td rowspan="4">登录认证</td>
|
||||
<td rowspan="5">登录认证</td>
|
||||
<td>资源统一登录与认证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -32,6 +32,9 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向
|
|||
<tr>
|
||||
<td>OpenID 认证(实现单点登录)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CAS 认证 (实现单点登录)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">MFA认证</td>
|
||||
<td>MFA 二次认证(Google Authenticator)</td>
|
||||
|
@ -177,17 +180,10 @@ Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向
|
|||
|
||||
## 演示视频和截屏
|
||||
|
||||
我们提供了演示视频和系统截图可以让你快速了解 Jumpserver:
|
||||
我们提供了演示视频和系统截图可以让你快速了解 JumpServer:
|
||||
|
||||
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
|
||||
- [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
## SDK
|
||||
|
||||
我们编写了一些SDK,供您的其它系统快速和 Jumpserver API 交互:
|
||||
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK
|
||||
- [系统截图](http://docs.JumpServer.org/zh/docs/snapshot.html)
|
||||
|
||||
## License & Copyright
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by Jumpserver Team.
|
||||
:copyright: (c) 2014-2018 by JumpServer Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
|||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi', 'AssetPlatformViewSet',
|
||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
||||
'AssetTaskCreateApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -36,7 +36,10 @@ class AssetViewSet(OrgBulkModelViewSet):
|
|||
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
serializer_class = serializers.AssetSerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetSerializer,
|
||||
'display': serializers.AssetDisplaySerializer,
|
||||
}
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
|
||||
|
||||
|
@ -80,53 +83,40 @@ class AssetPlatformViewSet(ModelViewSet):
|
|||
self.permission_denied(
|
||||
request, message={"detail": "Internal platform"}
|
||||
)
|
||||
|
||||
return super().check_object_permissions(request, obj)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Refresh asset hardware info
|
||||
"""
|
||||
class AssetTaskCreateApi(generics.CreateAPIView):
|
||||
model = Asset
|
||||
serializer_class = serializers.AssetSerializer
|
||||
serializer_class = serializers.AssetTaskSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
instance = get_object_or_404(Asset, pk=pk)
|
||||
return instance
|
||||
|
||||
def perform_create(self, serializer):
|
||||
asset = self.get_object()
|
||||
action = serializer.validated_data["action"]
|
||||
if action == "refresh":
|
||||
task = update_asset_hardware_info_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
model = Asset
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
else:
|
||||
task = test_asset_connectivity_manual.delay(asset)
|
||||
return Response({"task": task.id})
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
|
||||
|
||||
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
class AssetGatewayListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.GatewayWithAuthSerializer
|
||||
model = Asset
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_id = kwargs.get('pk')
|
||||
def get_queryset(self):
|
||||
asset_id = self.kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
if asset.domain and \
|
||||
asset.domain.gateways.filter(protocol='ssh').exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol='ssh'))
|
||||
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
return Response({"msg": "Not have gateway"}, status=404)
|
||||
if not asset.domain:
|
||||
return []
|
||||
queryset = asset.domain.gateways.filter(protocol='ssh')
|
||||
return queryset
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import generics
|
||||
from rest_framework import filters
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import Http404
|
||||
from django.conf import settings
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import generics, filters
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.mixins import CommonApiMixin
|
||||
from ..backends import AssetUserManager
|
||||
from ..models import Asset, Node, SystemUser, AdminUser
|
||||
from ..models import Asset, Node, SystemUser
|
||||
from .. import serializers
|
||||
from ..tasks import test_asset_users_connectivity_manual
|
||||
from ..tasks import (
|
||||
test_asset_users_connectivity_manual, push_system_user_a_asset_manual
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
|
||||
'AssetUserExportViewSet',
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoViewSet', 'AssetUserTaskCreateAPI',
|
||||
]
|
||||
|
||||
|
||||
|
@ -34,10 +31,17 @@ class AssetUserFilterBackend(filters.BaseFilterBackend):
|
|||
value = request.GET.get(field)
|
||||
if not value:
|
||||
continue
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
if field == "node_id":
|
||||
value = get_object_or_none(Node, pk=value)
|
||||
kwargs["node"] = value
|
||||
continue
|
||||
elif field == "asset_id":
|
||||
field = "asset"
|
||||
kwargs[field] = value
|
||||
return queryset.filter(**kwargs)
|
||||
if kwargs:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
logger.debug("Filter {}".format(kwargs))
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||
|
@ -45,72 +49,63 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
|
|||
value = request.GET.get('search')
|
||||
if not value:
|
||||
return queryset
|
||||
_queryset = AssetUserManager.none()
|
||||
for field in view.search_fields:
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
_queryset |= queryset.filter(**{field: value})
|
||||
return _queryset.distinct()
|
||||
queryset = queryset.search(value)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserLatestFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
latest = request.GET.get('latest') == '1'
|
||||
if latest:
|
||||
queryset = queryset.distinct()
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetUserWriteSerializer,
|
||||
'list': serializers.AssetUserReadSerializer,
|
||||
'retrieve': serializers.AssetUserReadSerializer,
|
||||
}
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
http_method_names = ['get', 'post']
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username", "asset_id", "node_id",
|
||||
"system_user_id", "admin_user_id"
|
||||
"id", "ip", "hostname", "username",
|
||||
"asset_id", "node_id",
|
||||
"prefer", "prefer_id",
|
||||
]
|
||||
search_fields = filter_fields
|
||||
filter_backends = (
|
||||
filters.OrderingFilter,
|
||||
search_fields = ["ip", "hostname", "username"]
|
||||
filter_backends = [
|
||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||
)
|
||||
AssetUserLatestFilterBackend,
|
||||
]
|
||||
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
return False
|
||||
|
||||
def get_queryset(self):
|
||||
# 尽可能先返回更少的数据
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
node_id = self.request.GET.get('node_id')
|
||||
admin_user_id = self.request.GET.get("admin_user_id")
|
||||
system_user_id = self.request.GET.get("system_user_id")
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
queryset = self.get_queryset()
|
||||
obj = queryset.get(id=pk)
|
||||
return obj
|
||||
|
||||
kwargs = {}
|
||||
assets = None
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
manager = AssetUserManager()
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
assets = system_user.get_all_assets()
|
||||
username = system_user.username
|
||||
elif admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
assets = admin_user.assets.all()
|
||||
username = admin_user.username
|
||||
manager.prefer('admin_user')
|
||||
manager.delete(instance)
|
||||
|
||||
if asset_id:
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
assets = [asset]
|
||||
elif node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
assets = node.get_all_assets()
|
||||
|
||||
if username:
|
||||
kwargs['username'] = username
|
||||
if assets is not None:
|
||||
kwargs['assets'] = assets
|
||||
|
||||
queryset = manager.filter(**kwargs)
|
||||
def get_queryset(self):
|
||||
manager = AssetUserManager()
|
||||
queryset = manager.all()
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserExportViewSet(AssetUserViewSet):
|
||||
serializer_class = serializers.AssetUserExportSerializer
|
||||
http_method_names = ['get']
|
||||
class AssetUserAuthInfoViewSet(AssetUserViewSet):
|
||||
serializer_classes = {"default": serializers.AssetUserAuthInfoSerializer}
|
||||
http_method_names = ['get', 'post']
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
|
||||
def get_permissions(self):
|
||||
|
@ -119,66 +114,31 @@ class AssetUserExportViewSet(AssetUserViewSet):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
serializer_class = serializers.AssetUserAuthInfoSerializer
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
|
||||
def get_permissions(self):
|
||||
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
||||
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
return super().get_permissions()
|
||||
|
||||
def get_object(self):
|
||||
query_params = self.request.query_params
|
||||
username = query_params.get('username')
|
||||
asset_id = query_params.get('asset_id')
|
||||
prefer = query_params.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
try:
|
||||
manger = AssetUserManager()
|
||||
instance = manger.get(username, asset, prefer=prefer)
|
||||
except Exception as e:
|
||||
raise Http404("Not found")
|
||||
else:
|
||||
return instance
|
||||
|
||||
|
||||
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
class AssetUserTaskCreateAPI(generics.CreateAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
serializer_class = serializers.AssetUserTaskSerializer
|
||||
filter_backends = AssetUserViewSet.filter_backends
|
||||
filter_fields = AssetUserViewSet.filter_fields
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
prefer = self.request.GET.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
manager = AssetUserManager()
|
||||
asset_users = manager.filter(username=username, assets=[asset], prefer=prefer)
|
||||
return asset_users
|
||||
queryset = manager.all()
|
||||
for cls in self.filter_backends:
|
||||
queryset = cls().filter_queryset(self.request, queryset, self)
|
||||
return list(queryset)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
def perform_create(self, serializer):
|
||||
asset_users = self.get_asset_users()
|
||||
prefer = self.request.GET.get("prefer")
|
||||
kwargs = {}
|
||||
if prefer == "admin_user":
|
||||
kwargs["run_as_admin"] = True
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users, **kwargs)
|
||||
return Response({"task": task.id})
|
||||
# action = serializer.validated_data["action"]
|
||||
# only this
|
||||
# if action == "test":
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
return task
|
||||
|
||||
|
||||
class AssetUserPushApi(generics.CreateAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
serializer_class = serializers.AssetUserPushSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
asset = serializer.validated_data["asset"]
|
||||
username = serializer.validated_data["username"]
|
||||
pass
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
|
|
@ -1,24 +1,11 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from collections import namedtuple
|
||||
from rest_framework import status
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import get_object_or_404, Http404
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.tree import TreeNodeSerializer
|
||||
|
@ -27,7 +14,8 @@ from orgs.mixins import generics
|
|||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import (
|
||||
update_assets_hardware_info_util, test_asset_connectivity_util
|
||||
update_node_assets_hardware_info_manual,
|
||||
test_node_assets_connectivity_manual,
|
||||
)
|
||||
from .. import serializers
|
||||
|
||||
|
@ -36,9 +24,9 @@ logger = get_logger(__file__)
|
|||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||
'TestNodeConnectiveApi', 'NodeListAsTreeApi',
|
||||
'NodeChildrenAsTreeApi', 'RefreshNodesCacheApi',
|
||||
'NodeAddChildrenApi', 'NodeListAsTreeApi',
|
||||
'NodeChildrenAsTreeApi',
|
||||
'NodeTaskCreateApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -64,9 +52,9 @@ class NodeViewSet(OrgModelViewSet):
|
|||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.has_children_or_contains_assets():
|
||||
msg = _("Deletion failed and the node contains children or assets")
|
||||
return Response(data={'msg': msg}, status=status.HTTP_403_FORBIDDEN)
|
||||
if node.has_children_or_has_assets():
|
||||
error = _("Deletion failed and the node contains children or assets")
|
||||
return Response(data={'error': error}, status=status.HTTP_403_FORBIDDEN)
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -261,41 +249,41 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
|||
asset.nodes.set([instance])
|
||||
|
||||
|
||||
class RefreshNodeHardwareInfoApi(APIView):
|
||||
class NodeTaskCreateApi(generics.CreateAPIView):
|
||||
model = Node
|
||||
serializer_class = serializers.NodeTaskSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.get_all_assets()
|
||||
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
def get_object(self):
|
||||
node_id = self.kwargs.get('pk')
|
||||
node = get_object_or_none(self.model, id=node_id)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def set_serializer_data(s, task):
|
||||
data = getattr(s, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(s, '_data', data)
|
||||
|
||||
class TestNodeConnectiveApi(APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
model = Node
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
node_id = kwargs.get('pk')
|
||||
node = get_object_or_404(self.model, id=node_id)
|
||||
assets = node.get_all_assets()
|
||||
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class RefreshNodesCacheApi(APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@staticmethod
|
||||
def refresh_nodes_cache():
|
||||
Node.refresh_nodes()
|
||||
return Response("Ok")
|
||||
Task = namedtuple('Task', ['id'])
|
||||
task = Task(id="0")
|
||||
return task
|
||||
|
||||
def perform_create(self, serializer):
|
||||
action = serializer.validated_data["action"]
|
||||
node = self.get_object()
|
||||
if action == "refresh_cache" and node is None:
|
||||
task = self.refresh_nodes_cache()
|
||||
self.set_serializer_data(serializer, task)
|
||||
return
|
||||
if node is None:
|
||||
raise Http404()
|
||||
if action == "refresh":
|
||||
task = update_node_assets_hardware_info_manual.delay(node)
|
||||
else:
|
||||
task = test_node_assets_connectivity_manual.delay(node)
|
||||
self.set_serializer_data(serializer, task)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.get(*args, **kwargs)
|
||||
return Response(status=204)
|
||||
|
|
|
@ -1,42 +1,25 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.response import Response
|
||||
from django.db.models import Count
|
||||
|
||||
from common.serializers import CeleryTaskSerializer
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsAppUser
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from orgs.utils import tmp_to_org
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..serializers import SystemUserWithAuthInfoSerializer
|
||||
from ..tasks import (
|
||||
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
|
||||
push_system_user_a_asset_manual, test_system_user_connectivity_a_asset,
|
||||
push_system_user_a_asset_manual,
|
||||
)
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
|
||||
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi',
|
||||
]
|
||||
|
||||
|
||||
|
@ -48,13 +31,12 @@ class SystemUserViewSet(OrgBulkModelViewSet):
|
|||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.SystemUserSerializer,
|
||||
'list': serializers.SystemUserListSerializer,
|
||||
}
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(_assets_amount=Count('assets'))
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
|
@ -62,7 +44,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
|||
"""
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
serializer_class = SystemUserWithAuthInfoSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
|
@ -75,88 +57,61 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
|
|||
Get system user with asset auth info
|
||||
"""
|
||||
model = SystemUser
|
||||
permission_classes = (IsAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = SystemUserWithAuthInfoSerializer
|
||||
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
||||
def get_object(self):
|
||||
instance = super().get_object()
|
||||
aid = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, pk=aid)
|
||||
instance.load_specific_asset_auth(asset)
|
||||
username = instance.username
|
||||
if instance.username_same_with_user:
|
||||
username = self.request.query_params.get("username")
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
with tmp_to_org(asset.org_id):
|
||||
instance.load_asset_special_auth(asset=asset, username=username)
|
||||
return instance
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
model = SystemUser
|
||||
class SystemUserTaskApi(generics.CreateAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = CeleryTaskSerializer
|
||||
serializer_class = serializers.SystemUserTaskSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
nodes = system_user.nodes.all()
|
||||
for node in nodes:
|
||||
system_user.assets.add(*tuple(node.get_all_assets()))
|
||||
def do_push(self, system_user, asset=None):
|
||||
if asset is None:
|
||||
task = push_system_user_to_assets_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
else:
|
||||
username = self.request.query_params.get('username')
|
||||
task = push_system_user_a_asset_manual.delay(
|
||||
system_user, asset, username=username
|
||||
)
|
||||
return task
|
||||
|
||||
|
||||
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
"""
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = CeleryTaskSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
@staticmethod
|
||||
def do_test(system_user, asset=None):
|
||||
task = test_system_user_connectivity_manual.delay(system_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
return task
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(SystemUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
def perform_create(self, serializer):
|
||||
action = serializer.validated_data["action"]
|
||||
asset = serializer.validated_data.get('asset')
|
||||
system_user = self.get_object()
|
||||
return system_user.assets.all()
|
||||
|
||||
|
||||
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = push_system_user_a_asset_manual.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
system_user = self.get_object()
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
task = test_system_user_connectivity_a_asset.delay(system_user, asset)
|
||||
return Response({"task": task.id})
|
||||
if action == 'push':
|
||||
task = self.do_push(system_user, asset)
|
||||
else:
|
||||
task = self.do_test(system_user, asset)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
|
||||
|
||||
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
from common.permissions import IsOrgAdmin
|
||||
|
@ -8,10 +10,13 @@ from orgs.mixins.api import OrgBulkModelViewSet
|
|||
from orgs.utils import current_org
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = ['SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet']
|
||||
__all__ = [
|
||||
'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet',
|
||||
'SystemUserUserRelationViewSet',
|
||||
]
|
||||
|
||||
|
||||
class RelationMixin(OrgBulkModelViewSet):
|
||||
class RelationMixin:
|
||||
def get_queryset(self):
|
||||
queryset = self.model.objects.all()
|
||||
org_id = current_org.org_id()
|
||||
|
@ -23,8 +28,40 @@ class RelationMixin(OrgBulkModelViewSet):
|
|||
))
|
||||
return queryset
|
||||
|
||||
def send_post_add_signal(self, instance):
|
||||
if not isinstance(instance, list):
|
||||
instance = [instance]
|
||||
|
||||
class SystemUserAssetRelationViewSet(RelationMixin):
|
||||
system_users_objects_map = defaultdict(list)
|
||||
model, object_field = self.get_objects_attr()
|
||||
|
||||
for i in instance:
|
||||
_id = getattr(i, object_field).id
|
||||
system_users_objects_map[i.systemuser].append(_id)
|
||||
|
||||
sender = self.get_sender()
|
||||
for system_user, objects in system_users_objects_map.items():
|
||||
m2m_changed.send(
|
||||
sender=sender, instance=system_user, action='post_add',
|
||||
reverse=False, model=model, pk_set=objects
|
||||
)
|
||||
|
||||
def get_sender(self):
|
||||
return self.model
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Asset, 'asset'
|
||||
|
||||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
self.send_post_add_signal(instance)
|
||||
|
||||
|
||||
class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserAssetRelationSerializer
|
||||
model = models.SystemUser.assets.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
@ -36,6 +73,9 @@ class SystemUserAssetRelationViewSet(RelationMixin):
|
|||
"systemuser__name", "systemuser__username"
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Asset, 'asset'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(
|
||||
|
@ -47,7 +87,7 @@ class SystemUserAssetRelationViewSet(RelationMixin):
|
|||
return queryset
|
||||
|
||||
|
||||
class SystemUserNodeRelationViewSet(RelationMixin):
|
||||
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserNodeRelationSerializer
|
||||
model = models.SystemUser.nodes.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
@ -58,8 +98,39 @@ class SystemUserNodeRelationViewSet(RelationMixin):
|
|||
"node__value", "systemuser__name", "systemuser_username"
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Node, 'node'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset \
|
||||
.annotate(node_key=F('node__key'))
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserUserRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserUserRelationSerializer
|
||||
model = models.SystemUser.users.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filterset_fields = [
|
||||
'id', 'user', 'systemuser',
|
||||
]
|
||||
search_fields = [
|
||||
"user__username", "user__name",
|
||||
"systemuser__name", "systemuser__username",
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
from users.models import User
|
||||
return User, 'user'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(
|
||||
user_display=Concat(
|
||||
F('user__name'), Value('('),
|
||||
F('user__username'), Value(')')
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AdminUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AdminUserBackend(AssetUserBackend):
|
||||
model = AdminUser
|
||||
backend = 'AdminUser'
|
|
@ -1,58 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
model = None
|
||||
backend = "AssetUser"
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, **kwargs):
|
||||
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:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(assets__in=assets).distinct()
|
||||
|
||||
queryset = cls.filter_queryset_more(queryset)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, asset_users, assets):
|
||||
instances = []
|
||||
assets_user_assets_map = defaultdict(set)
|
||||
if isinstance(asset_users, list):
|
||||
assets_user_assets_map = {
|
||||
asset_user.id: asset_user.assets.values_list('id', flat=True)
|
||||
for asset_user in asset_users
|
||||
}
|
||||
else:
|
||||
assets_user_assets = asset_users.values_list('id', 'assets')
|
||||
for i, asset_id in assets_user_assets:
|
||||
assets_user_assets_map[i].add(asset_id)
|
||||
|
||||
for asset_user in asset_users:
|
||||
if not assets:
|
||||
related_assets = asset_user.assets.all()
|
||||
else:
|
||||
assets_map = {a.id: a for a in assets}
|
||||
related_assets = [
|
||||
assets_map.get(i) for i in assets_user_assets_map.get(asset_user.id) if i in assets_map
|
||||
]
|
||||
for asset in related_assets:
|
||||
instance = asset_user.construct_to_authbook(asset)
|
||||
instance.backend = cls.backend
|
||||
instances.append(instance)
|
||||
return instances
|
|
@ -1,94 +1,48 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
|
||||
from ..models import Asset
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:param prefer: 优先使用
|
||||
:param prefer_id: 使用id
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
def all(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, username=None, hostname=None, ip=None, assets=None,
|
||||
node=None, prefer_id=None, **kwargs):
|
||||
pass
|
||||
|
||||
class AssetUserQuerySet(list):
|
||||
def order_by(self, *ordering):
|
||||
_ordering = []
|
||||
reverse = False
|
||||
for i in ordering:
|
||||
if i[0] == '-':
|
||||
reverse = True
|
||||
i = i[1:]
|
||||
_ordering.append(i)
|
||||
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
|
||||
return self
|
||||
@abstractmethod
|
||||
def search(self, item):
|
||||
pass
|
||||
|
||||
def filter_in(self, kwargs):
|
||||
in_kwargs = {}
|
||||
queryset = []
|
||||
for k, v in kwargs.items():
|
||||
if len(v) == 0:
|
||||
return self
|
||||
if k.find("__in") >= 0:
|
||||
_k = k.split('__')[0]
|
||||
in_kwargs[_k] = v
|
||||
else:
|
||||
in_kwargs[k] = v
|
||||
for k in in_kwargs:
|
||||
kwargs.pop(k, None)
|
||||
@abstractmethod
|
||||
def get_queryset(self):
|
||||
pass
|
||||
|
||||
if len(in_kwargs) == 0:
|
||||
return self
|
||||
for i in self:
|
||||
matched = False
|
||||
for k, v in in_kwargs.items():
|
||||
attr = getattr(i, k, None)
|
||||
# 如果属性或者value中是uuid,则转换成string
|
||||
if isinstance(v[0], uuid.UUID):
|
||||
v = [str(i) for i in v]
|
||||
if isinstance(attr, uuid.UUID):
|
||||
attr = str(attr)
|
||||
if attr in v:
|
||||
matched = True
|
||||
if matched:
|
||||
queryset.append(i)
|
||||
return AssetUserQuerySet(queryset)
|
||||
@abstractmethod
|
||||
def delete(self, union_id):
|
||||
pass
|
||||
|
||||
def filter_equal(self, kwargs):
|
||||
def filter_it(obj):
|
||||
wanted = []
|
||||
real = []
|
||||
for k, v in kwargs.items():
|
||||
wanted.append(v)
|
||||
value = getattr(obj, k, None)
|
||||
if isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
real.append(value)
|
||||
return wanted == real
|
||||
kwargs = {k: v for k, v in kwargs.items() if k.find('__in') == -1}
|
||||
if len(kwargs) > 0:
|
||||
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
|
||||
else:
|
||||
queryset = self
|
||||
return queryset
|
||||
@staticmethod
|
||||
def qs_to_values(qs):
|
||||
values = qs.values(
|
||||
'hostname', 'ip', "asset_id",
|
||||
'username', 'password', 'private_key', 'public_key',
|
||||
'score', 'version',
|
||||
"asset_username", "union_id",
|
||||
'date_created', 'date_updated',
|
||||
'org_id', 'backend',
|
||||
)
|
||||
return values
|
||||
|
||||
def filter(self, **kwargs):
|
||||
queryset = self.filter_in(kwargs).filter_equal(kwargs)
|
||||
return queryset
|
||||
|
||||
def distinct(self):
|
||||
items = list(set(self))
|
||||
self[:] = items
|
||||
return self
|
||||
|
||||
def __or__(self, other):
|
||||
self.extend(other)
|
||||
return self
|
||||
@staticmethod
|
||||
def make_assets_as_id(assets):
|
||||
if not assets:
|
||||
return []
|
||||
if isinstance(assets[0], Asset):
|
||||
assets = [a.id for a in assets]
|
||||
return assets
|
||||
|
|
|
@ -1,29 +1,318 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from functools import reduce
|
||||
from django.db.models import F, CharField, Value, IntegerField, Q, Count
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
from ..models import AuthBook
|
||||
from common.utils import get_object_or_none
|
||||
from orgs.utils import current_org
|
||||
from ..models import AuthBook, SystemUser, Asset, AdminUser
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, latest=True, **kwargs):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(asset__in=assets)
|
||||
if latest:
|
||||
queryset = queryset.latest_version()
|
||||
return queryset
|
||||
class DBBackend(BaseBackend):
|
||||
union_id_length = 2
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
auth_info = {
|
||||
'password': kwargs.pop('password', ''),
|
||||
'public_key': kwargs.pop('public_key', ''),
|
||||
'private_key': kwargs.pop('private_key', '')
|
||||
}
|
||||
obj = AuthBook.objects.create(**kwargs)
|
||||
obj.set_auth(**auth_info)
|
||||
return obj
|
||||
def __init__(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.all()
|
||||
self.queryset = queryset
|
||||
|
||||
def _clone(self):
|
||||
return self.__class__(self.queryset)
|
||||
|
||||
def all(self):
|
||||
return AuthBook.objects.none()
|
||||
|
||||
def count(self):
|
||||
return self.queryset.count()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset
|
||||
|
||||
def delete(self, union_id):
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
return
|
||||
return self._perform_delete_by_union_id(cleaned_union_id)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
pass
|
||||
|
||||
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
|
||||
union_id=None, id__in=None, **kwargs):
|
||||
clone = self._clone()
|
||||
clone._filter_union_id(union_id)
|
||||
clone._filter_prefer(prefer, prefer_id)
|
||||
clone._filter_node(node)
|
||||
clone._filter_assets(assets)
|
||||
clone._filter_other(kwargs)
|
||||
clone._filter_id_in(id__in)
|
||||
return clone
|
||||
|
||||
def _filter_union_id(self, union_id):
|
||||
if not union_id:
|
||||
return
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
return self._perform_filter_union_id(union_id, cleaned_union_id)
|
||||
|
||||
def _check_union_id(self, union_id, cleaned_union_id):
|
||||
return union_id and len(cleaned_union_id) == self.union_id_length
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
self.queryset = self.queryset.filter(union_id=union_id)
|
||||
|
||||
def _filter_assets(self, assets):
|
||||
assets_id = self.make_assets_as_id(assets)
|
||||
if assets_id:
|
||||
self.queryset = self.queryset.filter(asset_id__in=assets_id)
|
||||
|
||||
def _filter_node(self, node):
|
||||
pass
|
||||
|
||||
def _filter_id_in(self, ids):
|
||||
if ids and isinstance(ids, list):
|
||||
self.queryset = self.queryset.filter(union_id__in=ids)
|
||||
|
||||
@staticmethod
|
||||
def clean_kwargs(kwargs):
|
||||
return {k: v for k, v in kwargs.items() if v}
|
||||
|
||||
def _filter_other(self, kwargs):
|
||||
kwargs = self.clean_kwargs(kwargs)
|
||||
if kwargs:
|
||||
self.queryset = self.queryset.filter(**kwargs)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
pass
|
||||
|
||||
def search(self, item):
|
||||
qs = []
|
||||
for i in ['hostname', 'ip', 'username']:
|
||||
kwargs = {i + '__startswith': item}
|
||||
qs.append(Q(**kwargs))
|
||||
q = reduce(lambda x, y: x | y, qs)
|
||||
clone = self._clone()
|
||||
clone.queryset = clone.queryset.filter(q).distinct()
|
||||
return clone
|
||||
|
||||
|
||||
class SystemUserBackend(DBBackend):
|
||||
model = SystemUser.assets.through
|
||||
backend = 'system_user'
|
||||
prefer = backend
|
||||
base_score = 0
|
||||
union_id_length = 2
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.prefer:
|
||||
self.queryset = self.queryset.none()
|
||||
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser__id=system_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
if all((system_user, asset)):
|
||||
system_user.assets.remove(asset)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = dict(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
username=F("systemuser__username"),
|
||||
password=F("systemuser__password"),
|
||||
private_key=F("systemuser__private_key"),
|
||||
public_key=F("systemuser__public_key"),
|
||||
score=F("systemuser__priority") + self.base_score,
|
||||
version=Value(0, IntegerField()),
|
||||
date_created=F("systemuser__date_created"),
|
||||
date_updated=F("systemuser__date_updated"),
|
||||
asset_username=Concat(F("asset__id"), Value("_"),
|
||||
F("systemuser__username"),
|
||||
output_field=CharField()),
|
||||
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
output_field=CharField()),
|
||||
org_id=F("asset__org_id"),
|
||||
backend=Value(self.backend, CharField())
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
systemuser__username_same_with_user=False,
|
||||
)
|
||||
|
||||
def all(self):
|
||||
kwargs = self.get_annotate()
|
||||
filters = self.get_filter()
|
||||
qs = self.model.objects.all().annotate(**kwargs)
|
||||
if current_org.org_id() is not None:
|
||||
filters['org_id'] = current_org.org_id()
|
||||
qs = qs.filter(**filters)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class DynamicSystemUserBackend(SystemUserBackend):
|
||||
backend = 'system_user_dynamic'
|
||||
prefer = 'system_user'
|
||||
union_id_length = 3
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = super().get_annotate()
|
||||
kwargs.update(dict(
|
||||
username=F("systemuser__users__username"),
|
||||
asset_username=Concat(
|
||||
F("asset__id"), Value("_"),
|
||||
F("systemuser__users__username"),
|
||||
output_field=CharField()
|
||||
),
|
||||
union_id=Concat(
|
||||
F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
Value("_"), F("systemuser__users__id"),
|
||||
output_field=CharField()
|
||||
),
|
||||
users_count=Count('systemuser__users'),
|
||||
))
|
||||
return kwargs
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser_id=system_user_id,
|
||||
union_id=union_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
if not system_user:
|
||||
return
|
||||
system_user.users.remove(user_id)
|
||||
if system_user.users.count() == 0:
|
||||
system_user.assets.remove(asset_id)
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
users_count__gt=0,
|
||||
systemuser__username_same_with_user=True
|
||||
)
|
||||
|
||||
|
||||
class AdminUserBackend(DBBackend):
|
||||
model = Asset
|
||||
backend = 'admin_user'
|
||||
prefer = backend
|
||||
base_score = 200
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.backend:
|
||||
self.queryset = self.queryset.none()
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(nodes__id=node.id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
admin_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=asset_id, admin_user_id=admin_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
raise PermissionError(_("Could not remove asset admin user"))
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
asset_id=F("id"),
|
||||
username=F("admin_user__username"),
|
||||
password=F("admin_user__password"),
|
||||
private_key=F("admin_user__private_key"),
|
||||
public_key=F("admin_user__public_key"),
|
||||
score=Value(self.base_score, IntegerField()),
|
||||
version=Value(0, IntegerField()),
|
||||
date_updated=F("admin_user__date_updated"),
|
||||
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
|
||||
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class AuthbookBackend(DBBackend):
|
||||
model = AuthBook
|
||||
backend = 'db'
|
||||
prefer = backend
|
||||
base_score = 400
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if not prefer or not prefer_id:
|
||||
return
|
||||
if prefer.lower() == "admin_user":
|
||||
model = AdminUser
|
||||
elif prefer.lower() == "system_user":
|
||||
model = SystemUser
|
||||
else:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
obj = get_object_or_none(model, pk=prefer_id)
|
||||
if obj is None:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
username = obj.get_username()
|
||||
if isinstance(username, str):
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
# dynamic system user return more username
|
||||
else:
|
||||
self.queryset = self.queryset.filter(username__in=username)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=authbook_id, asset_id=asset_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
authbook = get_object_or_none(AuthBook, pk=authbook_id)
|
||||
if authbook.is_latest:
|
||||
raise PermissionError(_("Latest version could not be delete"))
|
||||
AuthBook.objects.filter(id=authbook_id).delete()
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
score=F('version') + self.base_score,
|
||||
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
|
||||
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
|
|
@ -1,110 +1,162 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from itertools import chain, groupby
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
|
||||
from .base import AssetUserQuerySet
|
||||
from .db import AuthBookBackend
|
||||
from .system_user import SystemUserBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
from orgs.utils import current_org
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from common.struct import QuerySetChain
|
||||
|
||||
from ..models import AssetUser, AuthBook
|
||||
from .db import (
|
||||
AuthbookBackend, SystemUserBackend, AdminUserBackend,
|
||||
DynamicSystemUserBackend
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
class AssetUserQueryset:
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
NotSupportError = NotSupportError
|
||||
MSG_NOT_EXIST = '{} Object matching query does not exist'
|
||||
MSG_MULTIPLE = '{} get() returned more than one object ' \
|
||||
'-- it returned {}!'
|
||||
|
||||
backends = (
|
||||
('db', AuthBookBackend),
|
||||
def __init__(self, backends=()):
|
||||
self.backends = backends
|
||||
self._distinct_queryset = None
|
||||
|
||||
def backends_queryset(self):
|
||||
return [b.get_queryset() for b in self.backends]
|
||||
|
||||
@lazyproperty
|
||||
def backends_counts(self):
|
||||
return [b.count() for b in self.backends]
|
||||
|
||||
def filter(self, hostname=None, ip=None, username=None,
|
||||
assets=None, asset=None, node=None,
|
||||
id=None, prefer_id=None, prefer=None, id__in=None):
|
||||
if not assets and asset:
|
||||
assets = [asset]
|
||||
|
||||
kwargs = dict(
|
||||
hostname=hostname, ip=ip, username=username,
|
||||
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
|
||||
id__in=id__in, union_id=id,
|
||||
)
|
||||
logger.debug("Filter: {}".format(kwargs))
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
clone = backend.filter(**kwargs)
|
||||
backends.append(clone)
|
||||
return self._clone(backends)
|
||||
|
||||
def _clone(self, backends=None):
|
||||
if backends is None:
|
||||
backends = self.backends
|
||||
return self.__class__(backends)
|
||||
|
||||
def search(self, item):
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
new = backend.search(item)
|
||||
backends.append(new)
|
||||
return self._clone(backends)
|
||||
|
||||
def distinct(self):
|
||||
logger.debug("Distinct asset user queryset")
|
||||
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
|
||||
queryset_sorted = sorted(
|
||||
queryset_chain,
|
||||
key=lambda item: (item["asset_username"], item["score"]),
|
||||
reverse=True,
|
||||
)
|
||||
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
|
||||
final = [next(result[1]) for result in results]
|
||||
self._distinct_queryset = final
|
||||
return self
|
||||
|
||||
def get(self, latest=False, **kwargs):
|
||||
queryset = self.filter(**kwargs)
|
||||
if latest:
|
||||
queryset = queryset.distinct()
|
||||
queryset = list(queryset)
|
||||
count = len(queryset)
|
||||
if count == 1:
|
||||
data = queryset[0]
|
||||
return data
|
||||
elif count > 1:
|
||||
msg = 'Should return 1 record, but get {}'.format(count)
|
||||
raise MultipleObjectsReturned(msg)
|
||||
else:
|
||||
msg = 'No record found(org is {})'.format(current_org.name)
|
||||
raise ObjectDoesNotExist(msg)
|
||||
|
||||
def get_latest(self, **kwargs):
|
||||
return self.get(latest=True, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def to_asset_user(data):
|
||||
obj = AssetUser()
|
||||
for k, v in data.items():
|
||||
setattr(obj, k, v)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def queryset(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return self._distinct_queryset
|
||||
return QuerySetChain(self.backends_queryset())
|
||||
|
||||
def count(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return len(self._distinct_queryset)
|
||||
else:
|
||||
return sum(self.backends_counts)
|
||||
|
||||
def __getitem__(self, ndx):
|
||||
return self.queryset.__getitem__(ndx)
|
||||
|
||||
def __iter__(self):
|
||||
self._data = iter(self.queryset)
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.to_asset_user(next(self._data))
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
support_backends = (
|
||||
('db', AuthbookBackend),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
('system_user_dynamic', DynamicSystemUserBackend),
|
||||
)
|
||||
|
||||
_prefer = "system_user"
|
||||
def __init__(self):
|
||||
self.backends = [backend() for name, backend in self.support_backends]
|
||||
self._queryset = AssetUserQueryset(self.backends)
|
||||
|
||||
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
if assets is not None and not assets:
|
||||
return AssetUserQuerySet([])
|
||||
def all(self):
|
||||
return self._queryset
|
||||
|
||||
if prefer:
|
||||
self._prefer = prefer
|
||||
|
||||
instances_map = {}
|
||||
instances = []
|
||||
for name, backend in self.backends:
|
||||
# if name != "db":
|
||||
# continue
|
||||
_instances = backend.filter(
|
||||
username=username, assets=assets, latest=latest,
|
||||
prefer=self._prefer, prefer_id=prefer_id,
|
||||
)
|
||||
instances_map[name] = _instances
|
||||
|
||||
# 如果不是获取最新版本,就不再merge
|
||||
if not latest:
|
||||
for _instances in instances_map.values():
|
||||
instances.extend(_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
# merge的顺序
|
||||
ordering = ["db"]
|
||||
if self._prefer == "system_user":
|
||||
ordering.extend(["system_user", "admin_user"])
|
||||
def delete(self, obj):
|
||||
name_backends_map = dict(self.support_backends)
|
||||
backend_name = obj.backend
|
||||
backend_cls = name_backends_map.get(backend_name)
|
||||
union_id = obj.union_id
|
||||
if backend_cls:
|
||||
backend_cls().delete(union_id)
|
||||
else:
|
||||
ordering.extend(["admin_user", "system_user"])
|
||||
# 根据prefer决定优先使用系统用户或管理用户谁的
|
||||
ordering_instances = [instances_map.get(i, []) for i in ordering]
|
||||
instances = self._merge_instances(*ordering_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
def get(self, username, asset, **kwargs):
|
||||
instances = self.filter(username, assets=[asset], **kwargs)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
self.raise_does_not_exist(self.__class__.__name__)
|
||||
else:
|
||||
self.raise_multiple_return(self.__class__.__name__, len(instances))
|
||||
|
||||
def raise_does_not_exist(self, name):
|
||||
raise self.ObjectDoesNotExist(self.MSG_NOT_EXIST.format(name))
|
||||
|
||||
def raise_multiple_return(self, name, length):
|
||||
raise self.MultipleObjectsReturned(self.MSG_MULTIPLE.format(name, length))
|
||||
raise ObjectDoesNotExist("Not backend found")
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
instance = AuthBookBackend.create(**kwargs)
|
||||
return instance
|
||||
authbook = AuthBook(**kwargs)
|
||||
authbook.save()
|
||||
return authbook
|
||||
|
||||
def all(self):
|
||||
return self.filter()
|
||||
|
||||
def prefer(self, s):
|
||||
self._prefer = s
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def none():
|
||||
return AssetUserQuerySet()
|
||||
|
||||
@staticmethod
|
||||
def _merge_instances(*args):
|
||||
instances = list(args[0])
|
||||
keywords = [obj.keyword for obj in instances]
|
||||
|
||||
for _instances in args[1:]:
|
||||
need_merge_instances = [obj for obj in _instances if obj.keyword not in keywords]
|
||||
need_merge_keywords = [obj.keyword for obj in need_merge_instances]
|
||||
instances.extend(need_merge_instances)
|
||||
keywords.extend(need_merge_keywords)
|
||||
return instances
|
||||
def __getattr__(self, item):
|
||||
return getattr(self._queryset, item)
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import SystemUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class SystemUserBackend(AssetUserBackend):
|
||||
model = SystemUser
|
||||
backend = 'SystemUser'
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
queryset = cls._distinct_system_users_by_username(queryset)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
|
|
@ -3,14 +3,5 @@
|
|||
|
||||
# from django.conf import settings
|
||||
|
||||
from .db import AuthBookBackend
|
||||
# from .vault import VaultBackend
|
||||
|
||||
|
||||
def get_backend():
|
||||
default_backend = AuthBookBackend
|
||||
|
||||
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
|
||||
# return VaultBackend
|
||||
|
||||
return default_backend
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class VaultBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
pass
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from itertools import groupby
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.forms import OrgModelForm
|
||||
|
||||
from ..models import Asset
|
||||
from ..models import Asset, Platform
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -42,9 +43,26 @@ class AssetCreateUpdateForm(OrgModelForm):
|
|||
]
|
||||
nodes_field.choices = nodes_choices
|
||||
|
||||
@staticmethod
|
||||
def sorted_platform(platform):
|
||||
if platform['base'] == 'Other':
|
||||
return 'zz'
|
||||
return platform['base']
|
||||
|
||||
def set_platform_to_name(self):
|
||||
choices = []
|
||||
platforms = Platform.objects.all().values('name', 'base')
|
||||
platforms_sorted = sorted(platforms, key=self.sorted_platform)
|
||||
platforms_grouped = groupby(platforms_sorted, key=lambda x: x['base'])
|
||||
for i in platforms_grouped:
|
||||
base = i[0]
|
||||
grouped = sorted(i[1], key=lambda x: x['name'])
|
||||
grouped = [(j['name'], j['name']) for j in grouped]
|
||||
choices.append(
|
||||
(base, grouped)
|
||||
)
|
||||
platform_field = self.fields['platform']
|
||||
platform_field.to_field_name = 'name'
|
||||
platform_field.choices = choices
|
||||
if self.instance:
|
||||
self.initial['platform'] = self.instance.platform.name
|
||||
|
||||
|
|
|
@ -88,7 +88,9 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key', 'auto_push', 'sudo',
|
||||
'username_same_with_user',
|
||||
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
|
||||
'sftp_root',
|
||||
]
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
|
@ -97,11 +99,17 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
'class': 'select2', 'data-placeholder': _('Command filter')
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
'username_same_with_user': _("Username same with user"),
|
||||
}
|
||||
help_texts = {
|
||||
'auto_push': _('Auto push system user to asset'),
|
||||
'priority': _('1-100, High level will be using login asset as default, '
|
||||
'if user was granted more than 2 system user'),
|
||||
'login_mode': _('If you choose manual login mode, you do not '
|
||||
'need to fill in the username and password.'),
|
||||
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
|
||||
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"),
|
||||
'sftp_root': _("SFTP root dir, tmp, home or custom"),
|
||||
'username_same_with_user': _("Username is dynamic, When connect asset, using current user's username"),
|
||||
# 'username_same_with_user': _("用户名是动态的,登录资产时使用当前用户的用户名登录"),
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by Jumpserver Team.
|
||||
:copyright: (c) 2014-2018 by JumpServer Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Generated by Django 2.2.7 on 2020-01-06 07:34
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0046_auto_20191218_1705'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AssetUser',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('assets.authbook',),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.7 on 2019-12-30 07:12
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0047_assetuser'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='authbook',
|
||||
name='is_active',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='username_same_with_user',
|
||||
field=models.BooleanField(default=False, verbose_name='Username same with user'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Users'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(blank=True, to='users.UserGroup',
|
||||
verbose_name='User groups'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.7 on 2020-01-19 07:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0048_auto_20191230_1512'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='sftp_root',
|
||||
field=models.CharField(default='tmp', max_length=128, verbose_name='SFTP Root'),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,8 @@
|
|||
from .base import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
from .asset_user import *
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from common.fields.model import JsonDictTextField
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from .base import ConnectivityMixin
|
||||
from .utils import Connectivity
|
||||
|
||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
|
||||
|
@ -40,10 +41,11 @@ def default_node():
|
|||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().annotate(
|
||||
platform_base=models.F('platform__base')
|
||||
)
|
||||
# def get_queryset(self):
|
||||
# return super().get_queryset().annotate(
|
||||
# platform_base=models.F('platform__base')
|
||||
# )
|
||||
pass
|
||||
|
||||
|
||||
class AssetQuerySet(models.QuerySet):
|
||||
|
@ -243,6 +245,13 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
def platform_base(self):
|
||||
return self.platform.base
|
||||
|
||||
@lazyproperty
|
||||
def admin_user_username(self):
|
||||
"""求可连接性时,直接用用户名去取,避免再查一次admin user
|
||||
serializer 中直接通过annotate方式返回了这个
|
||||
"""
|
||||
return self.admin_user.username
|
||||
|
||||
def is_windows(self):
|
||||
return self.platform.is_windows()
|
||||
|
||||
|
@ -275,9 +284,11 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
def connectivity(self):
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
if not self.admin_user:
|
||||
if not self.admin_user_username:
|
||||
return Connectivity.unknown()
|
||||
connectivity = self.admin_user.get_asset_connectivity(self)
|
||||
connectivity = ConnectivityMixin.get_asset_username_connectivity(
|
||||
self, self.admin_user_username
|
||||
)
|
||||
return connectivity
|
||||
|
||||
@connectivity.setter
|
||||
|
@ -290,7 +301,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
if not self.admin_user:
|
||||
return {}
|
||||
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
self.admin_user.load_asset_special_auth(self)
|
||||
info = {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .authbook import AuthBook
|
||||
|
||||
|
||||
class AssetUser(AuthBook):
|
||||
hostname = ""
|
||||
ip = ""
|
||||
backend = ""
|
||||
union_id = ""
|
||||
asset_username = ""
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
|
@ -5,26 +5,24 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgManager
|
||||
from .base import AssetUser
|
||||
from .base import BaseUser
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
||||
class AuthBookQuerySet(models.QuerySet):
|
||||
|
||||
def latest_version(self):
|
||||
return self.filter(is_latest=True).filter(is_active=True)
|
||||
return self.filter(is_latest=True)
|
||||
|
||||
|
||||
class AuthBookManager(OrgManager):
|
||||
pass
|
||||
|
||||
|
||||
class AuthBook(AssetUser):
|
||||
class AuthBook(BaseUser):
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
|
||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
backend = "db"
|
||||
|
|
|
@ -12,98 +12,29 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.conf import settings
|
||||
|
||||
from common.utils import (
|
||||
signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
|
||||
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
|
||||
)
|
||||
from common.validators import alphanumeric
|
||||
from common import fields
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .utils import private_key_validator, Connectivity
|
||||
from .utils import Connectivity
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
class ConnectivityMixin:
|
||||
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
|
||||
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
|
||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 3600 * 24
|
||||
|
||||
_prefer = "system_user"
|
||||
_assets_amount = None
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self.private_key:
|
||||
return ssh_key_string_to_obj(self.private_key, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
id = ''
|
||||
username = ''
|
||||
|
||||
@property
|
||||
def part_id(self):
|
||||
i = '-'.join(str(self.id).split('-')[:3])
|
||||
return i
|
||||
|
||||
def get_private_key(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
string_io = io.StringIO()
|
||||
self.private_key_obj.write_private_key(string_io)
|
||||
private_key = string_io.getvalue()
|
||||
return private_key
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self.password = password
|
||||
update_fields.append('password')
|
||||
if private_key:
|
||||
self.private_key = private_key
|
||||
update_fields.append('private_key')
|
||||
if public_key:
|
||||
self.public_key = public_key
|
||||
update_fields.append('public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def set_connectivity(self, summary):
|
||||
unreachable = summary.get('dark', {}).keys()
|
||||
reachable = summary.get('contacted', {}).keys()
|
||||
|
@ -150,20 +81,10 @@ class AssetUser(OrgModelMixin):
|
|||
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
|
||||
return amount
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
if self._assets_amount is not None:
|
||||
return self._assets_amount
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cached = cache.get(cache_key)
|
||||
if not cached:
|
||||
cached = self.get_related_assets().count()
|
||||
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
|
||||
return cached
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
@classmethod
|
||||
def get_asset_username_connectivity(cls, asset, username):
|
||||
key = cls.CONNECTIVITY_ASSET_CACHE_KEY.format(username, asset.id)
|
||||
return Connectivity.get(key)
|
||||
|
||||
def get_asset_connectivity(self, asset):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
|
@ -176,28 +97,103 @@ class AssetUser(OrgModelMixin):
|
|||
key = self.get_asset_connectivity_key(asset)
|
||||
Connectivity.set(key, c)
|
||||
|
||||
def get_asset_user(self, asset):
|
||||
from ..backends import AssetUserManager
|
||||
|
||||
class AuthMixin:
|
||||
private_key = ''
|
||||
password = ''
|
||||
public_key = ''
|
||||
username = ''
|
||||
_prefer = 'system_user'
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self.private_key:
|
||||
key_obj = ssh_key_string_to_obj(self.private_key, password=self.password)
|
||||
return key_obj
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
def get_private_key(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
string_io = io.StringIO()
|
||||
self.private_key_obj.write_private_key(string_io)
|
||||
private_key = string_io.getvalue()
|
||||
return private_key
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
manager = AssetUserManager().prefer(self._prefer)
|
||||
other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self.password = password
|
||||
update_fields.append('password')
|
||||
if private_key:
|
||||
self.private_key = private_key
|
||||
update_fields.append('private_key')
|
||||
if public_key:
|
||||
self.public_key = public_key
|
||||
update_fields.append('public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def has_special_auth(self, asset=None):
|
||||
from .authbook import AuthBook
|
||||
queryset = AuthBook.objects.filter(username=self.username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
return queryset.exists()
|
||||
|
||||
def get_asset_user(self, asset, username=None):
|
||||
from ..backends import AssetUserManager
|
||||
if username is None:
|
||||
username = self.username
|
||||
try:
|
||||
manager = AssetUserManager()
|
||||
other = manager.get_latest(
|
||||
username=username, asset=asset,
|
||||
prefer_id=self.id, prefer=self._prefer,
|
||||
)
|
||||
return other
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
instance = self.get_asset_user(asset)
|
||||
def load_asset_special_auth(self, asset=None, username=None):
|
||||
if not asset:
|
||||
return self
|
||||
|
||||
instance = self.get_asset_user(asset, username=username)
|
||||
if instance:
|
||||
self._merge_auth(instance)
|
||||
|
||||
def _merge_auth(self, other):
|
||||
if other.password:
|
||||
self.password = other.password
|
||||
if other.public_key:
|
||||
self.public_key = other.public_key
|
||||
if other.private_key:
|
||||
if other.public_key or other.private_key:
|
||||
self.private_key = other.private_key
|
||||
self.public_key = other.public_key
|
||||
|
||||
def clear_auth(self):
|
||||
self.password = ''
|
||||
|
@ -216,19 +212,57 @@ class AssetUser(OrgModelMixin):
|
|||
)
|
||||
return private_key, public_key
|
||||
|
||||
def auto_gen_auth(self):
|
||||
password = str(uuid.uuid4())
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=self.username
|
||||
)
|
||||
def auto_gen_auth(self, password=True, key=True):
|
||||
_password = None
|
||||
_private_key = None
|
||||
_public_key = None
|
||||
|
||||
if password:
|
||||
_password = self.gen_password()
|
||||
if key:
|
||||
_private_key, _public_key = self.gen_key(self.username)
|
||||
self.set_auth(
|
||||
password=password, private_key=private_key,
|
||||
public_key=public_key
|
||||
password=_password, private_key=_private_key,
|
||||
public_key=_public_key
|
||||
)
|
||||
|
||||
def auto_gen_auth_password(self):
|
||||
password = str(uuid.uuid4())
|
||||
self.set_auth(password=password)
|
||||
|
||||
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 600
|
||||
|
||||
_prefer = "system_user"
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.filter(org_id=self.org_id)
|
||||
return assets
|
||||
|
||||
def get_username(self):
|
||||
return self.username
|
||||
|
||||
@lazyproperty
|
||||
def assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cached = cache.get(cache_key)
|
||||
if not cached:
|
||||
cached = self.get_related_assets().count()
|
||||
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
|
||||
return cached
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
|
@ -240,26 +274,6 @@ class AssetUser(OrgModelMixin):
|
|||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
user_id = [self.part_id]
|
||||
asset_id = str(asset.id).split('-')[3:]
|
||||
ids = user_id + asset_id
|
||||
return '-'.join(ids)
|
||||
|
||||
def construct_to_authbook(self, asset):
|
||||
from . import AuthBook
|
||||
fields = [
|
||||
'name', 'username', 'comment', 'org_id',
|
||||
'password', 'private_key', 'public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
i = self.generate_id_with_asset(asset)
|
||||
obj = AuthBook(id=i, asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(self, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .base import AssetUser
|
||||
from .base import BaseUser
|
||||
|
||||
__all__ = ['Domain', 'Gateway']
|
||||
|
||||
|
@ -39,7 +39,7 @@ class Domain(OrgModelMixin):
|
|||
return random.choice(self.gateways)
|
||||
|
||||
|
||||
class Gateway(AssetUser):
|
||||
class Gateway(BaseUser):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_CHOICES = (
|
||||
|
|
|
@ -11,9 +11,9 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils.translation import ugettext
|
||||
from django.core.cache import cache
|
||||
|
||||
from common.utils import get_logger, timeit, lazyproperty
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from orgs.utils import set_current_org, get_current_org, tmp_to_org
|
||||
from orgs.utils import get_current_org, tmp_to_org, current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
|
||||
|
@ -26,63 +26,108 @@ class NodeQuerySet(models.QuerySet):
|
|||
raise PermissionError("Bulk delete node deny")
|
||||
|
||||
|
||||
class TreeCache:
|
||||
updated_time_cache_key = 'NODE_TREE_UPDATED_AT_{}'
|
||||
cache_time = 3600
|
||||
assets_updated_time_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT_{}'
|
||||
|
||||
def __init__(self, tree, org_id):
|
||||
now = time.time()
|
||||
self.created_time = now
|
||||
self.assets_created_time = now
|
||||
self.tree = tree
|
||||
self.org_id = org_id
|
||||
|
||||
def _has_changed(self, tp="tree"):
|
||||
if tp == "assets":
|
||||
key = self.assets_updated_time_cache_key.format(self.org_id)
|
||||
else:
|
||||
key = self.updated_time_cache_key.format(self.org_id)
|
||||
updated_time = cache.get(key, 0)
|
||||
if updated_time > self.created_time:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def set_changed(cls, tp="tree", t=None, org_id=None):
|
||||
if org_id is None:
|
||||
org_id = current_org.id
|
||||
if tp == "assets":
|
||||
key = cls.assets_updated_time_cache_key.format(org_id)
|
||||
else:
|
||||
key = cls.updated_time_cache_key.format(org_id)
|
||||
ttl = cls.cache_time
|
||||
if not t:
|
||||
t = time.time()
|
||||
cache.set(key, t, ttl)
|
||||
|
||||
def tree_has_changed(self):
|
||||
return self._has_changed("tree")
|
||||
|
||||
def set_tree_changed(self, t=None):
|
||||
logger.debug("Set tree tree changed")
|
||||
self.__class__.set_changed(t=t, tp="tree")
|
||||
|
||||
def assets_has_changed(self):
|
||||
return self._has_changed("assets")
|
||||
|
||||
def set_tree_assets_changed(self, t=None):
|
||||
logger.debug("Set tree assets changed")
|
||||
self.__class__.set_changed(t=t, tp="assets")
|
||||
|
||||
def get(self):
|
||||
if self.tree_has_changed():
|
||||
self.renew()
|
||||
return self.tree
|
||||
if self.assets_has_changed():
|
||||
self.tree.init_assets()
|
||||
return self.tree
|
||||
|
||||
def renew(self):
|
||||
new_obj = self.__class__.new(self.org_id)
|
||||
self.tree = new_obj.tree
|
||||
self.created_time = new_obj.created_time
|
||||
self.assets_created_time = new_obj.assets_created_time
|
||||
|
||||
@classmethod
|
||||
def new(cls, org_id=None):
|
||||
from ..utils import TreeService
|
||||
logger.debug("Create node tree")
|
||||
if not org_id:
|
||||
org_id = current_org.id
|
||||
with tmp_to_org(org_id):
|
||||
tree = TreeService.new()
|
||||
obj = cls(tree, org_id)
|
||||
obj.tree = tree
|
||||
return obj
|
||||
|
||||
|
||||
class TreeMixin:
|
||||
tree_created_time = None
|
||||
tree_updated_time_cache_key = 'NODE_TREE_UPDATED_AT'
|
||||
tree_cache_time = 3600
|
||||
tree_assets_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT'
|
||||
tree_assets_created_time = None
|
||||
_tree_service = None
|
||||
_org_tree_map = {}
|
||||
|
||||
@classmethod
|
||||
def tree(cls):
|
||||
from ..utils import TreeService
|
||||
tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0)
|
||||
now = time.time()
|
||||
# 什么时候重新初始化 _tree_service
|
||||
if not cls.tree_created_time or \
|
||||
tree_updated_time > cls.tree_created_time:
|
||||
logger.debug("Create node tree")
|
||||
tree = TreeService.new()
|
||||
cls.tree_created_time = now
|
||||
cls.tree_assets_created_time = now
|
||||
cls._tree_service = tree
|
||||
return tree
|
||||
# 是否要重新初始化节点资产
|
||||
node_assets_updated_time = cache.get(cls.tree_assets_cache_key, 0)
|
||||
if not cls.tree_assets_created_time or \
|
||||
node_assets_updated_time > cls.tree_assets_created_time:
|
||||
cls._tree_service.init_assets()
|
||||
cls.tree_assets_created_time = now
|
||||
logger.debug("Refresh node tree assets")
|
||||
return cls._tree_service
|
||||
org_id = current_org.org_id()
|
||||
t = cls.get_local_tree_cache(org_id)
|
||||
|
||||
if t is None:
|
||||
t = TreeCache.new()
|
||||
cls._org_tree_map[org_id] = t
|
||||
return t.get()
|
||||
|
||||
@classmethod
|
||||
def get_local_tree_cache(cls, org_id=None):
|
||||
t = cls._org_tree_map.get(org_id)
|
||||
return t
|
||||
|
||||
@classmethod
|
||||
def refresh_tree(cls, t=None):
|
||||
logger.debug("Refresh node tree")
|
||||
key = cls.tree_updated_time_cache_key
|
||||
ttl = cls.tree_cache_time
|
||||
if not t:
|
||||
t = time.time()
|
||||
cache.set(key, t, ttl)
|
||||
TreeCache.set_changed(tp="tree", t=t, org_id=current_org.id)
|
||||
|
||||
@classmethod
|
||||
def refresh_node_assets(cls, t=None):
|
||||
logger.debug("Refresh node assets")
|
||||
key = cls.tree_assets_cache_key
|
||||
ttl = cls.tree_cache_time
|
||||
if not t:
|
||||
t = time.time()
|
||||
cache.set(key, t, ttl)
|
||||
|
||||
@staticmethod
|
||||
def refresh_user_tree_cache():
|
||||
"""
|
||||
当节点-节点关系,节点-资产关系发生变化时,应该刷新用户授权树缓存
|
||||
:return:
|
||||
"""
|
||||
from perms.utils.asset_permission import AssetPermissionUtilV2
|
||||
AssetPermissionUtilV2.expire_all_user_tree_cache()
|
||||
TreeCache.set_changed(tp="assets", t=t, org_id=current_org.id)
|
||||
|
||||
|
||||
class FamilyMixin:
|
||||
|
@ -376,15 +421,6 @@ class SomeNodesMixin:
|
|||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def empty_node(cls):
|
||||
with tmp_to_org(Organization.system()):
|
||||
defaults = {'value': cls.empty_value}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.empty_key
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
with tmp_to_org(Organization.default()):
|
||||
|
@ -413,7 +449,6 @@ class SomeNodesMixin:
|
|||
@classmethod
|
||||
def initial_some_nodes(cls):
|
||||
cls.default_node()
|
||||
cls.empty_node()
|
||||
cls.ungrouped_node()
|
||||
cls.favorite_node()
|
||||
|
||||
|
@ -523,13 +558,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
|
|||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
def has_children_or_contains_assets(self):
|
||||
if self.children or self.get_assets():
|
||||
def has_children_or_has_assets(self):
|
||||
if self.children or self.get_assets().exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.has_children_or_contains_assets():
|
||||
if self.has_children_or_has_assets():
|
||||
return
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
import logging
|
||||
|
||||
from functools import reduce
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import signer
|
||||
from .base import AssetUser
|
||||
from .base import BaseUser
|
||||
from .asset import Asset
|
||||
|
||||
|
||||
|
@ -19,7 +17,7 @@ __all__ = ['AdminUser', 'SystemUser']
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminUser(AssetUser):
|
||||
class AdminUser(BaseUser):
|
||||
"""
|
||||
A privileged user that ansible can use it to push system user and so on
|
||||
"""
|
||||
|
@ -87,7 +85,7 @@ class AdminUser(AssetUser):
|
|||
continue
|
||||
|
||||
|
||||
class SystemUser(AssetUser):
|
||||
class SystemUser(BaseUser):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
|
@ -107,9 +105,11 @@ class SystemUser(AssetUser):
|
|||
(LOGIN_AUTO, _('Automatic login')),
|
||||
(LOGIN_MANUAL, _('Manually login'))
|
||||
)
|
||||
|
||||
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
|
||||
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
|
||||
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
|
@ -117,9 +117,20 @@ class SystemUser(AssetUser):
|
|||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||
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)
|
||||
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
|
||||
_prefer = 'system_user'
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
username = self.username
|
||||
if self.username_same_with_user:
|
||||
username = 'dynamic'
|
||||
return '{0.name}({1})'.format(self, username)
|
||||
|
||||
def get_username(self):
|
||||
if self.username_same_with_user:
|
||||
return list(self.users.values_list('username', flat=True))
|
||||
else:
|
||||
return self.username
|
||||
|
||||
@property
|
||||
def nodes_amount(self):
|
||||
|
@ -147,6 +158,11 @@ class SystemUser(AssetUser):
|
|||
def can_perm_to_asset(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
|
||||
def _merge_auth(self, other):
|
||||
super()._merge_auth(other)
|
||||
if self.username_same_with_user:
|
||||
self.username = other.username
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
|
|
|
@ -55,3 +55,11 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
|
|||
|
||||
class TaskIDSerializer(serializers.Serializer):
|
||||
task = serializers.CharField(read_only=True)
|
||||
|
||||
|
||||
class AssetUserTaskSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
('test', 'test'),
|
||||
)
|
||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||
task = serializers.CharField(read_only=True)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from rest_framework import serializers
|
||||
from django.db.models import Prefetch
|
||||
from django.db.models import Prefetch, F
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
@ -12,8 +12,9 @@ from .base import ConnectivitySerializer
|
|||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetSimpleSerializer',
|
||||
'AssetDisplaySerializer',
|
||||
'ProtocolsField', 'PlatformSerializer',
|
||||
'AssetDetailSerializer',
|
||||
'AssetDetailSerializer', 'AssetTaskSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -66,8 +67,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||
)
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
|
@ -81,7 +80,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||
'hardware_info', 'connectivity',
|
||||
'hardware_info',
|
||||
]
|
||||
read_only_fields = (
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
|
@ -102,7 +101,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
||||
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
||||
).select_related('admin_user', 'domain', 'platform')
|
||||
).select_related('admin_user', 'domain', 'platform') \
|
||||
.annotate(platform_base=F('platform__base'))
|
||||
return queryset
|
||||
|
||||
def compatible_with_old_protocol(self, validated_data):
|
||||
|
@ -130,6 +130,28 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class AssetDisplaySerializer(AssetSerializer):
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
class Meta(AssetSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'ip', 'hostname', 'protocol', 'port',
|
||||
'protocols', 'is_active', 'public_ip',
|
||||
'number', 'vendor', 'model', 'sn',
|
||||
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||
'hardware_info', 'connectivity',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset\
|
||||
.annotate(admin_user_username=F('admin_user__username'))
|
||||
return queryset
|
||||
|
||||
|
||||
class PlatformSerializer(serializers.ModelSerializer):
|
||||
meta = serializers.DictField(required=False, allow_null=True)
|
||||
|
||||
|
@ -151,3 +173,12 @@ class AssetSimpleSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
|
||||
|
||||
|
||||
class AssetTaskSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
('refresh', 'refresh'),
|
||||
('test', 'test'),
|
||||
)
|
||||
task = serializers.CharField(read_only=True)
|
||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||
|
|
|
@ -8,39 +8,23 @@ from common.serializers import AdaptedBulkListSerializer
|
|||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
|
||||
from .base import ConnectivitySerializer, AuthSerializerMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
|
||||
'AssetUserExportSerializer', 'AssetUserPushSerializer',
|
||||
'AssetUserWriteSerializer', 'AssetUserReadSerializer',
|
||||
'AssetUserAuthInfoSerializer', 'AssetUserPushSerializer',
|
||||
]
|
||||
|
||||
|
||||
class BasicAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['hostname', 'ip']
|
||||
|
||||
|
||||
class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class AssetUserWriteSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
read_only_fields = (
|
||||
'date_created', 'date_updated', 'created_by',
|
||||
'is_latest', 'version', 'connectivity',
|
||||
)
|
||||
fields = [
|
||||
"id", "hostname", "ip", "username", "password", "asset", "version",
|
||||
"is_latest", "connectivity", "backend",
|
||||
"date_created", "date_updated", "private_key", "public_key",
|
||||
'id', 'username', 'password', 'private_key', "public_key",
|
||||
'asset', 'comment',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True},
|
||||
|
@ -57,7 +41,32 @@ class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
return instance
|
||||
|
||||
|
||||
class AssetUserExportSerializer(AssetUserSerializer):
|
||||
class AssetUserReadSerializer(AssetUserWriteSerializer):
|
||||
id = serializers.CharField(read_only=True, source='union_id', label=_("ID"))
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
asset = serializers.CharField(source='asset_id', label=_('Asset'))
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class Meta(AssetUserWriteSerializer.Meta):
|
||||
read_only_fields = (
|
||||
'date_created', 'date_updated',
|
||||
'created_by', 'version',
|
||||
)
|
||||
fields = [
|
||||
'id', 'username', 'password', 'private_key', "public_key",
|
||||
'asset', 'hostname', 'ip', 'backend', 'version',
|
||||
'date_created', "date_updated", 'comment',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True},
|
||||
'password': {'write_only': True},
|
||||
'private_key': {'write_only': True},
|
||||
'public_key': {'write_only': True},
|
||||
}
|
||||
|
||||
|
||||
class AssetUserAuthInfoSerializer(AssetUserReadSerializer):
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Password')
|
||||
|
@ -72,12 +81,6 @@ class AssetUserExportSerializer(AssetUserSerializer):
|
|||
)
|
||||
|
||||
|
||||
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
fields = ['password', 'private_key', 'public_key']
|
||||
|
||||
|
||||
class AssetUserPushSerializer(serializers.Serializer):
|
||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset"))
|
||||
username = serializers.CharField(max_length=1024)
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.utils import ssh_pubkey_gen, validate_ssh_private_key
|
||||
from ..models import AssetUser
|
||||
|
||||
|
||||
class AuthSerializer(serializers.ModelSerializer):
|
||||
|
@ -60,9 +61,6 @@ class AuthSerializerMixin:
|
|||
if not value:
|
||||
validated_data.pop(field, None)
|
||||
|
||||
# print(validated_data)
|
||||
# raise serializers.ValidationError(">>>>>>")
|
||||
|
||||
def create(self, validated_data):
|
||||
self.clean_auth_fields(validated_data)
|
||||
return super().create(validated_data)
|
||||
|
@ -70,3 +68,15 @@ class AuthSerializerMixin:
|
|||
def update(self, instance, validated_data):
|
||||
self.clean_auth_fields(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class AuthInfoSerializer(serializers.ModelSerializer):
|
||||
private_key = serializers.ReadOnlyField(source='get_private_key')
|
||||
|
||||
class Meta:
|
||||
model = AssetUser
|
||||
fields = [
|
||||
'username', 'password',
|
||||
'private_key', 'public_key',
|
||||
'date_updated',
|
||||
]
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..models import Asset, Node
|
|||
|
||||
__all__ = [
|
||||
'NodeSerializer', "NodeAddChildrenSerializer",
|
||||
"NodeAssetsSerializer",
|
||||
"NodeAssetsSerializer", "NodeTaskSerializer",
|
||||
]
|
||||
|
||||
|
||||
|
@ -51,3 +51,12 @@ class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
|
|||
class NodeAddChildrenSerializer(serializers.Serializer):
|
||||
nodes = serializers.ListField()
|
||||
|
||||
|
||||
class NodeTaskSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
('refresh', 'refresh'),
|
||||
('test', 'test'),
|
||||
('refresh_cache', 'refresh_cache'),
|
||||
)
|
||||
task = serializers.CharField(read_only=True)
|
||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import re
|
||||
from rest_framework import serializers
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.mixins.serializers import BulkSerializerMixin
|
||||
from common.utils import ssh_pubkey_gen
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from assets.models import Node
|
||||
from ..models import SystemUser
|
||||
from .base import AuthSerializer, AuthSerializerMixin
|
||||
from ..models import SystemUser, Asset
|
||||
from .base import AuthSerializerMixin
|
||||
|
||||
__all__ = [
|
||||
'SystemUserSerializer', 'SystemUserAuthSerializer',
|
||||
'SystemUserSerializer', 'SystemUserListSerializer',
|
||||
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
|
||||
'SystemUserNodeRelationSerializer',
|
||||
'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer',
|
||||
'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -28,10 +28,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
model = SystemUser
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'username', 'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display', 'priority', 'protocol',
|
||||
'id', 'name', 'username', 'protocol',
|
||||
'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display',
|
||||
'priority', 'username_same_with_user',
|
||||
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
|
||||
'assets_amount', 'nodes_amount', 'auto_generate_key'
|
||||
'auto_generate_key', 'sftp_root',
|
||||
'assets_amount',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
|
@ -67,17 +70,43 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
value = False
|
||||
return value
|
||||
|
||||
def validate_username_same_with_user(self, username_same_with_user):
|
||||
if not username_same_with_user:
|
||||
return username_same_with_user
|
||||
protocol = self.initial_data.get("protocol", "ssh")
|
||||
queryset = SystemUser.objects.filter(
|
||||
protocol=protocol, username_same_with_user=True
|
||||
)
|
||||
if self.instance:
|
||||
queryset = queryset.exclude(id=self.instance.id)
|
||||
exists = queryset.exists()
|
||||
if not exists:
|
||||
return username_same_with_user
|
||||
error = _("Username same with user with protocol {} only allow 1").format(protocol)
|
||||
raise serializers.ValidationError(error)
|
||||
|
||||
def validate_username(self, username):
|
||||
if username:
|
||||
return username
|
||||
login_mode = self.initial_data.get("login_mode")
|
||||
protocol = self.initial_data.get("protocol")
|
||||
username_same_with_user = self.initial_data.get("username_same_with_user")
|
||||
if username_same_with_user:
|
||||
return ''
|
||||
if login_mode == SystemUser.LOGIN_AUTO and \
|
||||
protocol != SystemUser.PROTOCOL_VNC:
|
||||
msg = _('* Automatic login mode must fill in the username.')
|
||||
raise serializers.ValidationError(msg)
|
||||
return username
|
||||
|
||||
def validate_sftp_root(self, value):
|
||||
if value in ['home', 'tmp']:
|
||||
return value
|
||||
if not value.startswith('/'):
|
||||
error = _("Path should starts with /")
|
||||
raise serializers.ValidationError(error)
|
||||
return value
|
||||
|
||||
def validate_password(self, password):
|
||||
super().validate_password(password)
|
||||
auto_gen_key = self.initial_data.get("auto_generate_key", False)
|
||||
|
@ -112,29 +141,42 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
|||
attrs["public_key"] = public_key
|
||||
return attrs
|
||||
|
||||
|
||||
class SystemUserListSerializer(SystemUserSerializer):
|
||||
class Meta(SystemUserSerializer.Meta):
|
||||
fields = [
|
||||
'id', 'name', 'username', 'protocol',
|
||||
'login_mode', 'login_mode_display',
|
||||
'priority', "username_same_with_user",
|
||||
'auto_push', 'sudo', 'shell', 'comment',
|
||||
"assets_amount",
|
||||
'auto_generate_key',
|
||||
'sftp_root',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('cmd_filters', 'nodes')
|
||||
queryset = queryset.annotate(assets_amount=Count("assets"))
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserAuthSerializer(AuthSerializer):
|
||||
"""
|
||||
系统用户认证信息
|
||||
"""
|
||||
private_key = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
class SystemUserWithAuthInfoSerializer(SystemUserSerializer):
|
||||
class Meta(SystemUserSerializer.Meta):
|
||||
fields = [
|
||||
"id", "name", "username", "protocol",
|
||||
"login_mode", "password", "private_key",
|
||||
'id', 'name', 'username', 'protocol',
|
||||
'password', 'public_key', 'private_key',
|
||||
'login_mode', 'login_mode_display',
|
||||
'priority', 'username_same_with_user',
|
||||
'auto_push', 'sudo', 'shell', 'comment',
|
||||
'auto_generate_key', 'sftp_root',
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_private_key(obj):
|
||||
return obj.get_private_key()
|
||||
extra_kwargs = {
|
||||
'nodes_amount': {'label': _('Node')},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True},
|
||||
}
|
||||
|
||||
|
||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||
|
@ -186,3 +228,25 @@ class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerialize
|
|||
return self.tree.get_node_full_tag(obj.node_key)
|
||||
else:
|
||||
return obj.node.full_value
|
||||
|
||||
|
||||
class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
|
||||
user_display = serializers.ReadOnlyField()
|
||||
|
||||
class Meta(RelationMixin.Meta):
|
||||
model = SystemUser.users.through
|
||||
fields = [
|
||||
'id', "user", "user_display",
|
||||
]
|
||||
|
||||
|
||||
class SystemUserTaskSerializer(serializers.Serializer):
|
||||
ACTION_CHOICES = (
|
||||
("test", "test"),
|
||||
("push", "push"),
|
||||
)
|
||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||
asset = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Asset.objects, allow_null=True, required=False, write_only=True
|
||||
)
|
||||
task = serializers.CharField(read_only=True)
|
||||
|
|
|
@ -7,14 +7,17 @@ from django.db.models.signals import (
|
|||
from django.db.models.aggregates import Count
|
||||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger, timeit
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from .models import Asset, SystemUser, Node, AuthBook
|
||||
from .utils import TreeService
|
||||
from .tasks import (
|
||||
update_assets_hardware_info_util,
|
||||
test_asset_connectivity_util,
|
||||
push_system_user_to_assets,
|
||||
push_system_user_to_assets_manual,
|
||||
push_system_user_to_assets,
|
||||
add_nodes_assets_to_system_users
|
||||
)
|
||||
|
||||
|
@ -94,6 +97,25 @@ def on_system_user_assets_change(sender, instance=None, action='', model=None, p
|
|||
push_system_user_to_assets.delay(system_user, assets)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.users.through)
|
||||
def on_system_user_users_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
|
||||
"""
|
||||
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
|
||||
"""
|
||||
if action != "post_add":
|
||||
return
|
||||
if not instance.username_same_with_user:
|
||||
return
|
||||
logger.debug("System user users change signal recv: {}".format(instance))
|
||||
queryset = model.objects.filter(pk__in=pk_set)
|
||||
if model == SystemUser:
|
||||
system_users = queryset
|
||||
else:
|
||||
system_users = [instance]
|
||||
for s in system_users:
|
||||
push_system_user_to_assets_manual.delay(s)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
|
||||
"""
|
||||
|
@ -113,6 +135,20 @@ def on_system_user_nodes_change(sender, instance=None, action=None, model=None,
|
|||
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=SystemUser.groups.through)
|
||||
def on_system_user_groups_change(sender, instance=None, action=None, model=None,
|
||||
pk_set=None, reverse=False, **kwargs):
|
||||
"""
|
||||
当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上
|
||||
"""
|
||||
if action != "post_add" or reverse:
|
||||
return
|
||||
logger.info("System user groups update signal recv: {}".format(instance))
|
||||
groups = model.objects.filter(pk__in=pk_set).annotate(users_count=Count("users"))
|
||||
users = groups.filter(users_count__gt=0).values_list('users', flat=True)
|
||||
instance.users.add(*tuple(users))
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
|
||||
"""
|
||||
|
@ -121,6 +157,8 @@ def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
|
|||
if action.startswith('post'):
|
||||
logger.debug("Asset nodes change signal recv: {}".format(instance))
|
||||
Node.refresh_assets()
|
||||
with tmp_to_root_org():
|
||||
Node.refresh_assets()
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||
|
@ -195,6 +233,8 @@ def on_asset_nodes_remove(sender, instance=None, action='', model=None,
|
|||
def on_node_update_or_created(sender, **kwargs):
|
||||
# 刷新节点
|
||||
Node.refresh_nodes()
|
||||
with tmp_to_root_org():
|
||||
Node.refresh_nodes()
|
||||
|
||||
|
||||
@receiver(post_save, sender=AuthBook)
|
||||
|
|
|
@ -4,11 +4,12 @@ from celery import shared_task
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.utils import tmp_to_root_org, org_aware_func
|
||||
from common.utils import get_logger
|
||||
from ops.celery.decorator import register_as_period_task
|
||||
|
||||
from ..models import AdminUser
|
||||
from .utils import clean_hosts
|
||||
from .utils import clean_ansible_task_hosts
|
||||
from .asset_connectivity import test_asset_connectivity_util
|
||||
from . import const
|
||||
|
||||
|
@ -20,7 +21,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("admin_user")
|
||||
def test_admin_user_connectivity_util(admin_user, task_name):
|
||||
"""
|
||||
Test asset admin user can connect or not. Using ansible api do that
|
||||
|
@ -29,7 +30,7 @@ def test_admin_user_connectivity_util(admin_user, task_name):
|
|||
:return:
|
||||
"""
|
||||
assets = admin_user.get_related_assets()
|
||||
hosts = clean_hosts(assets)
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
summary = test_asset_connectivity_util(hosts, task_name)
|
||||
|
@ -51,9 +52,12 @@ def test_admin_user_connectivity_period():
|
|||
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
|
||||
return
|
||||
cache.set(key, 1, 60*40)
|
||||
with tmp_to_root_org():
|
||||
admin_users = AdminUser.objects.all()
|
||||
for admin_user in admin_users:
|
||||
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
|
||||
task_name = _("Test admin user connectivity period: {}").format(
|
||||
admin_user.name
|
||||
)
|
||||
test_admin_user_connectivity_util(admin_user, task_name)
|
||||
cache.set(key, 1, 60*40)
|
||||
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
from itertools import groupby
|
||||
from collections import defaultdict
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import org_aware_func
|
||||
from ..models.utils import Connectivity
|
||||
from . import const
|
||||
from .utils import clean_hosts
|
||||
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['test_asset_connectivity_util', 'test_asset_connectivity_manual']
|
||||
__all__ = [
|
||||
'test_asset_connectivity_util', 'test_asset_connectivity_manual',
|
||||
'test_node_assets_connectivity_manual',
|
||||
]
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("assets")
|
||||
def test_asset_connectivity_util(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if task_name is None:
|
||||
task_name = _("Test assets connectivity")
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
platform_hosts_map = {}
|
||||
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
|
||||
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
|
||||
for i in platform_hosts:
|
||||
platform_hosts_map[i[0]] = list(i[1])
|
||||
|
||||
hosts_category = {
|
||||
'linux': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
|
||||
},
|
||||
'windows': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
|
||||
platform_tasks_map = {
|
||||
"unixlike": const.PING_UNIXLIKE_TASKS,
|
||||
"windows": const.PING_WINDOWS_TASKS
|
||||
}
|
||||
}
|
||||
for host in hosts:
|
||||
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
|
||||
else hosts_category['linux']['hosts']
|
||||
hosts_list.append(host)
|
||||
|
||||
results_summary = dict(
|
||||
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
|
||||
)
|
||||
created_by = assets[0].org_id
|
||||
for k, value in hosts_category.items():
|
||||
if not value['hosts']:
|
||||
for platform, _hosts in platform_hosts_map.items():
|
||||
if not _hosts:
|
||||
continue
|
||||
logger.debug("System user not has special auth")
|
||||
tasks = platform_tasks_map.get(platform)
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
|
||||
task_name=task_name, hosts=_hosts, tasks=tasks,
|
||||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
raw, summary = task.run()
|
||||
success = summary.get('success', False)
|
||||
|
@ -59,6 +59,7 @@ def test_asset_connectivity_util(assets, task_name=None):
|
|||
results_summary['success'] &= success
|
||||
results_summary['contacted'].update(contacted)
|
||||
results_summary['dark'].update(dark)
|
||||
continue
|
||||
|
||||
for asset in assets:
|
||||
if asset.hostname in results_summary.get('dark', {}).keys():
|
||||
|
@ -79,3 +80,12 @@ def test_asset_connectivity_manual(asset):
|
|||
return False, summary['dark']
|
||||
else:
|
||||
return True, ""
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_node_assets_connectivity_manual(node):
|
||||
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
|
||||
assets = node.get_all_assets()
|
||||
result = test_asset_connectivity_util(assets, task_name=task_name)
|
||||
return result
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from orgs.utils import org_aware_func
|
||||
from ..models import Asset
|
||||
from . import const
|
||||
from .utils import check_asset_can_run_ansible
|
||||
|
||||
|
@ -13,15 +15,16 @@ logger = get_logger(__file__)
|
|||
|
||||
__all__ = [
|
||||
'test_asset_user_connectivity_util', 'test_asset_users_connectivity_manual',
|
||||
'get_test_asset_user_connectivity_tasks',
|
||||
'get_test_asset_user_connectivity_tasks', 'test_user_connectivity',
|
||||
'run_adhoc',
|
||||
]
|
||||
|
||||
|
||||
def get_test_asset_user_connectivity_tasks(asset):
|
||||
if asset.is_unixlike():
|
||||
tasks = const.TEST_ASSET_USER_CONN_TASKS
|
||||
tasks = const.PING_UNIXLIKE_TASKS
|
||||
elif asset.is_windows():
|
||||
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
|
||||
tasks = const.PING_WINDOWS_TASKS
|
||||
else:
|
||||
msg = _(
|
||||
"The asset {} system platform {} does not "
|
||||
|
@ -32,46 +35,98 @@ def get_test_asset_user_connectivity_tasks(asset):
|
|||
return tasks
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
||||
def run_adhoc(task_name, tasks, inventory):
|
||||
"""
|
||||
:param task_name
|
||||
:param tasks
|
||||
:param inventory
|
||||
"""
|
||||
from ops.ansible.runner import AdHocRunner
|
||||
runner = AdHocRunner(inventory, options=const.TASK_OPTIONS)
|
||||
result = runner.run(tasks, 'all', task_name)
|
||||
return result.results_raw, result.results_summary
|
||||
|
||||
|
||||
def test_user_connectivity(task_name, asset, username, password=None, private_key=None):
|
||||
"""
|
||||
:param task_name
|
||||
:param asset
|
||||
:param username
|
||||
:param password
|
||||
:param private_key
|
||||
"""
|
||||
from ops.inventory import JMSCustomInventory
|
||||
|
||||
tasks = get_test_asset_user_connectivity_tasks(asset)
|
||||
if not tasks:
|
||||
logger.debug("No tasks ")
|
||||
return {}, {}
|
||||
inventory = JMSCustomInventory(
|
||||
assets=[asset], username=username, password=password,
|
||||
private_key=private_key
|
||||
)
|
||||
raw, summary = run_adhoc(
|
||||
task_name=task_name, tasks=tasks, inventory=inventory
|
||||
)
|
||||
return raw, summary
|
||||
|
||||
|
||||
@org_aware_func("asset_user")
|
||||
def test_asset_user_connectivity_util(asset_user, task_name):
|
||||
"""
|
||||
:param asset_user: <AuthBook>对象
|
||||
:param task_name:
|
||||
:param run_as_admin:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
if not check_asset_can_run_ansible(asset_user.asset):
|
||||
return
|
||||
|
||||
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
|
||||
if not tasks:
|
||||
logger.debug("No tasks ")
|
||||
try:
|
||||
raw, summary = test_user_connectivity(
|
||||
task_name=task_name, asset=asset_user.asset,
|
||||
username=asset_user.username, password=asset_user.password,
|
||||
private_key=asset_user.private_key
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warn("Failed run adhoc {}, {}".format(task_name, e))
|
||||
return
|
||||
|
||||
args = (task_name,)
|
||||
kwargs = {
|
||||
'hosts': [asset_user.asset], 'tasks': tasks,
|
||||
'pattern': 'all', 'options': const.TASK_OPTIONS,
|
||||
'created_by': asset_user.org_id,
|
||||
}
|
||||
if run_as_admin:
|
||||
kwargs["run_as_admin"] = True
|
||||
else:
|
||||
kwargs["run_as"] = asset_user.username
|
||||
task, created = update_or_create_ansible_task(*args, **kwargs)
|
||||
raw, summary = task.run()
|
||||
asset_user.set_connectivity(summary)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
|
||||
def test_asset_users_connectivity_manual(asset_users):
|
||||
"""
|
||||
:param asset_users: <AuthBook>对象
|
||||
"""
|
||||
for asset_user in asset_users:
|
||||
task_name = _("Test asset user connectivity: {}").format(asset_user)
|
||||
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
|
||||
test_asset_user_connectivity_util(asset_user, task_name)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_asset_user_util(asset_user):
|
||||
"""
|
||||
:param asset_user: <Asset user>对象
|
||||
"""
|
||||
from .push_system_user import push_system_user_util
|
||||
if not asset_user.backend.startswith('system_user'):
|
||||
logger.error("Asset user is not from system user")
|
||||
return
|
||||
union_id = asset_user.union_id
|
||||
union_id_list = union_id.split('_')
|
||||
if len(union_id_list) < 2:
|
||||
logger.error("Asset user union id length less than 2")
|
||||
return
|
||||
system_user_id = union_id_list[0]
|
||||
asset_id = union_id_list[1]
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
system_user = None
|
||||
if not asset:
|
||||
return
|
||||
hosts = check_asset_can_run_ansible([asset])
|
||||
if asset.is_unixlike:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -18,27 +18,10 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
|
|||
}
|
||||
]
|
||||
|
||||
TEST_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
|
||||
|
||||
SYSTEM_USER_CONN_CACHE_KEY = "SYSTEM_USER_CONN_{}"
|
||||
TEST_SYSTEM_USER_CONN_TASKS = [
|
||||
PING_UNIXLIKE_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
|
@ -46,7 +29,7 @@ TEST_SYSTEM_USER_CONN_TASKS = [
|
|||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
||||
PING_WINDOWS_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
|
@ -55,24 +38,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
|||
}
|
||||
]
|
||||
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
TASK_OPTIONS = {
|
||||
'timeout': 10,
|
||||
'forks': 10,
|
||||
|
@ -98,7 +63,9 @@ GATHER_ASSET_USERS_TASKS = [
|
|||
"name": "get last login",
|
||||
"action": {
|
||||
"module": "shell",
|
||||
"args": "users=$(getent passwd | grep -v 'nologin' | grep -v 'shudown' | awk -F: '{ print $1 }');for i in $users;do last -F $i -1 | head -1 | grep -v '^$' | awk '{ print $1\"@\"$3\"@\"$5,$6,$7,$8 }';done"
|
||||
"args": "users=$(getent passwd | grep -v 'nologin' | "
|
||||
"grep -v 'shudown' | awk -F: '{ print $1 }');for i in $users;do last -F $i -1 | "
|
||||
"head -1 | grep -v '^$' | awk '{ print $1\"@\"$3\"@\"$5,$6,$7,$8 }';done"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -9,15 +9,16 @@ from django.utils.translation import ugettext as _
|
|||
from common.utils import (
|
||||
capacity_convert, sum_capacity, get_logger
|
||||
)
|
||||
from orgs.utils import org_aware_func
|
||||
from . import const
|
||||
from .utils import clean_hosts
|
||||
from .utils import clean_ansible_task_hosts
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
|
||||
__all__ = [
|
||||
'update_assets_hardware_info_util', 'update_asset_hardware_info_manual',
|
||||
'update_assets_hardware_info_period',
|
||||
'update_assets_hardware_info_period', 'update_node_assets_hardware_info_manual',
|
||||
]
|
||||
|
||||
|
||||
|
@ -82,6 +83,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
|
|||
|
||||
|
||||
@shared_task
|
||||
@org_aware_func("assets")
|
||||
def update_assets_hardware_info_util(assets, task_name=None):
|
||||
"""
|
||||
Using ansible api to update asset hardware info
|
||||
|
@ -93,13 +95,13 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
if task_name is None:
|
||||
task_name = _("Update some assets hardware info")
|
||||
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
|
||||
hosts = clean_hosts(assets)
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
created_by = str(assets[0].org_id)
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
|
||||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
task_name, hosts=hosts, tasks=tasks,
|
||||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as_admin=True,
|
||||
)
|
||||
result = task.run()
|
||||
set_assets_hardware_info(assets, result)
|
||||
|
@ -109,9 +111,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
|
|||
@shared_task(queue="ansible")
|
||||
def update_asset_hardware_info_manual(asset):
|
||||
task_name = _("Update asset hardware info: {}").format(asset.hostname)
|
||||
update_assets_hardware_info_util(
|
||||
[asset], task_name=task_name
|
||||
)
|
||||
update_assets_hardware_info_util([asset], task_name=task_name)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
|
@ -123,3 +123,11 @@ def update_assets_hardware_info_period():
|
|||
if not const.PERIOD_TASK_ENABLED:
|
||||
logger.debug("Period task disabled, update assets hardware info pass")
|
||||
return
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def update_node_assets_hardware_info_manual(node):
|
||||
task_name = _("Update node asset hardware information: {}").format(node.name)
|
||||
assets = node.get_all_assets()
|
||||
result = update_assets_hardware_info_util.delay(assets, task_name=task_name)
|
||||
return result
|
||||
|
|
|
@ -7,10 +7,10 @@ from celery import shared_task
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.utils import timezone
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
from orgs.utils import tmp_to_org, org_aware_func
|
||||
from common.utils import get_logger
|
||||
from ..models import GatheredUser, Node
|
||||
from .utils import clean_hosts
|
||||
from .utils import clean_ansible_task_hosts
|
||||
from . import const
|
||||
|
||||
__all__ = ['gather_asset_users', 'gather_nodes_asset_users']
|
||||
|
@ -101,11 +101,12 @@ def add_asset_users(assets, results):
|
|||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("assets")
|
||||
def gather_asset_users(assets, task_name=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if task_name is None:
|
||||
task_name = _("Gather assets users")
|
||||
assets = clean_hosts(assets)
|
||||
assets = clean_ansible_task_hosts(assets)
|
||||
if not assets:
|
||||
return
|
||||
hosts_category = {
|
||||
|
@ -131,7 +132,7 @@ def gather_asset_users(assets, task_name=None):
|
|||
task, created = update_or_create_ansible_task(
|
||||
task_name=_task_name, hosts=value['hosts'], tasks=value['tasks'],
|
||||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as_admin=True, created_by=value['hosts'][0].org_id,
|
||||
run_as_admin=True,
|
||||
)
|
||||
raw, summary = task.run()
|
||||
results[k].update(raw['ok'])
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from itertools import groupby
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import encrypt_password, get_logger
|
||||
from orgs.utils import tmp_to_org, org_aware_func
|
||||
from . import const
|
||||
from .utils import clean_hosts_by_protocol, clean_hosts
|
||||
from .utils import clean_ansible_task_hosts, group_asset_by_platform
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -15,31 +17,34 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
def get_push_linux_system_user_tasks(system_user):
|
||||
def get_push_unixlike_system_user_tasks(system_user, username=None):
|
||||
if username is None:
|
||||
username = system_user.username
|
||||
password = system_user.password
|
||||
public_key = system_user.public_key
|
||||
|
||||
tasks = [
|
||||
{
|
||||
'name': 'Add user {}'.format(system_user.username),
|
||||
'name': 'Add user {}'.format(username),
|
||||
'action': {
|
||||
'module': 'user',
|
||||
'args': 'name={} shell={} state=present'.format(
|
||||
system_user.username, system_user.shell,
|
||||
username, system_user.shell or '/bin/bash',
|
||||
),
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Add group {}'.format(system_user.username),
|
||||
'name': 'Add group {}'.format(username),
|
||||
'action': {
|
||||
'module': 'group',
|
||||
'args': 'name={} state=present'.format(
|
||||
system_user.username,
|
||||
),
|
||||
'args': 'name={} state=present'.format(username),
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
'args': 'path=/home/{}'.format(username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
|
@ -47,29 +52,29 @@ def get_push_linux_system_user_tasks(system_user):
|
|||
'name': "Set home dir permission",
|
||||
'action': {
|
||||
'module': 'file',
|
||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
|
||||
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
|
||||
},
|
||||
'when': 'home_existed.stat.exists == true'
|
||||
}
|
||||
]
|
||||
if system_user.password:
|
||||
if password:
|
||||
tasks.append({
|
||||
'name': 'Set {} password'.format(system_user.username),
|
||||
'name': 'Set {} password'.format(username),
|
||||
'action': {
|
||||
'module': 'user',
|
||||
'args': 'name={} shell={} state=present password={}'.format(
|
||||
system_user.username, system_user.shell,
|
||||
encrypt_password(system_user.password, salt="K3mIlKK"),
|
||||
username, system_user.shell,
|
||||
encrypt_password(password, salt="K3mIlKK"),
|
||||
),
|
||||
}
|
||||
})
|
||||
if system_user.public_key:
|
||||
if public_key:
|
||||
tasks.append({
|
||||
'name': 'Set {} authorized key'.format(system_user.username),
|
||||
'name': 'Set {} authorized key'.format(username),
|
||||
'action': {
|
||||
'module': 'authorized_key',
|
||||
'args': "user={} state=present key='{}'".format(
|
||||
system_user.username, system_user.public_key
|
||||
username, public_key
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -81,26 +86,27 @@ def get_push_linux_system_user_tasks(system_user):
|
|||
sudo_tmp.append(s.strip(','))
|
||||
sudo = ','.join(sudo_tmp)
|
||||
tasks.append({
|
||||
'name': 'Set {} sudo setting'.format(system_user.username),
|
||||
'name': 'Set {} sudo setting'.format(username),
|
||||
'action': {
|
||||
'module': 'lineinfile',
|
||||
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
|
||||
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
|
||||
"validate='visudo -cf %s'".format(
|
||||
system_user.username, sudo,
|
||||
)
|
||||
"validate='visudo -cf %s'".format(username, sudo)
|
||||
}
|
||||
})
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
def get_push_windows_system_user_tasks(system_user):
|
||||
def get_push_windows_system_user_tasks(system_user, username=None):
|
||||
if username is None:
|
||||
username = system_user.username
|
||||
password = system_user.password
|
||||
tasks = []
|
||||
if not system_user.password:
|
||||
if not password:
|
||||
return tasks
|
||||
tasks.append({
|
||||
'name': 'Add user {}'.format(system_user.username),
|
||||
task = {
|
||||
'name': 'Add user {}'.format(username),
|
||||
'action': {
|
||||
'module': 'win_user',
|
||||
'args': 'fullname={} '
|
||||
|
@ -112,84 +118,100 @@ def get_push_windows_system_user_tasks(system_user):
|
|||
'password_never_expires=yes '
|
||||
'groups="Users,Remote Desktop Users" '
|
||||
'groups_action=add '
|
||||
''.format(system_user.name,
|
||||
system_user.username,
|
||||
system_user.password),
|
||||
''.format(username, username, password),
|
||||
}
|
||||
})
|
||||
}
|
||||
tasks.append(task)
|
||||
return tasks
|
||||
|
||||
|
||||
def get_push_system_user_tasks(host, system_user):
|
||||
if host.is_unixlike():
|
||||
tasks = get_push_linux_system_user_tasks(system_user)
|
||||
elif host.is_windows():
|
||||
tasks = get_push_windows_system_user_tasks(system_user)
|
||||
else:
|
||||
msg = _(
|
||||
"The asset {} system platform {} does not "
|
||||
"support run Ansible tasks".format(host.hostname, host.platform)
|
||||
)
|
||||
logger.info(msg)
|
||||
def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
|
||||
"""
|
||||
:param system_user:
|
||||
:param platform:
|
||||
:param username: 当动态时,近推送某个
|
||||
:return:
|
||||
"""
|
||||
get_task_map = {
|
||||
"unixlike": get_push_unixlike_system_user_tasks,
|
||||
"windows": get_push_windows_system_user_tasks,
|
||||
}
|
||||
get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks)
|
||||
if not system_user.username_same_with_user:
|
||||
return get_tasks(system_user)
|
||||
tasks = []
|
||||
# 仅推送这个username
|
||||
if username is not None:
|
||||
tasks.extend(get_tasks(system_user, username))
|
||||
return tasks
|
||||
users = system_user.users.all().values_list('username', flat=True)
|
||||
print(_("System user is dynamic: {}").format(list(users)))
|
||||
for _username in users:
|
||||
tasks.extend(get_tasks(system_user, _username))
|
||||
return tasks
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_system_user_util(system_user, assets, task_name):
|
||||
@org_aware_func("system_user")
|
||||
def push_system_user_util(system_user, assets, task_name, username=None):
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
if not system_user.is_need_push():
|
||||
msg = _("Push system user task skip, auto push not enable or "
|
||||
"protocol is not ssh or rdp: {}").format(system_user.name)
|
||||
logger.info(msg)
|
||||
return {}
|
||||
|
||||
# Set root as system user is dangerous
|
||||
if system_user.username.lower() in ["root", "administrator"]:
|
||||
msg = _("For security, do not push user {}".format(system_user.username))
|
||||
logger.info(msg)
|
||||
return {}
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
hosts = clean_hosts_by_protocol(system_user, hosts)
|
||||
if not hosts:
|
||||
return {}
|
||||
platform_hosts_map = {}
|
||||
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
|
||||
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
|
||||
for i in platform_hosts:
|
||||
platform_hosts_map[i[0]] = list(i[1])
|
||||
|
||||
for host in hosts:
|
||||
system_user.load_specific_asset_auth(host)
|
||||
tasks = get_push_system_user_tasks(host, system_user)
|
||||
if not tasks:
|
||||
continue
|
||||
def run_task(_tasks, _hosts):
|
||||
if not _tasks:
|
||||
return
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
|
||||
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=system_user.org_id,
|
||||
)
|
||||
task.run()
|
||||
|
||||
for platform, _hosts in platform_hosts_map.items():
|
||||
if not _hosts:
|
||||
continue
|
||||
print(_("Start push system user for platform: [{}]").format(platform))
|
||||
print(_("Hosts count: {}").format(len(_hosts)))
|
||||
|
||||
if not system_user.has_special_auth():
|
||||
logger.debug("System user not has special auth")
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=username)
|
||||
run_task(tasks, _hosts)
|
||||
continue
|
||||
|
||||
for _host in _hosts:
|
||||
system_user.load_asset_special_auth(_host)
|
||||
tasks = get_push_system_user_tasks(system_user, platform, username=username)
|
||||
run_task(tasks, [_host])
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_system_user_to_assets_manual(system_user):
|
||||
assets = system_user.get_all_assets()
|
||||
def push_system_user_to_assets_manual(system_user, username=None):
|
||||
assets = system_user.get_related_assets()
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name=task_name)
|
||||
return push_system_user_util(system_user, assets, task_name=task_name, username=username)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_system_user_a_asset_manual(system_user, asset):
|
||||
task_name = _("Push system users to asset: {} => {}").format(
|
||||
system_user.name, asset
|
||||
def push_system_user_a_asset_manual(system_user, asset, username=None):
|
||||
if username is None:
|
||||
username = system_user.username
|
||||
task_name = _("Push system users to asset: {}({}) => {}").format(
|
||||
system_user.name, username, asset
|
||||
)
|
||||
return push_system_user_util(system_user, [asset], task_name=task_name)
|
||||
return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
def push_system_user_to_assets(system_user, assets):
|
||||
def push_system_user_to_assets(system_user, assets, username=None):
|
||||
task_name = _("Push system users to assets: {}").format(system_user.name)
|
||||
return push_system_user_util(system_user, assets, task_name)
|
||||
return push_system_user_util(system_user, assets, task_name, username=username)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
|
||||
from itertools import groupby
|
||||
from collections import defaultdict
|
||||
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from common.utils import get_logger
|
||||
|
||||
from orgs.utils import tmp_to_org, org_aware_func
|
||||
from ..models import SystemUser
|
||||
from . import const
|
||||
from .utils import clean_hosts, clean_hosts_by_protocol
|
||||
from .utils import (
|
||||
clean_ansible_task_hosts, group_asset_by_platform
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
__all__ = [
|
||||
|
@ -16,7 +20,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("system_user")
|
||||
def test_system_user_connectivity_util(system_user, assets, task_name):
|
||||
"""
|
||||
Test system cant connect his assets or not.
|
||||
|
@ -27,41 +31,34 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
|
||||
if not hosts:
|
||||
return {}
|
||||
platform_hosts_map = {}
|
||||
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
|
||||
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
|
||||
for i in platform_hosts:
|
||||
platform_hosts_map[i[0]] = list(i[1])
|
||||
|
||||
hosts = clean_hosts_by_protocol(system_user, hosts)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
hosts_category = {
|
||||
'linux': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
},
|
||||
'windows': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
|
||||
platform_tasks_map = {
|
||||
"unixlike": const.PING_UNIXLIKE_TASKS,
|
||||
"windows": const.PING_WINDOWS_TASKS
|
||||
}
|
||||
}
|
||||
for host in hosts:
|
||||
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
|
||||
else hosts_category['linux']['hosts']
|
||||
hosts_list.append(host)
|
||||
|
||||
results_summary = dict(
|
||||
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
|
||||
)
|
||||
for k, value in hosts_category.items():
|
||||
if not value['hosts']:
|
||||
continue
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
|
||||
|
||||
def run_task(_tasks, _hosts, _username):
|
||||
old_name = "{}".format(system_user)
|
||||
new_name = "{}({})".format(system_user.name, _username)
|
||||
_task_name = task_name.replace(old_name, new_name)
|
||||
_task, created = update_or_create_ansible_task(
|
||||
task_name=_task_name, hosts=_hosts, tasks=_tasks,
|
||||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username, created_by=system_user.org_id,
|
||||
run_as=_username,
|
||||
)
|
||||
raw, summary = task.run()
|
||||
raw, summary = _task.run()
|
||||
success = summary.get('success', False)
|
||||
contacted = summary.get('contacted', {})
|
||||
dark = summary.get('dark', {})
|
||||
|
@ -70,23 +67,45 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
results_summary['contacted'].update(contacted)
|
||||
results_summary['dark'].update(dark)
|
||||
|
||||
for platform, _hosts in platform_hosts_map.items():
|
||||
if not _hosts:
|
||||
continue
|
||||
if platform not in ["unixlike", "windows"]:
|
||||
continue
|
||||
|
||||
tasks = platform_tasks_map[platform]
|
||||
print(_("Start test system user connectivity for platform: [{}]").format(platform))
|
||||
print(_("Hosts count: {}").format(len(_hosts)))
|
||||
# 用户名不是动态的,用户名则是一个
|
||||
if not system_user.username_same_with_user:
|
||||
logger.debug("System user not has special auth")
|
||||
run_task(tasks, _hosts, system_user.username)
|
||||
# 否则需要多个任务
|
||||
else:
|
||||
users = system_user.users.all().values_list('username', flat=True)
|
||||
print(_("System user is dynamic: {}").format(list(users)))
|
||||
for username in users:
|
||||
run_task(tasks, _hosts, username)
|
||||
|
||||
system_user.set_connectivity(results_summary)
|
||||
return results_summary
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("system_user")
|
||||
def test_system_user_connectivity_manual(system_user):
|
||||
task_name = _("Test system user connectivity: {}").format(system_user)
|
||||
assets = system_user.get_all_assets()
|
||||
return test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
assets = system_user.get_related_assets()
|
||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
@org_aware_func("system_user")
|
||||
def test_system_user_connectivity_a_asset(system_user, asset):
|
||||
task_name = _("Test system user connectivity: {} => {}").format(
|
||||
system_user, asset
|
||||
)
|
||||
return test_system_user_connectivity_util(system_user, [asset], task_name)
|
||||
test_system_user_connectivity_util(system_user, [asset], task_name)
|
||||
|
||||
|
||||
@shared_task(queue="ansible")
|
||||
|
@ -94,8 +113,9 @@ def test_system_user_connectivity_period():
|
|||
if not const.PERIOD_TASK_ENABLED:
|
||||
logger.debug("Period task disabled, test system user connectivity pass")
|
||||
return
|
||||
system_users = SystemUser.objects.all()
|
||||
for system_user in system_users:
|
||||
queryset_map = SystemUser.objects.all_group_by_org()
|
||||
for org, system_user in queryset_map.items():
|
||||
task_name = _("Test system user connectivity period: {}").format(system_user)
|
||||
assets = system_user.get_all_assets()
|
||||
with tmp_to_org(org):
|
||||
assets = system_user.get_related_assets()
|
||||
test_system_user_connectivity_util(system_user, assets, task_name)
|
||||
|
|
|
@ -7,7 +7,8 @@ from common.utils import get_logger
|
|||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'check_asset_can_run_ansible', 'clean_hosts', 'clean_hosts_by_protocol'
|
||||
'check_asset_can_run_ansible', 'clean_ansible_task_hosts',
|
||||
'group_asset_by_platform',
|
||||
]
|
||||
|
||||
|
||||
|
@ -23,23 +24,43 @@ def check_asset_can_run_ansible(asset):
|
|||
return True
|
||||
|
||||
|
||||
def clean_hosts(assets):
|
||||
clean_assets = []
|
||||
def check_system_user_can_run_ansible(system_user):
|
||||
if not system_user.is_need_push():
|
||||
msg = _("Push system user task skip, auto push not enable or "
|
||||
"protocol is not ssh or rdp: {}").format(system_user.name)
|
||||
logger.info(msg)
|
||||
return False
|
||||
|
||||
# Push root as system user is dangerous
|
||||
if system_user.username.lower() in ["root", "administrator"]:
|
||||
msg = _("For security, do not push user {}".format(system_user.username))
|
||||
logger.info(msg)
|
||||
return False
|
||||
|
||||
# if system_user.protocol != "ssh":
|
||||
# msg = _("System user protocol not ssh: {}".format(system_user))
|
||||
# logger.info(msg)
|
||||
# return False
|
||||
return True
|
||||
|
||||
|
||||
def clean_ansible_task_hosts(assets, system_user=None):
|
||||
if system_user and not check_system_user_can_run_ansible(system_user):
|
||||
return []
|
||||
cleaned_assets = []
|
||||
for asset in assets:
|
||||
if not check_asset_can_run_ansible(asset):
|
||||
continue
|
||||
clean_assets.append(asset)
|
||||
if not clean_assets:
|
||||
cleaned_assets.append(asset)
|
||||
if not cleaned_assets:
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return clean_assets
|
||||
return cleaned_assets
|
||||
|
||||
|
||||
def clean_hosts_by_protocol(system_user, assets):
|
||||
hosts = [
|
||||
asset for asset in assets
|
||||
if asset.has_protocol(system_user.protocol)
|
||||
]
|
||||
if not hosts:
|
||||
msg = _("No assets matched related system user protocol, stop task")
|
||||
logger.info(msg)
|
||||
return hosts
|
||||
def group_asset_by_platform(asset):
|
||||
if asset.is_unixlike():
|
||||
return 'unixlike'
|
||||
elif asset.is_windows():
|
||||
return 'windows'
|
||||
else:
|
||||
return 'other'
|
||||
|
|
|
@ -6,25 +6,25 @@
|
|||
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_update_modal_confirm').trigger('click'); return false;}">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
|
||||
<label class="col-sm-2 control-label">{% trans "Hostname" %}: </label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_hostname_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
|
||||
<label class="col-sm-2 control-label">{% trans "Username" %}: </label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static" id="id_username_p"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
|
||||
<label class="col-sm-2 control-label">{% trans "Password" %}: </label>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" id="id_password_auth" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans "Private key" %}</label>
|
||||
<label class="col-sm-2 control-label">{% trans "Private key" %}: </label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row bootstrap3-multi-input">
|
||||
<div class="col-xs-12">
|
||||
|
|
|
@ -12,19 +12,19 @@
|
|||
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||
<div class="auth-field">
|
||||
<div class="form-group">
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}:</label>
|
||||
<div class="col-sm-8">
|
||||
<p class="form-control-static" id="id_hostname_view"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Username' %}</label>
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Username' %}:</label>
|
||||
<div class="col-sm-8" >
|
||||
<p class="form-control-static" id="id_username_view"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Password' %}</label>
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Password' %}:</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="id_password_view" type="password" class="form-control" value="" readonly style="border: none;padding-left: 0;background-color: #fff;width: 100%">
|
||||
</div>
|
||||
|
@ -38,11 +38,11 @@
|
|||
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
|
||||
<script>
|
||||
var showPassword = false;
|
||||
|
||||
var authAssetId = "";
|
||||
var authHostname = "";
|
||||
var authUsername = "";
|
||||
var mfaFor = "";
|
||||
var authUid = "";
|
||||
var authInfoDetailUrl = "{% url "api-assets:asset-user-auth-info-detail" pk=DEFAULT_PK %}";
|
||||
|
||||
function initClipboard() {
|
||||
var clipboard = new Clipboard('.btn-copy-password', {
|
||||
|
@ -56,12 +56,10 @@ function initClipboard() {
|
|||
}
|
||||
|
||||
function showAuth() {
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + authAssetId + "&username=" + authUsername;
|
||||
if (prefer) {
|
||||
url = setUrlParam(url, 'prefer', prefer)
|
||||
}
|
||||
var url = authInfoDetailUrl.replace("{{ DEFAULT_PK }}", authUid);
|
||||
$("#id_username_view").html(authUsername);
|
||||
$("#id_hostname_view").html(authHostname);
|
||||
$("#id_password_view").val('');
|
||||
var success = function (data) {
|
||||
var password = data.password;
|
||||
$("#id_password_view").val(password);
|
||||
|
@ -89,7 +87,13 @@ $(document).ready(function () {
|
|||
$("#id_password_view").attr("type", "password")
|
||||
}
|
||||
}).on("show.bs.modal", "#asset_user_auth_view", function () {
|
||||
showPassword = false;
|
||||
$("#id_password_view").attr("type", "password");
|
||||
showAuth();
|
||||
}).on("hide.bs.modal", "#asset_user_auth_view", function () {
|
||||
$("#id_username_view").html('');
|
||||
$("#id_hostname_view").html('');
|
||||
$("#id_password_view").val('');
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
table.dataTable tbody tr.selected a {
|
||||
color: rgb(103, 106, 108);;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<table class="table table-striped table-bordered table-hover" id="asset_user_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<th class="text-center">{% trans 'IP' %}</th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Version' %}</th>
|
||||
<th class="text-center">{% trans 'Connectivity'%}</th>
|
||||
{# <th class="text-center">{% trans 'Connectivity'%}</th>#}
|
||||
<th class="text-center">{% trans 'Datetime' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
|
@ -33,62 +33,60 @@
|
|||
{% include 'authentication/_mfa_confirm_modal.html' %}
|
||||
|
||||
<script>
|
||||
var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
|
||||
var defaultAssetUserListUrl = "{% url "api-assets:asset-user-list" %}";
|
||||
var defaultAssetUserDetail = "{% url "api-assets:asset-user-detail" pk=DEFAULT_PK %}";
|
||||
var assetUserTable;
|
||||
var needPush = false;
|
||||
var prefer = null;
|
||||
var defaultNeedPush = false;
|
||||
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
|
||||
var testDatetime = "{% trans 'Test datetime: ' %}";
|
||||
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
|
||||
var mfaNeedCheck = "{{ SECURITY_VIEW_AUTH_NEED_MFA }}" === "True";
|
||||
var onlyLatestEl = "<span style='padding-right:20px'><input type='checkbox' id='only_latest'> {% trans 'Only latest version' %}</span>";
|
||||
var onlyLatestChecked = false;
|
||||
var systemUserId = "";
|
||||
|
||||
function initAssetUserTable() {
|
||||
function initAssetUserTable(option) {
|
||||
if (!option) {
|
||||
option = {}
|
||||
}
|
||||
var assetUserListUrl = option.assetUserListUrl || defaultAssetUserListUrl;
|
||||
var needPush = option.needPush === undefined ? defaultNeedPush : option.needPush;
|
||||
var options = {
|
||||
ele: $('#asset_user_list_table'),
|
||||
toggle: true,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 5, createdCell: function (td, cellData) {
|
||||
var innerHtml = "";
|
||||
if (cellData.status == 1) {
|
||||
innerHtml = '<i class="fa fa-circle text-navy"></i>'
|
||||
} else if (cellData.status == 0) {
|
||||
innerHtml = '<i class="fa fa-circle text-danger"></i>'
|
||||
} else {
|
||||
innerHtml = '<i class="fa fa-circle text-warning"></i>'
|
||||
}
|
||||
var dateManual = toSafeLocalDateStr(cellData.datetime);
|
||||
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) {
|
||||
var data = toSafeLocalDateStr(cellData);
|
||||
$(td).html(data);
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: 7, createdCell: function (td, cellData, rowData) {
|
||||
var view_btn = '<button class="btn btn-xs btn-primary m-l-xs btn-view-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans "View" %}</button>'
|
||||
var update_btn = '<li><a class="btn-update-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Update' %}</a></li>';
|
||||
var test_btn = '<li><a class="btn-test-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Test' %}</a></li>';
|
||||
var push_btn = '<li><a class="btn-push-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Push' %}</a></li>';
|
||||
if (needPush) {
|
||||
test_btn += push_btn;
|
||||
targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var viewBtn = '<button class="btn btn-xs btn-primary m-l-xs btn-view-auth" DATA>{% trans "View" %}</button>';
|
||||
var updateBtn = '<li><a class="btn-update-auth" DATA>{% trans 'Update' %}</a></li>';
|
||||
var testBtn = '<li><a class="btn-test-auth" DATA>{% trans 'Test' %}</a></li>';
|
||||
var pushBtn = '<li><a class="btn-push-auth" DATA>{% trans 'Push' %}</a></li>';
|
||||
var delBtn = '<li><a class="btn-del-auth" DATA>{% trans 'Delete' %}</a></li>';
|
||||
if (!needPush) {
|
||||
pushBtn = ''
|
||||
}
|
||||
var actions = '<div class="btn-group">' + view_btn +
|
||||
|
||||
var data = "data-hostname=hostname123 data-username=username123 data-uid=uid123 data-asset=asset123";
|
||||
data = data.replaceAll("username123", rowData.username)
|
||||
.replaceAll("hostname123", rowData.hostname)
|
||||
.replaceAll("uid123", rowData.id)
|
||||
.replaceAll("asset123", rowData.asset);
|
||||
|
||||
var actions = '<div class="btn-group">' + viewBtn +
|
||||
' <button data-toggle="dropdown" class="btn btn-primary btn-xs dropdown-toggle">' +
|
||||
' <span class="caret"></span>' +
|
||||
' </button>' +
|
||||
' <ul class="dropdown-menu">' +
|
||||
update_btn + test_btn +
|
||||
updateBtn + delBtn + testBtn + pushBtn
|
||||
' </ul>' +
|
||||
' </div>';
|
||||
actions = actions.replaceAll("username123", rowData.username)
|
||||
.replaceAll("hostname123", rowData.hostname)
|
||||
.replaceAll("asset123", rowData.asset);
|
||||
actions = actions.replaceAll("DATA", data);
|
||||
$(td).html(actions);
|
||||
},
|
||||
width: '70px'
|
||||
|
@ -98,21 +96,23 @@ function initAssetUserTable() {
|
|||
columns: [
|
||||
{data: "id"}, {data: "hostname"}, {data: "ip"},
|
||||
{data: "username"}, {data: "version", orderable: false},
|
||||
{data: "connectivity"},
|
||||
{data: "date_created", orderable: false},
|
||||
{data: "asset", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
op_html: $('#actions').html(),
|
||||
lb_html: onlyLatestEl,
|
||||
};
|
||||
table = jumpserver.initServerSideDataTable(options);
|
||||
return table
|
||||
assetUserTable = jumpserver.initServerSideDataTable(options);
|
||||
return assetUserTable
|
||||
}
|
||||
$(document).ready(function(){
|
||||
})
|
||||
.on('click', '.btn-view-auth', function () {
|
||||
// 通知给view auth modal
|
||||
authAssetId = $(this).data("asset");
|
||||
authHostname = $(this).data("hostname");
|
||||
authUsername = $(this).data('user');
|
||||
authUsername = $(this).data('username');
|
||||
authUid = $(this).data("uid");
|
||||
if (!mfaNeedCheck){
|
||||
$("#asset_user_auth_view").modal('show');
|
||||
return
|
||||
|
@ -133,29 +133,56 @@ $(document).ready(function(){
|
|||
$("#asset_user_auth_view").modal("show");
|
||||
})
|
||||
.on('click', '.btn-update-auth', function() {
|
||||
authUsername = $(this).data("user") ;
|
||||
authUsername = $(this).data("username") ;
|
||||
authHostname = $(this).data("hostname");
|
||||
authAssetId = $(this).data("asset");
|
||||
$("#asset_user_auth_update_modal").modal('show');
|
||||
})
|
||||
.on("click", '.btn-test-auth', function () {
|
||||
authUsername = $(this).data("user") ;
|
||||
authAssetId = $(this).data("asset");
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id=" + authAssetId + "&username=" + authUsername;
|
||||
if (prefer) {
|
||||
the_url = setUrlParam(the_url, "prefer", prefer)
|
||||
}
|
||||
authUid = $(this).data('uid');
|
||||
var theUrl = "{% url 'api-assets:asset-user-task-create'%}?id={{ DEFAULT_PK }}"
|
||||
.replace("{{ DEFAULT_PK }}", authUid);
|
||||
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
var taskId = data.task;
|
||||
showCeleryTaskLog(taskId);
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
url: theUrl,
|
||||
method: 'POST',
|
||||
data: JSON.stringify({action: 'test'}),
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-del-auth', function () {
|
||||
var uid = $(this).data("uid");
|
||||
var theUrl = defaultAssetUserDetail.replace("{{ DEFAULT_PK }}", uid);
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
assetUserTable.ajax.reload(null, false);
|
||||
},
|
||||
success_message: "{% trans 'Delete success' %}"
|
||||
})
|
||||
|
||||
})
|
||||
.on("change", '#only_latest', function () {
|
||||
var checked = $("#only_latest").is(":checked");
|
||||
if (checked === onlyLatestChecked) {
|
||||
return
|
||||
}
|
||||
var ajaxUrl = assetUserTable.ajax.url();
|
||||
if (checked) {
|
||||
ajaxUrl = setUrlParam(ajaxUrl, 'latest', 1)
|
||||
} else {
|
||||
ajaxUrl = setUrlParam(ajaxUrl, 'latest', 0)
|
||||
}
|
||||
onlyLatestChecked = !onlyLatestChecked;
|
||||
assetUserTable.ajax.url(ajaxUrl);
|
||||
assetUserTable.ajax.reload();
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
|
|
@ -113,10 +113,11 @@ function initNodeTree(options) {
|
|||
$.get(treeUrl, function (data, status) {
|
||||
zTree = $.fn.zTree.init($("#nodeTree"), setting, data);
|
||||
rootNodeAddDom(zTree, function () {
|
||||
const url = '{% url 'api-assets:refresh-nodes-cache' %}';
|
||||
const url = '{% url 'api-assets:node-task-create' pk=DEFAULT_PK %}';
|
||||
requestApi({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
data: {action: "refresh_cache"},
|
||||
flash_message: false,
|
||||
success: function () {
|
||||
initNodeTree(options);
|
||||
|
@ -173,20 +174,14 @@ function removeTreeNode() {
|
|||
if (!current_node){
|
||||
return
|
||||
}
|
||||
if (current_node.children && current_node.children.length > 0) {
|
||||
toastr.error("{% trans 'Have child node, cancel' %}");
|
||||
} else if (current_node.meta.node.assets_amount !== 0) {
|
||||
toastr.error("{% trans 'Have assets, cancel' %}");
|
||||
} else {
|
||||
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
$.ajax({
|
||||
requestApi({
|
||||
url: url,
|
||||
method: "DELETE",
|
||||
success: function () {
|
||||
zTree.removeNode(current_node);
|
||||
}
|
||||
});
|
||||
zTree.removeNode(current_node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function editTreeNode() {
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.login_mode layout="horizontal" %}
|
||||
{% bootstrap_field form.username layout="horizontal" %}
|
||||
{% bootstrap_field form.username_same_with_user layout="horizontal" %}
|
||||
{% bootstrap_field form.priority layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
|
||||
|
@ -63,6 +64,7 @@
|
|||
{% bootstrap_field form.cmd_filters layout="horizontal" %}
|
||||
</div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.sftp_root layout="horizontal" %}
|
||||
{% bootstrap_field form.sudo layout="horizontal" %}
|
||||
{% bootstrap_field form.shell layout="horizontal" %}
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
|
@ -226,6 +228,10 @@ $(document).ready(function () {
|
|||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
fieldDisplay();
|
||||
var checked = $("#id_username_same_with_user").prop('checked');
|
||||
if (checked) {
|
||||
$("#id_username").attr("disabled", true)
|
||||
}
|
||||
})
|
||||
.on('change', auto_generate_key, function(){
|
||||
authFieldsDisplay();
|
||||
|
@ -246,7 +252,7 @@ $(document).ready(function () {
|
|||
var data = form.serializeObject();
|
||||
|
||||
objectAttrsIsList(data, ['cmd_filters']);
|
||||
objectAttrsIsBool(data, ["auto_generate_key", "auto_push"]);
|
||||
objectAttrsIsBool(data, ["auto_generate_key", "auto_push", "username_same_with_user"]);
|
||||
data["private_key"] = $("#id_private_key").data('file');
|
||||
|
||||
var props = {
|
||||
|
@ -261,6 +267,15 @@ $(document).ready(function () {
|
|||
readFile($(this)).on("onload", function (evt, data) {
|
||||
$(this).data("file", data)
|
||||
})
|
||||
}).on("change", '#id_username_same_with_user', function () {
|
||||
var checked = $(this).prop('checked');
|
||||
var usernameRef = $("#id_username");
|
||||
if (checked) {
|
||||
usernameRef.val('');
|
||||
usernameRef.attr("disabled", true)
|
||||
} else {
|
||||
usernameRef.attr("disabled", false)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -72,9 +72,9 @@
|
|||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "admin_user_id", "{{ admin_user.id }}");
|
||||
prefer = "admin_user";
|
||||
initAssetUserTable();
|
||||
var assetUserListUrl = setUrlParam(defaultAssetUserListUrl, "prefer_id", "{{ admin_user.id }}");
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "prefer", "admin_user");
|
||||
initAssetUserTable({assetUserListUrl: assetUserListUrl});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:admin-user-connective' pk=admin_user.id %}";
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
<td><b>{{ admin_user.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n static %}
|
||||
{% block help_message %}
|
||||
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
|
||||
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||
{% trans 'JumpServer users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
|
||||
{% endblock %}
|
||||
{% block table_search %}
|
||||
{% include '_csv_import_export.html' %}
|
||||
|
|
|
@ -73,19 +73,22 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "asset_id", "{{ asset.id }}");
|
||||
initAssetUserTable()
|
||||
assetUserListUrl = setUrlParam(defaultAssetUserListUrl, "asset_id", "{{ asset.id }}");
|
||||
initAssetUserTable({assetUserListUrl: assetUserListUrl})
|
||||
})
|
||||
|
||||
.on('click', '#btn-bulk-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
|
||||
var assetId = "{{ asset.id }}";
|
||||
var theUrl = "{% url 'api-assets:asset-user-task-create' %}?asset_id={{ DEFAULT_PK }}&latest=1"
|
||||
.replace("{{ DEFAULT_PK }}", assetId);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
url: theUrl,
|
||||
method: 'POST',
|
||||
data: {action: "test"},
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
|
|
|
@ -268,16 +268,16 @@ function updateAssetNodes(nodes) {
|
|||
}
|
||||
|
||||
function refreshAssetHardware() {
|
||||
var the_url = "{% url 'api-assets:asset-refresh' pk=asset.id %}";
|
||||
var the_url = "{% url 'api-assets:asset-task-create' pk=asset.id %}";
|
||||
var success = function(data) {
|
||||
console.log(data);
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
success: success,
|
||||
method: 'GET'
|
||||
data: {action: "refresh"},
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -345,16 +345,15 @@ $(document).ready(function () {
|
|||
}).on('click', '#btn_refresh_asset', function () {
|
||||
refreshAssetHardware()
|
||||
}).on('click', '#btn-test-is-alive', function () {
|
||||
var the_url = "{% url 'api-assets:asset-alive-test' pk=asset.id %}";
|
||||
|
||||
var the_url = "{% url 'api-assets:asset-task-create' pk=asset.id %}";
|
||||
var success = function(data) {
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
};
|
||||
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
data: {action: "test"},
|
||||
success: success
|
||||
});
|
||||
})
|
||||
|
|
|
@ -258,6 +258,10 @@ $(document).ready(function(){
|
|||
confirmButtonText: "{% trans 'Confirm' %}",
|
||||
closeOnConfirm: false
|
||||
},function () {
|
||||
function fail() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||
}
|
||||
function success(data) {
|
||||
url = setUrlParam(the_url, 'spm', data.spm);
|
||||
requestApi({
|
||||
|
@ -268,13 +272,10 @@ $(document).ready(function(){
|
|||
swal("{% trans 'Asset Delete' %}", msg, "success");
|
||||
reloadTable();
|
||||
},
|
||||
error: fail,
|
||||
flash_message: false,
|
||||
});
|
||||
}
|
||||
function fail() {
|
||||
var msg = "{% trans 'Asset Deleting failed.' %}";
|
||||
swal("{% trans 'Asset Delete' %}", msg, "error");
|
||||
}
|
||||
requestApi({
|
||||
url: "{% url 'api-common:resources-cache' %}",
|
||||
method: 'POST',
|
||||
|
@ -306,6 +307,7 @@ $(document).ready(function(){
|
|||
|
||||
function doRemove() {
|
||||
if (!current_node_id) {
|
||||
toastr.error("{% trans 'Please select node' %}");
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -350,7 +352,7 @@ $(document).ready(function(){
|
|||
}).on('click', '#menu_asset_move', function () {
|
||||
update_node_action = "move"
|
||||
}).on('click', '.btn-test-connective', function () {
|
||||
var url = "{% url 'api-assets:node-test-connective' pk=DEFAULT_PK %}";
|
||||
var url = "{% url 'api-assets:node-task-create' pk=DEFAULT_PK %}";
|
||||
if (!current_node_id) {
|
||||
return null;
|
||||
}
|
||||
|
@ -362,12 +364,13 @@ $(document).ready(function(){
|
|||
}
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
data: {action: "test"},
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
}).on('click', '.btn-refresh-hardware', function () {
|
||||
var url = "{% url 'api-assets:node-refresh-hardware-info' pk=DEFAULT_PK %}";
|
||||
var url = "{% url 'api-assets:node-task-create' pk=DEFAULT_PK %}";
|
||||
var the_url = url.replace("{{ DEFAULT_PK }}", current_node_id);
|
||||
function success(data) {
|
||||
rMenu.css({"visibility" : "hidden"});
|
||||
|
@ -376,7 +379,8 @@ $(document).ready(function(){
|
|||
}
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: "GET",
|
||||
method: "POST",
|
||||
data: {action: "refresh"},
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
|
|
|
@ -63,10 +63,6 @@
|
|||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ object.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ object.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ object.comment }}</b></td>
|
||||
|
|
|
@ -13,7 +13,12 @@
|
|||
<a href="{% url 'assets:platform-detail' pk=object.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'assets:platform-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
<a class="btn btn-outline btn-default" {% if object.internal %} disabled="true" {% endif %} href="{% url 'assets:platform-update' pk=object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-del" {% if object.internal %} disabled="true" {% endif %}>
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -72,4 +77,15 @@
|
|||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
}).on('click', '.btn-del', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ object.name}}";
|
||||
var uid = "{{ object.id }}";
|
||||
var the_url = '{% url "api-assets:asset-platform-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||
var redirect_url = "{% url 'assets:platform-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -23,16 +23,23 @@
|
|||
</li>
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Asset list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% if system_user.username_same_with_user %}
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-user' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'User list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Assets of ' %} <b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
|
||||
<span style="float: left"><b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
|
@ -82,6 +89,30 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Assets' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table" id="add-assets">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select assets' %}" id="id_assets" class="assets-select2 select2" style="width: 100%" multiple="" tabindex="4">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-to-assets">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Nodes' %}
|
||||
|
@ -92,7 +123,7 @@
|
|||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Add to node' %}" id="node_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
|
||||
<select data-placeholder="{% trans 'Select nodes' %}" id="node_selected" class="nodes-select2" style="width: 100%" multiple="" tabindex="4">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -114,6 +145,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_list_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
@ -133,7 +165,7 @@ function getRelationUrl(type) {
|
|||
return theUrl;
|
||||
}
|
||||
|
||||
function addObjects(objectsId, type) {
|
||||
function addObjects(objectsId, type, success, fail) {
|
||||
if (!objectsId || objectsId.length === 0) {
|
||||
return
|
||||
}
|
||||
|
@ -144,13 +176,21 @@ function addObjects(objectsId, type) {
|
|||
data[type] = v;
|
||||
body.push(data)
|
||||
});
|
||||
requestApi({
|
||||
if (!success) {
|
||||
success = reloadPage
|
||||
}
|
||||
var option = {
|
||||
url: theUrl,
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
success: reloadPage
|
||||
});
|
||||
success: success,
|
||||
};
|
||||
if (fail) {
|
||||
option.error = fail;
|
||||
}
|
||||
requestApi(option)
|
||||
}
|
||||
|
||||
|
||||
function removeObject(objectId, type, success) {
|
||||
if (!objectId) {
|
||||
|
@ -197,9 +237,13 @@ function initNodeTable() {
|
|||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
nodesSelect2Init(".nodes-select2");
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
|
||||
needPush = true;
|
||||
initAssetUserTable();
|
||||
initAssetTreeModel('#id_assets');
|
||||
var assetUserListUrl = setUrlParam(defaultAssetUserListUrl, "prefer_id", "{{ system_user.id }}");
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "prefer", "system_user");
|
||||
initAssetUserTable({
|
||||
assetUserListUrl: assetUserListUrl,
|
||||
needPush: true
|
||||
});
|
||||
initNodeTable();
|
||||
})
|
||||
.on('click', '.btn-remove-from-node', function() {
|
||||
|
@ -215,8 +259,31 @@ $(document).ready(function () {
|
|||
var nodes = $("#node_selected").val();
|
||||
addObjects(nodes, "node");
|
||||
})
|
||||
.on('click', '#btn-add-to-assets', function () {
|
||||
var assets = $("#id_assets").val();
|
||||
var options = $("#id_assets").find(":selected");
|
||||
var failed = function(s, data) {
|
||||
var invalidIndex = [];
|
||||
var validIndex = [];
|
||||
$.each(data, function (k, v) {
|
||||
if (isEmptyObject(v)) {
|
||||
validIndex.push(k)
|
||||
} else {
|
||||
invalidIndex.push(k)
|
||||
}
|
||||
});
|
||||
var invalidLabel = [];
|
||||
$.each(invalidIndex, function (k, v) {
|
||||
invalidLabel.push(options[v].text)
|
||||
});
|
||||
var errorMsg = "{% trans 'Have existed: ' %}";
|
||||
errorMsg += invalidLabel.join(", ");
|
||||
toastr.error(errorMsg)
|
||||
};
|
||||
addObjects(assets, "asset", null, failed);
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var theUrl = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
var theUrl = "{% url 'api-assets:system-user-task-create' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
|
@ -224,18 +291,26 @@ $(document).ready(function () {
|
|||
var taskId = data.task;
|
||||
showCeleryTaskLog(taskId);
|
||||
};
|
||||
var data = {
|
||||
action: 'push'
|
||||
};
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
error: error,
|
||||
method: 'GET',
|
||||
data: data,
|
||||
method: 'POST',
|
||||
success: success
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-push-auth', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('asset');
|
||||
var theUrl = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
theUrl = theUrl.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var assetId = $this.data('asset');
|
||||
var username = $this.data("username");
|
||||
var theUrl = "{% url 'api-assets:system-user-task-create' pk=object.id %}?username=" + username;
|
||||
var data = {
|
||||
action: 'push',
|
||||
asset: assetId,
|
||||
};
|
||||
var success = function (data) {
|
||||
var taskId = data.task;
|
||||
showCeleryTaskLog(taskId);
|
||||
|
@ -245,13 +320,15 @@ $(document).ready(function () {
|
|||
};
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: 'GET',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var theUrl = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
|
||||
var theUrl = "{% url 'api-assets:system-user-task-create' pk=system_user.id %}";
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
|
@ -261,9 +338,11 @@ $(document).ready(function () {
|
|||
};
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
data: {action: "test"},
|
||||
error: error,
|
||||
method: 'GET',
|
||||
success: success
|
||||
method: 'POST',
|
||||
success: success,
|
||||
flash_message: false,
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -15,7 +15,14 @@
|
|||
{% if system_user.can_perm_to_asset %}
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Assets' %}
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Asset list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if system_user.username_same_with_user %}
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-user' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'User list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
@ -57,7 +64,11 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Username' %}:</td>
|
||||
{% if system_user.username_same_with_user %}
|
||||
<td><b>{% trans 'Username same with user' %}</b></td>
|
||||
{% else %}
|
||||
<td><b>{{ system_user.username }}</b></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Login mode' %}:</td>
|
||||
|
@ -95,7 +106,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ asset_group.created_by }}</b></td>
|
||||
<td><b>{{ system_user.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
|
@ -131,26 +142,6 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if system_user.auto_push %}
|
||||
<tr class="only-ssh-rdp">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-push" style="width: 54px">{% trans 'Push' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if system_user.is_need_test_asset_connective %}
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
<button type="button" class="btn btn-primary btn-xs btn-test-connective" style="width: 54px">{% trans 'Test' %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -246,32 +237,7 @@ $(document).ready(function () {
|
|||
var redirect_url = "{% url 'assets:system-user-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-connective' pk=system_user.id %}";
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
showCeleryTaskLog(task_id);
|
||||
};
|
||||
requestApi({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
}).on('click', '#btn-binding-command-filters', function () {
|
||||
.on('click', '#btn-binding-command-filters', function () {
|
||||
var new_selected_cmd_filters = $.map($('#command_filters_selected').select2('data'), function (i) {
|
||||
return i.id;
|
||||
});
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block help_message %}
|
||||
{% trans 'System user is Jumpserver jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
|
||||
{% trans 'In simple terms, users log into Jumpserver using their own username, and Jumpserver uses system users to log into assets. '%}
|
||||
{% trans 'When system users are created, if you choose auto push Jumpserver to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
|
||||
{% trans 'System user is JumpServer jump login assets used by the users, can be understood as the user login assets, such as web, sa, the dba (` ssh web@some-host `), rather than using a user the username login server jump (` ssh xiaoming@some-host `); '%}
|
||||
{% trans 'In simple terms, users log into JumpServer using their own username, and JumpServer uses system users to log into assets. '%}
|
||||
{% trans 'When system users are created, if you choose auto push JumpServer to use Ansible push system users into the asset, if the asset (Switch) does not support ansible, please manually fill in the account password.' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block table_search %}
|
||||
|
@ -26,9 +26,6 @@
|
|||
<th class="text-center">{% trans 'Protocol' %}</th>
|
||||
<th class="text-center">{% trans 'Login mode' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
|
||||
{# <th class="text-center">{% trans 'Unreachable' %}</th>#}
|
||||
{# <th class="text-center">{% trans 'Ratio' %}</th>#}
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
|
@ -40,6 +37,8 @@
|
|||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var system_user_table = 0;
|
||||
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#system_user_list_table'),
|
||||
|
@ -61,7 +60,7 @@ function initTable() {
|
|||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||
columns: [
|
||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"},
|
||||
{data: "login_mode"}, {data: "assets_amount", orderable: false },
|
||||
{data: "login_mode"}, {data: "assets_amount", width: "60px"},
|
||||
{data: "comment" }, {data: "id", orderable: false, width: "120px"}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<style>
|
||||
.table.node_edit {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Asset list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% if system_user.username_same_with_user %}
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-user' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'User list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left"><b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-striped table-bordered table-hover" id="user_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table" id="add-users">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select users' %}" id="id_users" class="users-select2 select2" style="width: 100%" multiple="" tabindex="4">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-users">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var usersRelationUrl = "{% url 'api-assets:system-users-users-relation-list' %}?systemuser={{ system_user.id }}";
|
||||
var userTable = null;
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#user_list_table'),
|
||||
toggle: true,
|
||||
ajax_url: usersRelationUrl,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var removeBtn = '<button class="btn btn-xs btn-danger m-l-xs btn-remove" data-uid=UID>{% trans "Remove" %}</button>';
|
||||
removeBtn = removeBtn.replace("UID", rowData.user);
|
||||
$(td).html(removeBtn);
|
||||
},
|
||||
width: '70px'
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{data: "id"}, {data: "user_display"},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html(),
|
||||
};
|
||||
userTable = jumpserver.initServerSideDataTable(options);
|
||||
return userTable
|
||||
}
|
||||
|
||||
function addUsers(objectsId, success, fail) {
|
||||
if (!objectsId || objectsId.length === 0) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersRelationUrl;
|
||||
var body = [];
|
||||
objectsId.forEach(function (v) {
|
||||
var data = {systemuser: "{{ object.id }}"};
|
||||
data.user = v;
|
||||
body.push(data)
|
||||
});
|
||||
if (!success) {
|
||||
success = reloadPage
|
||||
}
|
||||
var option = {
|
||||
url: theUrl,
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
success: success,
|
||||
};
|
||||
if (fail) {
|
||||
option.error = fail;
|
||||
}
|
||||
requestApi(option)
|
||||
}
|
||||
|
||||
function removeUser(userId, success) {
|
||||
if (!userId) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersRelationUrl;
|
||||
theUrl = setUrlParam(theUrl, 'user', userId);
|
||||
if (!success) {
|
||||
success = function () {
|
||||
userTable.ajax.reload();
|
||||
}
|
||||
}
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "DELETE",
|
||||
success: success,
|
||||
success_message: "{% trans "Remove success" %}"
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initTable();
|
||||
usersSelect2Init('.users-select2');
|
||||
})
|
||||
.on('click', '.btn-remove', function() {
|
||||
var userId = $(this).data("uid");
|
||||
removeUser(userId);
|
||||
})
|
||||
.on('click', '#btn-add-users', function() {
|
||||
var usersId = $('.users-select2').val();
|
||||
var options = $(".users-select2").find(":selected");
|
||||
var failed = function(s, data) {
|
||||
var invalidIndex = [];
|
||||
var validIndex = [];
|
||||
$.each(data, function (k, v) {
|
||||
if (isEmptyObject(v)) {
|
||||
validIndex.push(k)
|
||||
} else {
|
||||
invalidIndex.push(k)
|
||||
}
|
||||
});
|
||||
var invalidLabel = [];
|
||||
$.each(invalidIndex, function (k, v) {
|
||||
invalidLabel.push(options[v].text)
|
||||
});
|
||||
var errorMsg = "{% trans 'Have existed: ' %}";
|
||||
errorMsg += invalidLabel.join(", ");
|
||||
toastr.error(errorMsg)
|
||||
};
|
||||
addUsers(usersId, null, failed);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1,3 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -21,82 +21,46 @@ router.register(r'domains', api.DomainViewSet, 'domain')
|
|||
router.register(r'gateways', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'cmd-filters', api.CommandFilterViewSet, 'cmd-filter')
|
||||
router.register(r'asset-users', api.AssetUserViewSet, 'asset-user')
|
||||
router.register(r'asset-users-info', api.AssetUserExportViewSet, 'asset-user-info')
|
||||
router.register(r'asset-user-auth-infos', api.AssetUserAuthInfoViewSet, 'asset-user-auth-info')
|
||||
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
|
||||
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
|
||||
router.register(r'system-users-assets-relations', api.SystemUserAssetRelationViewSet, 'system-users-assets-relation')
|
||||
router.register(r'system-users-nodes-relations', api.SystemUserNodeRelationViewSet, 'system-users-nodes-relation')
|
||||
router.register(r'system-users-users-relations', api.SystemUserUserRelationViewSet, 'system-users-users-relation')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filters', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('assets/<uuid:pk>/refresh/',
|
||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||
path('assets/<uuid:pk>/alive/',
|
||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||
path('assets/<uuid:pk>/gateway/',
|
||||
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||
path('assets/<uuid:pk>/platform/',
|
||||
api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'),
|
||||
path('assets/<uuid:pk>/gateways/', api.AssetGatewayListApi.as_view(), name='asset-gateway-list'),
|
||||
path('assets/<uuid:pk>/platform/', api.AssetPlatformRetrieveApi.as_view(), name='asset-platform-detail'),
|
||||
path('assets/<uuid:pk>/tasks/', api.AssetTaskCreateApi.as_view(), name='asset-task-create'),
|
||||
|
||||
path('asset-users/auth-info/',
|
||||
api.AssetUserAuthInfoApi.as_view(), name='asset-user-auth-info'),
|
||||
path('asset-users/test-connective/',
|
||||
api.AssetUserTestConnectiveApi.as_view(), name='asset-user-connective'),
|
||||
path('asset-users/tasks/', api.AssetUserTaskCreateAPI.as_view(), name='asset-user-task-create'),
|
||||
|
||||
path('admin-users/<uuid:pk>/nodes/', api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-users/<uuid:pk>/auth/', api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-users/<uuid:pk>/connective/', api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
path('admin-users/<uuid:pk>/assets/', api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
||||
|
||||
path('admin-users/<uuid:pk>/nodes/',
|
||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||
path('admin-users/<uuid:pk>/auth/',
|
||||
api.AdminUserAuthApi.as_view(), name='admin-user-auth'),
|
||||
path('admin-users/<uuid:pk>/connective/',
|
||||
api.AdminUserTestConnectiveApi.as_view(), name='admin-user-connective'),
|
||||
path('admin-users/<uuid:pk>/assets/',
|
||||
api.AdminUserAssetsListView.as_view(), name='admin-user-assets'),
|
||||
|
||||
path('system-users/<uuid:pk>/auth-info/',
|
||||
api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-users/<uuid:pk>/assets/<uuid:aid>/auth-info/',
|
||||
api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
||||
path('system-users/<uuid:pk>/assets/',
|
||||
api.SystemUserAssetsListView.as_view(), name='system-user-assets'),
|
||||
path('system-users/<uuid:pk>/push/',
|
||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||
path('system-users/<uuid:pk>/assets/<uuid:aid>/push/',
|
||||
api.SystemUserPushToAssetApi.as_view(), name='system-user-push-to-asset'),
|
||||
path('system-users/<uuid:pk>/assets/<uuid:aid>/test/',
|
||||
api.SystemUserTestAssetConnectivityApi.as_view(), name='system-user-test-to-asset'),
|
||||
path('system-users/<uuid:pk>/connective/',
|
||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||
path('system-users/<uuid:pk>/cmd-filter-rules/',
|
||||
api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
path('system-users/<uuid:pk>/auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'),
|
||||
path('system-users/<uuid:pk>/assets/<uuid:aid>/auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'),
|
||||
path('system-users/<uuid:pk>/tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'),
|
||||
path('system-users/<uuid:pk>/cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'),
|
||||
|
||||
path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
|
||||
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
|
||||
path('nodes/<uuid:pk>/children/',
|
||||
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
|
||||
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||
path('nodes/<uuid:pk>/children/add/',
|
||||
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
path('nodes/<uuid:pk>/assets/',
|
||||
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
path('nodes/<uuid:pk>/assets/add/',
|
||||
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
path('nodes/<uuid:pk>/assets/replace/',
|
||||
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
path('nodes/<uuid:pk>/assets/remove/',
|
||||
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
path('nodes/<uuid:pk>/refresh-hardware-info/',
|
||||
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||
path('nodes/<uuid:pk>/test-connective/',
|
||||
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||
path('nodes/<uuid:pk>/children/add/', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||
path('nodes/<uuid:pk>/assets/', api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||
path('nodes/<uuid:pk>/assets/add/', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||
path('nodes/<uuid:pk>/assets/replace/', api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||
path('nodes/<uuid:pk>/assets/remove/', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||
path('nodes/<uuid:pk>/tasks/', api.NodeTaskCreateApi.as_view(), name='node-task-create'),
|
||||
|
||||
path('nodes/cache/', api.RefreshNodesCacheApi.as_view(), name='refresh-nodes-cache'),
|
||||
|
||||
path('gateways/<uuid:pk>/test-connective/',
|
||||
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
path('gateways/<uuid:pk>/test-connective/', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||
|
||||
]
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ urlpatterns = [
|
|||
path('system-user/<uuid:pk>/update/', views.SystemUserUpdateView.as_view(), name='system-user-update'),
|
||||
path('system-user/<uuid:pk>/delete/', views.SystemUserDeleteView.as_view(), name='system-user-delete'),
|
||||
path('system-user/<uuid:pk>/asset/', views.SystemUserAssetView.as_view(), name='system-user-asset'),
|
||||
path('system-user/<uuid:pk>/user/', views.SystemUserUserView.as_view(), name='system-user-user'),
|
||||
|
||||
path('label/', views.LabelListView.as_view(), name='label-list'),
|
||||
path('label/create/', views.LabelCreateView.as_view(), name='label-create'),
|
||||
|
|
|
@ -5,43 +5,35 @@ from treelib.exceptions import NodeIDAbsentError
|
|||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
from common.utils import get_object_or_none, get_logger, timeit
|
||||
from .models import SystemUser, Asset
|
||||
from common.utils import get_logger, timeit, lazyproperty
|
||||
from .models import Asset, Node
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def get_system_user_by_name(name):
|
||||
system_user = get_object_or_none(SystemUser, name=name)
|
||||
return system_user
|
||||
|
||||
|
||||
def get_system_user_by_id(id):
|
||||
system_user = get_object_or_none(SystemUser, id=id)
|
||||
return system_user
|
||||
|
||||
|
||||
class TreeService(Tree):
|
||||
tag_sep = ' / '
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.nodes_assets_map = defaultdict(set)
|
||||
self.all_nodes_assets_map = {}
|
||||
self._invalid_assets = frozenset()
|
||||
@staticmethod
|
||||
@timeit
|
||||
def get_nodes_assets_map():
|
||||
nodes_assets_map = defaultdict(set)
|
||||
asset_node_list = Node.assets.through.objects.values_list(
|
||||
'asset', 'node__key'
|
||||
)
|
||||
for asset_id, key in asset_node_list:
|
||||
nodes_assets_map[key].add(asset_id)
|
||||
return nodes_assets_map
|
||||
|
||||
@classmethod
|
||||
@timeit
|
||||
def new(cls):
|
||||
from .models import Node
|
||||
from orgs.utils import tmp_to_root_org
|
||||
|
||||
with tmp_to_root_org():
|
||||
all_nodes = list(Node.objects.all().values("key", "value"))
|
||||
all_nodes.sort(key=lambda x: len(x["key"].split(":")))
|
||||
tree = cls()
|
||||
tree.create_node(tag='', identifier='')
|
||||
tree.create_node(tag='', identifier='', data={})
|
||||
for node in all_nodes:
|
||||
key = node["key"]
|
||||
value = node["value"]
|
||||
|
@ -53,20 +45,13 @@ class TreeService(Tree):
|
|||
tree.init_assets()
|
||||
return tree
|
||||
|
||||
@timeit
|
||||
def init_assets(self):
|
||||
from orgs.utils import tmp_to_root_org
|
||||
self.all_nodes_assets_map = {}
|
||||
self.nodes_assets_map = defaultdict(set)
|
||||
with tmp_to_root_org():
|
||||
queryset = Asset.objects.all().values_list('id', 'nodes__key')
|
||||
invalid_assets = Asset.objects.filter(is_active=False)\
|
||||
.values_list('id', flat=True)
|
||||
self._invalid_assets = frozenset(invalid_assets)
|
||||
for asset_id, key in queryset:
|
||||
if not key:
|
||||
continue
|
||||
self.nodes_assets_map[key].add(asset_id)
|
||||
node_assets_map = self.get_nodes_assets_map()
|
||||
for node in self.all_nodes_itr():
|
||||
key = node.identifier
|
||||
assets = node_assets_map.get(key, set())
|
||||
data = {"assets": assets, "all_assets": None}
|
||||
node.data = data
|
||||
|
||||
def safe_create_node(self, **kwargs):
|
||||
parent = kwargs.get("parent")
|
||||
|
@ -125,32 +110,43 @@ class TreeService(Tree):
|
|||
parent = self.copy_node(parent)
|
||||
return parent
|
||||
|
||||
@lazyproperty
|
||||
def invalid_assets(self):
|
||||
assets = Asset.objects.filter(is_active=False).values_list('id', flat=True)
|
||||
return assets
|
||||
|
||||
def set_assets(self, nid, assets):
|
||||
self.nodes_assets_map[nid] = set(assets)
|
||||
node = self.get_node(nid)
|
||||
if node.data is None:
|
||||
node.data = {}
|
||||
node.data["assets"] = assets
|
||||
|
||||
def assets(self, nid):
|
||||
assets = self.nodes_assets_map[nid]
|
||||
return assets
|
||||
node = self.get_node(nid)
|
||||
return node.data.get("assets", set())
|
||||
|
||||
def valid_assets(self, nid):
|
||||
return set(self.assets(nid)) - set(self._invalid_assets)
|
||||
return set(self.assets(nid)) - set(self.invalid_assets)
|
||||
|
||||
def all_assets(self, nid):
|
||||
assets = self.all_nodes_assets_map.get(nid)
|
||||
if assets:
|
||||
return assets
|
||||
assets = set(self.assets(nid))
|
||||
node = self.get_node(nid)
|
||||
if node.data is None:
|
||||
node.data = {}
|
||||
all_assets = node.data.get("all_assets")
|
||||
if all_assets is not None:
|
||||
return all_assets
|
||||
all_assets = set(self.assets(nid))
|
||||
try:
|
||||
children = self.children(nid)
|
||||
except NodeIDAbsentError:
|
||||
children = []
|
||||
for child in children:
|
||||
assets.update(self.all_assets(child.identifier))
|
||||
self.all_nodes_assets_map[nid] = assets
|
||||
return assets
|
||||
all_assets.update(self.all_assets(child.identifier))
|
||||
node.data["all_assets"] = all_assets
|
||||
return all_assets
|
||||
|
||||
def all_valid_assets(self, nid):
|
||||
return set(self.all_assets(nid)) - set(self._invalid_assets)
|
||||
return set(self.all_assets(nid)) - set(self.invalid_assets)
|
||||
|
||||
def assets_amount(self, nid):
|
||||
return len(self.all_assets(nid))
|
||||
|
@ -186,15 +182,12 @@ class TreeService(Tree):
|
|||
else:
|
||||
# logger.debug('Add node: {}'.format(node.identifier))
|
||||
self.add_node(node, parent)
|
||||
|
||||
#
|
||||
# def __getstate__(self):
|
||||
# self.mutex = None
|
||||
# self.all_nodes_assets_map = {}
|
||||
# self.nodes_assets_map = {}
|
||||
# return self.__dict__
|
||||
#
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__ = state
|
||||
if '_invalid_assets' not in state:
|
||||
self._invalid_assets = frozenset()
|
||||
# self.mutex = threading.Lock()
|
||||
# def __setstate__(self, state):
|
||||
# self.__dict__ = state
|
||||
|
|
|
@ -106,7 +106,7 @@ class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Admin user detail'),
|
||||
'action': _('Admin user assets'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -48,6 +48,9 @@ class PlatformUpdateView(generic.UpdateView):
|
|||
model = Platform
|
||||
template_name = 'assets/platform_create_update.html'
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
meta_form = PlatformMetaForm(initial=self.object.meta)
|
||||
|
|
|
@ -17,6 +17,7 @@ __all__ = [
|
|||
'SystemUserCreateView', 'SystemUserUpdateView',
|
||||
'SystemUserDetailView', 'SystemUserDeleteView',
|
||||
'SystemUserAssetView', 'SystemUserListView',
|
||||
'SystemUserUserView',
|
||||
]
|
||||
|
||||
|
||||
|
@ -100,7 +101,22 @@ class SystemUserAssetView(PermissionsMixin, DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('System user asset'),
|
||||
'action': _('System user assets'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserUserView(PermissionsMixin, DetailView):
|
||||
model = SystemUser
|
||||
template_name = 'assets/system_user_users.html'
|
||||
context_object_name = 'system_user'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('assets'),
|
||||
'action': _('System user users'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .backends import *
|
||||
from .callback import *
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django_cas_ng.backends import CASBackend as _CASBackend
|
||||
|
||||
|
||||
__all__ = ['CASBackend']
|
||||
|
||||
|
||||
class CASBackend(_CASBackend):
|
||||
def user_can_authenticate(self, user):
|
||||
return True
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def cas_callback(response):
|
||||
username = response['username']
|
||||
user, user_created = User.objects.get_or_create(username=username)
|
||||
profile, created = user.get_profile()
|
||||
|
||||
profile.role = response['attributes']['role']
|
||||
profile.birth_date = response['attributes']['birth_date']
|
||||
profile.save()
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
import django_cas_ng.views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', django_cas_ng.views.LoginView.as_view(), name='cas-login'),
|
||||
path('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'),
|
||||
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
|
||||
]
|
|
@ -29,26 +29,27 @@ class LDAPAuthorizationBackend(LDAPBackend):
|
|||
|
||||
def pre_check(self, username, password):
|
||||
if not settings.AUTH_LDAP:
|
||||
return False
|
||||
logger.info('Authentication LDAP backend')
|
||||
error = 'Not enabled auth ldap'
|
||||
return False, error
|
||||
if not username:
|
||||
logger.info('Authenticate failed: username is None')
|
||||
return False
|
||||
error = 'Username is None'
|
||||
return False, error
|
||||
if not password:
|
||||
logger.info('Authenticate failed: password is None')
|
||||
return False
|
||||
error = 'Password is None'
|
||||
return False, error
|
||||
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
|
||||
user_model = self.get_user_model()
|
||||
exist = user_model.objects.filter(username=username).exists()
|
||||
if not exist:
|
||||
msg = 'Authentication failed: user ({}) is not in the user list'
|
||||
logger.info(msg.format(username))
|
||||
return False
|
||||
return True
|
||||
error = 'user ({}) is not in the user list'.format(username)
|
||||
return False, error
|
||||
return True, ''
|
||||
|
||||
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||
match = self.pre_check(username, password)
|
||||
logger.info('Authentication LDAP backend')
|
||||
match, msg = self.pre_check(username, password)
|
||||
if not match:
|
||||
logger.info('Authenticate failed: {}'.format(msg))
|
||||
return None
|
||||
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||
user = self.authenticate_ldap_user(ldap_user, password)
|
||||
|
@ -130,5 +131,5 @@ class LDAPUser(_LDAPUser):
|
|||
setattr(self._user, field, value)
|
||||
|
||||
email = getattr(self._user, 'email', '')
|
||||
email = construct_user_email(email, self._user.username)
|
||||
email = construct_user_email(self._user.username, email)
|
||||
setattr(self._user, 'email', email)
|
||||
|
|
|
@ -11,6 +11,7 @@ from users.utils import (
|
|||
|
||||
reason_password_failed = 'password_failed'
|
||||
reason_mfa_failed = 'mfa_failed'
|
||||
reason_mfa_unset = 'mfa_unset'
|
||||
reason_user_not_exist = 'user_not_exist'
|
||||
reason_password_expired = 'password_expired'
|
||||
reason_user_invalid = 'user_invalid'
|
||||
|
@ -18,7 +19,8 @@ reason_user_inactive = 'user_inactive'
|
|||
|
||||
reason_choices = {
|
||||
reason_password_failed: _('Username/password check failed'),
|
||||
reason_mfa_failed: _('MFA authentication failed'),
|
||||
reason_mfa_failed: _('MFA failed'),
|
||||
reason_mfa_unset: _('MFA unset'),
|
||||
reason_user_not_exist: _("Username does not exist"),
|
||||
reason_password_expired: _("Password expired"),
|
||||
reason_user_invalid: _('Disabled or expired'),
|
||||
|
@ -46,6 +48,7 @@ block_login_msg = _(
|
|||
mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
|
||||
|
||||
mfa_required_msg = _("MFA required")
|
||||
mfa_unset_msg = _("MFA not set, please set it first")
|
||||
login_confirm_required_msg = _("Login confirm required")
|
||||
login_confirm_wait_msg = _("Wait login confirm ticket for accept")
|
||||
login_confirm_error_msg = _("Login confirm ticket was {}")
|
||||
|
@ -116,6 +119,16 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
|
|||
super().__init__(username=username, request=request)
|
||||
|
||||
|
||||
class MFAUnsetError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||
error = reason_mfa_unset
|
||||
msg = mfa_unset_msg
|
||||
|
||||
def __init__(self, user, request, url):
|
||||
super().__init__(username=user.username, request=request)
|
||||
self.user = user
|
||||
self.url = url
|
||||
|
||||
|
||||
class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
|
||||
error = 'block_login'
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.conf import settings
|
|||
from common.utils import get_object_or_none, get_request_ip, get_logger
|
||||
from users.models import User
|
||||
from users.utils import (
|
||||
is_block_login, clean_failed_count, increase_login_failed_count,
|
||||
is_block_login, clean_failed_count
|
||||
)
|
||||
from . import errors
|
||||
from .utils import check_user_valid
|
||||
|
@ -91,8 +91,9 @@ class AuthMixin:
|
|||
return
|
||||
if not user.mfa_enabled:
|
||||
return
|
||||
if not user.otp_secret_key and user.mfa_is_otp():
|
||||
return
|
||||
unset, url = user.mfa_enabled_but_not_set()
|
||||
if unset:
|
||||
raise errors.MFAUnsetError(user, self.request, url)
|
||||
raise errors.MFARequiredError()
|
||||
|
||||
def check_user_mfa(self, code):
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<label for="mfa" class="col-sm-2 control-label">{% trans 'MFA' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="mfa" class="form-control input-sm" name="mfa">
|
||||
<span id="mfa_error" class="help-block">{% trans "Need otp auth for view auth" %}</span>
|
||||
<span id="mfa_error" class="help-block">{% trans "Need MFA for view auth" %}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
|
|
|
@ -1,49 +1,12 @@
|
|||
{% extends '_base_only_msg_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jumpserver</title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
{% block content_title %}
|
||||
{% trans 'Login' %}
|
||||
{% endblock %}
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<h2 class="font-bold" style="text-align: center">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
<p>
|
||||
{% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Changes the world, starting with a little bit." %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{{ LOGO_URL }}" width="60" height="60">
|
||||
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'Login' %}</span>
|
||||
</div>
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
|
@ -101,16 +64,4 @@
|
|||
{% endif %}
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,88 +1,32 @@
|
|||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> {{ JMS_TITLE }} </title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
<p>
|
||||
{% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Changes the world, starting with a little bit." %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{% static 'img/logo.png' %}" width="60" height="60">
|
||||
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'MFA certification' %}</span>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
|
||||
<div class="form-group">
|
||||
<p style="margin:30px auto;" class="text-center"><strong style="color:#000000">{% trans 'The account protection has been opened, please complete the following operations according to the prompts' %}</strong></p>
|
||||
<div class="text-center">
|
||||
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
|
||||
</div>
|
||||
<p style="margin: 30px auto"> {% trans 'Open Authenticator and enter the 6-bit dynamic code' %}</p>
|
||||
</div>
|
||||
{% block title %}
|
||||
{% trans 'MFA' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
|
||||
{% csrf_token %}
|
||||
{% if 'otp_code' in form.errors %}
|
||||
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||
<select class="form-control">
|
||||
<option value="otp" selected>{% trans 'One-time password' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="" required="" autofocus="autofocus">
|
||||
<span class="help-block">
|
||||
{% trans 'Open Google Authenticator and enter the 6-bit dynamic code' %}
|
||||
</span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
|
||||
<a href="#">
|
||||
<div>
|
||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="m-t">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# coding:utf-8
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.urls import path, include
|
||||
|
||||
from .. import views
|
||||
|
@ -10,13 +8,14 @@ from .. import views
|
|||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
# openid
|
||||
path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')),
|
||||
|
||||
# login
|
||||
path('login/', views.UserLoginView.as_view(), name='login'),
|
||||
path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'),
|
||||
path('login/wait-confirm/', views.UserLoginWaitConfirmView.as_view(), name='login-wait-confirm'),
|
||||
path('login/guard/', views.UserLoginGuardView.as_view(), name='login-guard'),
|
||||
path('logout/', views.UserLogoutView.as_view(), name='logout'),
|
||||
|
||||
# openid
|
||||
path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')),
|
||||
path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')),
|
||||
]
|
||||
|
|
|
@ -20,7 +20,7 @@ from django.urls import reverse_lazy
|
|||
|
||||
from common.utils import get_request_ip, get_object_or_none
|
||||
from users.utils import (
|
||||
redirect_user_first_login_or_index, set_tmp_user_to_cache
|
||||
redirect_user_first_login_or_index
|
||||
)
|
||||
from .. import forms, mixins, errors
|
||||
|
||||
|
@ -52,17 +52,29 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
template_name = 'authentication/xpack_login.html'
|
||||
return template_name
|
||||
|
||||
def get_redirect_url_if_need(self, request):
|
||||
redirect_url = ''
|
||||
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
||||
if self.request.GET.get("admin", 0):
|
||||
return None
|
||||
if settings.AUTH_OPENID:
|
||||
redirect_url = reverse("authentication:openid:openid-login")
|
||||
elif settings.AUTH_CAS:
|
||||
redirect_url = reverse(settings.CAS_LOGIN_URL_NAME)
|
||||
|
||||
if redirect_url:
|
||||
query_string = request.GET.urlencode()
|
||||
redirect_url = "{}?{}".format(redirect_url, query_string)
|
||||
return redirect_url
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_staff:
|
||||
return redirect(redirect_user_first_login_or_index(
|
||||
request, self.redirect_field_name)
|
||||
)
|
||||
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
|
||||
if settings.AUTH_OPENID and not self.request.GET.get('admin', 0):
|
||||
query_string = request.GET.urlencode()
|
||||
openid_login_url = reverse_lazy("authentication:openid:openid-login")
|
||||
login_url = "{}?{}".format(openid_login_url, query_string)
|
||||
return redirect(login_url)
|
||||
redirect_url = self.get_redirect_url_if_need(request)
|
||||
if redirect_url:
|
||||
return redirect(redirect_url)
|
||||
request.session.set_test_cookie()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
@ -127,12 +139,9 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
|||
return self.format_redirect_url(self.login_otp_url)
|
||||
except errors.LoginConfirmBaseError:
|
||||
return self.format_redirect_url(self.login_confirm_url)
|
||||
except errors.MFAUnsetError as e:
|
||||
return e.url
|
||||
else:
|
||||
# 启用但是没有设置otp, 排除radius
|
||||
if user.mfa_enabled_but_not_set():
|
||||
# 1,2,mfa_setting & F
|
||||
set_tmp_user_to_cache(self.request, user)
|
||||
return reverse('users:user-otp-enable-authentication')
|
||||
auth_login(self.request, user)
|
||||
self.send_auth_signal(success=True, user=user)
|
||||
self.clear_auth_mark()
|
||||
|
@ -174,8 +183,17 @@ class UserLoginWaitConfirmView(TemplateView):
|
|||
class UserLogoutView(TemplateView):
|
||||
template_name = 'flash_message_standalone.html'
|
||||
|
||||
@staticmethod
|
||||
def get_backend_logout_url():
|
||||
# if settings.AUTH_CAS:
|
||||
# return settings.CAS_LOGOUT_URL_NAME
|
||||
return None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
auth_logout(request)
|
||||
backend_logout_url = self.get_backend_logout_url()
|
||||
if backend_logout_url:
|
||||
return redirect(backend_logout_url)
|
||||
next_uri = request.COOKIES.get("next")
|
||||
if next_uri:
|
||||
return redirect(next_uri)
|
||||
|
|
|
@ -16,7 +16,7 @@ provide this ability, not common, You should write it on your app utils.
|
|||
## Celery usage
|
||||
|
||||
|
||||
Jumpserver use celery to run task async. Using redis as the broker, so
|
||||
JumpServer use celery to run task async. Using redis as the broker, so
|
||||
you should run a redis instance
|
||||
|
||||
#### Run redis
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import time
|
||||
from hashlib import md5
|
||||
from threading import Thread
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.http import JsonResponse
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
|
||||
from common.drf.filters import IDSpmFilter, CustomFilter
|
||||
from ..utils import lazyproperty
|
||||
|
||||
__all__ = [
|
||||
"JSONResponseMixin", "CommonApiMixin",
|
||||
"IDSpmFilterMixin",
|
||||
"IDSpmFilterMixin", 'AsyncApiMixin',
|
||||
]
|
||||
|
||||
|
||||
|
@ -62,3 +69,122 @@ class ExtraFilterFieldsMixin:
|
|||
|
||||
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
|
||||
pass
|
||||
|
||||
|
||||
class InterceptMixin:
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
request = self.initialize_request(request, *args, **kwargs)
|
||||
self.request = request
|
||||
self.headers = self.default_response_headers # deprecate?
|
||||
|
||||
try:
|
||||
self.initial(request, *args, **kwargs)
|
||||
|
||||
# Get the appropriate handler method
|
||||
if request.method.lower() in self.http_method_names:
|
||||
handler = getattr(self, request.method.lower(),
|
||||
self.http_method_not_allowed)
|
||||
else:
|
||||
handler = self.http_method_not_allowed
|
||||
|
||||
response = self.do(handler, request, *args, **kwargs)
|
||||
|
||||
except Exception as exc:
|
||||
response = self.handle_exception(exc)
|
||||
|
||||
self.response = self.finalize_response(request, response, *args, **kwargs)
|
||||
return self.response
|
||||
|
||||
|
||||
class AsyncApiMixin(InterceptMixin):
|
||||
def get_request_user_id(self):
|
||||
user = self.request.user
|
||||
if hasattr(user, 'id'):
|
||||
return str(user.id)
|
||||
return ''
|
||||
|
||||
@lazyproperty
|
||||
def async_cache_key(self):
|
||||
method = self.request.method
|
||||
path = self.get_request_md5()
|
||||
user = self.get_request_user_id()
|
||||
key = '{}_{}_{}'.format(method, path, user)
|
||||
return key
|
||||
|
||||
def get_request_md5(self):
|
||||
path = self.request.path
|
||||
query = {k: v for k, v in self.request.GET.items()}
|
||||
query.pop("_", None)
|
||||
query.pop('refresh', None)
|
||||
query = "&".join(["{}={}".format(k, v) for k, v in query.items()])
|
||||
full_path = "{}?{}".format(path, query)
|
||||
return md5(full_path.encode()).hexdigest()
|
||||
|
||||
@lazyproperty
|
||||
def initial_data(self):
|
||||
data = {
|
||||
"status": "running",
|
||||
"start_time": time.time(),
|
||||
"key": self.async_cache_key,
|
||||
}
|
||||
return data
|
||||
|
||||
def get_cache_data(self):
|
||||
key = self.async_cache_key
|
||||
if self.is_need_refresh():
|
||||
cache.delete(key)
|
||||
return None
|
||||
data = cache.get(key)
|
||||
return data
|
||||
|
||||
def do(self, handler, *args, **kwargs):
|
||||
if not self.is_need_async():
|
||||
return handler(*args, **kwargs)
|
||||
resp = self.do_async(handler, *args, **kwargs)
|
||||
return resp
|
||||
|
||||
def is_need_refresh(self):
|
||||
if self.request.GET.get("refresh"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_need_async(self):
|
||||
return False
|
||||
|
||||
def do_async(self, handler, *args, **kwargs):
|
||||
data = self.get_cache_data()
|
||||
if not data:
|
||||
t = Thread(
|
||||
target=self.do_in_thread,
|
||||
args=(handler, *args),
|
||||
kwargs=kwargs
|
||||
)
|
||||
t.start()
|
||||
resp = Response(self.initial_data)
|
||||
return resp
|
||||
status = data.get("status")
|
||||
resp = data.get("resp")
|
||||
if status == "ok" and resp:
|
||||
resp = Response(**resp)
|
||||
else:
|
||||
resp = Response(data)
|
||||
return resp
|
||||
|
||||
def do_in_thread(self, handler, *args, **kwargs):
|
||||
key = self.async_cache_key
|
||||
data = self.initial_data
|
||||
cache.set(key, data, 600)
|
||||
try:
|
||||
response = handler(*args, **kwargs)
|
||||
data["status"] = "ok"
|
||||
data["resp"] = {
|
||||
"data": response.data,
|
||||
"status": response.status_code
|
||||
}
|
||||
cache.set(key, data, 600)
|
||||
except Exception as e:
|
||||
data["error"] = str(e)
|
||||
data["status"] = "error"
|
||||
cache.set(key, data, 600)
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import models
|
|||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = [
|
||||
"NoDeleteManager", "NoDeleteModelMixin", "NoDeleteQuerySet",
|
||||
"CommonModelMixin"
|
||||
|
@ -65,3 +64,5 @@ class DebugQueryManager(models.Manager):
|
|||
print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
|
||||
queryset = super().get_queryset()
|
||||
return queryset
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from itertools import chain
|
||||
from .utils import lazyproperty
|
||||
|
||||
|
||||
class Stack(list):
|
||||
|
@ -23,3 +25,82 @@ class Stack(list):
|
|||
|
||||
def push(self, item):
|
||||
self.append(item)
|
||||
|
||||
|
||||
class QuerySetChain:
|
||||
def __init__(self, querysets):
|
||||
self.querysets = querysets
|
||||
|
||||
@lazyproperty
|
||||
def querysets_counts(self):
|
||||
counts = [s.count() for s in self.querysets]
|
||||
return counts
|
||||
|
||||
def count(self):
|
||||
return self.total_count
|
||||
|
||||
@lazyproperty
|
||||
def total_count(self):
|
||||
return sum(self.querysets_counts)
|
||||
|
||||
def __iter__(self):
|
||||
self._chain = chain(*self.querysets)
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return next(self._chain)
|
||||
|
||||
def __getitem__(self, ndx):
|
||||
querysets_count_zip = zip(self.querysets, self.querysets_counts)
|
||||
length = 0 # 加上本数组后的大数组长度
|
||||
pre_length = 0 # 不包含本数组的大数组长度
|
||||
items = [] # 返回的值
|
||||
loop = 0
|
||||
|
||||
if isinstance(ndx, slice):
|
||||
ndx_start = ndx.start or 0
|
||||
ndx_stop = ndx.stop or self.total_count
|
||||
ndx_step = ndx.step or 1
|
||||
else:
|
||||
ndx_start = ndx
|
||||
ndx_stop, ndx_step = None, None
|
||||
|
||||
for queryset, count in querysets_count_zip:
|
||||
length += count
|
||||
loop += 1
|
||||
# 取当前数组的start角标, 存在3中情况
|
||||
# 1. start角标在当前数组
|
||||
if length > ndx_start >= pre_length:
|
||||
start = ndx_start - pre_length
|
||||
# print("[loop {}] Start is: {}".format(loop, start))
|
||||
if ndx_step is None:
|
||||
return queryset[start]
|
||||
# 2. 不包含当前数组,因为起始已经超过了当前数组的长度
|
||||
elif ndx_start >= length:
|
||||
pre_length += count
|
||||
continue
|
||||
# 3. 不在当前数组,但是应该从当前数组0开始计算
|
||||
else:
|
||||
start = 0
|
||||
|
||||
# 可能取单个值, ndx_stop 为None, 不应该再找
|
||||
if ndx_stop is None:
|
||||
pre_length += count
|
||||
continue
|
||||
|
||||
# 取当前数组的stop角标, 存在2中情况
|
||||
# 不存在第3中情况是因为找到了会提交结束循环
|
||||
# 1. 结束角标小于length代表 结束位在当前数组上
|
||||
if ndx_stop < length:
|
||||
stop = ndx_stop - pre_length
|
||||
# 2. 结束位置包含改数组到了最后
|
||||
else:
|
||||
stop = count
|
||||
# print("[loop {}] Slice: {} {} {}".format(loop, start, stop, ndx_step))
|
||||
items.extend(list(queryset[slice(start, stop, ndx_step)]))
|
||||
pre_length += count
|
||||
|
||||
# 如果结束再当前数组,则结束循环
|
||||
if ndx_stop < length:
|
||||
break
|
||||
return items
|
||||
|
|
|
@ -199,11 +199,15 @@ logger = get_logger(__name__)
|
|||
|
||||
def timeit(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
logger.debug("Start call: {}".format(func.__name__))
|
||||
if hasattr(func, '__name__'):
|
||||
name = func.__name__
|
||||
else:
|
||||
name = func
|
||||
logger.debug("Start call: {}".format(name))
|
||||
now = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
using = (time.time() - now) * 1000
|
||||
msg = "End call {}, using: {:.1f}ms".format(func.__name__, using)
|
||||
msg = "End call {}, using: {:.1f}ms".format(name, using)
|
||||
logger.debug(msg)
|
||||
return result
|
||||
return wrapper
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import socket
|
||||
import struct
|
||||
import random
|
||||
|
||||
|
||||
def random_datetime(date_start, date_end):
|
||||
random_delta = (date_end - date_start) * random.random()
|
||||
return date_start + random_delta
|
||||
|
||||
|
||||
def random_ip():
|
||||
return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
|
||||
|
||||
|
||||
|
||||
# def strTimeProp(start, end, prop, fmt):
|
||||
# time_start = time.mktime(time.strptime(start, fmt))
|
||||
# time_end = time.mktime(time.strptime(end, fmt))
|
||||
# ptime = time_start + prop * (time_end - time_start)
|
||||
# return int(ptime)
|
||||
#
|
||||
#
|
||||
# def randomTimestamp(start, end, fmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return strTimeProp(start, end, random.random(), fmt)
|
||||
#
|
||||
#
|
||||
# def randomDate(start, end, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return time.strftime(frmt, time.localtime(strTimeProp(start, end, random.random(), frmt)))
|
||||
#
|
||||
#
|
||||
# def randomTimestampList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return [randomTimestamp(start, end, frmt) for _ in range(n)]
|
||||
#
|
||||
#
|
||||
# def randomDateList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return [randomDate(start, end, frmt) for _ in range(n)]
|
||||
|
|
@ -84,11 +84,10 @@ class Config(dict):
|
|||
:param defaults: an optional dictionary of default values
|
||||
"""
|
||||
defaults = {
|
||||
# Django Config
|
||||
# Django Config, Must set before start
|
||||
'SECRET_KEY': '',
|
||||
'BOOTSTRAP_TOKEN': '',
|
||||
'DEBUG': True,
|
||||
'SITE_URL': 'http://localhost:8080',
|
||||
'LOG_LEVEL': 'DEBUG',
|
||||
'LOG_DIR': os.path.join(PROJECT_DIR, 'logs'),
|
||||
'DB_ENGINE': 'mysql',
|
||||
|
@ -100,10 +99,13 @@ class Config(dict):
|
|||
'REDIS_HOST': '127.0.0.1',
|
||||
'REDIS_PORT': 6379,
|
||||
'REDIS_PASSWORD': '',
|
||||
# Default value
|
||||
'REDIS_DB_CELERY': 3,
|
||||
'REDIS_DB_CACHE': 4,
|
||||
'REDIS_DB_SESSION': 5,
|
||||
'REDIS_DB_WS': 6,
|
||||
|
||||
'SITE_URL': 'http://localhost:8080',
|
||||
'CAPTCHA_TEST_MODE': None,
|
||||
'TOKEN_EXPIRATION': 3600 * 24,
|
||||
'DISPLAY_PER_PAGE': 25,
|
||||
|
@ -140,6 +142,7 @@ class Config(dict):
|
|||
'AUTH_OPENID_CLIENT_SECRET': '',
|
||||
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
|
||||
'AUTH_OPENID_SHARE_SESSION': True,
|
||||
'CAS_ROOT_PROXIED_AS': '',
|
||||
|
||||
'AUTH_RADIUS': False,
|
||||
'RADIUS_SERVER': 'localhost',
|
||||
|
@ -148,8 +151,13 @@ class Config(dict):
|
|||
'RADIUS_ENCRYPT_PASSWORD': True,
|
||||
'OTP_IN_RADIUS': False,
|
||||
|
||||
'AUTH_CAS': False,
|
||||
'CAS_SERVER_URL': "http://host/cas/",
|
||||
'CAS_LOGOUT_COMPLETELY': True,
|
||||
'CAS_VERSION': 3,
|
||||
|
||||
'OTP_VALID_WINDOW': 2,
|
||||
'OTP_ISSUER_NAME': 'Jumpserver',
|
||||
'OTP_ISSUER_NAME': 'JumpServer',
|
||||
'EMAIL_SUFFIX': 'jumpserver.org',
|
||||
|
||||
'TERMINAL_PASSWORD_AUTH': True,
|
||||
|
@ -179,6 +187,7 @@ class Config(dict):
|
|||
'HTTP_LISTEN_PORT': 8080,
|
||||
'WS_LISTEN_PORT': 8070,
|
||||
'LOGIN_LOG_KEEP_DAYS': 90,
|
||||
'TASK_LOG_KEEP_DAYS': 10,
|
||||
'ASSETS_PERM_CACHE_TIME': 3600 * 24,
|
||||
'SECURITY_MFA_VERIFY_TTL': 3600,
|
||||
'ASSETS_PERM_CACHE_ENABLE': False,
|
||||
|
@ -284,6 +293,8 @@ class DynamicConfig:
|
|||
]
|
||||
if self.get('AUTH_LDAP'):
|
||||
backends.insert(0, 'authentication.backends.ldap.LDAPAuthorizationBackend')
|
||||
if self.static_config.get('AUTH_CAS'):
|
||||
backends.insert(0, 'authentication.backends.cas.CASBackend')
|
||||
if self.static_config.get('AUTH_OPENID'):
|
||||
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
|
||||
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
|
||||
|
|
|
@ -7,6 +7,6 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG', 'DYNAMIC']
|
|||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
VERSION = '1.5.6'
|
||||
VERSION = '1.5.7'
|
||||
CONFIG = ConfigManager.load_user_config()
|
||||
DYNAMIC = ConfigManager.get_dynamic_config(CONFIG)
|
||||
|
|
|
@ -13,7 +13,7 @@ def jumpserver_processor(request):
|
|||
'LOGO_TEXT_URL': static('img/logo_text.png'),
|
||||
'LOGIN_IMAGE_URL': static('img/login_image.png'),
|
||||
'FAVICON_URL': static('img/facio.ico'),
|
||||
'JMS_TITLE': 'Jumpserver',
|
||||
'JMS_TITLE': 'JumpServer',
|
||||
'VERSION': settings.VERSION,
|
||||
'COPYRIGHT': 'FIT2CLOUD 飞致云' + ' © 2014-2020',
|
||||
'SECURITY_COMMAND_EXECUTION': settings.SECURITY_COMMAND_EXECUTION,
|
||||
|
|
|
@ -64,7 +64,22 @@ RADIUS_SERVER = CONFIG.RADIUS_SERVER
|
|||
RADIUS_PORT = CONFIG.RADIUS_PORT
|
||||
RADIUS_SECRET = CONFIG.RADIUS_SECRET
|
||||
|
||||
# CAS Auth
|
||||
AUTH_CAS = CONFIG.AUTH_CAS
|
||||
CAS_SERVER_URL = CONFIG.CAS_SERVER_URL
|
||||
CAS_VERIFY_SSL_CERTIFICATE = False
|
||||
CAS_LOGIN_URL_NAME = "authentication:cas:cas-login"
|
||||
CAS_LOGOUT_URL_NAME = "authentication:cas:cas-logout"
|
||||
CAS_LOGIN_MSG = None
|
||||
CAS_LOGGED_MSG = None
|
||||
CAS_LOGOUT_COMPLETELY = CONFIG.CAS_LOGOUT_COMPLETELY
|
||||
CAS_VERSION = CONFIG.CAS_VERSION
|
||||
CAS_ROOT_PROXIED_AS = CONFIG.CAS_ROOT_PROXIED_AS
|
||||
|
||||
|
||||
# Other setting
|
||||
TOKEN_EXPIRATION = CONFIG.TOKEN_EXPIRATION
|
||||
LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE
|
||||
OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
|
||||
|
||||
AUTHENTICATION_BACKENDS = DYNAMIC.AUTHENTICATION_BACKENDS
|
||||
|
|
|
@ -51,6 +51,7 @@ INSTALLED_APPS = [
|
|||
'rest_framework',
|
||||
'rest_framework_swagger',
|
||||
'drf_yasg',
|
||||
'django_cas_ng',
|
||||
'channels',
|
||||
'django_filters',
|
||||
'bootstrap3',
|
||||
|
@ -75,6 +76,7 @@ MIDDLEWARE = [
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'authentication.backends.openid.middleware.OpenIDAuthenticationMiddleware',
|
||||
'django_cas_ng.middleware.CASMiddleware',
|
||||
'jumpserver.middleware.TimezoneMiddleware',
|
||||
'jumpserver.middleware.DemoMiddleware',
|
||||
'jumpserver.middleware.RequestMiddleware',
|
||||
|
@ -220,8 +222,6 @@ EMAIL_USE_SSL = DYNAMIC.EMAIL_USE_SSL
|
|||
EMAIL_USE_TLS = DYNAMIC.EMAIL_USE_TLS
|
||||
|
||||
|
||||
AUTHENTICATION_BACKENDS = DYNAMIC.AUTHENTICATION_BACKENDS
|
||||
|
||||
# Custom User Auth model
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
|
|
|
@ -82,5 +82,6 @@ USER_GUIDE_URL = DYNAMIC.USER_GUIDE_URL
|
|||
HTTP_LISTEN_PORT = CONFIG.HTTP_LISTEN_PORT
|
||||
WS_LISTEN_PORT = CONFIG.WS_LISTEN_PORT
|
||||
LOGIN_LOG_KEEP_DAYS = DYNAMIC.LOGIN_LOG_KEEP_DAYS
|
||||
TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS
|
||||
ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL
|
||||
WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
|
||||
|
|
|
@ -53,6 +53,7 @@ SWAGGER_SETTINGS = {
|
|||
'in': 'header'
|
||||
}
|
||||
},
|
||||
'DEFAULT_INFO': 'jumpserver.views.swagger.api_info',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -96,12 +96,16 @@ LOGGING = {
|
|||
'handlers': ['syslog'],
|
||||
'level': 'INFO'
|
||||
},
|
||||
# 'django.db': {
|
||||
# 'handlers': ['console', 'file'],
|
||||
# 'level': 'DEBUG'
|
||||
# }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if os.environ.get("DEBUG_DB"):
|
||||
LOGGING['loggers']['django.db'] = {
|
||||
'handlers': ['console', 'file'],
|
||||
'level': 'DEBUG'
|
||||
}
|
||||
|
||||
SYSLOG_ENABLE = CONFIG.SYSLOG_ENABLE
|
||||
|
||||
if CONFIG.SYSLOG_ADDR != '' and len(CONFIG.SYSLOG_ADDR.split(':')) == 2:
|
||||
|
|
|
@ -1,30 +1,214 @@
|
|||
import datetime
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.views.generic import TemplateView
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, Max
|
||||
from django.shortcuts import redirect
|
||||
|
||||
|
||||
from users.models import User
|
||||
from assets.models import Asset
|
||||
from terminal.models import Session
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
from common.utils import timeit, lazyproperty
|
||||
|
||||
__all__ = ['IndexView']
|
||||
|
||||
|
||||
class IndexView(PermissionsMixin, TemplateView):
|
||||
class MonthLoginMetricMixin:
|
||||
@lazyproperty
|
||||
def session_month(self):
|
||||
month_ago = timezone.now() - timezone.timedelta(days=30)
|
||||
session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
return session_month
|
||||
|
||||
@lazyproperty
|
||||
def session_month_dates(self):
|
||||
return self.session_month.dates('date_start', 'day')
|
||||
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [
|
||||
d.strftime('%m-%d') for d in self.session_month_dates
|
||||
] or ['0']
|
||||
return month_str
|
||||
|
||||
@staticmethod
|
||||
def get_cache_key(date, tp):
|
||||
date_str = date.strftime("%Y%m%d")
|
||||
key = "SESSION_MONTH_{}_{}".format(tp, date_str)
|
||||
return key
|
||||
|
||||
def __get_data_from_cache(self, date, tp):
|
||||
if date == timezone.now().date():
|
||||
return None
|
||||
cache_key = self.get_cache_key(date, tp)
|
||||
count = cache.get(cache_key)
|
||||
return count
|
||||
|
||||
def __set_data_to_cache(self, date, tp, count):
|
||||
cache_key = self.get_cache_key(date, tp)
|
||||
cache.set(cache_key, count, 3600*24*7)
|
||||
|
||||
@lazyproperty
|
||||
def user_disabled_total(self):
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
|
||||
@lazyproperty
|
||||
def asset_disabled_total(self):
|
||||
return Asset.objects.filter(is_active=False).count()
|
||||
|
||||
def get_date_login_count(self, date):
|
||||
tp = "LOGIN"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date).count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_login_metrics(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
count = self.get_date_login_count(d)
|
||||
data.append(count)
|
||||
if len(data) == 0:
|
||||
data = [0]
|
||||
return data
|
||||
|
||||
def get_date_user_count(self, date):
|
||||
tp = "USER"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date)\
|
||||
.values('user').distinct().count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_active_user_metrics(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
count = self.get_date_user_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
||||
def get_date_asset_count(self, date):
|
||||
tp = "ASSET"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date) \
|
||||
.values('asset').distinct().count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_active_asset_metrics(self):
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
count = self.get_date_asset_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
||||
@lazyproperty
|
||||
def month_active_user_total(self):
|
||||
count = self.session_month.values('user').distinct().count()
|
||||
return count
|
||||
|
||||
@lazyproperty
|
||||
def month_inactive_user_total(self):
|
||||
total = current_org.get_org_members().count()
|
||||
active = self.month_active_user_total
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
@lazyproperty
|
||||
def month_active_asset_total(self):
|
||||
return self.session_month.values('asset').distinct().count()
|
||||
|
||||
@lazyproperty
|
||||
def month_inactive_asset_total(self):
|
||||
total = Asset.objects.all().count()
|
||||
active = self.month_active_asset_total
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'month_str': self.get_month_day_metrics(),
|
||||
'month_total_visit_count': self.get_month_login_metrics(),
|
||||
'month_user': self.get_month_active_user_metrics(),
|
||||
'mouth_asset': self.get_month_active_asset_metrics(),
|
||||
'month_user_active': self.month_active_user_total,
|
||||
'month_user_inactive': self.month_inactive_user_total,
|
||||
'month_user_disabled': self.user_disabled_total,
|
||||
'month_asset_active': self.month_active_asset_total,
|
||||
'month_asset_inactive': self.month_inactive_asset_total,
|
||||
'month_asset_disabled': self.asset_disabled_total,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class WeekSessionMetricMixin:
|
||||
session_week = None
|
||||
|
||||
@lazyproperty
|
||||
def session_week(self):
|
||||
week_ago = timezone.now() - timezone.timedelta(weeks=1)
|
||||
session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
return session_week
|
||||
|
||||
def get_top5_user_a_week(self):
|
||||
users = self.session_week.values('user') \
|
||||
.annotate(total=Count('user')) \
|
||||
.order_by('-total')[:5]
|
||||
return users
|
||||
|
||||
def get_week_login_user_count(self):
|
||||
return self.session_week.values('user').distinct().count()
|
||||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.count()
|
||||
|
||||
def get_week_top10_assets(self):
|
||||
assets = self.session_week.values("asset")\
|
||||
.annotate(total=Count("asset"))\
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
return assets
|
||||
|
||||
def get_week_top10_users(self):
|
||||
users = self.session_week.values("user") \
|
||||
.annotate(total=Count("user")) \
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
return users
|
||||
|
||||
def get_last10_sessions(self):
|
||||
sessions = self.session_week.order_by('-date_start')[:10]
|
||||
for session in sessions:
|
||||
session.avatar_url = User.get_avatar_url("")
|
||||
return sessions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'user_visit_count_weekly': self.get_week_login_user_count(),
|
||||
'asset_visit_count_weekly': self.get_week_login_asset_count(),
|
||||
'user_visit_count_top_five': self.get_top5_user_a_week(),
|
||||
'last_login_ten': self.get_last10_sessions(),
|
||||
'week_asset_hot_ten': self.get_week_top10_assets(),
|
||||
'week_user_hot_ten': self.get_week_top10_users(),
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
session_week = None
|
||||
session_month = None
|
||||
session_month_dates = []
|
||||
session_month_dates_archive = []
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
@ -42,141 +226,21 @@ class IndexView(PermissionsMixin, TemplateView):
|
|||
|
||||
@staticmethod
|
||||
def get_online_user_count():
|
||||
return len(set(Session.objects.filter(is_finished=False).values_list('user', flat=True)))
|
||||
count = Session.objects.filter(is_finished=False)\
|
||||
.values_list('user', flat=True).distinct().count()
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def get_online_session_count():
|
||||
return Session.objects.filter(is_finished=False).count()
|
||||
|
||||
def get_top5_user_a_week(self):
|
||||
return self.session_week.values('user').annotate(total=Count('user')).order_by('-total')[:5]
|
||||
|
||||
def get_week_login_user_count(self):
|
||||
return self.session_week.values('user').distinct().count()
|
||||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.count()
|
||||
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||
return month_str
|
||||
|
||||
def get_month_login_metrics(self):
|
||||
data = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(tzinfo=timezone.get_current_timezone())
|
||||
data.append(self.session_month.filter(date_start__range=(ds, de)).count())
|
||||
return data
|
||||
|
||||
def get_month_active_user_metrics(self):
|
||||
if self.session_month_dates_archive:
|
||||
return [q.values('user').distinct().count()
|
||||
for q in self.session_month_dates_archive]
|
||||
else:
|
||||
return [0]
|
||||
|
||||
def get_month_active_asset_metrics(self):
|
||||
if self.session_month_dates_archive:
|
||||
return [q.values('asset').distinct().count()
|
||||
for q in self.session_month_dates_archive]
|
||||
else:
|
||||
return [0]
|
||||
|
||||
def get_month_active_user_total(self):
|
||||
return self.session_month.values('user').distinct().count()
|
||||
|
||||
def get_month_inactive_user_total(self):
|
||||
count = current_org.get_org_members().count() - self.get_month_active_user_total()
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
def get_month_active_asset_total(self):
|
||||
return self.session_month.values('asset').distinct().count()
|
||||
|
||||
def get_month_inactive_asset_total(self):
|
||||
count = Asset.objects.all().count() - self.get_month_active_asset_total()
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def get_user_disabled_total():
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
|
||||
@staticmethod
|
||||
def get_asset_disabled_total():
|
||||
return Asset.objects.filter(is_active=False).count()
|
||||
|
||||
def get_week_top10_asset(self):
|
||||
assets = list(self.session_week.values('asset').annotate(total=Count('asset')).order_by('-total')[:10])
|
||||
for asset in assets:
|
||||
last_login = self.session_week.filter(asset=asset["asset"]).order_by('date_start').last()
|
||||
asset['last'] = last_login
|
||||
return assets
|
||||
|
||||
def get_week_top10_user(self):
|
||||
users = list(self.session_week.values('user').annotate(
|
||||
total=Count('asset')).order_by('-total')[:10])
|
||||
for user in users:
|
||||
last_login = self.session_week.filter(user=user["user"]).order_by('date_start').last()
|
||||
user['last'] = last_login
|
||||
return users
|
||||
|
||||
def get_last10_sessions(self):
|
||||
sessions = self.session_week.order_by('-date_start')[:10]
|
||||
for session in sessions:
|
||||
try:
|
||||
session.avatar_url = User.objects.get(username=session.user).avatar_url()
|
||||
except User.DoesNotExist:
|
||||
session.avatar_url = User.objects.first().avatar_url()
|
||||
return sessions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
week_ago = timezone.now() - timezone.timedelta(weeks=1)
|
||||
month_ago = timezone.now() - timezone.timedelta(days=30)
|
||||
self.session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
self.session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
self.session_month_dates = self.session_month.dates('date_start', 'day')
|
||||
|
||||
self.session_month_dates_archive = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
self.session_month_dates_archive.append(
|
||||
self.session_month.filter(date_start__range=(ds, de)))
|
||||
|
||||
context = {
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'assets_count': self.get_asset_count(),
|
||||
'users_count': self.get_user_count(),
|
||||
'online_user_count': self.get_online_user_count(),
|
||||
'online_asset_count': self.get_online_session_count(),
|
||||
'user_visit_count_weekly': self.get_week_login_user_count(),
|
||||
'asset_visit_count_weekly': self.get_week_login_asset_count(),
|
||||
'user_visit_count_top_five': self.get_top5_user_a_week(),
|
||||
'month_str': self.get_month_day_metrics(),
|
||||
'month_total_visit_count': self.get_month_login_metrics(),
|
||||
'month_user': self.get_month_active_user_metrics(),
|
||||
'mouth_asset': self.get_month_active_asset_metrics(),
|
||||
'month_user_active': self.get_month_active_user_total(),
|
||||
'month_user_inactive': self.get_month_inactive_user_total(),
|
||||
'month_user_disabled': self.get_user_disabled_total(),
|
||||
'month_asset_active': self.get_month_active_asset_total(),
|
||||
'month_asset_inactive': self.get_month_inactive_asset_total(),
|
||||
'month_asset_disabled': self.get_asset_disabled_total(),
|
||||
'week_asset_hot_ten': self.get_week_top10_asset(),
|
||||
'last_login_ten': self.get_last10_sessions(),
|
||||
'week_user_hot_ten': self.get_week_top10_user(),
|
||||
'app': _("Dashboard"),
|
||||
}
|
||||
|
||||
kwargs.update(context)
|
||||
return super(IndexView, self).get_context_data(**kwargs)
|
||||
})
|
||||
return context
|
||||
|
|
|
@ -49,6 +49,16 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
|||
return fields
|
||||
|
||||
|
||||
api_info = openapi.Info(
|
||||
title="JumpServer API Docs",
|
||||
default_version='v1',
|
||||
description="JumpServer Restful api docs",
|
||||
terms_of_service="https://www.jumpserver.org",
|
||||
contact=openapi.Contact(email="support@fit2cloud.com"),
|
||||
license=openapi.License(name="GPLv2 License"),
|
||||
)
|
||||
|
||||
|
||||
def get_swagger_view(version='v1'):
|
||||
from ..urls import api_v1, api_v2
|
||||
from django.urls import path, include
|
||||
|
@ -65,14 +75,7 @@ def get_swagger_view(version='v1'):
|
|||
else:
|
||||
patterns = api_v1_patterns
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Jumpserver API Docs",
|
||||
default_version=version,
|
||||
description="Jumpserver Restful api docs",
|
||||
terms_of_service="https://www.jumpserver.org",
|
||||
contact=openapi.Contact(email="support@fit2cloud.com"),
|
||||
license=openapi.License(name="GPLv2 License"),
|
||||
),
|
||||
api_info,
|
||||
public=True,
|
||||
patterns=patterns,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue