mirror of https://github.com/jumpserver/jumpserver
Dev (#2838)
* Dev ansible windows 2 (#2783) * [Update] 改密支持windows * [Update] 修改asset表结构 * [Feature] Windows支持批量改密、测试可连接性等功能 * [Update] 处理创建资产时labels的问题 * [Update] 优化测试管理系统、系统用户可连接性任务执行逻辑 * [Update] 优化ansible任务逻辑;添加自动推送rdp系统用户功能 * [Update] 添加翻译 * [Update] 优化ansible任务逻辑(测试系统用户可连接性, 通过协议过滤资产) * [Update] 更新翻译 * [Update] 更新翻译 * [Update] 推送windows系统用户,默认添加到Users、Remote Desktop Users组中 * [Update] 优化小细节 * [Update] 更新翻译,删除多余代码 * [Update] 更新翻译信息 * [Bugfix] 修复windows推送系统用户小bug (#2794) * [Update] 邮件设置添加配置项:发送账号 (#2796) * [Bugfix] 和资产相关的Serializer添加protocols字段; (#2800) * [Bugfix] 和资产相关的Serializer添加protocols字段; * [Bugfix] RemoteApp Form 修改过滤RDP协议资产 * [Bugfix] 修改小问题 * [Update] 用户授权相关API,如果需要切换到root org (#2803) * [Update] 用户授权相关API,如果需要切换到root org * [Update] 优化小问题 * [Update] 增加审计员权限控制 (#2792) * [Update] 审计员 * [Update] 增加审计员的权限控制 * [Update] 增加审计员Api全校的控制 * [Update] 优化auditor的api权限控制 * [Update] 优化审计员权限控制 * [Update]优化管理员权限的View * [Update] 优化超级管理权限的View * [Update] 添加审计员切换组织查询会话管理数据 * [Update] 前端禁用审计员在线会话终断按钮 * [Update]优化细节问题 * [Update] Auth Info (#2806) * [Update] 修改支持auth info导出 * [Update] 统一认证查看 * [Update] 修改auth book manager * [Update] 修改auth info * [Update] 完成修改auth info * [Update] 优化api * [Update] 修改assets 的related * [Update] serializer mixin继承 (#2810) * [Update] serializer mixin继承 * [Update] 修改system user更新serialzier * [Update] 修改success message * [Update] 添加一键禁用LDAP认证脚本 (#2813) * [Update] 修改资产创建格式 * [Update] 兼容之前的protocols格式 * [Update] Merge master_bugfix to dev_bugfix (#2817) * [Update] 邮件设置添加配置项:发送账号 (#2795) * [Bugfix] 修复普通用户被授权的RemoteApp列表加载为空的bug * [Bugfix] 修复普通用户加载被授权的RemoteApp为空的bug * [Update] 修改邮件测试的接受者为发送者 * [Update] 修改小问题 * [Update] 修改资产授权序列类返回资产protocols的协议格式/, 同时添加protocol和port字段 * [Update] 修改文案 (#2823) * [Update] 修改文案 * [Update] 修改文案2 * [Bugfix] 修复资产没有管理用户时获取connectivity字段失败的bug * [Update] 优化测试可连接性时结果获取 (#2825) * [Update] 修改资产使用patch方法更新时页面不提示messages信息 * [Update] 添加迁移文件,修改设置资产可连接性时管理用户为None的bug * [Update] 修改org.middleware自动切换组织的bug (#2829) * [Update] 修改org.middleware自动切换组织的bug * [Update] 将切换组织逻辑移动到PermsUtil中 * [Update] 修改首页组织名称显示来源pull/2847/head^2
parent
844f9bf409
commit
9ca4a8c941
|
@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
|
|||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from assets.models import Asset, SystemUser
|
||||
from assets.models import SystemUser, Protocol
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import const
|
||||
|
@ -89,7 +89,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
field_asset = self.fields['asset']
|
||||
field_asset.queryset = field_asset.queryset.filter(
|
||||
protocol=Asset.PROTOCOL_RDP
|
||||
protocols__name=Protocol.PROTOCOL_RDP
|
||||
)
|
||||
field_system_user = self.fields['system_user']
|
||||
field_system_user.queryset = field_system_user.queryset.filter(
|
||||
|
|
|
@ -11,6 +11,5 @@
|
|||
"""
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from .. import const
|
||||
from ..models import RemoteApp
|
||||
|
@ -66,7 +66,7 @@ class RemoteAppParamsDictField(serializers.DictField):
|
|||
return value
|
||||
|
||||
|
||||
class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
||||
params = RemoteAppParamsDictField()
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -6,11 +6,10 @@ from django.views.generic import TemplateView
|
|||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
|
||||
from ..models import RemoteApp
|
||||
|
@ -23,27 +22,29 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class RemoteAppListView(AdminUserRequiredMixin, TemplateView):
|
||||
class RemoteAppListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'applications/remote_app_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'app': _('Applications'),
|
||||
'action': _('RemoteApp list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'app': _('Applications'),
|
||||
'action': _('Create RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -53,18 +54,19 @@ class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
|
|||
return create_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_initial(self):
|
||||
return {k: v for k, v in self.object.params.items()}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'app': _('Applications'),
|
||||
'action': _('Update RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
@ -74,22 +76,24 @@ class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
|
|||
return update_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class RemoteAppDetailView(PermissionsMixin, DetailView):
|
||||
template_name = 'applications/remote_app_detail.html'
|
||||
model = RemoteApp
|
||||
context_object_name = 'remote_app'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'app': _('Applications'),
|
||||
'action': _('RemoteApp detail'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserRemoteAppListView(LoginRequiredMixin, TemplateView):
|
||||
class UserRemoteAppListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'applications/user_remote_app_list.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
|
|
@ -16,7 +16,7 @@ from django.urls import reverse_lazy
|
|||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
|
@ -36,17 +36,18 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
class AssetViewSet(IDInCacheFilterMixin, LabelFilter, ApiMessageMixin, BulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
success_message = _("%(hostname)s was %(action)s successfully")
|
||||
|
||||
def set_assets_node(self, assets):
|
||||
if not isinstance(assets, list):
|
||||
|
@ -169,8 +170,8 @@ class AssetGatewayApi(generics.RetrieveAPIView):
|
|||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
if asset.domain and \
|
||||
asset.domain.gateways.filter(protocol=asset.protocol).exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
|
||||
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:
|
||||
|
|
|
@ -1,58 +1,121 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import time
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status, generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework import filters
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..models import Asset
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from ..backends import AssetUserManager
|
||||
from ..models import Asset, Node, SystemUser, AdminUser
|
||||
from .. import serializers
|
||||
from ..tasks import test_asset_users_connectivity_manual
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
|
||||
'AssetUserExportViewSet',
|
||||
]
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AssetUserViewSet(viewsets.GenericViewSet):
|
||||
class AssetUserFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
kwargs = {}
|
||||
for field in view.filter_fields:
|
||||
value = request.GET.get(field)
|
||||
if not value:
|
||||
continue
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
kwargs[field] = value
|
||||
return queryset.filter(**kwargs)
|
||||
|
||||
|
||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
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
|
||||
|
||||
|
||||
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
pagination_class = LimitOffsetPagination
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser, )
|
||||
http_method_names = ['get', 'post']
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username", "asset_id", "node_id",
|
||||
"system_user_id", "admin_user_id"
|
||||
]
|
||||
search_fields = filter_fields
|
||||
filter_backends = (
|
||||
filters.OrderingFilter,
|
||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
# 尽可能先返回更少的数据
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
queryset = AssetUserManager.filter(username=username, asset=asset)
|
||||
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")
|
||||
|
||||
kwargs = {}
|
||||
assets = []
|
||||
|
||||
manager = AssetUserManager()
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
assets = system_user.assets.all()
|
||||
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')
|
||||
|
||||
if asset_id:
|
||||
asset = get_object_or_none(Asset, pk=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:
|
||||
kwargs['assets'] = assets
|
||||
|
||||
queryset = manager.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = sorted(
|
||||
queryset,
|
||||
key=lambda q: (q.asset.hostname, q.connectivity, q.username)
|
||||
)
|
||||
return queryset
|
||||
|
||||
class AssetUserExportViewSet(AssetUserViewSet):
|
||||
serializer_class = serializers.AssetUserExportSerializer
|
||||
http_method_names = ['get']
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
|
||||
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
|
||||
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
|
@ -60,6 +123,10 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
|||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
|
||||
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
|
||||
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
|
||||
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
status_code = status.HTTP_200_OK
|
||||
|
@ -70,9 +137,13 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
|||
def get_object(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)
|
||||
try:
|
||||
instance = AssetUserManager.get(username, asset)
|
||||
manger = AssetUserManager()
|
||||
if prefer:
|
||||
manger.prefer(prefer)
|
||||
instance = manger.get(username, asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
@ -84,18 +155,36 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
|||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
asset_users = AssetUserManager.filter(username=username, asset=asset)
|
||||
manager = AssetUserManager()
|
||||
asset_users = manager.filter(username=username, assets=[asset])
|
||||
return asset_users
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_users = self.get_asset_users()
|
||||
task = test_asset_users_connectivity_manual.delay(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})
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from .manager import AssetUserManager
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AdminUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AdminUserBackend(AssetUserBackend):
|
||||
model = AdminUser
|
||||
backend = 'AdminUser'
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
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()
|
||||
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 = []
|
||||
for asset_user in asset_users:
|
||||
if not assets:
|
||||
assets = asset_user.assets.all()
|
||||
for asset in assets:
|
||||
instance = asset_user.construct_to_authbook(asset)
|
||||
instance.backend = cls.backend
|
||||
instances.append(instance)
|
||||
return instances
|
|
@ -1,60 +1,84 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
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 {}!'
|
||||
|
||||
@classmethod
|
||||
def get(cls, username, asset):
|
||||
instances = cls.filter(username, asset)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
cls.raise_does_not_exist(cls.__name__)
|
||||
else:
|
||||
cls.raise_multiple_return(cls.__name__, len(instances))
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param asset: <Asset>对象
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def create(cls, **kwargs):
|
||||
"""
|
||||
:param kwargs:
|
||||
{
|
||||
name, username, asset, comment, password, public_key, private_key,
|
||||
(org_id)
|
||||
}
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def raise_does_not_exist(cls, name):
|
||||
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name))
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def raise_multiple_return(cls, name, length):
|
||||
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length))
|
||||
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:
|
||||
in_kwargs[k] = v
|
||||
for k in in_kwargs:
|
||||
kwargs.pop(k)
|
||||
|
||||
if len(in_kwargs) == 0:
|
||||
return self
|
||||
for i in self:
|
||||
matched = True
|
||||
for k, v in in_kwargs.items():
|
||||
key = k.split('__')[0]
|
||||
attr = getattr(i, key, 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 not in v:
|
||||
matched = False
|
||||
if matched:
|
||||
queryset.append(i)
|
||||
return AssetUserQuerySet(queryset)
|
||||
|
||||
def filter_equal(self, kwargs):
|
||||
def filter_it(obj):
|
||||
wanted = []
|
||||
real = []
|
||||
for k, v in kwargs.items():
|
||||
wanted.append(v)
|
||||
value = getattr(obj, k)
|
||||
if isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
real.append(value)
|
||||
return wanted == real
|
||||
if len(kwargs) > 0:
|
||||
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
|
||||
else:
|
||||
queryset = self
|
||||
return queryset
|
||||
|
||||
def filter(self, **kwargs):
|
||||
queryset = self.filter_in(kwargs).filter_equal(kwargs)
|
||||
return queryset
|
||||
|
||||
def __or__(self, other):
|
||||
self.extend(other)
|
||||
return self
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
from ..base import BaseBackend
|
||||
from ..models import AuthBook
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
def filter(cls, username=None, assets=None, latest=True):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
if assets:
|
||||
queryset = queryset.filter(asset__in=assets)
|
||||
if latest:
|
||||
queryset = queryset.latest_version()
|
||||
return queryset
|
|
@ -1,2 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..base import BaseBackend
|
||||
|
||||
|
||||
class VaultBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def get(cls, username, asset):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
pass
|
|
@ -1,4 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class AdminUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets(cls, asset):
|
||||
if not asset:
|
||||
assets = Asset.objects.all().prefetch_related('admin_user')
|
||||
else:
|
||||
assets = [asset]
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
instances = []
|
||||
assets = cls._get_assets(asset)
|
||||
for asset in assets:
|
||||
if username is not None and asset.admin_user.username != username:
|
||||
continue
|
||||
instance = construct_authbook_object(asset.admin_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
|
@ -1,32 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
from .system_user import SystemUserBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
admin_user_instances = AdminUserBackend.filter(username, asset)
|
||||
system_user_instances = SystemUserBackend.filter(username, asset)
|
||||
instances = cls._merge_instances(admin_user_instances, system_user_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, admin_user_instances, system_user_instances):
|
||||
admin_user_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in admin_user_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in system_user_instances
|
||||
if instance.keyword not in admin_user_instances_keyword_list
|
||||
]
|
||||
admin_user_instances.extend(instances)
|
||||
return admin_user_instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise cls.NotSupportError("Not support create")
|
|
@ -1,75 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import Asset
|
||||
|
||||
from ..base import BaseBackend
|
||||
from .utils import construct_authbook_object
|
||||
|
||||
|
||||
class SystemUserBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
instances = cls.construct_authbook_objects(username, asset)
|
||||
return instances
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def _filter_system_users_by_username(cls, system_users, username):
|
||||
_system_users = cls._distinct_system_users_by_username(system_users)
|
||||
if username is not None:
|
||||
_system_users = [su for su in _system_users if username == su.username]
|
||||
return _system_users
|
||||
|
||||
@classmethod
|
||||
def _construct_authbook_objects(cls, system_users, asset):
|
||||
instances = []
|
||||
for system_user in system_users:
|
||||
instance = construct_authbook_object(system_user, asset)
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def _get_assets_with_system_users(cls, asset=None):
|
||||
"""
|
||||
{ 'asset': set(<SystemUser>, <SystemUser>, ...) }
|
||||
"""
|
||||
if not asset:
|
||||
_assets = Asset.objects.all().prefetch_related('systemuser_set')
|
||||
else:
|
||||
_assets = [asset]
|
||||
|
||||
assets = {asset: set(asset.systemuser_set.all()) for asset in _assets}
|
||||
return assets
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, username, asset):
|
||||
"""
|
||||
:return: [<AuthBook>, <AuthBook>, ...]
|
||||
"""
|
||||
instances = []
|
||||
assets = cls._get_assets_with_system_users(asset)
|
||||
for _asset, _system_users in assets.items():
|
||||
_system_users = cls._filter_system_users_by_username(_system_users, username)
|
||||
_instances = cls._construct_authbook_objects(_system_users, _asset)
|
||||
instances.extend(_instances)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
raise Exception("Not support create")
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from assets.models import AuthBook
|
||||
|
||||
|
||||
def construct_authbook_object(asset_user, asset):
|
||||
"""
|
||||
作用: 将<AssetUser>对象构造成为<AuthBook>对象并返回
|
||||
|
||||
:param asset_user: <AdminUser>或<SystemUser>对象
|
||||
:param asset: <Asset>对象
|
||||
:return: <AuthBook>对象
|
||||
"""
|
||||
fields = [
|
||||
'id', 'name', 'username', 'comment', 'org_id',
|
||||
'_password', '_private_key', '_public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
|
||||
obj = AuthBook(asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(asset_user, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
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
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
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),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
)
|
||||
|
||||
_prefer = "system_user"
|
||||
_using = None
|
||||
|
||||
def filter(self, username=None, assets=None, latest=True):
|
||||
if self._using:
|
||||
backend = dict(self.backends).get(self._using)
|
||||
if not backend:
|
||||
return self.none()
|
||||
instances = backend.filter(username=username, assets=assets, latest=latest)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
instances_map = {}
|
||||
instances = []
|
||||
for name, backend in self.backends:
|
||||
_instances = backend.filter(
|
||||
username=username, assets=assets, latest=latest
|
||||
)
|
||||
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"])
|
||||
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):
|
||||
instances = self.filter(username, assets=[asset])
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
self.raise_does_not_exist(self.__name__)
|
||||
else:
|
||||
self.raise_multiple_return(self.__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))
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
instance = AuthBookBackend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
def all(self):
|
||||
return self.filter()
|
||||
|
||||
def prefer(self, s):
|
||||
self._prefer = s
|
||||
return self
|
||||
|
||||
def using(self, s):
|
||||
self._using = 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
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
from .external.utils import get_backend
|
||||
from .internal.asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AssetUserManager(BaseBackend):
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
external_backend = get_backend()
|
||||
internal_backend = AssetUserBackend
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, **kwargs):
|
||||
external_instance = list(cls.external_backend.filter(username, asset))
|
||||
internal_instance = list(cls.internal_backend.filter(username, asset))
|
||||
instances = cls._merge_instances(external_instance, internal_instance)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
instance = cls.external_backend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def _merge_instances(cls, external_instances, internal_instances):
|
||||
external_instances_keyword_list = [
|
||||
{'username': instance.username, 'asset': instance.asset}
|
||||
for instance in external_instances
|
||||
]
|
||||
instances = [
|
||||
instance for instance in internal_instances
|
||||
if instance.keyword not in external_instances_keyword_list
|
||||
]
|
||||
external_instances.extend(instances)
|
||||
return external_instances
|
|
@ -0,0 +1,30 @@
|
|||
# -*- 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
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class VaultBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
pass
|
|
@ -1,8 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
{
|
||||
|
@ -22,6 +20,14 @@ TEST_ADMIN_USER_CONN_TASKS = [
|
|||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
|
||||
|
||||
|
@ -34,9 +40,16 @@ TEST_SYSTEM_USER_CONN_TASKS = [
|
|||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}_{}'
|
||||
ASSET_USER_CONN_CACHE_KEY = 'ASSET_USER_CONN_{}'
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
|
@ -45,6 +58,14 @@ TEST_ASSET_USER_CONN_TASKS = [
|
|||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
TASK_OPTIONS = {
|
||||
|
|
|
@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
|
|||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from ..models import Asset, Protocol
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
__all__ = [
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm',
|
||||
'ProtocolForm'
|
||||
]
|
||||
|
||||
|
||||
class ProtocolForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Protocol
|
||||
fields = ['name', 'port']
|
||||
widgets = {
|
||||
'name': forms.Select(attrs={
|
||||
'class': 'form-control protocol-name'
|
||||
}),
|
||||
'port': forms.TextInput(attrs={
|
||||
'class': 'form-control protocol-port'
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain', 'protocol',
|
||||
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
|
@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
|
|||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
|
@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
'domain', 'protocol',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
|
@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
|
|||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
|
@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'admin_user', 'labels', 'platform',
|
||||
'protocol', 'domain',
|
||||
'assets', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'labels': forms.SelectMultiple(
|
||||
|
|
|
@ -100,16 +100,18 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
|||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.LOGIN_MANUAL or \
|
||||
protocol in [SystemUser.PROTOCOL_RDP,
|
||||
SystemUser.PROTOCOL_TELNET,
|
||||
protocol in [SystemUser.PROTOCOL_TELNET,
|
||||
SystemUser.PROTOCOL_VNC]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
auto_generate_key = False
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
if protocol == SystemUser.PROTOCOL_SSH:
|
||||
system_user.auto_gen_auth()
|
||||
elif protocol == SystemUser.PROTOCOL_RDP:
|
||||
system_user.auto_gen_auth_password()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
|
|
@ -11,6 +11,5 @@
|
|||
"""
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
|
|
|
@ -15,4 +15,9 @@ class Migration(migrations.Migration):
|
|||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='public_ip',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 2.1.7 on 2019-05-22 02:58
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0027_auto_20190521_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Protocol',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
|
||||
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=models.ManyToManyField(to='assets.Protocol',
|
||||
verbose_name='Protocol'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 2.1.7 on 2019-05-22 03:14
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_assets_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
assets = asset_model.objects.using(db_alias).all()
|
||||
for asset in assets:
|
||||
asset.protocols.create(name=asset.protocol, port=asset.port)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0028_protocol'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_assets_protocol),
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.1.7 on 2019-06-19 03:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0029_auto_20190522_1114'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
# Generated by Django 2.1.7 on 2019-06-21 05:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0030_auto_20190619_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
]
|
|
@ -8,4 +8,3 @@ from .asset import *
|
|||
from .cmd_filter import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
from applications.models.remote_app import *
|
||||
|
|
|
@ -12,11 +12,12 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.cache import cache
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from .user import AdminUser, SystemUser
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
__all__ = ['Asset', 'Protocol']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -47,6 +48,35 @@ class AssetQuerySet(models.QuerySet):
|
|||
return self.active()
|
||||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().prefetch_related("nodes", "protocols")
|
||||
return queryset
|
||||
|
||||
|
||||
class Protocol(models.Model):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES,
|
||||
default=PROTOCOL_SSH, verbose_name=_("Name"))
|
||||
port = models.IntegerField(default=22, verbose_name=_("Port"),
|
||||
validators=PORT_VALIDATORS)
|
||||
|
||||
def __str__(self):
|
||||
return "{}/{}".format(self.name, self.port)
|
||||
|
||||
|
||||
class Asset(OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
|
@ -59,32 +89,25 @@ class Asset(OrgModelMixin):
|
|||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet (beta)'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH,
|
||||
choices=Protocol.PROTOCOL_CHOICES,
|
||||
verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
|
||||
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocol"))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
|
@ -110,7 +133,7 @@ class Asset(OrgModelMixin):
|
|||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
|
@ -131,19 +154,53 @@ class Asset(OrgModelMixin):
|
|||
return True, ''
|
||||
return False, warning
|
||||
|
||||
def support_ansible(self):
|
||||
if self.platform in ("Windows", "Windows2016", "Other"):
|
||||
return False
|
||||
if self.protocol != 'ssh':
|
||||
return False
|
||||
return True
|
||||
@property
|
||||
def protocols_name(self):
|
||||
names = []
|
||||
for protocol in self.protocols.all():
|
||||
names.append(protocol.name)
|
||||
return names
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Windows2016"):
|
||||
def has_protocol(self, name):
|
||||
return name in self.protocols_name
|
||||
|
||||
def get_protocol_by_name(self, name):
|
||||
for i in self.protocols.all():
|
||||
if i.name.lower() == name.lower():
|
||||
return i
|
||||
return None
|
||||
|
||||
@property
|
||||
def protocol_ssh(self):
|
||||
return self.get_protocol_by_name("ssh")
|
||||
|
||||
@property
|
||||
def protocol_rdp(self):
|
||||
return self.get_protocol_by_name("rdp")
|
||||
|
||||
@property
|
||||
def ssh_port(self):
|
||||
if self.protocol_ssh:
|
||||
port = self.protocol_ssh.port
|
||||
else:
|
||||
port = 22
|
||||
return port
|
||||
|
||||
def is_windows(self):
|
||||
if self.platform in ("Windows", "Windows2016"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Windows2016", "Other"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_support_ansible(self):
|
||||
return self.has_protocol('ssh') and self.platform not in ("Other",)
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
nodes = self.nodes.all() or [Node.root()]
|
||||
|
@ -172,6 +229,15 @@ class Asset(OrgModelMixin):
|
|||
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
|
||||
return Asset.objects.filter(filter_arg)
|
||||
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
if self.cpu_model:
|
||||
info += self.cpu_model
|
||||
if self.cpu_count and self.cpu_cores:
|
||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||
return info
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
|
@ -184,26 +250,27 @@ class Asset(OrgModelMixin):
|
|||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return self.REACHABLE
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
if not self.admin_user:
|
||||
return self.UNKNOWN
|
||||
return self.admin_user.get_connectivity_of(self)
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cache.set(key, value, 3600*2)
|
||||
if not self.admin_user:
|
||||
return
|
||||
self.admin_user.set_connectivity_of(self, value)
|
||||
|
||||
def get_auth_info(self):
|
||||
if self.admin_user:
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
return {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
'become': self.admin_user.become_info,
|
||||
}
|
||||
if not self.admin_user:
|
||||
return {}
|
||||
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
info = {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
}
|
||||
return info
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
|
@ -215,35 +282,6 @@ class Asset(OrgModelMixin):
|
|||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def to_json(self):
|
||||
info = {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
if self.domain and self.domain.gateway_set.all():
|
||||
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
|
||||
return info
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory
|
||||
Todo: May be move to ops implements it
|
||||
"""
|
||||
data = self.to_json()
|
||||
if self.admin_user:
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
admin_user = self.admin_user
|
||||
data.update({
|
||||
'username': admin_user.username,
|
||||
'password': admin_user.password,
|
||||
'private_key': admin_user.private_key_file,
|
||||
'become': admin_user.become_info,
|
||||
'groups': [node.value for node in self.nodes.all()],
|
||||
})
|
||||
return data
|
||||
|
||||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
|
@ -265,9 +303,11 @@ class Asset(OrgModelMixin):
|
|||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'protocols': [
|
||||
{"name": p.name, "port": p.port}
|
||||
for p in self.protocols.all()
|
||||
],
|
||||
'platform': self.platform,
|
||||
'protocol': self.protocol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,10 +331,10 @@ class Asset(OrgModelMixin):
|
|||
asset = cls(ip='.'.join(ip),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
port=22,
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
asset.protocols.create(name="ssh", port=22)
|
||||
if nodes and len(nodes) > 3:
|
||||
_nodes = random.sample(nodes, 3)
|
||||
else:
|
||||
|
|
|
@ -29,6 +29,9 @@ class AuthBook(AssetUser):
|
|||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
backend = "db"
|
||||
# 用于system user和admin_user的动态设置
|
||||
_connectivity = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
@ -40,7 +43,8 @@ class AuthBook(AssetUser):
|
|||
|
||||
def _get_pre_obj(self):
|
||||
pre_obj = self.__class__.objects.filter(
|
||||
username=self.username, asset=self.asset).latest_version().first()
|
||||
username=self.username, asset=self.asset
|
||||
).latest_version().first()
|
||||
return pre_obj
|
||||
|
||||
def _remove_pre_obj_latest(self):
|
||||
|
@ -63,30 +67,30 @@ class AuthBook(AssetUser):
|
|||
|
||||
@property
|
||||
def _conn_cache_key(self):
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(self.id, self.asset.id)
|
||||
return ASSET_USER_CONN_CACHE_KEY.format(self.id)
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
value = cache.get(self._conn_cache_key, self.UNKNOWN)
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
_connectivity = self.UNKNOWN
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.UNREACHABLE
|
||||
|
||||
for host in value.get('contacted', []):
|
||||
if host == self.asset.hostname:
|
||||
_connectivity = self.REACHABLE
|
||||
|
||||
cache.set(self._conn_cache_key, _connectivity, 3600)
|
||||
cache.set(self._conn_cache_key, value, 3600)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
return {'username': self.username, 'asset': self.asset}
|
||||
return '{}_#_{}'.format(self.username, str(self.asset.id))
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.asset.hostname
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.asset.ip
|
||||
|
||||
def __str__(self):
|
||||
return '{}@{}'.format(self.username, self.asset)
|
||||
|
|
|
@ -6,6 +6,7 @@ from hashlib import md5
|
|||
|
||||
import sshpubkeys
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -29,8 +30,8 @@ class AssetUser(OrgModelMixin):
|
|||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
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'))
|
||||
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
|
@ -39,6 +40,8 @@ class AssetUser(OrgModelMixin):
|
|||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}"
|
||||
_prefer = "system_user"
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
|
@ -124,10 +127,21 @@ class AssetUser(OrgModelMixin):
|
|||
def get_auth(self, asset=None):
|
||||
pass
|
||||
|
||||
def get_connectivity_of(self, asset):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
return cache.get(key)
|
||||
|
||||
def set_connectivity_of(self, asset, c):
|
||||
i = self.generate_id_with_asset(asset)
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(i)
|
||||
cache.set(key, c, 3600)
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..backends import AssetUserManager
|
||||
try:
|
||||
other = AssetUserManager.get(username=self.username, asset=asset)
|
||||
manager = AssetUserManager().prefer(self._prefer)
|
||||
other = manager.get(username=self.username, asset=asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
else:
|
||||
|
@ -158,6 +172,10 @@ class AssetUser(OrgModelMixin):
|
|||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
def auto_gen_auth_password(self):
|
||||
password = str(uuid.uuid4())
|
||||
self.set_auth(password=password)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
|
@ -168,5 +186,25 @@ class AssetUser(OrgModelMixin):
|
|||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
id_ = '{}_{}'.format(asset.id, self.id)
|
||||
id_ = uuid.UUID(md5(id_.encode()).hexdigest())
|
||||
return id_
|
||||
|
||||
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'
|
||||
]
|
||||
id_ = self.generate_id_with_asset(asset)
|
||||
obj = AuthBook(id=id_, asset=asset, version=0, is_latest=True)
|
||||
obj._connectivity = self.get_connectivity_of(asset)
|
||||
for field in fields:
|
||||
value = getattr(self, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -32,6 +32,7 @@ class AdminUser(AssetUser):
|
|||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', max_length=128)
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
|
||||
_prefer = "admin_user"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -61,7 +62,7 @@ class AdminUser(AssetUser):
|
|||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.asset_set.all()
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
|
@ -174,17 +175,20 @@ class SystemUser(AssetUser):
|
|||
data = self.connectivity
|
||||
unreachable = data['unreachable']
|
||||
reachable = data['reachable']
|
||||
assets = {asset.hostname: asset for asset in self.assets.all()}
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host not in unreachable:
|
||||
unreachable.append(host)
|
||||
if host in reachable:
|
||||
reachable.remove(host)
|
||||
self.set_connectivity_of(assets.get(host), self.UNREACHABLE)
|
||||
for host in value.get('contacted'):
|
||||
if host not in reachable:
|
||||
reachable.append(host)
|
||||
if host in unreachable:
|
||||
unreachable.remove(host)
|
||||
self.set_connectivity_of(assets.get(host), self.REACHABLE)
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
cache.set(cache_key, data, 3600)
|
||||
|
||||
|
@ -201,7 +205,7 @@ class SystemUser(AssetUser):
|
|||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
|
||||
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -8,11 +8,12 @@ from common.serializers import AdaptedBulkListSerializer
|
|||
|
||||
from ..models import Node, AdminUser
|
||||
from ..const import ADMIN_USER_CONN_CACHE_KEY
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from .base import AuthSerializer
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
class AdminUserSerializer(BulkOrgResourceModelSerializer):
|
||||
"""
|
||||
管理用户
|
||||
"""
|
||||
|
@ -27,7 +28,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
|
|||
list_serializer_class = AdaptedBulkListSerializer
|
||||
model = AdminUser
|
||||
fields = [
|
||||
'id', 'org_id', 'name', 'username', 'assets_amount',
|
||||
'id', 'name', 'username', 'assets_amount',
|
||||
'reachable_amount', 'unreachable_amount', 'password', 'comment',
|
||||
'date_created', 'date_updated', 'become', 'become_method',
|
||||
'become_user', 'created_by',
|
||||
|
|
|
@ -1,49 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import ValidationError
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgResourceSerializerMixin
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset
|
||||
from ..models import Asset, Protocol
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
|
||||
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolSerializer',
|
||||
]
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResourceSerializerMixin):
|
||||
class ProtocolSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Protocol
|
||||
fields = ["name", "port"]
|
||||
|
||||
|
||||
class ProtocolsRelatedField(serializers.RelatedField):
|
||||
def to_representation(self, value):
|
||||
return str(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
if '/' not in data:
|
||||
raise ValidationError("protocol not contain /: {}".format(data))
|
||||
v = data.split("/")
|
||||
if len(v) != 2:
|
||||
raise ValidationError("protocol format should be name/port: {}".format(data))
|
||||
name, port = v
|
||||
cleaned_data = {"name": name, "port": port}
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||
protocols = ProtocolsRelatedField(
|
||||
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
|
||||
)
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
# validators = [] # 解决批量导入时unique_together字段校验失败
|
||||
fields = [
|
||||
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port',
|
||||
'platform', 'is_active', 'public_ip', 'domain', 'admin_user',
|
||||
'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
|
||||
'id', 'ip', 'hostname', 'protocol', 'port',
|
||||
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
|
||||
'admin_user', 'nodes', 'labels', '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'
|
||||
]
|
||||
read_only_fields = (
|
||||
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||
'created_by', 'date_created',
|
||||
)
|
||||
extra_kwargs = {
|
||||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
'hardware_info': {'label': _('Hardware info')},
|
||||
'connectivity': {'label': _('Connectivity')},
|
||||
'org_name': {'label': _('Org name')}
|
||||
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -53,24 +80,79 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
|
|||
.select_related('admin_user')
|
||||
return queryset
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'connectivity', 'org_name'
|
||||
])
|
||||
return fields
|
||||
@staticmethod
|
||||
def validate_protocols(attr):
|
||||
protocols_serializer = ProtocolSerializer(data=attr, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols_name = [i.get("name", "ssh") for i in attr]
|
||||
errors = [{} for i in protocols_name]
|
||||
for i, name in enumerate(protocols_name):
|
||||
if name in protocols_name[:i]:
|
||||
errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
|
||||
if any(errors):
|
||||
raise ValidationError(errors)
|
||||
return attr
|
||||
|
||||
def create(self, validated_data):
|
||||
protocols_data = validated_data.pop("protocols", [])
|
||||
|
||||
# 兼容老的api
|
||||
protocol = validated_data.get("protocol")
|
||||
port = validated_data.get("port")
|
||||
if not protocols_data and protocol and port:
|
||||
protocols_data = [{"name": protocol, "port": port}]
|
||||
|
||||
if not protocol and not port and protocols_data:
|
||||
validated_data["protocol"] = protocols_data[0]["name"]
|
||||
validated_data["port"] = protocols_data[0]["port"]
|
||||
|
||||
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols = protocols_serializer.save()
|
||||
instance = super().create(validated_data)
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
protocols_data = validated_data.pop("protocols", [])
|
||||
|
||||
# 兼容老的api
|
||||
protocol = validated_data.get("protocol")
|
||||
port = validated_data.get("port")
|
||||
if not protocols_data and protocol and port:
|
||||
protocols_data = [{"name": protocol, "port": port}]
|
||||
|
||||
if not protocol and not port and protocols_data:
|
||||
validated_data["protocol"] = protocols_data[0]["name"]
|
||||
validated_data["port"] = protocols_data[0]["port"]
|
||||
protocols = None
|
||||
if protocols_data:
|
||||
protocols_serializer = ProtocolSerializer(data=protocols_data, many=True)
|
||||
protocols_serializer.is_valid(raise_exception=True)
|
||||
protocols = protocols_serializer.save()
|
||||
|
||||
instance = super().update(instance, validated_data)
|
||||
if protocols:
|
||||
instance.protocols.all().delete()
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
|
||||
class AssetAsNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
|
||||
# class AssetAsNodeSerializer(serializers.ModelSerializer):
|
||||
# protocols = ProtocolSerializer(many=True)
|
||||
#
|
||||
# class Meta:
|
||||
# model = Asset
|
||||
# fields = ['id', 'hostname', 'ip', 'platform', 'protocols']
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
"""
|
||||
protocols = ProtocolsRelatedField(
|
||||
many=True, queryset=Protocol.objects.all(), label=_("Protocols")
|
||||
)
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
|
@ -78,9 +160,9 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment", "protocol", "org_id", "org_name",
|
||||
"id", "hostname", "ip", "protocol", "port", "protocols",
|
||||
"system_users_granted", "is_active", "system_users_join", "os",
|
||||
'domain', "platform", "comment", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -89,21 +171,23 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
|||
return ', '.join(system_users)
|
||||
|
||||
|
||||
class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
"""
|
||||
普通用户获取授权的资产定义的数据结构
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
# class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
# """
|
||||
# 普通用户获取授权的资产定义的数据结构
|
||||
# """
|
||||
# protocols = ProtocolSerializer(many=True)
|
||||
#
|
||||
# class Meta:
|
||||
# model = Asset
|
||||
# fields = (
|
||||
# "id", "hostname", "system_users_granted",
|
||||
# "is_active", "system_users_join", "org_name",
|
||||
# "os", "platform", "comment", "org_id", "protocols"
|
||||
# )
|
||||
|
||||
|
||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
|
||||
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
|
||||
|
|
|
@ -4,49 +4,70 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import AuthBook
|
||||
from ..backends.multi import AssetUserManager
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
from common.utils import validate_ssh_private_key
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
|
||||
'AssetUserExportSerializer', 'AssetUserPushSerializer',
|
||||
]
|
||||
|
||||
|
||||
class AssetUserSerializer(serializers.ModelSerializer):
|
||||
class BasicAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['hostname', 'ip']
|
||||
|
||||
|
||||
class AssetUserSerializer(BulkOrgResourceModelSerializer):
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
connectivity = serializers.CharField(read_only=True, label=_("Connectivity"))
|
||||
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Password')
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Public key')
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True, write_only=True,
|
||||
required=False, help_text=_('Private key')
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
read_only_fields = (
|
||||
'date_created', 'date_updated', 'created_by',
|
||||
'is_latest', 'version', 'connectivity',
|
||||
)
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
"id", "hostname", "ip", "username", "password", "asset", "version",
|
||||
"is_latest", "connectivity", "backend",
|
||||
"date_created", "date_updated", "private_key", "public_key",
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True}
|
||||
'username': {'required': True},
|
||||
}
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields = [f for f in fields if not f.startswith('_') and f != 'id']
|
||||
fields.extend(['connectivity'])
|
||||
return fields
|
||||
def validate_private_key(self, key):
|
||||
password = self.initial_data.get("password")
|
||||
valid = validate_ssh_private_key(key, password)
|
||||
if not valid:
|
||||
raise serializers.ValidationError(_("private key invalid"))
|
||||
return key
|
||||
|
||||
def create(self, validated_data):
|
||||
kwargs = {
|
||||
'name': validated_data.get('name'),
|
||||
'name': validated_data.get('username'),
|
||||
'username': validated_data.get('username'),
|
||||
'asset': validated_data.get('asset'),
|
||||
'comment': validated_data.get('comment', ''),
|
||||
|
@ -59,7 +80,33 @@ class AssetUserSerializer(serializers.ModelSerializer):
|
|||
return instance
|
||||
|
||||
|
||||
class AssetUserExportSerializer(AssetUserSerializer):
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
|
||||
|
||||
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
fields = ['password', 'private_key', 'public_key']
|
||||
|
||||
|
||||
class AssetUserPushSerializer(serializers.Serializer):
|
||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
|
||||
username = serializers.CharField(max_length=1024)
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
|
|
|
@ -5,9 +5,10 @@ from rest_framework import serializers
|
|||
from common.fields import ChoiceDisplayField
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
|
||||
class CommandFilterSerializer(serializers.ModelSerializer):
|
||||
class CommandFilterSerializer(BulkOrgResourceModelSerializer):
|
||||
rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
|
||||
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
|
||||
|
||||
|
@ -17,7 +18,7 @@ class CommandFilterSerializer(serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class CommandFilterRuleSerializer(serializers.ModelSerializer):
|
||||
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
|
||||
serializer_choice_field = ChoiceDisplayField
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from ..models import Domain, Gateway
|
||||
|
||||
|
||||
class DomainSerializer(serializers.ModelSerializer):
|
||||
class DomainSerializer(BulkOrgResourceModelSerializer):
|
||||
asset_count = serializers.SerializerMethodField()
|
||||
gateway_count = serializers.SerializerMethodField()
|
||||
|
||||
|
@ -25,7 +26,7 @@ class DomainSerializer(serializers.ModelSerializer):
|
|||
return obj.gateway_set.all().count()
|
||||
|
||||
|
||||
class GatewaySerializer(serializers.ModelSerializer):
|
||||
class GatewaySerializer(BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = Gateway
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
|
@ -45,7 +46,7 @@ class GatewayWithAuthSerializer(GatewaySerializer):
|
|||
return fields
|
||||
|
||||
|
||||
class DomainWithGatewaySerializer(serializers.ModelSerializer):
|
||||
class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):
|
||||
gateways = GatewayWithAuthSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from ..models import Label
|
||||
|
||||
|
||||
class LabelSerializer(serializers.ModelSerializer):
|
||||
class LabelSerializer(BulkOrgResourceModelSerializer):
|
||||
asset_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
|
@ -25,7 +26,7 @@ class LabelSerializer(serializers.ModelSerializer):
|
|||
return fields
|
||||
|
||||
|
||||
class LabelDistinctSerializer(serializers.ModelSerializer):
|
||||
class LabelDistinctSerializer(BulkOrgResourceModelSerializer):
|
||||
value = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from rest_framework import serializers
|
||||
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import Asset, Node
|
||||
|
||||
|
||||
|
@ -10,7 +11,7 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class NodeSerializer(serializers.ModelSerializer):
|
||||
class NodeSerializer(BulkOrgResourceModelSerializer):
|
||||
assets_amount = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -3,12 +3,12 @@ from rest_framework import serializers
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from ..models import SystemUser, Asset
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import SystemUser
|
||||
from .base import AuthSerializer
|
||||
|
||||
|
||||
class SystemUserSerializer(serializers.ModelSerializer):
|
||||
class SystemUserSerializer(BulkOrgResourceModelSerializer):
|
||||
"""
|
||||
系统用户
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
model = SystemUser
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'org_id', 'name', 'username', 'login_mode',
|
||||
'id', 'name', 'username', 'login_mode', 'login_mode_display',
|
||||
'login_mode_display', 'priority', 'protocol', 'auto_push',
|
||||
'password', 'assets_amount', 'reachable_amount', 'reachable_assets',
|
||||
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo',
|
||||
|
@ -39,17 +39,9 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
|||
]
|
||||
extra_kwargs = {
|
||||
'login_mode_display': {'label': _('Login mode display')},
|
||||
'created_by': {'read_only': True}, 'nodes': {'read_only': True},
|
||||
'assets': {'read_only': True}
|
||||
'created_by': {'read_only': True},
|
||||
}
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'login_mode_display',
|
||||
])
|
||||
return fields
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_assets(obj):
|
||||
return obj.assets_unreachable
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
|
|||
from django.dispatch import receiver
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.decorator import on_transaction_commit
|
||||
from .models import Asset, SystemUser, Node, AuthBook
|
||||
from .tasks import (
|
||||
update_assets_hardware_info_util,
|
||||
|
@ -32,9 +33,12 @@ def set_asset_root_node(asset):
|
|||
|
||||
|
||||
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||
@on_transaction_commit
|
||||
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||
if created:
|
||||
logger.info("Asset `{}` create signal received".format(instance))
|
||||
|
||||
# 获取资产硬件信息
|
||||
update_asset_hardware_info_on_created(instance)
|
||||
test_asset_conn_on_created(instance)
|
||||
|
||||
|
@ -61,6 +65,7 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
|||
@receiver(m2m_changed, sender=SystemUser.nodes.through)
|
||||
def on_system_user_nodes_change(sender, instance=None, **kwargs):
|
||||
if instance and kwargs["action"] == "post_add":
|
||||
logger.info("System user `{}` nodes update signal received".format(instance))
|
||||
assets = set()
|
||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
for node in nodes:
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import re
|
||||
import os
|
||||
|
||||
from collections import defaultdict
|
||||
from celery import shared_task
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.cache import cache
|
||||
|
@ -31,7 +32,7 @@ def check_asset_can_run_ansible(asset):
|
|||
msg = _("Asset has been disabled, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
return False
|
||||
if not asset.support_ansible():
|
||||
if not asset.is_support_ansible():
|
||||
msg = _("Asset may not be support ansible, skipped: {}").format(asset)
|
||||
logger.info(msg)
|
||||
return False
|
||||
|
@ -45,10 +46,21 @@ def clean_hosts(assets):
|
|||
continue
|
||||
clean_assets.append(asset)
|
||||
if not clean_assets:
|
||||
print(_("No assets matched, stop task"))
|
||||
logger.info(_("No assets matched, stop task"))
|
||||
return clean_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
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_assets_hardware_info(assets, result, **kwargs):
|
||||
"""
|
||||
|
@ -96,7 +108,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
|
|||
___disk_total = '%s %s' % sum_capacity(disk_info.values())
|
||||
___disk_info = json.dumps(disk_info)
|
||||
|
||||
___platform = info.get('ansible_system', 'Unknown')
|
||||
# ___platform = info.get('ansible_system', 'Unknown')
|
||||
___os = info.get('ansible_distribution', 'Unknown')
|
||||
___os_version = info.get('ansible_distribution_version', 'Unknown')
|
||||
___os_arch = info.get('ansible_architecture', 'Unknown')
|
||||
|
@ -163,25 +175,57 @@ def test_asset_connectivity_util(assets, task_name=None):
|
|||
|
||||
if task_name is None:
|
||||
task_name = _("Test assets connectivity")
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
tasks = const.TEST_ADMIN_USER_CONN_TASKS
|
||||
created_by = assets[0].org_id
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by,
|
||||
|
||||
hosts_category = {
|
||||
'linux': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
|
||||
},
|
||||
'windows': {
|
||||
'hosts': [],
|
||||
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_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
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
created_by = assets[0].org_id
|
||||
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'],
|
||||
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
created_by=created_by,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
success = summary.get('success', False)
|
||||
contacted = summary.get('contacted', {})
|
||||
dark = summary.get('dark', {})
|
||||
|
||||
results_summary['success'] &= success
|
||||
results_summary['contacted'].update(contacted)
|
||||
results_summary['dark'].update(dark)
|
||||
|
||||
for asset in assets:
|
||||
if asset.hostname in summary.get('dark', {}):
|
||||
if asset.hostname in results_summary.get('dark', {}):
|
||||
asset.connectivity = asset.UNREACHABLE
|
||||
elif asset.hostname in summary.get('contacted', []):
|
||||
elif asset.hostname in results_summary.get('contacted', []):
|
||||
asset.connectivity = asset.REACHABLE
|
||||
else:
|
||||
asset.connectivity = asset.UNKNOWN
|
||||
return summary
|
||||
|
||||
return results_summary
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -243,8 +287,7 @@ def test_admin_user_connectivity_manual(admin_user):
|
|||
## System user connective ##
|
||||
|
||||
@shared_task
|
||||
def set_system_user_connectivity_info(system_user, result):
|
||||
summary = result[1]
|
||||
def set_system_user_connectivity_info(system_user, summary):
|
||||
system_user.connectivity = summary
|
||||
|
||||
|
||||
|
@ -258,18 +301,53 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
|
||||
|
||||
hosts = clean_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=hosts, tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username, created_by=system_user.org_id,
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
)
|
||||
result = task.run()
|
||||
set_system_user_connectivity_info(system_user, result)
|
||||
return result
|
||||
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'],
|
||||
pattern='all', options=const.TASK_OPTIONS,
|
||||
run_as=system_user.username, created_by=system_user.org_id,
|
||||
)
|
||||
result = task.run()
|
||||
summary = result[1]
|
||||
success = summary.get('success', False)
|
||||
contacted = summary.get('contacted', {})
|
||||
dark = summary.get('dark', {})
|
||||
|
||||
results_summary['success'] &= success
|
||||
results_summary['contacted'].update(contacted)
|
||||
results_summary['dark'].update(dark)
|
||||
|
||||
set_system_user_connectivity_info(system_user, results_summary)
|
||||
return results_summary
|
||||
|
||||
|
||||
@shared_task
|
||||
|
@ -301,11 +379,7 @@ def test_system_user_connectivity_period():
|
|||
|
||||
#### Push system user tasks ####
|
||||
|
||||
def get_push_system_user_tasks(system_user):
|
||||
# Set root as system user is dangerous
|
||||
if system_user.username == "root":
|
||||
return []
|
||||
|
||||
def get_push_linux_system_user_tasks(system_user):
|
||||
tasks = []
|
||||
if system_user.password:
|
||||
tasks.append({
|
||||
|
@ -320,12 +394,12 @@ def get_push_system_user_tasks(system_user):
|
|||
})
|
||||
tasks.extend([
|
||||
{
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
'name': 'Check home dir exists',
|
||||
'action': {
|
||||
'module': 'stat',
|
||||
'args': 'path=/home/{}'.format(system_user.username)
|
||||
},
|
||||
'register': 'home_existed'
|
||||
},
|
||||
{
|
||||
'name': "Set home dir permission",
|
||||
|
@ -364,6 +438,46 @@ def get_push_system_user_tasks(system_user):
|
|||
)
|
||||
}
|
||||
})
|
||||
|
||||
return tasks
|
||||
|
||||
|
||||
def get_push_windows_system_user_tasks(system_user):
|
||||
tasks = []
|
||||
if system_user.password:
|
||||
tasks.append({
|
||||
'name': 'Add user {}'.format(system_user.username),
|
||||
'action': {
|
||||
'module': 'win_user',
|
||||
'args': 'fullname={} '
|
||||
'name={} '
|
||||
'password={} '
|
||||
'state=present '
|
||||
'update_password=always '
|
||||
'password_expired=no '
|
||||
'password_never_expires=yes '
|
||||
'groups="Users,Remote Desktop Users" '
|
||||
'groups_action=add '
|
||||
''.format(system_user.name,
|
||||
system_user.username,
|
||||
system_user.password),
|
||||
}
|
||||
})
|
||||
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)
|
||||
tasks = []
|
||||
return tasks
|
||||
|
||||
|
||||
|
@ -372,16 +486,29 @@ def push_system_user_util(system_user, assets, task_name):
|
|||
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: {}").format(system_user.name)
|
||||
"protocol is not ssh or rdp: {}").format(system_user.name)
|
||||
logger.info(msg)
|
||||
return
|
||||
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)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
hosts = clean_hosts_by_protocol(system_user, hosts)
|
||||
if not hosts:
|
||||
return {}
|
||||
|
||||
for host in hosts:
|
||||
system_user.load_specific_asset_auth(host)
|
||||
tasks = get_push_system_user_tasks(system_user)
|
||||
tasks = get_push_system_user_tasks(host, system_user)
|
||||
if not tasks:
|
||||
continue
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS, run_as_admin=True,
|
||||
|
@ -423,41 +550,74 @@ def test_admin_user_connectability_period():
|
|||
pass
|
||||
|
||||
|
||||
#### Test Asset user connectivity task ####
|
||||
|
||||
def get_test_asset_user_connectivity_tasks(asset):
|
||||
if asset.is_unixlike():
|
||||
tasks = const.TEST_ASSET_USER_CONN_TASKS
|
||||
elif asset.is_windows():
|
||||
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
|
||||
else:
|
||||
msg = _(
|
||||
"The asset {} system platform {} does not "
|
||||
"support run Ansible tasks".format(asset.hostname, asset.platform)
|
||||
)
|
||||
logger.info(msg)
|
||||
tasks = []
|
||||
return tasks
|
||||
|
||||
|
||||
@shared_task
|
||||
def set_asset_user_connectivity_info(asset_user, result):
|
||||
summary = result[1]
|
||||
asset_user.connectivity = summary
|
||||
if summary.get('contacted'):
|
||||
connectivity = 1
|
||||
elif summary.get("dark"):
|
||||
connectivity = 0
|
||||
else:
|
||||
connectivity = 3
|
||||
asset_user.connectivity = connectivity
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_user_connectivity_util(asset_user, task_name):
|
||||
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
|
||||
"""
|
||||
:param asset_user: <AuthBook>对象
|
||||
:param task_name:
|
||||
:return:
|
||||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
tasks = const.TEST_ASSET_USER_CONN_TASKS
|
||||
|
||||
if not check_asset_can_run_ansible(asset_user.asset):
|
||||
return
|
||||
|
||||
task, created = update_or_create_ansible_task(
|
||||
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all',
|
||||
options=const.TASK_OPTIONS,
|
||||
run_as=asset_user.username, created_by=asset_user.org_id
|
||||
)
|
||||
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
|
||||
if not tasks:
|
||||
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)
|
||||
result = task.run()
|
||||
set_asset_user_connectivity_info(asset_user, result)
|
||||
|
||||
|
||||
@shared_task
|
||||
def test_asset_users_connectivity_manual(asset_users):
|
||||
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
|
||||
"""
|
||||
: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)
|
||||
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
|
||||
|
||||
|
||||
# @shared_task
|
||||
|
|
|
@ -76,6 +76,7 @@ function initTable2() {
|
|||
function onNodeSelected2(event, treeNode) {
|
||||
var url = asset_table2.ajax.url();
|
||||
url = setUrlParam(url, "node_id", treeNode.meta.node.id);
|
||||
url = setUrlParam(url, "show_current_asset", "");
|
||||
asset_table2.ajax.url(url);
|
||||
asset_table2.ajax.reload();
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_user_auth_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_modal_confirm').trigger('click'); return false;}">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<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>
|
||||
<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>
|
||||
<div class="col-sm-10">
|
||||
<input class="form-control" id="id_password" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_user_auth_modal_confirm{% endblock %}
|
|
@ -0,0 +1,87 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% block modal_id %}asset_user_auth_update_modal{% endblock %}
|
||||
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<div class="col-sm-10">
|
||||
<div class="row bootstrap3-multi-input">
|
||||
<div class="col-xs-12">
|
||||
<input id="id_private_key" type="file" name="private_key"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
var authHostname, authUsername, authAssetId = null;
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
}).on("show.bs.modal", "#asset_user_auth_update_modal", function () {
|
||||
$('#id_hostname_p').html(authHostname);
|
||||
$('#id_username_p').html(authUsername);
|
||||
$('#id_password_auth').parent().removeClass('has-error');
|
||||
$('#id_password_auth').val('');
|
||||
}).on('click', '#btn_asset_user_auth_update_modal_confirm', function(){
|
||||
var password = $('#id_password_auth').val();
|
||||
var privateKey = $('#id_private_key').prop('files');
|
||||
var hasPrivateKey = privateKey.length > 0;
|
||||
if (!password && !hasPrivateKey) {
|
||||
$('#id_password_auth').parent().addClass('has-error');
|
||||
return
|
||||
}
|
||||
var data = {
|
||||
'asset': authAssetId,
|
||||
'username': authUsername
|
||||
};
|
||||
if (password) {
|
||||
data["password"] = password
|
||||
}
|
||||
var props = {
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
form: $("form"),
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
$("#asset_user_auth_update_modal").modal('hide');
|
||||
}
|
||||
};
|
||||
if (hasPrivateKey) {
|
||||
var reader = new FileReader();//新建一个FileReader
|
||||
reader.readAsText(privateKey[0], "UTF-8");//读取文件
|
||||
reader.onload = function(evt){ //读取完文件之后会回来这里
|
||||
data["private_key"] = evt.target.result;
|
||||
formSubmit(props);
|
||||
}
|
||||
}
|
||||
if (!hasPrivateKey) {
|
||||
formSubmit(props);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %}
|
|
@ -10,17 +10,7 @@
|
|||
}
|
||||
</style>
|
||||
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||
<div class="form-group mfa-field">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div hidden class="auth-field">
|
||||
<div class="auth-field">
|
||||
<div class="form-group">
|
||||
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
|
||||
<div class="col-sm-8">
|
||||
|
@ -48,10 +38,11 @@
|
|||
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
|
||||
<script>
|
||||
var showPassword = false;
|
||||
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||
var asset_id = "";
|
||||
var host = "";
|
||||
var username = "";
|
||||
|
||||
var authAssetId = "";
|
||||
var authHostname = "";
|
||||
var authUsername = "";
|
||||
var mfaFor = "";
|
||||
|
||||
function initClipboard() {
|
||||
var clipboard = new Clipboard('.btn-copy-password', {
|
||||
|
@ -65,12 +56,12 @@ function initClipboard() {
|
|||
}
|
||||
|
||||
function showAuth() {
|
||||
$(".mfa-field").hide();
|
||||
$(".auth-field").show();
|
||||
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + asset_id + "&username=" + username;
|
||||
$("#id_username_view").html(username);
|
||||
$("#id_hostname_view").html(host);
|
||||
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + authAssetId + "&username=" + authUsername;
|
||||
if (prefer) {
|
||||
url = setUrlParam(url, 'prefer', prefer)
|
||||
}
|
||||
$("#id_username_view").html(authUsername);
|
||||
$("#id_hostname_view").html(authHostname);
|
||||
var success = function (data) {
|
||||
var password = data.password;
|
||||
$("#id_password_view").val(password);
|
||||
|
@ -88,11 +79,6 @@ function showAuth() {
|
|||
})
|
||||
}
|
||||
|
||||
function showMFA() {
|
||||
$(".mfa-field").show();
|
||||
$(".auth-field").hide();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initClipboard();
|
||||
}).on("click", ".btn-show-password", function () {
|
||||
|
@ -103,35 +89,7 @@ $(document).ready(function () {
|
|||
$("#id_password_view").attr("type", "password")
|
||||
}
|
||||
}).on("show.bs.modal", "#asset_user_auth_view", function () {
|
||||
var now = new Date();
|
||||
if (lastMFATime === "") {
|
||||
lastMFATime = 0
|
||||
}
|
||||
var nowTime = now.getTime() / 1000;
|
||||
if (nowTime - lastMFATime < 60*10 ) {
|
||||
showAuth();
|
||||
}
|
||||
}).on("click", ".btn-mfa", function () {
|
||||
var url = "{% url 'api-auth:user-otp-verify' %}";
|
||||
var data = {
|
||||
code: $("#mfa").val()
|
||||
};
|
||||
var success = function () {
|
||||
var now = new Date();
|
||||
lastMFATime = now.getTime() / 1000;
|
||||
showAuth()
|
||||
};
|
||||
var error = function () {
|
||||
$("#mfa_error").addClass("text-danger").html("Code error");
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
showAuth();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,145 @@
|
|||
{% load i18n %}
|
||||
<style>
|
||||
.btn-group>.btn+.dropdown-toggle {
|
||||
padding-right: 4px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
<table class="table table-striped table-bordered table-hover" id="asset_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 'Hostname' %}</th>
|
||||
<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 'Datetime' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_auth_update_modal.html' %}
|
||||
{% include 'assets/_asset_user_auth_view_modal.html' %}
|
||||
{% include 'authentication/_mfa_confirm_modal.html' %}
|
||||
|
||||
<script>
|
||||
var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
|
||||
var assetUserTable;
|
||||
var needPush = false;
|
||||
var prefer = null;
|
||||
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
|
||||
|
||||
function initAssetUserTable() {
|
||||
var options = {
|
||||
ele: $('#asset_user_list_table'),
|
||||
toggle: true,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 5, createdCell: function (td, cellData) {
|
||||
if (cellData == 1) {
|
||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
||||
} else if (cellData == 0) {
|
||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
||||
} else {
|
||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: 6, createdCell: function (td, cellData) {
|
||||
var date = new Date(cellData);
|
||||
$(td).html(date.toLocaleString());
|
||||
},
|
||||
},
|
||||
{
|
||||
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;
|
||||
}
|
||||
var actions = '<div class="btn-group">' + view_btn +
|
||||
' <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 +
|
||||
' </ul>' +
|
||||
' </div>';
|
||||
actions = actions.replaceAll("username123", rowData.username)
|
||||
.replaceAll("hostname123", rowData.hostname)
|
||||
.replaceAll("asset123", rowData.asset);
|
||||
$(td).html(actions);
|
||||
},
|
||||
width: '70px'
|
||||
}
|
||||
],
|
||||
ajax_url: assetUserListUrl,
|
||||
columns: [
|
||||
{data: "id"}, {data: "hostname"}, {data: "ip"},
|
||||
{data: "username", orderable: false}, {data: "version", orderable: false},
|
||||
{data: "connectivity", orderable: false},
|
||||
{data: "date_created", orderable: false},
|
||||
{data: "asset", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
table = jumpserver.initServerSideDataTable(options);
|
||||
return table
|
||||
}
|
||||
$(document).ready(function(){
|
||||
})
|
||||
.on('click', '.btn-view-auth', function () {
|
||||
authAssetId = $(this).data("asset") ;
|
||||
authHostname = $(this).data("hostname");
|
||||
authUsername = $(this).data('user');
|
||||
var now = new Date();
|
||||
var nowTime = now.getTime() / 1000;
|
||||
if (nowTime - lastMFATime > 60*10 ) {
|
||||
mfaFor = "viewAuth";
|
||||
$("#mfa_auth_confirm").modal("show");
|
||||
} else {
|
||||
$("#asset_user_auth_view").modal('show');
|
||||
}
|
||||
})
|
||||
.on("success", '#mfa_auth_confirm', function () {
|
||||
if (mfaFor !== "viewAuth") {
|
||||
return
|
||||
}
|
||||
$("#asset_user_auth_view").modal("show");
|
||||
})
|
||||
.on('click', '.btn-update-auth', function() {
|
||||
authUsername = $(this).data("user") ;
|
||||
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)
|
||||
}
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
|
@ -95,43 +95,99 @@ var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
|
|||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||
|
||||
var need_change_field = [
|
||||
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
|
||||
];
|
||||
var need_change_field_login_mode = [
|
||||
auto_generate_key, private_key_id, auto_push_id, password_id
|
||||
];
|
||||
|
||||
function protocolChange() {
|
||||
function autoLoginModeProtocol() {
|
||||
// 协议+自动登录模式字段控制
|
||||
$('#auth_title_id').removeClass('hidden');
|
||||
var protocol = $(protocol_id + " option:selected").text();
|
||||
if (protocol === 'rdp' || protocol === 'vnc') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
if (protocol === 'rdp') {
|
||||
authFieldsDisplay();
|
||||
$(auto_generate_key).closest('.form-group').removeClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').removeClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else if (protocol === 'vnc') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else if (protocol === 'telnet (beta)') {
|
||||
$('.auth-fields').removeClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').removeClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
});
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else {
|
||||
if($(login_mode_id).val() === 'manual'){
|
||||
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||
return
|
||||
}
|
||||
authFieldsDisplay();
|
||||
$(auto_generate_key).closest('.form-group').removeClass('hidden');
|
||||
$(private_key_id).closest('.form-group').removeClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').removeClass('hidden');
|
||||
$('#command-filter-block').removeClass('hidden');
|
||||
$.each(need_change_field, function (index, value) {
|
||||
$(value).closest('.form-group').removeClass('hidden')
|
||||
});
|
||||
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function manualLoginModeProtocol() {
|
||||
// 协议+手动登录模式字段控制
|
||||
$('#auth_title_id').addClass('hidden');
|
||||
var protocol = $(protocol_id + " option:selected").text();
|
||||
if (protocol === 'rdp') {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else if (protocol === 'vnc') {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').addClass('hidden');
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else if (protocol === 'telnet (beta)') {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').removeClass('hidden');
|
||||
$(sudo_id).closest('.form-group').addClass('hidden');
|
||||
$(shell_id).closest('.form-group').addClass('hidden');
|
||||
}
|
||||
else {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
$(auto_generate_key).closest('.form-group').addClass('hidden');
|
||||
$(password_id).closest('.form-group').addClass('hidden');
|
||||
$(private_key_id).closest('.form-group').addClass('hidden');
|
||||
$(auto_push_id).closest('.form-group').addClass('hidden');
|
||||
$('#command-filter-block').removeClass('hidden');
|
||||
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function authFieldsDisplay() {
|
||||
if ($(auto_generate_key).prop('checked')) {
|
||||
$('.auth-fields').addClass('hidden');
|
||||
|
@ -139,34 +195,29 @@ function authFieldsDisplay() {
|
|||
$('.auth-fields').removeClass('hidden');
|
||||
}
|
||||
}
|
||||
function loginModeChange(){
|
||||
if ($(login_mode_id).val() === 'manual'){
|
||||
$('#auth_title_id').addClass('hidden');
|
||||
$.each(need_change_field_login_mode, function(index, value){
|
||||
$(value).closest('.form-group').addClass('hidden')
|
||||
})
|
||||
function fieldDisplay(){
|
||||
var login_mode = $(login_mode_id).val();
|
||||
if (login_mode === 'manual'){
|
||||
manualLoginModeProtocol();
|
||||
}
|
||||
else if($(login_mode_id).val() === 'auto'){
|
||||
$('#auth_title_id').removeClass('hidden');
|
||||
$(password_id).closest('.form-group').removeClass('hidden')
|
||||
protocolChange();
|
||||
else if(login_mode === 'auto'){
|
||||
autoLoginModeProtocol();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2();
|
||||
authFieldsDisplay();
|
||||
protocolChange();
|
||||
loginModeChange();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
protocolChange();
|
||||
fieldDisplay();
|
||||
})
|
||||
.on('change', auto_generate_key, function(){
|
||||
authFieldsDisplay();
|
||||
})
|
||||
.on('change', login_mode_id, function(){
|
||||
loginModeChange();
|
||||
fieldDisplay();
|
||||
})
|
||||
.on('change', protocol_id, function(){
|
||||
fieldDisplay();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="col-sm-9" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
|
||||
|
@ -42,23 +42,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-striped table-bordered table-hover" id="asset_list_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="col-sm-3" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||
|
@ -84,64 +72,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
|
||||
function initTable() {
|
||||
var reachable = {{ admin_user.REACHABLE }};
|
||||
var unreachable = {{ admin_user.UNREACHABLE }};
|
||||
var options = {
|
||||
ele: $('#asset_list_table'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (cellData === unreachable) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === reachable) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
btn += view_btn;
|
||||
btn += test_btn;
|
||||
$(td).html(btn);
|
||||
}}
|
||||
],
|
||||
|
||||
ajax_url: '{% url "api-assets:admin-user-assets" pk=admin_user.id %}',
|
||||
columns: [
|
||||
{data: "hostname" }, {data: "ip" },
|
||||
{data: "port" }, {data: "connectivity" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function initAssetUserAuthModalForm(hostname, username){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
|
||||
var assetId ;
|
||||
|
||||
$(document).ready(function () {
|
||||
initTable();
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "admin_user_id", "{{ admin_user.id }}");
|
||||
prefer = "admin_user";
|
||||
initAssetUserTable();
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var asset_id = $(this).data('uid');
|
||||
|
@ -173,37 +111,10 @@ $(document).ready(function () {
|
|||
});
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
assetId = $(this).data('aid');
|
||||
var hostname = $(this).data('hostname');
|
||||
var username = '{{ admin_user.username }}';
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': "{{ admin_user.username }}",
|
||||
'asset': assetId,
|
||||
'username': "{{ admin_user.username }}",
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
asset_id = $(this).data('aid');
|
||||
hostname = $(this).data('hostname');
|
||||
username = '{{ admin_user.username }}';
|
||||
$("#asset_user_auth_update_modal").modal();
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = $(this).data("aid") ;
|
||||
|
|
|
@ -37,20 +37,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover" id="asset_user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
|
||||
<th class="text-center">{% trans 'Username' %}</th>
|
||||
<th class="text-center">{% trans 'Password version' %}</th>
|
||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||
<th class="text-center">{% trans 'Date updated' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,7 +49,7 @@
|
|||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
{% if asset.is_support_ansible %}
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Test connective' %}:</td>
|
||||
<td>
|
||||
|
@ -82,110 +69,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetUserAuthModalForm(hostname){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
function initAssetUserTable() {
|
||||
var reachable = {{ asset.admin_user.REACHABLE }};
|
||||
var unreachable = {{ asset.admin_user.UNREACHABLE }};
|
||||
var options = {
|
||||
ele: $('#asset_user_list'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (cellData === unreachable) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (cellData === reachable) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData) {
|
||||
$(td).html(cellData.slice(0, -6));
|
||||
}},
|
||||
{targets: 5, createdCell: function (td, cellData) {
|
||||
var btn = '<a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-username="DEFAULT_USERNAME">{% trans "Update auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-username="DEFAULT_USERNAME">{% trans "View auth" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-connective" data-username="DEFAULT_USERNAME">{% trans "Test" %}</a>'.replace("DEFAULT_USERNAME", cellData);
|
||||
btn += view_btn;
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
btn += test_btn;
|
||||
{% endif %}
|
||||
$(td).html(btn);
|
||||
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:asset-user-list" %}' + '?asset_id={{ asset.id }}',
|
||||
columns: [
|
||||
{data: function (){return ''}}, {data: "username" },
|
||||
{data: "version"}, {data: "connectivity"}, {data: "date_updated"},
|
||||
{data: "username", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initDataTable(options);
|
||||
}
|
||||
var username;
|
||||
$(document).ready(function () {
|
||||
initAssetUserTable();
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
username = $(this).data('username');
|
||||
var hostname = "{{ asset.hostname }}";
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': username,
|
||||
'asset': "{{ asset.id }}",
|
||||
'username': username,
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
})
|
||||
.on('click', '.btn-test-connective', function () {
|
||||
var username = $(this).data('username');
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}" + "&username=" + username;
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
flash_message: false
|
||||
});
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "asset_id", "{{ asset.id }}");
|
||||
initAssetUserTable()
|
||||
})
|
||||
|
||||
.on('click', '#btn-bulk-test-connective', function () {
|
||||
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
|
||||
var success = function (data) {
|
||||
|
@ -200,11 +91,5 @@ $(document).ready(function () {
|
|||
flash_message: false
|
||||
});
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = "{{ asset.id }}" ;
|
||||
host = "{{ asset.hostname }}";
|
||||
username = $(this).data("username");
|
||||
$("#asset_user_auth_view").modal();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -16,12 +16,24 @@
|
|||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
||||
<h3>{% trans 'Protocols' %}</h3>
|
||||
<div class="protocols">
|
||||
{% for fm in formset.forms %}
|
||||
<div class="form-group">
|
||||
<div class="col-md-2 col-md-offset-2" style="text-align: right">{{ fm.name }}</div>
|
||||
<div class="col-md-6">{{ fm.port }}</div>
|
||||
<div class="col-md-1" style="padding: 6px 0">
|
||||
<a class="btn btn-danger btn-xs btn-protocol btn-del"><span class="fa fa-minus"></span> </a>
|
||||
<a class="btn btn-primary btn-xs btn-protocol btn-add" style="display: none"><span class="fa fa-plus"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
@ -55,6 +67,8 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% block extra %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
|
@ -73,11 +87,25 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var instanceId = "{{ object.id }}";
|
||||
var protocolLen = 0;
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
|
||||
function protocolBtnShow() {
|
||||
$(".btn-protocol.btn-add").hide();
|
||||
$(".btn-protocol.btn-add:last").show();
|
||||
var btnDel = $(".btn-protocol.btn-del");
|
||||
if (btnDel.length === 1) {
|
||||
btnDel.addClass("disabled")
|
||||
} else {
|
||||
btnDel.removeClass("disabled")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
|
@ -89,20 +117,131 @@ $(document).ready(function () {
|
|||
$('#id_nodes.select2').select2({
|
||||
closeOnSelect: false
|
||||
});
|
||||
$("#id_protocol").change(function (){
|
||||
var protocol = $("#id_protocol option:selected").text();
|
||||
var port = 22;
|
||||
if(protocol === 'rdp'){
|
||||
port = 3389;
|
||||
}
|
||||
else if(protocol === 'telnet (beta)'){
|
||||
port = 23;
|
||||
}
|
||||
else if(protocol === 'vnc'){
|
||||
port = 5901;
|
||||
}
|
||||
$("#id_port").val(port);
|
||||
});
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("change", "#id_platform", function () {
|
||||
if (instanceId !== "") {
|
||||
return
|
||||
}
|
||||
var platform = $(this).val();
|
||||
var protocolRef = $(".protocols").find("select").first()
|
||||
var protocol = protocolRef.val();
|
||||
|
||||
var protocolShould = "";
|
||||
if (platform.startsWith("Windows")){
|
||||
protocolShould = "rdp"
|
||||
} else {
|
||||
protocolShould = "ssh"
|
||||
}
|
||||
if (protocol !== protocolShould) {
|
||||
protocolRef.val(protocolShould);
|
||||
protocolRef.trigger("change")
|
||||
}
|
||||
|
||||
})
|
||||
.on("click", ".btn-protocol.btn-del", function () {
|
||||
$(this).parent().parent().remove();
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("click", ".btn-protocol.btn-add", function () {
|
||||
var protocol = "";
|
||||
var protocolsRef = $(".protocols");
|
||||
var firstProtocolForm = protocolsRef.children().first();
|
||||
var newProtocolForm = firstProtocolForm.clone();
|
||||
var protocolChoices = $.map($(firstProtocolForm.find('select option')), function (option) {
|
||||
return option.value
|
||||
});
|
||||
var protocolsSet = $.map(protocolsRef.find('select option:selected'), function(option) {
|
||||
return option.value
|
||||
});
|
||||
for (var i=0;i<protocolChoices.length;i++) {
|
||||
var p = protocolChoices[i];
|
||||
if (protocolsSet.indexOf(p) === -1) {
|
||||
protocol = p;
|
||||
break
|
||||
}
|
||||
}
|
||||
if (protocol === "") {
|
||||
return
|
||||
}
|
||||
if (protocolLen === 0) {
|
||||
protocolLen = protocolsRef.length;
|
||||
}
|
||||
var selectName = "form-" + protocolLen + "-name";
|
||||
var selectId = "id_" + selectName;
|
||||
var portName = "form-" + protocolLen + "-port";
|
||||
var portId = "id_" + portName;
|
||||
newProtocolForm.find("select").prop("name", selectName).prop("id", selectId);
|
||||
newProtocolForm.find("input").prop("name", portName).prop("id", portId);
|
||||
newProtocolForm.find("option[value='" + protocol + "']").attr("selected", true);
|
||||
protocolsRef.append(newProtocolForm);
|
||||
protocolLen += 1;
|
||||
$("#" + selectId).trigger("change");
|
||||
protocolBtnShow()
|
||||
})
|
||||
.on("change", ".protocol-name", function () {
|
||||
var name = $(this).val();
|
||||
var port = 22;
|
||||
switch (name) {
|
||||
case "ssh":
|
||||
port = 22;
|
||||
break;
|
||||
case "rdp":
|
||||
port = 3389;
|
||||
break;
|
||||
case "telnet":
|
||||
port = 23;
|
||||
break;
|
||||
case "vnc":
|
||||
port = 5901;
|
||||
break;
|
||||
default:
|
||||
port = 22;
|
||||
break
|
||||
}
|
||||
$(this).parent().parent().find(".protocol-port").val(port);
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
{% block formUrl %}
|
||||
var the_url = '{% url 'api-assets:asset-list' %}';
|
||||
var redirect_to = '{% url "assets:asset-list" %}';
|
||||
var method = "POST";
|
||||
{% endblock %}
|
||||
var form = $("form");
|
||||
var protocols = {};
|
||||
var data = form.serializeObject();
|
||||
$.each(data, function (k, v) {
|
||||
if (k.startsWith("form")){
|
||||
delete data[k];
|
||||
var _k = k.split("-");
|
||||
var formName = _k.slice(0, 2).join("-");
|
||||
var key = _k[_k.length-1];
|
||||
if (!protocols[formName]) {
|
||||
protocols[formName] = {}
|
||||
}
|
||||
protocols[formName][key] = v
|
||||
}
|
||||
});
|
||||
|
||||
protocols = $.map(protocols, function (v) {
|
||||
return v.name + '/' + v.port
|
||||
});
|
||||
data["protocols"] = protocols;
|
||||
if (typeof data.labels === "string") {
|
||||
data["labels"] = [data["labels"]];
|
||||
}
|
||||
if (typeof data["nodes"] == "string") {
|
||||
data["nodes"] = [data["nodes"]]
|
||||
}
|
||||
var props = {
|
||||
url: the_url,
|
||||
data: data,
|
||||
method: method,
|
||||
form: form,
|
||||
redirect_to: redirect_to
|
||||
};
|
||||
formSubmit(props);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -69,12 +69,12 @@
|
|||
<td><b>{{ asset.public_ip|default:"" }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Port' %}:</td>
|
||||
<td><b>{{ asset.port }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Protocol' %}:</td>
|
||||
<td><b>{{ asset.protocol }}</b></td>
|
||||
<td>{% trans 'Protocol' %}</td>
|
||||
<td>
|
||||
{% for protocol in asset.protocols.all %}
|
||||
<b>{{ protocol }}</b>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Admin user' %}:</td>
|
||||
|
@ -94,7 +94,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'CPU' %}:</td>
|
||||
<td><b>{{ asset.cpu_model|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:"" }}</b></td>
|
||||
<td><b>{{ asset.cpu_info }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Memory' %}:</td>
|
||||
|
@ -166,7 +166,7 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if asset.protocol == 'ssh' %}
|
||||
{% if asset.is_support_ansible %}
|
||||
<tr>
|
||||
<td>{% trans 'Refresh hardware' %}:</td>
|
||||
<td>
|
||||
|
|
|
@ -1,95 +1,15 @@
|
|||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% extends 'assets/asset_create.html' %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
{% load asset_tags %}
|
||||
{% load common_tags %}
|
||||
|
||||
{% block custom_head_css_js_create %}
|
||||
<link href="{% static "css/plugins/inputTags.css" %}" rel="stylesheet">
|
||||
<script src="{% static "js/plugins/inputTags.jquery.min.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
<h3>{% trans 'Basic' %}</h3>
|
||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||
{% bootstrap_field form.ip layout="horizontal" %}
|
||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||
{% bootstrap_field form.port layout="horizontal" %}
|
||||
{% bootstrap_field form.platform layout="horizontal" %}
|
||||
{% bootstrap_field form.public_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.domain layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Auth' %}</h3>
|
||||
{% bootstrap_field form.admin_user layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Node' %}</h3>
|
||||
{% bootstrap_field form.nodes layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Labels' %}</h3>
|
||||
<div class="form-group">
|
||||
<label for="{{ form.labels.id_for_label }}" class="col-md-2 control-label">{% trans 'Label' %}</label>
|
||||
<div class="col-md-9">
|
||||
<select name="labels" class="select2 labels" data-placeholder="{% trans 'Label' %}" style="width: 100%" multiple="" tabindex="4" id="{{ form.labels.id_for_label }}">
|
||||
{% for name, labels in form.labels.field.queryset|group_labels %}
|
||||
<optgroup label="{{ name }}">
|
||||
{% for label in labels %}
|
||||
{% if label in form.labels.initial %}
|
||||
<option value="{{ label.id }}" selected>{{ label.value }}</option>
|
||||
{% else %}
|
||||
<option value="{{ label.id }}">{{ label.value }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block extra %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Configuration' %}</h3>
|
||||
{% bootstrap_field form.number layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<h3>{% trans 'Other' %}</h3>
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
{% bootstrap_field form.is_active layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function format(item) {
|
||||
var group = item.element.parentElement.label;
|
||||
return group + ':' + item.text;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
$(".labels").select2({
|
||||
allowClear: true,
|
||||
templateSelection: format
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% block formUrl %}
|
||||
var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
|
||||
var redirect_to = '{% url "assets:asset-list" %}';
|
||||
var method = 'PUT';
|
||||
{% endblock %}
|
|
@ -44,19 +44,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-hover" id="system_user_list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Hostname' %}</th>
|
||||
<th>{% trans 'IP' %}</th>
|
||||
<th>{% trans 'Port' %}</th>
|
||||
<th>{% trans 'Reachable' %}</th>
|
||||
<th>{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'assets/_asset_user_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -132,50 +120,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'assets/_asset_user_auth_modal.html' %}
|
||||
{% include 'assets/_asset_user_view_auth_modal.html' %}
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initAssetsTable() {
|
||||
var connectivity = {{ system_user.connectivity | safe }};
|
||||
var options = {
|
||||
ele: $('#system_user_list'),
|
||||
buttons: [],
|
||||
order: [],
|
||||
columnDefs: [
|
||||
{targets: 0, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
var detail_btn = '<a href="{% url "assets:asset-detail" pk=DEFAULT_PK %}" data-aid="'+rowData.id+'">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData) {
|
||||
if (connectivity.unreachable.indexOf(cellData) >= 0) {
|
||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||
} else if (connectivity.reachable.indexOf(cellData) >= 0 ) {
|
||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||
} else {
|
||||
$(td).html('')
|
||||
}
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var push_btn = '';
|
||||
{% if system_user.auto_push %}
|
||||
push_btn = ' <a class="btn btn-xs btn-primary btn-push-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Push" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
{% endif %}
|
||||
var test_btn = ' <a class="btn btn-xs btn-info btn-test-asset" data-uid="{{ DEFAULT_PK }}" >{% trans "Test" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var view_btn = ' <a class="btn btn-xs btn-primary btn-view-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "View auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
{#var unbound_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-asset-unbound" data-uid="{{ DEFAULT_PK }}">{% trans "Unbound" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);#}
|
||||
var update_auth_btn = ' <a class="btn btn-xs btn-primary btn-update-asset-user-auth" data-aid="{{ DEFAULT_PK }}" data-hostname="hostname777">{% trans "Update auth" %}</a>'.replace("{{ DEFAULT_PK }}", cellData).replace("hostname777", rowData.hostname);
|
||||
$(td).html(update_auth_btn + view_btn + push_btn + test_btn);
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-assets:system-user-assets" pk=system_user.id %}',
|
||||
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }, {data: "id"}],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
|
||||
function updateSystemUserNode(nodes) {
|
||||
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
|
||||
|
@ -207,15 +154,6 @@ function updateSystemUserNode(nodes) {
|
|||
}
|
||||
jumpserver.nodes_selected = {};
|
||||
|
||||
function initAssetUserAuthModalForm(hostname, username){
|
||||
$('#id_hostname_p').html(hostname);
|
||||
$('#id_username_p').html(username);
|
||||
$('#id_password').parent().removeClass('has-error');
|
||||
$('#id_password').val('');
|
||||
}
|
||||
|
||||
var assetId;
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2()
|
||||
.on('select2:select', function(evt) {
|
||||
|
@ -226,7 +164,10 @@ $(document).ready(function () {
|
|||
var data = evt.params.data;
|
||||
delete jumpserver.nodes_selected[data.id];
|
||||
});
|
||||
initAssetsTable();
|
||||
assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
|
||||
needPush = true;
|
||||
initAssetUserTable();
|
||||
|
||||
})
|
||||
.on('click', '.btn-push', function () {
|
||||
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
|
||||
|
@ -289,9 +230,9 @@ $(document).ready(function () {
|
|||
});
|
||||
updateSystemUserNode(nodes);
|
||||
})
|
||||
.on('click', '.btn-push-asset', function () {
|
||||
.on('click', '.btn-push-auth', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var asset_id = $this.data('asset');
|
||||
var the_url = "{% url 'api-assets:system-user-push-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
|
@ -309,64 +250,7 @@ $(document).ready(function () {
|
|||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-test-asset', function () {
|
||||
var $this = $(this);
|
||||
var asset_id = $this.data('uid');
|
||||
var the_url = "{% url 'api-assets:system-user-test-to-asset' pk=object.id aid=DEFAULT_PK %}";
|
||||
the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
|
||||
var success = function (data) {
|
||||
var task_id = data.task;
|
||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||
};
|
||||
var error = function (data) {
|
||||
alert(data)
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'GET',
|
||||
success: success,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
.on('click', '.btn-update-asset-user-auth', function() {
|
||||
assetId = $(this).data('aid');
|
||||
var hostname = $(this).data('hostname');
|
||||
var username = '{{ system_user.username }}';
|
||||
initAssetUserAuthModalForm(hostname, username);
|
||||
$("#asset_user_auth_modal").modal();
|
||||
})
|
||||
.on('click', '#btn_asset_user_auth_modal_confirm', function(){
|
||||
var password = $('#id_password').val();
|
||||
if (password){
|
||||
var data = {
|
||||
'name': "{{ system_user.username }}",
|
||||
'asset': assetId,
|
||||
'username': "{{ system_user.username }}",
|
||||
'password': password
|
||||
};
|
||||
formSubmit({
|
||||
data: data,
|
||||
url: "{% url 'api-assets:asset-user-list' %}",
|
||||
method: 'POST',
|
||||
success: function () {
|
||||
toastr.success("{% trans 'Update successfully!' %}");
|
||||
},
|
||||
error: function () {
|
||||
toastr.error("{% trans 'Update failed!' %}");
|
||||
}
|
||||
});
|
||||
$("#asset_user_auth_modal").modal('hide');
|
||||
}
|
||||
else{
|
||||
$('#id_password').parent().addClass('has-error');
|
||||
}
|
||||
})
|
||||
.on("click", ".btn-view-auth", function (evt) {
|
||||
asset_id = $(this).data("aid") ;
|
||||
host = $(this).data("hostname");
|
||||
username = "{{ system_user.username }}";
|
||||
$("#asset_user_auth_view").modal();
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="only-ssh">
|
||||
<tr class="only-ssh-rdp">
|
||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||
<td>
|
||||
<span class="pull-right">
|
||||
|
@ -135,7 +135,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% if system_user.auto_push %}
|
||||
<tr class="only-ssh">
|
||||
<tr class="only-ssh-rdp">
|
||||
<td width="50%">{% trans 'Push system user now' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -144,7 +144,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="only-ssh">
|
||||
<tr>
|
||||
<td width="50%">{% trans 'Test assets connective' %}:</td>
|
||||
<td>
|
||||
<span style="float: right">
|
||||
|
@ -219,8 +219,12 @@ function updateCommandFilters(command_filters) {
|
|||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
if($('#id_protocol_type').text() === 'rdp'){
|
||||
$('.only-ssh').addClass('hidden')
|
||||
var protocol = $('#id_protocol_type').text();
|
||||
if(protocol !== 'ssh'){
|
||||
$('.only-ssh').addClass("hidden")
|
||||
}
|
||||
if(["ssh", "rdp"].indexOf(protocol) === -1){
|
||||
$('.only-ssh-rdp').addClass('hidden');
|
||||
}
|
||||
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||
$('.select2').select2()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{# 目前还不支持Windows的自动推送#}
|
||||
{% 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, Windows) does not support ansible, please manually fill in the account password. Automatic push for Windows is not currently supported.' %}
|
||||
{% 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.' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ router.register(r'domain', api.DomainViewSet, 'domain')
|
|||
router.register(r'gateway', api.GatewayViewSet, 'gateway')
|
||||
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
|
||||
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user')
|
||||
router.register(r'asset-user-info', api.AssetUserExportViewSet, 'asset-user-info')
|
||||
|
||||
cmd_filter_router = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
|
||||
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
|
|||
from common.const import create_success_msg, update_success_msg
|
||||
from .. import forms
|
||||
from ..models import AdminUser, Node
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
|
||||
__all__ = [
|
||||
'AdminUserCreateView', 'AdminUserDetailView',
|
||||
|
@ -20,9 +20,10 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
class AdminUserListView(PermissionsMixin, TemplateView):
|
||||
model = AdminUser
|
||||
template_name = 'assets/admin_user_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -33,7 +34,7 @@ class AdminUserListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserCreateView(AdminUserRequiredMixin,
|
||||
class AdminUserCreateView(PermissionsMixin,
|
||||
SuccessMessageMixin,
|
||||
CreateView):
|
||||
model = AdminUser
|
||||
|
@ -41,6 +42,7 @@ class AdminUserCreateView(AdminUserRequiredMixin,
|
|||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
success_message = create_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -51,12 +53,13 @@ class AdminUserCreateView(AdminUserRequiredMixin,
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class AdminUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
model = AdminUser
|
||||
form_class = forms.AdminUserForm
|
||||
template_name = 'assets/admin_user_create_update.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -67,11 +70,12 @@ class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class AdminUserDetailView(PermissionsMixin, DetailView):
|
||||
model = AdminUser
|
||||
template_name = 'assets/admin_user_detail.html'
|
||||
context_object_name = 'admin_user'
|
||||
object = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -83,18 +87,19 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
||||
class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
template_name = 'assets/admin_user_assets.html'
|
||||
context_object_name = 'admin_user'
|
||||
object = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=AdminUser.objects.all())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.object.asset_set.all()
|
||||
self.queryset = self.object.assets.all()
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -108,9 +113,10 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
class AdminUserDeleteView(PermissionsMixin, DeleteView):
|
||||
model = AdminUser
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:admin-user-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
|
||||
|
|
|
@ -20,18 +20,16 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
from django.utils.decorators import method_decorator
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.forms.formsets import formset_factory
|
||||
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import (
|
||||
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||
)
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
|
||||
|
||||
|
@ -44,8 +42,9 @@ __all__ = [
|
|||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetListView(AdminUserRequiredMixin, TemplateView):
|
||||
class AssetListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/asset_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
Node.root()
|
||||
|
@ -59,10 +58,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetUserListView(AdminUserRequiredMixin, DetailView):
|
||||
class AssetUserListView(PermissionsMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_asset_user_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -73,8 +73,9 @@ class AssetUserListView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserAssetListView(LoginRequiredMixin, TemplateView):
|
||||
class UserAssetListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/user_asset_list.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -86,11 +87,12 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class AssetCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetCreateForm
|
||||
template_name = 'assets/asset_create.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class=form_class)
|
||||
|
@ -102,10 +104,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
form["nodes"].initial = node
|
||||
return form
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
formset = ProtocolFormset()
|
||||
return formset
|
||||
|
||||
def form_valid(self, form):
|
||||
formset = self.get_protocol_formset()
|
||||
valid = formset.is_valid()
|
||||
if not valid:
|
||||
return self.form_invalid(form)
|
||||
protocols = formset.save()
|
||||
instance = super().form_valid(form)
|
||||
instance.protocols.set(protocols)
|
||||
return instance
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Create asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -114,7 +136,7 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
|||
return create_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
||||
class AssetBulkUpdateView(PermissionsMixin, ListView):
|
||||
model = Asset
|
||||
form_class = forms.AssetBulkUpdateForm
|
||||
template_name = 'assets/asset_bulk_update.html'
|
||||
|
@ -122,6 +144,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
|||
success_message = _("Bulk update asset success")
|
||||
id_list = None
|
||||
form = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
spm = request.GET.get('spm', '')
|
||||
|
@ -154,16 +177,28 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class AssetUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Asset
|
||||
form_class = forms.AssetUpdateForm
|
||||
template_name = 'assets/asset_update.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_protocol_formset(self):
|
||||
ProtocolFormset = formset_factory(forms.ProtocolForm, extra=0, min_num=1, max_num=5)
|
||||
if self.request.method == "POST":
|
||||
formset = ProtocolFormset(self.request.POST)
|
||||
else:
|
||||
initial_data = [{"name": p.name, "port": p.port} for p in self.object.protocols.all()]
|
||||
formset = ProtocolFormset(initial=initial_data)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
formset = self.get_protocol_formset()
|
||||
context = {
|
||||
'app': _('Assets'),
|
||||
'action': _('Update asset'),
|
||||
'formset': formset,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
@ -172,16 +207,18 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
|||
return update_success_msg % ({"name": cleaned_data["hostname"]})
|
||||
|
||||
|
||||
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
class AssetDeleteView(PermissionsMixin, DeleteView):
|
||||
model = Asset
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:asset-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
|
||||
class AssetDetailView(LoginRequiredMixin, DetailView):
|
||||
class AssetDetailView(PermissionsMixin, DetailView):
|
||||
model = Asset
|
||||
context_object_name = 'asset'
|
||||
template_name = 'assets/asset_detail.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = Node.objects.exclude(assets=self.object)
|
||||
|
@ -195,7 +232,9 @@ class AssetDetailView(LoginRequiredMixin, DetailView):
|
|||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetExportView(LoginRequiredMixin, View):
|
||||
class AssetExportView(PermissionsMixin, View):
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get(self, request):
|
||||
spm = request.GET.get('spm', '')
|
||||
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
|
||||
|
@ -242,8 +281,9 @@ class AssetExportView(LoginRequiredMixin, View):
|
|||
return JsonResponse({'redirect': url})
|
||||
|
||||
|
||||
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
|
||||
class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView):
|
||||
form_class = forms.FileForm
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def form_valid(self, form):
|
||||
node_id = self.request.GET.get("node_id")
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import CommandFilter, CommandFilterRule, SystemUser
|
||||
from ..forms import CommandFilterForm, CommandFilterRuleForm
|
||||
|
@ -22,8 +22,9 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
|
||||
class CommandFilterListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -34,12 +35,13 @@ class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class CommandFilterCreateView(PermissionsMixin, CreateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = create_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -50,12 +52,13 @@ class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class CommandFilterUpdateView(PermissionsMixin, UpdateView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_create_update.html'
|
||||
form_class = CommandFilterForm
|
||||
success_url = reverse_lazy('assets:cmd-filter-list')
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -66,9 +69,10 @@ class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class CommandFilterDetailView(PermissionsMixin, DetailView):
|
||||
model = CommandFilter
|
||||
template_name = 'assets/cmd_filter_detail.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
system_users_remain = SystemUser.objects\
|
||||
|
@ -83,10 +87,11 @@ class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView):
|
||||
class CommandFilterRuleListView(PermissionsMixin, SingleObjectMixin, TemplateView):
|
||||
template_name = 'assets/cmd_filter_rule_list.html'
|
||||
model = CommandFilter
|
||||
object = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=self.model.objects.all())
|
||||
|
@ -102,12 +107,13 @@ class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, Templ
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class CommandFilterRuleCreateView(PermissionsMixin, CreateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
|
@ -135,12 +141,13 @@ class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandFilterRuleUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView):
|
||||
template_name = 'assets/cmd_filter_rule_create_update.html'
|
||||
model = CommandFilterRule
|
||||
form_class = CommandFilterRuleForm
|
||||
success_message = create_success_msg
|
||||
cmd_filter = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('assets:cmd-filter-rule-list', kwargs={
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin ,IsOrgAdmin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from common.utils import get_object_or_none
|
||||
from ..models import Domain, Gateway
|
||||
|
@ -21,8 +21,9 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class DomainListView(AdminUserRequiredMixin, TemplateView):
|
||||
class DomainListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/domain_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -33,12 +34,13 @@ class DomainListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class DomainCreateView(PermissionsMixin, CreateView):
|
||||
model = Domain
|
||||
template_name = 'assets/domain_create_update.html'
|
||||
form_class = DomainForm
|
||||
success_url = reverse_lazy('assets:domain-list')
|
||||
success_message = create_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -49,12 +51,13 @@ class DomainCreateView(AdminUserRequiredMixin, CreateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class DomainUpdateView(PermissionsMixin, UpdateView):
|
||||
model = Domain
|
||||
template_name = 'assets/domain_create_update.html'
|
||||
form_class = DomainForm
|
||||
success_url = reverse_lazy('assets:domain-list')
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -65,9 +68,10 @@ class DomainUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class DomainDetailView(PermissionsMixin, DetailView):
|
||||
model = Domain
|
||||
template_name = 'assets/domain_detail.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -78,16 +82,18 @@ class DomainDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
class DomainDeleteView(PermissionsMixin, DeleteView):
|
||||
model = Domain
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:domain-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
|
||||
class DomainGatewayListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView):
|
||||
class DomainGatewayListView(PermissionsMixin, SingleObjectMixin, TemplateView):
|
||||
template_name = 'assets/domain_gateway_list.html'
|
||||
model = Domain
|
||||
object = None
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(queryset=self.model.objects.all())
|
||||
|
@ -103,11 +109,12 @@ class DomainGatewayListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateV
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainGatewayCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class DomainGatewayCreateView(PermissionsMixin, CreateView):
|
||||
model = Gateway
|
||||
template_name = 'assets/gateway_create_update.html'
|
||||
form_class = GatewayForm
|
||||
success_message = create_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_success_url(self):
|
||||
domain = self.object.domain
|
||||
|
@ -130,11 +137,12 @@ class DomainGatewayCreateView(AdminUserRequiredMixin, CreateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class DomainGatewayUpdateView(PermissionsMixin, UpdateView):
|
||||
model = Gateway
|
||||
template_name = 'assets/gateway_create_update.html'
|
||||
form_class = GatewayForm
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_success_url(self):
|
||||
domain = self.object.domain
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
from ..models import Label
|
||||
from ..forms import LabelForm
|
||||
|
@ -18,8 +18,9 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class LabelListView(AdminUserRequiredMixin, TemplateView):
|
||||
class LabelListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/label_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -30,13 +31,14 @@ class LabelListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
||||
class LabelCreateView(PermissionsMixin, CreateView):
|
||||
model = Label
|
||||
template_name = 'assets/label_create_update.html'
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = create_success_msg
|
||||
disable_name = ['draw', 'search', 'limit', 'offset', '_']
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -57,12 +59,13 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
|
|||
return super().form_valid(form)
|
||||
|
||||
|
||||
class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||
class LabelUpdateView(PermissionsMixin, UpdateView):
|
||||
model = Label
|
||||
template_name = 'assets/label_create_update.html'
|
||||
form_class = LabelForm
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -73,11 +76,12 @@ class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LabelDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class LabelDetailView(PermissionsMixin, DetailView):
|
||||
pass
|
||||
|
||||
|
||||
class LabelDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
class LabelDeleteView(PermissionsMixin, DeleteView):
|
||||
model = Label
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:label-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
|
|
@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView
|
|||
from common.const import create_success_msg, update_success_msg
|
||||
from ..forms import SystemUserForm
|
||||
from ..models import SystemUser, Node, CommandFilter
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -20,8 +20,9 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class SystemUserListView(AdminUserRequiredMixin, TemplateView):
|
||||
class SystemUserListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'assets/system_user_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -32,12 +33,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class SystemUserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserForm
|
||||
template_name = 'assets/system_user_create.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
success_message = create_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -48,12 +50,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class SystemUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
model = SystemUser
|
||||
form_class = SystemUserForm
|
||||
template_name = 'assets/system_user_update.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
success_message = update_success_msg
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -64,10 +67,11 @@ class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVi
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class SystemUserDetailView(PermissionsMixin, DetailView):
|
||||
template_name = 'assets/system_user_detail.html'
|
||||
context_object_name = 'system_user'
|
||||
model = SystemUser
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -79,16 +83,18 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView):
|
||||
class SystemUserDeleteView(PermissionsMixin, DeleteView):
|
||||
model = SystemUser
|
||||
template_name = 'delete_confirm.html'
|
||||
success_url = reverse_lazy('assets:system-user-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
|
||||
class SystemUserAssetView(AdminUserRequiredMixin, DetailView):
|
||||
class SystemUserAssetView(PermissionsMixin, DetailView):
|
||||
model = SystemUser
|
||||
template_name = 'assets/system_user_asset.html'
|
||||
context_object_name = 'system_user'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from rest_framework import viewsets
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser
|
||||
from common.permissions import IsOrgAdminOrAppUser, IsAuditor
|
||||
from .models import FTPLog
|
||||
from .serializers import FTPLogSerializer
|
||||
|
||||
|
@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
|
|||
class FTPLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = FTPLog.objects.all()
|
||||
serializer_class = FTPLogSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
|
||||
|
|
|
@ -14,12 +14,11 @@ from django.views import View
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import ListView
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
|
||||
from audits.utils import get_excel_response, write_content_to_excel
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser
|
||||
|
||||
from orgs.utils import current_org
|
||||
from ops.views import CommandExecutionListView as UserCommandExecutionListView
|
||||
|
@ -42,12 +41,13 @@ def get_resource_type_list():
|
|||
return [model._meta.verbose_name for model in models]
|
||||
|
||||
|
||||
class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class FTPLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
model = FTPLog
|
||||
template_name = 'audits/ftp_log_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = asset = system_user = filename = ''
|
||||
date_from = date_to = None
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()
|
||||
|
@ -89,13 +89,14 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
model = OperateLog
|
||||
template_name = 'audits/operate_log_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = action = resource_type = ''
|
||||
date_from = date_to = None
|
||||
actions_dict = dict(OperateLog.ACTION_CHOICES)
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = super().get_queryset()
|
||||
|
@ -124,7 +125,6 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
|||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'user': self.user,
|
||||
'action': self.action,
|
||||
'resource_type': self.resource_type,
|
||||
"app": _("Audits"),
|
||||
"action": _("Operate log"),
|
||||
|
@ -133,12 +133,13 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
model = PasswordChangeLog
|
||||
template_name = 'audits/password_change_log_list.html'
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = ''
|
||||
date_from = date_to = None
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
def get_queryset(self):
|
||||
users = current_org.get_org_users()
|
||||
|
@ -169,12 +170,13 @@ class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListVie
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
template_name = 'audits/login_log_list.html'
|
||||
model = UserLoginLog
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
user = keyword = ""
|
||||
date_to = date_from = None
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
@staticmethod
|
||||
def get_org_users():
|
||||
|
@ -246,11 +248,12 @@ class CommandExecutionListView(UserCommandExecutionListView):
|
|||
'keyword': self.keyword,
|
||||
'user_id': self.user_id,
|
||||
})
|
||||
return super().get_context_data(**context)
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class LoginLogExportView(LoginRequiredMixin, View):
|
||||
class LoginLogExportView(PermissionsMixin, View):
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get(self, request):
|
||||
fields = [
|
||||
|
|
|
@ -147,7 +147,23 @@ class PrivateTokenAuthentication(authentication.TokenAuthentication):
|
|||
|
||||
|
||||
class SessionAuthentication(authentication.SessionAuthentication):
|
||||
def enforce_csrf(self, request):
|
||||
reason = CSRFCheck().process_view(request, None, (), {})
|
||||
if reason:
|
||||
raise exceptions.AuthenticationFailed(reason)
|
||||
def authenticate(self, request):
|
||||
"""
|
||||
Returns a `User` if the request session currently has a logged in user.
|
||||
Otherwise returns `None`.
|
||||
"""
|
||||
|
||||
# Get the session-based user from the underlying HttpRequest object
|
||||
user = getattr(request._request, 'user', None)
|
||||
|
||||
# Unauthenticated, CSRF validation not required
|
||||
if not user or not user.is_active:
|
||||
return None
|
||||
|
||||
try:
|
||||
self.enforce_csrf(request)
|
||||
except exceptions.AuthenticationFailed:
|
||||
return None
|
||||
|
||||
# CSRF passed with authenticated user
|
||||
return user, None
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
{% extends '_modal.html' %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block modal_id %}mfa_auth_confirm{% endblock %}
|
||||
{% block modal_title%}{% trans "MFA confirm" %}{% endblock %}
|
||||
{% block modal_body %}
|
||||
<style>
|
||||
.inmodal .modal-body {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
<form class="form-horizontal" action="" style="padding-top: 20px">
|
||||
<div class="form-group mfa-field">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="btn btn-primary btn-sm btn-mfa">{% trans "Confirm" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
var codeError = "{% trans 'Code error' %}";
|
||||
|
||||
$(document).ready(function () {
|
||||
}).on("click", ".btn-mfa", function () {
|
||||
var url = "{% url 'api-auth:user-otp-verify' %}";
|
||||
var data = {
|
||||
code: $("#mfa").val()
|
||||
};
|
||||
var success = function () {
|
||||
var now = new Date();
|
||||
lastMFATime = now.getTime() / 1000;
|
||||
$("#mfa_auth_confirm").modal("hide").trigger("success");
|
||||
};
|
||||
var error = function () {
|
||||
$("#mfa_error").addClass("text-danger").html(codeError);
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: url,
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
flash_message: false,
|
||||
error: error
|
||||
})
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block modal_button %}
|
||||
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
|
||||
{% endblock %}
|
|
@ -48,7 +48,7 @@ class LogTailApi(generics.RetrieveAPIView):
|
|||
return line
|
||||
|
||||
def read_from_file(self):
|
||||
with open(self.log_path, 'r') as f:
|
||||
with open(self.log_path, 'rt', encoding='utf8') as f:
|
||||
offset = cache.get(self.mark, 0)
|
||||
f.seek(offset)
|
||||
data = f.read(self.buff_size).replace('\n', '\r\n')
|
||||
|
@ -79,7 +79,6 @@ class LogTailApi(generics.RetrieveAPIView):
|
|||
|
||||
|
||||
class ResourcesIDCacheApi(APIView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
spm = str(uuid.uuid4())
|
||||
resources_id = request.data.get('resources')
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db import transaction
|
||||
|
||||
|
||||
def on_transaction_commit(func):
|
||||
"""
|
||||
如果不调用on_commit, 对象创建时添加多对多字段值失败
|
||||
"""
|
||||
def inner(*args, **kwargs):
|
||||
transaction.on_commit(lambda: func(*args, **kwargs))
|
||||
return inner
|
|
@ -5,6 +5,7 @@ from django.http import JsonResponse
|
|||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
from rest_framework.utils import html
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
@ -203,3 +204,31 @@ class DatetimeSearchMixin:
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.get_date_range()
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ApiMessageMixin:
|
||||
success_message = _("%(name)s was %(action)s successfully")
|
||||
_action_map = {"create": _("create"), "update": _("update")}
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
if not isinstance(cleaned_data, dict):
|
||||
return ''
|
||||
data = {k: v for k, v in cleaned_data.items()}
|
||||
action = getattr(self, "action", "create")
|
||||
data["action"] = self._action_map.get(action)
|
||||
try:
|
||||
message = self.success_message % data
|
||||
except:
|
||||
message = ''
|
||||
return message
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
if request.method.lower() in ("get", "delete", "patch"):
|
||||
return resp
|
||||
if resp.status_code >= 400:
|
||||
return resp
|
||||
message = self.get_success_message(resp.data)
|
||||
if message:
|
||||
messages.success(request, message)
|
||||
return resp
|
||||
|
|
|
@ -27,6 +27,12 @@ class IsAppUser(IsValidUser):
|
|||
and request.user.is_app
|
||||
|
||||
|
||||
class IsAuditor(IsValidUser):
|
||||
def has_permission(self, request, view):
|
||||
return super(IsAuditor, self).has_permission(request, view) \
|
||||
and request.user.is_auditor
|
||||
|
||||
|
||||
class IsSuperUser(IsValidUser):
|
||||
def has_permission(self, request, view):
|
||||
return super(IsSuperUser, self).has_permission(request, view) \
|
||||
|
@ -115,3 +121,14 @@ class WithBootstrapToken(permissions.BasePermission):
|
|||
return False
|
||||
request_bootstrap_token = authorization.split()[-1]
|
||||
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token
|
||||
|
||||
|
||||
class PermissionsMixin(UserPassesTestMixin):
|
||||
permission_classes = []
|
||||
|
||||
def test_func(self):
|
||||
permission_classes = self.permission_classes
|
||||
for permission_class in permission_classes:
|
||||
if not permission_class().has_permission(self.request, self):
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -3,5 +3,22 @@
|
|||
from django.core.validators import RegexValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from rest_framework.validators import (
|
||||
UniqueTogetherValidator, ValidationError
|
||||
)
|
||||
|
||||
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
||||
|
||||
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
||||
|
||||
|
||||
class ProjectUniqueValidator(UniqueTogetherValidator):
|
||||
def __call__(self, attrs):
|
||||
try:
|
||||
super().__call__(attrs)
|
||||
except ValidationError as e:
|
||||
errors = {}
|
||||
for field in self.fields:
|
||||
if field == "org_id":
|
||||
continue
|
||||
errors[field] = _('This field must be unique.')
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -287,7 +287,10 @@ class Config(dict):
|
|||
return v
|
||||
|
||||
try:
|
||||
v = tp(v)
|
||||
if tp in [list, dict]:
|
||||
v = json.loads(v)
|
||||
else:
|
||||
v = tp(v)
|
||||
except Exception:
|
||||
pass
|
||||
return v
|
||||
|
|
|
@ -358,7 +358,7 @@ EMAIL_USE_SSL = False
|
|||
EMAIL_USE_TLS = False
|
||||
EMAIL_SUBJECT_PREFIX = '[JMS] '
|
||||
|
||||
#Email custom content
|
||||
# Email custom content
|
||||
EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
|
||||
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
|
||||
EMAIL_CUSTOM_USER_CREATED_BODY = ''
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.utils import timezone
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from rest_framework.response import Response
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
|
@ -18,10 +17,12 @@ 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
|
||||
|
||||
|
||||
class IndexView(LoginRequiredMixin, TemplateView):
|
||||
class IndexView(PermissionsMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
session_week = None
|
||||
session_month = None
|
||||
|
@ -31,6 +32,8 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
if request.user.is_auditor:
|
||||
return super(IndexView, self).dispatch(request, *args, **kwargs)
|
||||
if not request.user.is_org_admin:
|
||||
return redirect('assets:user-asset-list')
|
||||
if not current_org or not current_org.can_admin_by(request.user):
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-27 15:53+0800\n"
|
||||
"POT-Creation-Date: 2019-06-20 16:30+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -17,58 +17,58 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: static/js/jumpserver.js:249
|
||||
#: static/js/jumpserver.js:263
|
||||
msgid "Update is successful!"
|
||||
msgstr "更新成功"
|
||||
|
||||
#: static/js/jumpserver.js:251
|
||||
#: static/js/jumpserver.js:265
|
||||
msgid "An unknown error occurred while updating.."
|
||||
msgstr "更新时发生未知错误"
|
||||
|
||||
#: static/js/jumpserver.js:315 static/js/jumpserver.js:352
|
||||
#: static/js/jumpserver.js:355
|
||||
#: static/js/jumpserver.js:329 static/js/jumpserver.js:366
|
||||
#: static/js/jumpserver.js:369
|
||||
msgid "Error"
|
||||
msgstr "错误"
|
||||
|
||||
#: static/js/jumpserver.js:315
|
||||
#: static/js/jumpserver.js:329
|
||||
msgid "Being used by the asset, please unbind the asset first."
|
||||
msgstr "正在被资产使用中,请先解除资产绑定"
|
||||
|
||||
#: static/js/jumpserver.js:321 static/js/jumpserver.js:362
|
||||
#: static/js/jumpserver.js:335 static/js/jumpserver.js:376
|
||||
msgid "Delete the success"
|
||||
msgstr "删除成功"
|
||||
|
||||
#: static/js/jumpserver.js:327
|
||||
#: static/js/jumpserver.js:341
|
||||
msgid "Are you sure about deleting it?"
|
||||
msgstr "你确定删除吗 ?"
|
||||
|
||||
#: static/js/jumpserver.js:331 static/js/jumpserver.js:372
|
||||
#: static/js/jumpserver.js:345 static/js/jumpserver.js:386
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: static/js/jumpserver.js:333 static/js/jumpserver.js:374
|
||||
#: static/js/jumpserver.js:347 static/js/jumpserver.js:388
|
||||
msgid "Confirm"
|
||||
msgstr "确认"
|
||||
|
||||
#: static/js/jumpserver.js:352
|
||||
#: static/js/jumpserver.js:366
|
||||
msgid ""
|
||||
"The organization contains undeleted information. Please try again after "
|
||||
"deleting"
|
||||
msgstr "组织中包含未删除信息,请删除后重试"
|
||||
|
||||
#: static/js/jumpserver.js:355
|
||||
#: static/js/jumpserver.js:369
|
||||
msgid ""
|
||||
"Do not perform this operation under this organization. Try again after "
|
||||
"switching to another organization"
|
||||
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
|
||||
|
||||
#: static/js/jumpserver.js:368
|
||||
#: static/js/jumpserver.js:382
|
||||
msgid ""
|
||||
"Please ensure that the following information in the organization has been "
|
||||
"deleted"
|
||||
msgstr "请确保组织内的以下信息已删除"
|
||||
|
||||
#: static/js/jumpserver.js:369
|
||||
#: static/js/jumpserver.js:383
|
||||
msgid ""
|
||||
"User list、User group、Asset list、Domain list、Admin user、System user、"
|
||||
"Labels、Asset permission"
|
||||
|
@ -76,76 +76,80 @@ msgstr ""
|
|||
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
|
||||
"规则"
|
||||
|
||||
#: static/js/jumpserver.js:408
|
||||
#: static/js/jumpserver.js:422
|
||||
msgid "Loading ..."
|
||||
msgstr "加载中 ..."
|
||||
|
||||
#: static/js/jumpserver.js:409
|
||||
#: static/js/jumpserver.js:423
|
||||
msgid "Search"
|
||||
msgstr "搜索"
|
||||
|
||||
#: static/js/jumpserver.js:412
|
||||
#: static/js/jumpserver.js:426
|
||||
#, javascript-format
|
||||
msgid "Selected item %d"
|
||||
msgstr "选中 %d 项"
|
||||
|
||||
#: static/js/jumpserver.js:416
|
||||
#: static/js/jumpserver.js:430
|
||||
msgid "Per page _MENU_"
|
||||
msgstr "每页 _MENU_"
|
||||
|
||||
#: static/js/jumpserver.js:417
|
||||
#: static/js/jumpserver.js:431
|
||||
msgid ""
|
||||
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
|
||||
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
|
||||
|
||||
#: static/js/jumpserver.js:420
|
||||
#: static/js/jumpserver.js:434
|
||||
msgid "No match"
|
||||
msgstr "没有匹配项"
|
||||
|
||||
#: static/js/jumpserver.js:421
|
||||
#: static/js/jumpserver.js:435
|
||||
msgid "No record"
|
||||
msgstr "没有记录"
|
||||
|
||||
#: static/js/jumpserver.js:563
|
||||
#: static/js/jumpserver.js:577
|
||||
msgid "Unknown error occur"
|
||||
msgstr ""
|
||||
|
||||
#: static/js/jumpserver.js:800
|
||||
#: static/js/jumpserver.js:816
|
||||
msgid "Password minimum length {N} bits"
|
||||
msgstr "密码最小长度 {N} 位"
|
||||
|
||||
#: static/js/jumpserver.js:801
|
||||
#: static/js/jumpserver.js:817
|
||||
msgid "Must contain capital letters"
|
||||
msgstr "必须包含大写字母"
|
||||
|
||||
#: static/js/jumpserver.js:802
|
||||
#: static/js/jumpserver.js:818
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr "必须包含小写字母"
|
||||
|
||||
#: static/js/jumpserver.js:803
|
||||
#: static/js/jumpserver.js:819
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr "必须包含数字字符"
|
||||
|
||||
#: static/js/jumpserver.js:804
|
||||
#: static/js/jumpserver.js:820
|
||||
msgid "Must contain special characters"
|
||||
msgstr "必须包含特殊字符"
|
||||
|
||||
#: static/js/jumpserver.js:976
|
||||
#: static/js/jumpserver.js:995
|
||||
msgid "Export failed"
|
||||
msgstr "导出失败"
|
||||
|
||||
#: static/js/jumpserver.js:993
|
||||
#: static/js/jumpserver.js:1012
|
||||
msgid "Import Success"
|
||||
msgstr "导入成功"
|
||||
|
||||
#: static/js/jumpserver.js:998
|
||||
#: static/js/jumpserver.js:1017
|
||||
msgid "Update Success"
|
||||
msgstr "更新成功"
|
||||
|
||||
#: static/js/jumpserver.js:1028
|
||||
#: static/js/jumpserver.js:1018
|
||||
msgid "Count"
|
||||
msgstr "数量"
|
||||
|
||||
#: static/js/jumpserver.js:1047
|
||||
msgid "Import failed"
|
||||
msgstr "导入失败"
|
||||
|
||||
#: static/js/jumpserver.js:1033
|
||||
#: static/js/jumpserver.js:1052
|
||||
msgid "Update failed"
|
||||
msgstr "更新失败"
|
||||
|
|
|
@ -21,7 +21,7 @@ class JMSBaseInventory(BaseInventory):
|
|||
'id': asset.id,
|
||||
'hostname': asset.hostname,
|
||||
'ip': asset.ip,
|
||||
'port': asset.port,
|
||||
'port': asset.ssh_port,
|
||||
'vars': dict(),
|
||||
'groups': [],
|
||||
}
|
||||
|
@ -29,8 +29,15 @@ class JMSBaseInventory(BaseInventory):
|
|||
info["vars"].update(self.make_proxy_command(asset))
|
||||
if run_as_admin:
|
||||
info.update(asset.get_auth_info())
|
||||
if asset.is_unixlike():
|
||||
info["become"] = asset.admin_user.become_info
|
||||
for node in asset.nodes.all():
|
||||
info["groups"].append(node.value)
|
||||
if asset.is_windows():
|
||||
info["vars"].update({
|
||||
"ansible_connection": "ssh",
|
||||
"ansible_shell_type": "cmd",
|
||||
})
|
||||
for label in asset.labels.all():
|
||||
info["vars"].update({
|
||||
label.name: label.value
|
||||
|
@ -73,7 +80,7 @@ class JMSInventory(JMSBaseInventory):
|
|||
"""
|
||||
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
|
||||
"""
|
||||
:param host_id_list: ["test1", ]
|
||||
:param assets: assets
|
||||
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
|
||||
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
|
||||
:param become_info: 是否become成某个用户去执行
|
||||
|
@ -86,28 +93,26 @@ class JMSInventory(JMSBaseInventory):
|
|||
host_list = []
|
||||
|
||||
for asset in assets:
|
||||
info = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
|
||||
host_list.append(info)
|
||||
|
||||
if run_as:
|
||||
for host in host_list:
|
||||
host = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
|
||||
if run_as:
|
||||
run_user_info = self.get_run_user_info(host)
|
||||
host.update(run_user_info)
|
||||
|
||||
if become_info:
|
||||
for host in host_list:
|
||||
if become_info and asset.is_unixlike():
|
||||
host.update(become_info)
|
||||
host_list.append(host)
|
||||
|
||||
super().__init__(host_list=host_list)
|
||||
|
||||
def get_run_user_info(self, host):
|
||||
from assets.backends.multi import AssetUserManager
|
||||
from assets.backends import AssetUserManager
|
||||
|
||||
if not self.run_as:
|
||||
return {}
|
||||
|
||||
try:
|
||||
asset = self.assets.get(id=host.get('id'))
|
||||
run_user = AssetUserManager.get(self.run_as, asset)
|
||||
manager = AssetUserManager()
|
||||
run_user = manager.get(self.run_as, asset)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return {}
|
||||
|
@ -133,12 +138,10 @@ class JMSCustomInventory(JMSBaseInventory):
|
|||
host_list = []
|
||||
|
||||
for asset in assets:
|
||||
info = self.convert_to_ansible(asset)
|
||||
host_list.append(info)
|
||||
|
||||
for host in host_list:
|
||||
host = self.convert_to_ansible(asset)
|
||||
run_user_info = self.get_run_user_info()
|
||||
host.update(run_user_info)
|
||||
host_list.append(host)
|
||||
|
||||
super().__init__(host_list=host_list)
|
||||
|
||||
|
|
|
@ -220,6 +220,7 @@ class AdHoc(models.Model):
|
|||
time_start = time.time()
|
||||
try:
|
||||
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
history.date_start = date_start
|
||||
print(_("{} Start task: {}").format(date_start, self.task.name))
|
||||
raw, summary = self._run_only()
|
||||
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# coding: utf-8
|
||||
import os
|
||||
import subprocess
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from celery import shared_task, subtask
|
||||
|
@ -104,6 +105,13 @@ def hello(name, callback=None):
|
|||
print("Hello {}".format(name))
|
||||
|
||||
|
||||
@shared_task
|
||||
# @after_app_shutdown_clean_periodic
|
||||
# @register_as_period_task(interval=30)
|
||||
def hello123():
|
||||
print("{} Hello world".format(datetime.datetime.now().strftime("%H:%M:%S")))
|
||||
|
||||
|
||||
@shared_task
|
||||
def hello_callback(result):
|
||||
print(result)
|
||||
|
|
|
@ -134,8 +134,11 @@ function getSelectedAssetsNode() {
|
|||
var assetsNodeId = [];
|
||||
var assetsNode = [];
|
||||
nodes.forEach(function (node) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden && node.meta.asset.protocol === 'ssh') {
|
||||
if (assetsNodeId.indexOf(node.id) === -1) {
|
||||
if (node.meta.type === 'asset' && !node.isHidden) {
|
||||
var protocols = $.map(node.meta.asset.protocols, function (v) {
|
||||
return v.name
|
||||
});
|
||||
if (assetsNodeId.indexOf(node.id) === -1 && protocols.indexOf("ssh") > -1) {
|
||||
assetsNodeId.push(node.id);
|
||||
assetsNode.push(node)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ def update_or_create_ansible_task(
|
|||
run_as_admin=False, run_as=None, become_info=None,
|
||||
):
|
||||
if not hosts or not tasks or not task_name:
|
||||
return
|
||||
return None, None
|
||||
set_to_root_org()
|
||||
defaults = {
|
||||
'name': task_name,
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.conf import settings
|
|||
from django.views.generic import ListView, DetailView
|
||||
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin
|
||||
from orgs.utils import current_org
|
||||
from ..models import Task, AdHoc, AdHocRunHistory
|
||||
|
||||
|
@ -17,13 +17,14 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class TaskListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
model = Task
|
||||
ordering = ('-date_created',)
|
||||
context_object_name = 'task_list'
|
||||
template_name = 'ops/task_list.html'
|
||||
keyword = ''
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
@ -51,9 +52,10 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class TaskDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class TaskDetailView(PermissionsMixin, DetailView):
|
||||
model = Task
|
||||
template_name = 'ops/task_detail.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
@ -73,9 +75,10 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class TaskAdhocView(AdminUserRequiredMixin, DetailView):
|
||||
class TaskAdhocView(PermissionsMixin, DetailView):
|
||||
model = Task
|
||||
template_name = 'ops/task_adhoc.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -86,9 +89,10 @@ class TaskAdhocView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class TaskHistoryView(AdminUserRequiredMixin, DetailView):
|
||||
class TaskHistoryView(PermissionsMixin, DetailView):
|
||||
model = Task
|
||||
template_name = 'ops/task_history.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -99,9 +103,10 @@ class TaskHistoryView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdHocDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class AdHocDetailView(PermissionsMixin, DetailView):
|
||||
model = AdHoc
|
||||
template_name = 'ops/adhoc_detail.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -112,9 +117,10 @@ class AdHocDetailView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdHocHistoryView(AdminUserRequiredMixin, DetailView):
|
||||
class AdHocHistoryView(PermissionsMixin, DetailView):
|
||||
model = AdHoc
|
||||
template_name = 'ops/adhoc_history.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
@ -125,9 +131,10 @@ class AdHocHistoryView(AdminUserRequiredMixin, DetailView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView):
|
||||
class AdHocHistoryDetailView(PermissionsMixin, DetailView):
|
||||
model = AdHocRunHistory
|
||||
template_name = 'ops/adhoc_history_detail.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
#
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
|
||||
|
||||
|
||||
__all__ = ['CeleryTaskLogView']
|
||||
|
||||
|
||||
class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView):
|
||||
class CeleryTaskLogView(PermissionsMixin, TemplateView):
|
||||
template_name = 'ops/celery_task_log.html'
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
|
|
@ -5,7 +5,9 @@ from django.utils.translation import ugettext as _
|
|||
from django.conf import settings
|
||||
from django.views.generic import ListView, TemplateView
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin, LoginRequiredMixin
|
||||
from common.permissions import (
|
||||
PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser
|
||||
)
|
||||
from common.mixins import DatetimeSearchMixin
|
||||
from ..models import CommandExecution
|
||||
from ..forms import CommandExecutionForm
|
||||
|
@ -16,13 +18,14 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class CommandExecutionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||
class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView):
|
||||
template_name = 'ops/command_execution_list.html'
|
||||
model = CommandExecution
|
||||
paginate_by = settings.DISPLAY_PER_PAGE
|
||||
ordering = ('-date_created',)
|
||||
context_object_name = 'task_list'
|
||||
keyword = ''
|
||||
permission_classes = [IsOrgAdmin | IsAuditor]
|
||||
|
||||
def _get_queryset(self):
|
||||
self.keyword = self.request.GET.get('keyword', '')
|
||||
|
@ -51,9 +54,10 @@ class CommandExecutionListView(AdminUserRequiredMixin, DatetimeSearchMixin, List
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class CommandExecutionStartView(LoginRequiredMixin, TemplateView):
|
||||
class CommandExecutionStartView(PermissionsMixin, TemplateView):
|
||||
template_name = 'ops/command_execution_create.html'
|
||||
form_class = CommandExecutionForm
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_user_system_users(self):
|
||||
from perms.utils import AssetPermissionUtil
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .utils import current_org, get_current_org
|
||||
from .utils import current_org, get_org_from_request
|
||||
from .models import Organization
|
||||
|
||||
|
||||
def org_processor(request):
|
||||
context = {
|
||||
'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user),
|
||||
'CURRENT_ORG': get_current_org(),
|
||||
'CURRENT_ORG': get_org_from_request(request),
|
||||
'HAS_ORG_PERM': current_org.can_admin_by(request.user),
|
||||
}
|
||||
return context
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .models import Organization
|
||||
from .utils import get_org_from_request, set_current_org
|
||||
|
||||
|
||||
|
@ -8,7 +9,21 @@ class OrgMiddleware:
|
|||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
@staticmethod
|
||||
def set_permed_org_if_need(request):
|
||||
if request.path.startswith('/api'):
|
||||
return
|
||||
if not (request.user.is_authenticated and request.user.is_org_admin):
|
||||
return
|
||||
org = get_org_from_request(request)
|
||||
if org.can_admin_by(request.user):
|
||||
return
|
||||
admin_orgs = Organization.get_user_admin_orgs(request.user)
|
||||
if admin_orgs:
|
||||
request.session['oid'] = str(admin_orgs[0].id)
|
||||
|
||||
def __call__(self, request):
|
||||
self.set_permed_org_if_need(request)
|
||||
org = get_org_from_request(request)
|
||||
request.current_org = org
|
||||
set_current_org(org)
|
||||
|
|
|
@ -9,8 +9,11 @@ from django.forms import ModelForm
|
|||
from django.http.response import HttpResponseForbidden
|
||||
from django.core.exceptions import ValidationError
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.validators import ProjectUniqueValidator
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from .utils import (
|
||||
current_org, set_current_org, set_to_root_org, get_current_org_id
|
||||
)
|
||||
|
@ -23,6 +26,7 @@ __all__ = [
|
|||
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
|
||||
'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
|
||||
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
|
||||
'BulkOrgResourceSerializerMixin', 'BulkOrgResourceModelSerializer',
|
||||
]
|
||||
|
||||
|
||||
|
@ -215,4 +219,29 @@ class OrgResourceSerializerMixin(serializers.Serializer):
|
|||
由于HiddenField字段不可读,API获取资产信息时获取不到org_id,
|
||||
但是coco需要资产的org_id字段,所以修改为CharField类型
|
||||
"""
|
||||
org_id = serializers.CharField(default=get_current_org_id)
|
||||
org_id = serializers.ReadOnlyField(default=get_current_org_id, label=_("Organization"))
|
||||
org_name = serializers.ReadOnlyField(label=_("Org name"))
|
||||
|
||||
def get_validators(self):
|
||||
_validators = super().get_validators()
|
||||
validators = []
|
||||
|
||||
for v in _validators:
|
||||
if isinstance(v, UniqueTogetherValidator) \
|
||||
and "org_id" in v.fields:
|
||||
v = ProjectUniqueValidator(v.queryset, v.fields)
|
||||
validators.append(v)
|
||||
return validators
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend(["org_id", "org_name"])
|
||||
return fields
|
||||
|
||||
|
||||
class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerMixin):
|
||||
pass
|
||||
|
||||
|
||||
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
|
||||
pass
|
||||
|
|
|
@ -96,7 +96,7 @@ class Organization(models.Model):
|
|||
admin_orgs = []
|
||||
if user.is_anonymous:
|
||||
return admin_orgs
|
||||
elif user.is_superuser:
|
||||
elif user.is_superuser or user.is_auditor:
|
||||
admin_orgs = list(cls.objects.all())
|
||||
admin_orgs.append(cls.default())
|
||||
elif user.is_org_admin:
|
||||
|
|
|
@ -14,7 +14,6 @@ from rest_framework.pagination import LimitOffsetPagination
|
|||
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
|
||||
from common.tree import TreeNodeSerializer
|
||||
from common.utils import get_logger
|
||||
from orgs.utils import set_to_root_org
|
||||
from ..utils import (
|
||||
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
|
||||
check_system_user_action, RemoteAppPermissionUtil,
|
||||
|
@ -26,7 +25,7 @@ from ..hands import (
|
|||
)
|
||||
from .. import serializers, const
|
||||
from ..mixins import (
|
||||
AssetsFilterMixin, RemoteAppFilterMixin, ChangeOrgIfNeedMixin
|
||||
AssetsFilterMixin, RemoteAppFilterMixin
|
||||
)
|
||||
from ..models import Action
|
||||
|
||||
|
@ -48,14 +47,6 @@ class UserPermissionCacheMixin:
|
|||
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
|
||||
_object = None
|
||||
|
||||
@staticmethod
|
||||
def change_org_if_need(request, kwargs):
|
||||
if request.user.is_authenticated and \
|
||||
request.user.is_superuser or \
|
||||
request.user.is_app or \
|
||||
kwargs.get('pk') is None:
|
||||
set_to_root_org()
|
||||
|
||||
def get_object(self):
|
||||
return None
|
||||
|
||||
|
@ -115,7 +106,6 @@ class UserPermissionCacheMixin:
|
|||
cache.set(key, response.data, self.CACHE_TIME)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.change_org_if_need(request, kwargs)
|
||||
self.cache_policy = request.GET.get('cache_policy', '0')
|
||||
|
||||
obj = self._get_object()
|
||||
|
@ -156,7 +146,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
|
|||
util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
|
||||
assets = util.get_assets()
|
||||
for k, v in assets.items():
|
||||
system_users_granted = [s for s in v if s.protocol == k.protocol]
|
||||
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
|
||||
k.system_users_granted = system_users_granted
|
||||
queryset.append(k)
|
||||
return queryset
|
||||
|
@ -217,8 +207,7 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
|
|||
for node, _assets in nodes.items():
|
||||
assets = _assets.keys()
|
||||
for k, v in _assets.items():
|
||||
system_users_granted = [s for s in v if
|
||||
s.protocol == k.protocol]
|
||||
system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
|
||||
k.system_users_granted = system_users_granted
|
||||
node.assets_granted = assets
|
||||
queryset.append(node)
|
||||
|
@ -366,7 +355,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
|
|||
for asset, system_users in nodes_granted[node].items():
|
||||
fake_node = asset.as_node()
|
||||
fake_node.assets_amount = 0
|
||||
system_users = [s for s in system_users if s.protocol == asset.protocol]
|
||||
system_users = [s for s in system_users if asset.has_protocol(s.protocol)]
|
||||
fake_node.asset.system_users_granted = system_users
|
||||
fake_node.key = node.key + ':0'
|
||||
fake_nodes.append(fake_node)
|
||||
|
@ -391,7 +380,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
|
|||
fake_node = asset.as_node()
|
||||
fake_node.assets_amount = 0
|
||||
system_users = [s for s in system_users if
|
||||
s.protocol == asset.protocol]
|
||||
asset.has_protocol(s.protocol)]
|
||||
fake_node.asset.system_users_granted = system_users
|
||||
fake_node.key = node.key + ':0'
|
||||
matched_assets.append(fake_node)
|
||||
|
@ -462,7 +451,7 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
|
|||
|
||||
# RemoteApp permission
|
||||
|
||||
class UserGrantedRemoteAppsApi(ChangeOrgIfNeedMixin, RemoteAppFilterMixin, ListAPIView):
|
||||
class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = RemoteAppSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
@ -487,7 +476,7 @@ class UserGrantedRemoteAppsApi(ChangeOrgIfNeedMixin, RemoteAppFilterMixin, ListA
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class UserGrantedRemoteAppsAsTreeApi(ChangeOrgIfNeedMixin, ListAPIView):
|
||||
class UserGrantedRemoteAppsAsTreeApi(ListAPIView):
|
||||
serializer_class = TreeNodeSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
|
@ -519,7 +508,7 @@ class UserGrantedRemoteAppsAsTreeApi(ChangeOrgIfNeedMixin, ListAPIView):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class ValidateUserRemoteAppPermissionApi(ChangeOrgIfNeedMixin, APIView):
|
||||
class ValidateUserRemoteAppPermissionApi(APIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
@ -533,5 +522,4 @@ class ValidateUserRemoteAppPermissionApi(ChangeOrgIfNeedMixin, APIView):
|
|||
remote_apps = util.get_remote_apps()
|
||||
if remote_app not in remote_apps:
|
||||
return Response({'msg': False}, status=403)
|
||||
|
||||
return Response({'msg': True}, status=200)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# ~*~ coding: utf-8 ~*~
|
||||
#
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from users.models import User, UserGroup
|
||||
from assets.models import Asset, SystemUser, Node, RemoteApp
|
||||
from assets.models import Asset, SystemUser, Node
|
||||
from assets.serializers import (
|
||||
AssetGrantedSerializer, NodeSerializer
|
||||
)
|
||||
from applications.serializers import RemoteAppSerializer
|
||||
from applications.models import RemoteApp
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from common.fields import StringManyToManyField
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from perms.models import AssetPermission, Action
|
||||
from assets.models import Node, Asset, SystemUser
|
||||
from assets.models import Node
|
||||
from assets.serializers import AssetGrantedSerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
|
||||
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
|
||||
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
|
||||
'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
|
||||
'ActionSerializer', 'NodeGrantedSerializer',
|
||||
]
|
||||
|
||||
|
@ -23,13 +23,13 @@ class ActionSerializer(serializers.ModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = AssetPermission
|
||||
exclude = ('created_by', 'date_created')
|
||||
|
||||
|
||||
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
||||
class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
|
||||
users = StringManyToManyField(many=True, read_only=True)
|
||||
user_groups = StringManyToManyField(many=True, read_only=True)
|
||||
assets = StringManyToManyField(many=True, read_only=True)
|
||||
|
@ -122,19 +122,21 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class GrantedAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'id', 'hostname', 'ip', 'port', 'protocol', 'platform',
|
||||
'domain', 'is_active', 'comment'
|
||||
]
|
||||
# class GrantedAssetSerializer(serializers.ModelSerializer):
|
||||
# protocols = ProtocolSerializer(many=True)
|
||||
#
|
||||
# class Meta:
|
||||
# model = Asset
|
||||
# fields = [
|
||||
# 'id', 'hostname', 'ip', 'protocols', 'port', 'protocol',
|
||||
# 'platform', 'domain', 'is_active', 'comment'
|
||||
# ]
|
||||
|
||||
|
||||
class GrantedSystemUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'id', 'name', 'username', 'protocol', 'priority',
|
||||
'login_mode', 'comment'
|
||||
]
|
||||
# class GrantedSystemUserSerializer(serializers.ModelSerializer):
|
||||
# class Meta:
|
||||
# model = SystemUser
|
||||
# fields = [
|
||||
# 'id', 'name', 'username', 'protocol', 'priority',
|
||||
# 'login_mode', 'comment'
|
||||
# ]
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import RemoteAppPermission
|
||||
|
||||
|
||||
|
@ -13,13 +15,14 @@ __all__ = [
|
|||
]
|
||||
|
||||
|
||||
class RemoteAppPermissionSerializer(serializers.ModelSerializer):
|
||||
class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer):
|
||||
class Meta:
|
||||
model = RemoteAppPermission
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment',
|
||||
'is_active', 'date_start', 'date_expired', 'is_valid',
|
||||
'created_by', 'date_created', 'org_id'
|
||||
'created_by', 'date_created',
|
||||
]
|
||||
read_only_fields = ['created_by', 'date_created']
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue