Unverified Commit 9ca4a8c9 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

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] 修改首页组织名称显示来源
parent 844f9bf4
......@@ -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:
......
# -*- 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
from .manager import AssetUserManager
# -*- coding: utf-8 -*-
#
from ..models import AdminUser
from .asset_user import AssetUserBackend
class AdminUserBackend(AssetUserBackend):
model = AdminUser
backend = 'AdminUser'
# -*- 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
# -*- 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
# -*- 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
......
# -*- 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")
# -*- 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")
# -*- 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")
# -*- 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
# -*- 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
# -*- 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
# -*- 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
# -*- coding: utf-8 -*-
#
from ..base import BaseBackend
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
# -*- 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'),
),
]
# 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'),
),
]
# 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),
]
# 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'),
),
]
# 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 *
This diff is collapsed.
......@@ -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
cache.set(self._conn_cache_key, value, 3600)
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
@property
def keyword(self):
return '{}_#_{}'.format(self.username, str(self.asset.id))
cache.set(self._conn_cache_key, _connectivity, 3600)
@property
def hostname(self):
return self.asset.hostname
@property
def keyword(self):
return {'username': self.username, 'asset': self.asset}
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',
......
# -*- 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
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
@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):
# 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:
......
# -*- 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:
......
This diff is collapsed.
......@@ -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();
}
......
{% 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 %}
{% 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 %}
......
{% 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>
\ No newline at end of file
......@@ -95,41 +95,97 @@ 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') {
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');
$(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');
$.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 === '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');
$(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');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').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() {
......@@ -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 %}
\ No newline at end of file
......@@ -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;
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
}
else if(protocol === 'telnet (beta)'){
}
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;
}
else if(protocol === 'vnc'){
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
}
$("#id_port").val(port);
});
})
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 %}
\ No newline at end of file
{% 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>
......
{% 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 %}
\ No newline at end of file
......@@ -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
{% 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')
......
# -*- 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'))
\ No newline at end of file
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):
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -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')
......
# 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,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment