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 _ ...@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django import forms from django import forms
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from assets.models import Asset, SystemUser from assets.models import SystemUser, Protocol
from ..models import RemoteApp from ..models import RemoteApp
from .. import const from .. import const
...@@ -89,7 +89,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm): ...@@ -89,7 +89,7 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
field_asset = self.fields['asset'] field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.filter( field_asset.queryset = field_asset.queryset.filter(
protocol=Asset.PROTOCOL_RDP protocols__name=Protocol.PROTOCOL_RDP
) )
field_system_user = self.fields['system_user'] field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter( field_system_user.queryset = field_system_user.queryset.filter(
......
...@@ -11,6 +11,5 @@ ...@@ -11,6 +11,5 @@
""" """
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup from users.models import User, UserGroup
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
from rest_framework import serializers from rest_framework import serializers
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from .. import const from .. import const
from ..models import RemoteApp from ..models import RemoteApp
...@@ -66,7 +66,7 @@ class RemoteAppParamsDictField(serializers.DictField): ...@@ -66,7 +66,7 @@ class RemoteAppParamsDictField(serializers.DictField):
return value return value
class RemoteAppSerializer(BulkSerializerMixin, serializers.ModelSerializer): class RemoteAppSerializer(BulkOrgResourceModelSerializer):
params = RemoteAppParamsDictField() params = RemoteAppParamsDictField()
class Meta: class Meta:
......
...@@ -6,11 +6,10 @@ from django.views.generic import TemplateView ...@@ -6,11 +6,10 @@ from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy 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 common.const import create_success_msg, update_success_msg
from ..models import RemoteApp from ..models import RemoteApp
...@@ -23,27 +22,29 @@ __all__ = [ ...@@ -23,27 +22,29 @@ __all__ = [
] ]
class RemoteAppListView(AdminUserRequiredMixin, TemplateView): class RemoteAppListView(PermissionsMixin, TemplateView):
template_name = 'applications/remote_app_list.html' template_name = 'applications/remote_app_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Applications'),
'action': _('RemoteApp list'), 'action': _('RemoteApp list'),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
template_name = 'applications/remote_app_create_update.html' template_name = 'applications/remote_app_create_update.html'
model = RemoteApp model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list') success_url = reverse_lazy('applications:remote-app-list')
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Applications'),
'action': _('Create RemoteApp'), 'action': _('Create RemoteApp'),
} }
kwargs.update(context) kwargs.update(context)
...@@ -53,18 +54,19 @@ class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie ...@@ -53,18 +54,19 @@ class RemoteAppCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
return create_success_msg % ({'name': cleaned_data['name']}) 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' template_name = 'applications/remote_app_create_update.html'
model = RemoteApp model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list') success_url = reverse_lazy('applications:remote-app-list')
permission_classes = [IsOrgAdmin]
def get_initial(self): def get_initial(self):
return {k: v for k, v in self.object.params.items()} return {k: v for k, v in self.object.params.items()}
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Applications'),
'action': _('Update RemoteApp'), 'action': _('Update RemoteApp'),
} }
kwargs.update(context) kwargs.update(context)
...@@ -74,22 +76,24 @@ class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie ...@@ -74,22 +76,24 @@ class RemoteAppUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
return update_success_msg % ({'name': cleaned_data['name']}) return update_success_msg % ({'name': cleaned_data['name']})
class RemoteAppDetailView(AdminUserRequiredMixin, DetailView): class RemoteAppDetailView(PermissionsMixin, DetailView):
template_name = 'applications/remote_app_detail.html' template_name = 'applications/remote_app_detail.html'
model = RemoteApp model = RemoteApp
context_object_name = 'remote_app' context_object_name = 'remote_app'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Assets'), 'app': _('Applications'),
'action': _('RemoteApp detail'), 'action': _('RemoteApp detail'),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserRemoteAppListView(LoginRequiredMixin, TemplateView): class UserRemoteAppListView(PermissionsMixin, TemplateView):
template_name = 'applications/user_remote_app_list.html' template_name = 'applications/user_remote_app_list.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -16,7 +16,7 @@ from django.urls import reverse_lazy ...@@ -16,7 +16,7 @@ from django.urls import reverse_lazy
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Q 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.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
...@@ -36,17 +36,18 @@ __all__ = [ ...@@ -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. API endpoint that allows Asset to be viewed or edited.
""" """
filter_fields = ("hostname", "ip") filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
search_fields = filter_fields search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores") ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all() queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully")
def set_assets_node(self, assets): def set_assets_node(self, assets):
if not isinstance(assets, list): if not isinstance(assets, list):
...@@ -169,8 +170,8 @@ class AssetGatewayApi(generics.RetrieveAPIView): ...@@ -169,8 +170,8 @@ class AssetGatewayApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id) asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \ if asset.domain and \
asset.domain.gateways.filter(protocol=asset.protocol).exists(): asset.domain.gateways.filter(protocol='ssh').exists():
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol)) gateway = random.choice(asset.domain.gateways.filter(protocol='ssh'))
serializer = serializers.GatewayWithAuthSerializer(instance=gateway) serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data) return Response(serializer.data)
else: else:
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import time
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets, status, generics from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
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.permissions import IsOrgAdminOrAppUser
from common.utils import get_object_or_none, get_logger from common.utils import get_object_or_none, get_logger
from common.mixins import IDInCacheFilterMixin
from ..backends.multi import AssetUserManager from ..backends import AssetUserManager
from ..models import Asset from ..models import Asset, Node, SystemUser, AdminUser
from .. import serializers from .. import serializers
from ..tasks import test_asset_users_connectivity_manual from ..tasks import test_asset_users_connectivity_manual
__all__ = [ __all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi', 'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
'AssetUserExportViewSet',
] ]
logger = get_logger(__name__) 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 pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer serializer_class = serializers.AssetUserSerializer
permission_classes = (IsOrgAdminOrAppUser, ) permission_classes = (IsOrgAdminOrAppUser, )
http_method_names = ['get', 'post'] http_method_names = ['get', 'post']
filter_fields = [
def create(self, request, *args, **kwargs): "id", "ip", "hostname", "username", "asset_id", "node_id",
serializer = self.get_serializer(data=request.data) "system_user_id", "admin_user_id"
serializer.is_valid(raise_exception=True) ]
serializer.save() search_fields = filter_fields
return Response(serializer.data, status=status.HTTP_201_CREATED) filter_backends = (
filters.OrderingFilter,
def list(self, request, *args, **kwargs): AssetUserFilterBackend, AssetUserSearchBackend,
queryset = self.filter_queryset(self.get_queryset()) )
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get_queryset(self): def get_queryset(self):
# 尽可能先返回更少的数据
username = self.request.GET.get('username') username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id') asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=asset_id) node_id = self.request.GET.get('node_id')
queryset = AssetUserManager.filter(username=username, asset=asset) 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 return queryset
def filter_queryset(self, queryset):
queryset = sorted( class AssetUserExportViewSet(AssetUserViewSet):
queryset, serializer_class = serializers.AssetUserExportSerializer
key=lambda q: (q.asset.hostname, q.connectivity, q.username) http_method_names = ['get']
)
return queryset def list(self, request, *args, **kwargs):
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
return super().list(request, *args, **kwargs)
class AssetUserAuthInfoApi(generics.RetrieveAPIView): class AssetUserAuthInfoApi(generics.RetrieveAPIView):
...@@ -60,6 +123,10 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -60,6 +123,10 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
otp_last_verify = request.session.get("OTP_LAST_VERIFY_TIME")
if not otp_last_verify or time.time() - int(otp_last_verify) > 600:
return Response({"error": "Need MFA confirm mfa auth"}, status=403)
instance = self.get_object() instance = self.get_object()
serializer = self.get_serializer(instance) serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK status_code = status.HTTP_200_OK
...@@ -70,9 +137,13 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView): ...@@ -70,9 +137,13 @@ class AssetUserAuthInfoApi(generics.RetrieveAPIView):
def get_object(self): def get_object(self):
username = self.request.GET.get('username') username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id') asset_id = self.request.GET.get('asset_id')
prefer = self.request.GET.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id) asset = get_object_or_none(Asset, pk=asset_id)
try: try:
instance = AssetUserManager.get(username, asset) manger = AssetUserManager()
if prefer:
manger.prefer(prefer)
instance = manger.get(username, asset)
except Exception as e: except Exception as e:
logger.error(e, exc_info=True) logger.error(e, exc_info=True)
return None return None
...@@ -84,18 +155,36 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -84,18 +155,36 @@ class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
""" """
Test asset users connective Test asset users connective
""" """
permission_classes = (IsOrgAdminOrAppUser,)
def get_asset_users(self): def get_asset_users(self):
username = self.request.GET.get('username') username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id') asset_id = self.request.GET.get('asset_id')
asset = get_object_or_none(Asset, pk=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 return asset_users
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
asset_users = self.get_asset_users() 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}) 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 -*- # -*- coding: utf-8 -*-
# #
import uuid
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from abc import abstractmethod from abc import abstractmethod
class NotSupportError(Exception):
pass
class BaseBackend: 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 @classmethod
@abstractmethod @abstractmethod
def filter(cls, username=None, asset=None, latest=True): def filter(cls, username=None, assets=None, latest=True):
""" """
:param username: 用户名 :param username: 用户名
:param asset: <Asset>对象 :param assets: <Asset>对象
:param latest: 是否是最新记录 :param latest: 是否是最新记录
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>) :return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
""" """
pass pass
@classmethod
@abstractmethod
def create(cls, **kwargs):
"""
:param kwargs:
{
name, username, asset, comment, password, public_key, private_key,
(org_id)
}
:return: <AuthBook>对象
"""
pass
@classmethod class AssetUserQuerySet(list):
def raise_does_not_exist(cls, name): def order_by(self, *ordering):
raise cls.ObjectDoesNotExist(cls.MSG_NOT_EXIST.format(name)) _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 filter_in(self, kwargs):
def raise_multiple_return(cls, name, length): in_kwargs = {}
raise cls.MultipleObjectsReturned(cls.MSG_MULTIPLE.format(name, length)) 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 -*- # -*- coding: utf-8 -*-
# #
from assets.models import AuthBook from ..models import AuthBook
from .base import BaseBackend
from ..base import BaseBackend
class AuthBookBackend(BaseBackend): class AuthBookBackend(BaseBackend):
@classmethod @classmethod
def filter(cls, username=None, asset=None, latest=True): def filter(cls, username=None, assets=None, latest=True):
queryset = AuthBook.objects.all() queryset = AuthBook.objects.all()
if username is not None: if username is not None:
queryset = queryset.filter(username=username) queryset = queryset.filter(username=username)
if asset: if assets:
queryset = queryset.filter(asset=asset) queryset = queryset.filter(asset__in=assets)
if latest: if latest:
queryset = queryset.latest_version() queryset = queryset.latest_version()
return queryset 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 -*- # -*- coding: utf-8 -*-
# #
from ..base import BaseBackend from .base import BaseBackend
class VaultBackend(BaseBackend): class VaultBackend(BaseBackend):
@classmethod
def get(cls, username, asset):
pass
@classmethod @classmethod
def filter(cls, username=None, asset=None, latest=True): def filter(cls, username=None, asset=None, latest=True):
pass pass
@classmethod
def create(cls, **kwargs):
pass
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
UPDATE_ASSETS_HARDWARE_TASKS = [ UPDATE_ASSETS_HARDWARE_TASKS = [
{ {
...@@ -22,6 +20,14 @@ TEST_ADMIN_USER_CONN_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_{}" ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
...@@ -34,9 +40,16 @@ TEST_SYSTEM_USER_CONN_TASKS = [ ...@@ -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 = [ TEST_ASSET_USER_CONN_TASKS = [
{ {
"name": "ping", "name": "ping",
...@@ -45,6 +58,14 @@ TEST_ASSET_USER_CONN_TASKS = [ ...@@ -45,6 +58,14 @@ TEST_ASSET_USER_CONN_TASKS = [
} }
} }
] ]
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
TASK_OPTIONS = { TASK_OPTIONS = {
......
...@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _ ...@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from orgs.mixins import OrgModelForm from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser from ..models import Asset, Protocol
logger = get_logger(__file__) 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): class AssetCreateForm(OrgModelForm):
PROTOCOL_CHOICES = Protocol.PROTOCOL_CHOICES
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment', 'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform', 'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'protocol', 'domain',
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
...@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm): ...@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={ 'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label') 'class': 'select2', 'data-placeholder': _('Label')
}), }),
'port': forms.TextInput(),
'domain': forms.Select(attrs={ 'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain') 'class': 'select2', 'data-placeholder': _('Domain')
}), }),
...@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm): ...@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform', 'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels', 'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain', 'protocol', 'domain',
] ]
widgets = { widgets = {
'nodes': forms.SelectMultiple(attrs={ 'nodes': forms.SelectMultiple(attrs={
...@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm): ...@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={ 'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label') 'class': 'select2', 'data-placeholder': _('Label')
}), }),
'port': forms.TextInput(),
'domain': forms.Select(attrs={ 'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain') 'class': 'select2', 'data-placeholder': _('Domain')
}), }),
...@@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm): ...@@ -101,8 +117,8 @@ class AssetBulkUpdateForm(OrgModelForm):
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
'assets', 'port', 'admin_user', 'labels', 'platform', 'assets', 'admin_user', 'labels', 'platform',
'protocol', 'domain', 'domain',
] ]
widgets = { widgets = {
'labels': forms.SelectMultiple( 'labels': forms.SelectMultiple(
......
...@@ -100,16 +100,18 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm): ...@@ -100,16 +100,18 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
private_key, public_key = super().gen_keys() private_key, public_key = super().gen_keys()
if login_mode == SystemUser.LOGIN_MANUAL or \ if login_mode == SystemUser.LOGIN_MANUAL or \
protocol in [SystemUser.PROTOCOL_RDP, protocol in [SystemUser.PROTOCOL_TELNET,
SystemUser.PROTOCOL_TELNET,
SystemUser.PROTOCOL_VNC]: SystemUser.PROTOCOL_VNC]:
system_user.auto_push = 0 system_user.auto_push = 0
auto_generate_key = False
system_user.save() system_user.save()
auto_generate_key = False
if auto_generate_key: if auto_generate_key:
logger.info('Auto generate key and set system user auth') 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: else:
system_user.set_auth(password=password, private_key=private_key, system_user.set_auth(password=password, private_key=private_key,
public_key=public_key) public_key=public_key)
......
...@@ -11,6 +11,5 @@ ...@@ -11,6 +11,5 @@
""" """
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup from users.models import User, UserGroup
...@@ -15,4 +15,9 @@ class Migration(migrations.Migration): ...@@ -15,4 +15,9 @@ class Migration(migrations.Migration):
name='ip', name='ip',
field=models.CharField(db_index=True, max_length=128, verbose_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 * ...@@ -8,4 +8,3 @@ from .asset import *
from .cmd_filter import * from .cmd_filter import *
from .utils import * from .utils import *
from .authbook import * from .authbook import *
from applications.models.remote_app import *
...@@ -12,11 +12,12 @@ from django.db import models ...@@ -12,11 +12,12 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache from django.core.cache import cache
from django.core.validators import MinValueValidator, MaxValueValidator
from .user import AdminUser, SystemUser from .user import AdminUser, SystemUser
from orgs.mixins import OrgModelMixin, OrgManager from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset'] __all__ = ['Asset', 'Protocol']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -47,18 +48,13 @@ class AssetQuerySet(models.QuerySet): ...@@ -47,18 +48,13 @@ class AssetQuerySet(models.QuerySet):
return self.active() return self.active()
class Asset(OrgModelMixin): class AssetManager(OrgManager):
# Important def get_queryset(self):
PLATFORM_CHOICES = ( queryset = super().get_queryset().prefetch_related("nodes", "protocols")
('Linux', 'Linux'), return queryset
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
class Protocol(models.Model):
PROTOCOL_SSH = 'ssh' PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp' PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet' PROTOCOL_TELNET = 'telnet'
...@@ -69,22 +65,49 @@ class Asset(OrgModelMixin): ...@@ -69,22 +65,49 @@ class Asset(OrgModelMixin):
(PROTOCOL_TELNET, 'telnet (beta)'), (PROTOCOL_TELNET, 'telnet (beta)'),
(PROTOCOL_VNC, 'vnc'), (PROTOCOL_VNC, 'vnc'),
) )
PORT_VALIDATORS = [MaxValueValidator(65535), MinValueValidator(1)]
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=16, choices=PROTOCOL_CHOICES,
default=PROTOCOL_SSH, verbose_name=_("Name"))
port = models.IntegerField(default=22, verbose_name=_("Port"),
validators=PORT_VALIDATORS)
def __str__(self):
return "{}/{}".format(self.name, self.port)
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname')) hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol')) protocol = models.CharField(max_length=128, default=Protocol.PROTOCOL_SSH,
choices=Protocol.PROTOCOL_CHOICES,
verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port')) port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocol"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform')) platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL) domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes")) nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active')) is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth # Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user")) admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
# Some information # Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP')) public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number')) number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
# Collect # Collect
...@@ -110,7 +133,7 @@ class Asset(OrgModelMixin): ...@@ -110,7 +133,7 @@ class Asset(OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)() objects = AssetManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}' CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = ( CONNECTIVITY_CHOICES = (
...@@ -131,19 +154,53 @@ class Asset(OrgModelMixin): ...@@ -131,19 +154,53 @@ class Asset(OrgModelMixin):
return True, '' return True, ''
return False, warning return False, warning
def support_ansible(self): @property
if self.platform in ("Windows", "Windows2016", "Other"): def protocols_name(self):
return False names = []
if self.protocol != 'ssh': for protocol in self.protocols.all():
names.append(protocol.name)
return names
def has_protocol(self, name):
return name in self.protocols_name
def get_protocol_by_name(self, name):
for i in self.protocols.all():
if i.name.lower() == name.lower():
return i
return None
@property
def protocol_ssh(self):
return self.get_protocol_by_name("ssh")
@property
def protocol_rdp(self):
return self.get_protocol_by_name("rdp")
@property
def ssh_port(self):
if self.protocol_ssh:
port = self.protocol_ssh.port
else:
port = 22
return port
def is_windows(self):
if self.platform in ("Windows", "Windows2016"):
return True
else:
return False return False
return True
def is_unixlike(self): def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016"): if self.platform not in ("Windows", "Windows2016", "Other"):
return True return True
else: else:
return False return False
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
def get_nodes(self): def get_nodes(self):
from .node import Node from .node import Node
nodes = self.nodes.all() or [Node.root()] nodes = self.nodes.all() or [Node.root()]
...@@ -172,6 +229,15 @@ class Asset(OrgModelMixin): ...@@ -172,6 +229,15 @@ class Asset(OrgModelMixin):
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts) filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
return Asset.objects.filter(filter_arg) return Asset.objects.filter(filter_arg)
@property
def cpu_info(self):
info = ""
if self.cpu_model:
info += self.cpu_model
if self.cpu_count and self.cpu_cores:
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
return info
@property @property
def hardware_info(self): def hardware_info(self):
if self.cpu_count: if self.cpu_count:
...@@ -184,26 +250,27 @@ class Asset(OrgModelMixin): ...@@ -184,26 +250,27 @@ class Asset(OrgModelMixin):
@property @property
def connectivity(self): def connectivity(self):
if not self.is_unixlike(): if not self.admin_user:
return self.REACHABLE return self.UNKNOWN
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id)) return self.admin_user.get_connectivity_of(self)
cached = cache.get(key, None)
return cached if cached is not None else self.UNKNOWN
@connectivity.setter @connectivity.setter
def connectivity(self, value): def connectivity(self, value):
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id)) if not self.admin_user:
cache.set(key, value, 3600*2) return
self.admin_user.set_connectivity_of(self, value)
def get_auth_info(self): def get_auth_info(self):
if self.admin_user: if not self.admin_user:
self.admin_user.load_specific_asset_auth(self) return {}
return {
'username': self.admin_user.username, self.admin_user.load_specific_asset_auth(self)
'password': self.admin_user.password, info = {
'private_key': self.admin_user.private_key_file, 'username': self.admin_user.username,
'become': self.admin_user.become_info, 'password': self.admin_user.password,
} 'private_key': self.admin_user.private_key_file,
}
return info
def as_node(self): def as_node(self):
from .node import Node from .node import Node
...@@ -215,35 +282,6 @@ class Asset(OrgModelMixin): ...@@ -215,35 +282,6 @@ class Asset(OrgModelMixin):
fake_node.is_node = False fake_node.is_node = False
return fake_node return fake_node
def to_json(self):
info = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def _to_secret_json(self):
"""
Ansible use it create inventory
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
self.admin_user.load_specific_asset_auth(self)
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
def as_tree_node(self, parent_node): def as_tree_node(self, parent_node):
from common.tree import TreeNode from common.tree import TreeNode
icon_skin = 'file' icon_skin = 'file'
...@@ -265,9 +303,11 @@ class Asset(OrgModelMixin): ...@@ -265,9 +303,11 @@ class Asset(OrgModelMixin):
'id': self.id, 'id': self.id,
'hostname': self.hostname, 'hostname': self.hostname,
'ip': self.ip, 'ip': self.ip,
'port': self.port, 'protocols': [
{"name": p.name, "port": p.port}
for p in self.protocols.all()
],
'platform': self.platform, 'platform': self.platform,
'protocol': self.protocol,
} }
} }
} }
...@@ -291,10 +331,10 @@ class Asset(OrgModelMixin): ...@@ -291,10 +331,10 @@ class Asset(OrgModelMixin):
asset = cls(ip='.'.join(ip), asset = cls(ip='.'.join(ip),
hostname=forgery_py.internet.user_name(True), hostname=forgery_py.internet.user_name(True),
admin_user=choice(AdminUser.objects.all()), admin_user=choice(AdminUser.objects.all()),
port=22,
created_by='Fake') created_by='Fake')
try: try:
asset.save() asset.save()
asset.protocols.create(name="ssh", port=22)
if nodes and len(nodes) > 3: if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3) _nodes = random.sample(nodes, 3)
else: else:
......
...@@ -29,6 +29,9 @@ class AuthBook(AssetUser): ...@@ -29,6 +29,9 @@ class AuthBook(AssetUser):
version = models.IntegerField(default=1, verbose_name=_('Version')) version = models.IntegerField(default=1, verbose_name=_('Version'))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)() objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
backend = "db"
# 用于system user和admin_user的动态设置
_connectivity = None
class Meta: class Meta:
verbose_name = _('AuthBook') verbose_name = _('AuthBook')
...@@ -40,7 +43,8 @@ class AuthBook(AssetUser): ...@@ -40,7 +43,8 @@ class AuthBook(AssetUser):
def _get_pre_obj(self): def _get_pre_obj(self):
pre_obj = self.__class__.objects.filter( 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 return pre_obj
def _remove_pre_obj_latest(self): def _remove_pre_obj_latest(self):
...@@ -63,30 +67,30 @@ class AuthBook(AssetUser): ...@@ -63,30 +67,30 @@ class AuthBook(AssetUser):
@property @property
def _conn_cache_key(self): 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 @property
def connectivity(self): def connectivity(self):
if self._connectivity:
return self._connectivity
value = cache.get(self._conn_cache_key, self.UNKNOWN) value = cache.get(self._conn_cache_key, self.UNKNOWN)
return value return value
@connectivity.setter @connectivity.setter
def connectivity(self, value): def connectivity(self, value):
_connectivity = self.UNKNOWN cache.set(self._conn_cache_key, value, 3600)
for host in value.get('dark', {}).keys(): @property
if host == self.asset.hostname: def keyword(self):
_connectivity = self.UNREACHABLE return '{}_#_{}'.format(self.username, str(self.asset.id))
for host in value.get('contacted', []):
if host == self.asset.hostname:
_connectivity = self.REACHABLE
cache.set(self._conn_cache_key, _connectivity, 3600) @property
def hostname(self):
return self.asset.hostname
@property @property
def keyword(self): def ip(self):
return {'username': self.username, 'asset': self.asset} return self.asset.ip
def __str__(self): def __str__(self):
return '{}@{}'.format(self.username, self.asset) return '{}@{}'.format(self.username, self.asset)
......
...@@ -6,6 +6,7 @@ from hashlib import md5 ...@@ -6,6 +6,7 @@ from hashlib import md5
import sshpubkeys import sshpubkeys
from django.db import models from django.db import models
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
...@@ -29,8 +30,8 @@ class AssetUser(OrgModelMixin): ...@@ -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, ]) _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')) _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True) date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by')) created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3) UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
...@@ -39,6 +40,8 @@ class AssetUser(OrgModelMixin): ...@@ -39,6 +40,8 @@ class AssetUser(OrgModelMixin):
(REACHABLE, _('Reachable')), (REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")), (UNKNOWN, _("Unknown")),
) )
CONNECTIVITY_CACHE_KEY = "CONNECTIVITY_{}"
_prefer = "system_user"
@property @property
def password(self): def password(self):
...@@ -124,10 +127,21 @@ class AssetUser(OrgModelMixin): ...@@ -124,10 +127,21 @@ class AssetUser(OrgModelMixin):
def get_auth(self, asset=None): def get_auth(self, asset=None):
pass 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): def load_specific_asset_auth(self, asset):
from ..backends.multi import AssetUserManager from ..backends import AssetUserManager
try: 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: except Exception as e:
logger.error(e, exc_info=True) logger.error(e, exc_info=True)
else: else:
...@@ -158,6 +172,10 @@ class AssetUser(OrgModelMixin): ...@@ -158,6 +172,10 @@ class AssetUser(OrgModelMixin):
private_key=private_key, private_key=private_key,
public_key=public_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): def _to_secret_json(self):
"""Push system user use it""" """Push system user use it"""
return { return {
...@@ -168,5 +186,25 @@ class AssetUser(OrgModelMixin): ...@@ -168,5 +186,25 @@ class AssetUser(OrgModelMixin):
'private_key': self.private_key_file, '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: class Meta:
abstract = True abstract = True
...@@ -32,6 +32,7 @@ class AdminUser(AssetUser): ...@@ -32,6 +32,7 @@ class AdminUser(AssetUser):
become_user = models.CharField(default='root', max_length=64) become_user = models.CharField(default='root', max_length=64)
_become_pass = models.CharField(default='', max_length=128) _become_pass = models.CharField(default='', max_length=128)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}' CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user"
def __str__(self): def __str__(self):
return self.name return self.name
...@@ -61,7 +62,7 @@ class AdminUser(AssetUser): ...@@ -61,7 +62,7 @@ class AdminUser(AssetUser):
return info return info
def get_related_assets(self): def get_related_assets(self):
assets = self.asset_set.all() assets = self.assets.all()
return assets return assets
@property @property
...@@ -174,17 +175,20 @@ class SystemUser(AssetUser): ...@@ -174,17 +175,20 @@ class SystemUser(AssetUser):
data = self.connectivity data = self.connectivity
unreachable = data['unreachable'] unreachable = data['unreachable']
reachable = data['reachable'] reachable = data['reachable']
assets = {asset.hostname: asset for asset in self.assets.all()}
for host in value.get('dark', {}).keys(): for host in value.get('dark', {}).keys():
if host not in unreachable: if host not in unreachable:
unreachable.append(host) unreachable.append(host)
if host in reachable: if host in reachable:
reachable.remove(host) reachable.remove(host)
self.set_connectivity_of(assets.get(host), self.UNREACHABLE)
for host in value.get('contacted'): for host in value.get('contacted'):
if host not in reachable: if host not in reachable:
reachable.append(host) reachable.append(host)
if host in unreachable: if host in unreachable:
unreachable.remove(host) unreachable.remove(host)
self.set_connectivity_of(assets.get(host), self.REACHABLE)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id)) cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600) cache.set(cache_key, data, 3600)
...@@ -201,7 +205,7 @@ class SystemUser(AssetUser): ...@@ -201,7 +205,7 @@ class SystemUser(AssetUser):
return self.get_login_mode_display() return self.get_login_mode_display()
def is_need_push(self): 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 return True
else: else:
return False return False
......
...@@ -8,11 +8,12 @@ from common.serializers import AdaptedBulkListSerializer ...@@ -8,11 +8,12 @@ from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY from ..const import ADMIN_USER_CONN_CACHE_KEY
from orgs.mixins import BulkOrgResourceModelSerializer
from .base import AuthSerializer from .base import AuthSerializer
class AdminUserSerializer(serializers.ModelSerializer): class AdminUserSerializer(BulkOrgResourceModelSerializer):
""" """
管理用户 管理用户
""" """
...@@ -27,7 +28,7 @@ class AdminUserSerializer(serializers.ModelSerializer): ...@@ -27,7 +28,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
model = AdminUser model = AdminUser
fields = [ fields = [
'id', 'org_id', 'name', 'username', 'assets_amount', 'id', 'name', 'username', 'assets_amount',
'reachable_amount', 'unreachable_amount', 'password', 'comment', 'reachable_amount', 'unreachable_amount', 'password', 'comment',
'date_created', 'date_updated', 'become', 'become_method', 'date_created', 'date_updated', 'become', 'become_method',
'become_user', 'created_by', 'become_user', 'created_by',
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework.validators import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgResourceSerializerMixin from orgs.mixins import BulkOrgResourceModelSerializer
from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import Asset from ..models import Asset, Protocol
from .system_user import AssetSystemUserSerializer from .system_user import AssetSystemUserSerializer
__all__ = [ __all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', 'AssetSerializer', 'AssetGrantedSerializer', 'AssetSimpleSerializer',
'AssetAsNodeSerializer', '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: class Meta:
model = Asset model = Asset
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
# validators = [] # 解决批量导入时unique_together字段校验失败
fields = [ fields = [
'id', 'org_id', 'org_name', 'ip', 'hostname', 'protocol', 'port', 'id', 'ip', 'hostname', 'protocol', 'port',
'platform', 'is_active', 'public_ip', 'domain', 'admin_user', 'protocols', 'platform', 'is_active', 'public_ip', 'domain',
'nodes', 'labels', 'number', 'vendor', 'model', 'sn', 'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory', 'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch', 'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created', 'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity' 'hardware_info', 'connectivity'
] ]
read_only_fields = ( read_only_fields = (
'number', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info', 'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw', 'os', 'os_version', 'os_arch', 'hostname_raw',
'created_by', 'date_created', 'created_by', 'date_created',
) )
extra_kwargs = { extra_kwargs = {
'protocol': {'write_only': True},
'port': {'write_only': True},
'hardware_info': {'label': _('Hardware info')}, 'hardware_info': {'label': _('Hardware info')},
'connectivity': {'label': _('Connectivity')}, 'connectivity': {'label': _('Connectivity')},
'org_name': {'label': _('Org name')} 'org_name': {'label': _('Org name')}
} }
@classmethod @classmethod
...@@ -53,24 +80,79 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou ...@@ -53,24 +80,79 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer, OrgResou
.select_related('admin_user') .select_related('admin_user')
return queryset return queryset
def get_field_names(self, declared_fields, info): @staticmethod
fields = super().get_field_names(declared_fields, info) def validate_protocols(attr):
fields.extend([ protocols_serializer = ProtocolSerializer(data=attr, many=True)
'hardware_info', 'connectivity', 'org_name' protocols_serializer.is_valid(raise_exception=True)
]) protocols_name = [i.get("name", "ssh") for i in attr]
return fields errors = [{} for i in protocols_name]
for i, name in enumerate(protocols_name):
if name in protocols_name[:i]:
class AssetAsNodeSerializer(serializers.ModelSerializer): errors[i] = {"name": _("Protocol duplicate: {}").format(name)}
class Meta: if any(errors):
model = Asset raise ValidationError(errors)
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol'] 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): 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_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField() system_users_join = serializers.SerializerMethodField()
# nodes = NodeTMPSerializer(many=True, read_only=True) # nodes = NodeTMPSerializer(many=True, read_only=True)
...@@ -78,9 +160,9 @@ class AssetGrantedSerializer(serializers.ModelSerializer): ...@@ -78,9 +160,9 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Asset model = Asset
fields = ( fields = (
"id", "hostname", "ip", "port", "system_users_granted", "id", "hostname", "ip", "protocol", "port", "protocols",
"is_active", "system_users_join", "os", 'domain', "system_users_granted", "is_active", "system_users_join", "os",
"platform", "comment", "protocol", "org_id", "org_name", 'domain', "platform", "comment", "org_id", "org_name",
) )
@staticmethod @staticmethod
...@@ -89,21 +171,23 @@ class AssetGrantedSerializer(serializers.ModelSerializer): ...@@ -89,21 +171,23 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
return ', '.join(system_users) return ', '.join(system_users)
class MyAssetGrantedSerializer(AssetGrantedSerializer): # class MyAssetGrantedSerializer(AssetGrantedSerializer):
""" # """
普通用户获取授权的资产定义的数据结构 # 普通用户获取授权的资产定义的数据结构
""" # """
# protocols = ProtocolSerializer(many=True)
class Meta: #
model = Asset # class Meta:
fields = ( # model = Asset
"id", "hostname", "system_users_granted", # fields = (
"is_active", "system_users_join", "org_name", # "id", "hostname", "system_users_granted",
"os", "platform", "comment", "org_id", "protocol" # "is_active", "system_users_join", "org_name",
) # "os", "platform", "comment", "org_id", "protocols"
# )
class AssetSimpleSerializer(serializers.ModelSerializer): class AssetSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Asset model = Asset
fields = ['id', 'hostname', 'port', 'ip', 'connectivity'] fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
...@@ -4,49 +4,70 @@ ...@@ -4,49 +4,70 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework import serializers from rest_framework import serializers
from ..models import AuthBook from ..models import AuthBook, Asset
from ..backends.multi import AssetUserManager from ..backends import AssetUserManager
from common.utils import validate_ssh_private_key
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
__all__ = [ __all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer', '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( password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True, write_only=True, 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( public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True, 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( private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True, write_only=True, 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: class Meta:
model = AuthBook model = AuthBook
list_serializer_class = AdaptedBulkListSerializer
read_only_fields = ( read_only_fields = (
'date_created', 'date_updated', 'created_by', 'date_created', 'date_updated', 'created_by',
'is_latest', 'version', 'connectivity', '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 = { extra_kwargs = {
'username': {'required': True} 'username': {'required': True},
} }
def get_field_names(self, declared_fields, info): def validate_private_key(self, key):
fields = super().get_field_names(declared_fields, info) password = self.initial_data.get("password")
fields = [f for f in fields if not f.startswith('_') and f != 'id'] valid = validate_ssh_private_key(key, password)
fields.extend(['connectivity']) if not valid:
return fields raise serializers.ValidationError(_("private key invalid"))
return key
def create(self, validated_data): def create(self, validated_data):
kwargs = { kwargs = {
'name': validated_data.get('name'), 'name': validated_data.get('username'),
'username': validated_data.get('username'), 'username': validated_data.get('username'),
'asset': validated_data.get('asset'), 'asset': validated_data.get('asset'),
'comment': validated_data.get('comment', ''), 'comment': validated_data.get('comment', ''),
...@@ -59,7 +80,33 @@ class AssetUserSerializer(serializers.ModelSerializer): ...@@ -59,7 +80,33 @@ class AssetUserSerializer(serializers.ModelSerializer):
return instance 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 AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = AuthBook model = AuthBook
fields = ['password', 'private_key', 'public_key'] 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 ...@@ -5,9 +5,10 @@ from rest_framework import serializers
from common.fields import ChoiceDisplayField from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from ..models import CommandFilter, CommandFilterRule, SystemUser 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) rules = serializers.PrimaryKeyRelatedField(queryset=CommandFilterRule.objects.all(), many=True)
system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True) system_users = serializers.PrimaryKeyRelatedField(queryset=SystemUser.objects.all(), many=True)
...@@ -17,7 +18,7 @@ class CommandFilterSerializer(serializers.ModelSerializer): ...@@ -17,7 +18,7 @@ class CommandFilterSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class CommandFilterRuleSerializer(serializers.ModelSerializer): class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
serializer_choice_field = ChoiceDisplayField serializer_choice_field = ChoiceDisplayField
class Meta: class Meta:
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import Domain, Gateway from ..models import Domain, Gateway
class DomainSerializer(serializers.ModelSerializer): class DomainSerializer(BulkOrgResourceModelSerializer):
asset_count = serializers.SerializerMethodField() asset_count = serializers.SerializerMethodField()
gateway_count = serializers.SerializerMethodField() gateway_count = serializers.SerializerMethodField()
...@@ -25,7 +26,7 @@ class DomainSerializer(serializers.ModelSerializer): ...@@ -25,7 +26,7 @@ class DomainSerializer(serializers.ModelSerializer):
return obj.gateway_set.all().count() return obj.gateway_set.all().count()
class GatewaySerializer(serializers.ModelSerializer): class GatewaySerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = Gateway model = Gateway
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
...@@ -45,7 +46,7 @@ class GatewayWithAuthSerializer(GatewaySerializer): ...@@ -45,7 +46,7 @@ class GatewayWithAuthSerializer(GatewaySerializer):
return fields return fields
class DomainWithGatewaySerializer(serializers.ModelSerializer): class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):
gateways = GatewayWithAuthSerializer(many=True, read_only=True) gateways = GatewayWithAuthSerializer(many=True, read_only=True)
class Meta: class Meta:
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import Label from ..models import Label
class LabelSerializer(serializers.ModelSerializer): class LabelSerializer(BulkOrgResourceModelSerializer):
asset_count = serializers.SerializerMethodField() asset_count = serializers.SerializerMethodField()
class Meta: class Meta:
...@@ -25,7 +26,7 @@ class LabelSerializer(serializers.ModelSerializer): ...@@ -25,7 +26,7 @@ class LabelSerializer(serializers.ModelSerializer):
return fields return fields
class LabelDistinctSerializer(serializers.ModelSerializer): class LabelDistinctSerializer(BulkOrgResourceModelSerializer):
value = serializers.SerializerMethodField() value = serializers.SerializerMethodField()
class Meta: class Meta:
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from rest_framework import serializers from rest_framework import serializers
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import Asset, Node from ..models import Asset, Node
...@@ -10,7 +11,7 @@ __all__ = [ ...@@ -10,7 +11,7 @@ __all__ = [
] ]
class NodeSerializer(serializers.ModelSerializer): class NodeSerializer(BulkOrgResourceModelSerializer):
assets_amount = serializers.IntegerField(read_only=True) assets_amount = serializers.IntegerField(read_only=True)
class Meta: class Meta:
......
...@@ -3,12 +3,12 @@ from rest_framework import serializers ...@@ -3,12 +3,12 @@ from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset from ..models import SystemUser
from .base import AuthSerializer from .base import AuthSerializer
class SystemUserSerializer(serializers.ModelSerializer): class SystemUserSerializer(BulkOrgResourceModelSerializer):
""" """
系统用户 系统用户
""" """
...@@ -31,7 +31,7 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -31,7 +31,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
model = SystemUser model = SystemUser
list_serializer_class = AdaptedBulkListSerializer list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'org_id', 'name', 'username', 'login_mode', 'id', 'name', 'username', 'login_mode', 'login_mode_display',
'login_mode_display', 'priority', 'protocol', 'auto_push', 'login_mode_display', 'priority', 'protocol', 'auto_push',
'password', 'assets_amount', 'reachable_amount', 'reachable_assets', 'password', 'assets_amount', 'reachable_amount', 'reachable_assets',
'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo', 'unreachable_amount', 'unreachable_assets', 'cmd_filters', 'sudo',
...@@ -39,17 +39,9 @@ class SystemUserSerializer(serializers.ModelSerializer): ...@@ -39,17 +39,9 @@ class SystemUserSerializer(serializers.ModelSerializer):
] ]
extra_kwargs = { extra_kwargs = {
'login_mode_display': {'label': _('Login mode display')}, 'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True}, 'nodes': {'read_only': True}, 'created_by': {'read_only': True},
'assets': {'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 @staticmethod
def get_unreachable_assets(obj): def get_unreachable_assets(obj):
return obj.assets_unreachable return obj.assets_unreachable
......
...@@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete ...@@ -5,6 +5,7 @@ from django.db.models.signals import post_save, m2m_changed, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from common.utils import get_logger from common.utils import get_logger
from common.decorator import on_transaction_commit
from .models import Asset, SystemUser, Node, AuthBook from .models import Asset, SystemUser, Node, AuthBook
from .tasks import ( from .tasks import (
update_assets_hardware_info_util, update_assets_hardware_info_util,
...@@ -32,9 +33,12 @@ def set_asset_root_node(asset): ...@@ -32,9 +33,12 @@ def set_asset_root_node(asset):
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") @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): def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
if created: if created:
logger.info("Asset `{}` create signal received".format(instance)) logger.info("Asset `{}` create signal received".format(instance))
# 获取资产硬件信息
update_asset_hardware_info_on_created(instance) update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance) test_asset_conn_on_created(instance)
...@@ -61,6 +65,7 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs): ...@@ -61,6 +65,7 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
@receiver(m2m_changed, sender=SystemUser.nodes.through) @receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_nodes_change(sender, instance=None, **kwargs): def on_system_user_nodes_change(sender, instance=None, **kwargs):
if instance and kwargs["action"] == "post_add": if instance and kwargs["action"] == "post_add":
logger.info("System user `{}` nodes update signal received".format(instance))
assets = set() assets = set()
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
for node in nodes: for node in nodes:
......
...@@ -3,6 +3,7 @@ import json ...@@ -3,6 +3,7 @@ import json
import re import re
import os import os
from collections import defaultdict
from celery import shared_task from celery import shared_task
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.cache import cache from django.core.cache import cache
...@@ -31,7 +32,7 @@ def check_asset_can_run_ansible(asset): ...@@ -31,7 +32,7 @@ def check_asset_can_run_ansible(asset):
msg = _("Asset has been disabled, skipped: {}").format(asset) msg = _("Asset has been disabled, skipped: {}").format(asset)
logger.info(msg) logger.info(msg)
return False return False
if not asset.support_ansible(): if not asset.is_support_ansible():
msg = _("Asset may not be support ansible, skipped: {}").format(asset) msg = _("Asset may not be support ansible, skipped: {}").format(asset)
logger.info(msg) logger.info(msg)
return False return False
...@@ -45,10 +46,21 @@ def clean_hosts(assets): ...@@ -45,10 +46,21 @@ def clean_hosts(assets):
continue continue
clean_assets.append(asset) clean_assets.append(asset)
if not clean_assets: if not clean_assets:
print(_("No assets matched, stop task")) logger.info(_("No assets matched, stop task"))
return clean_assets return clean_assets
def clean_hosts_by_protocol(system_user, assets):
hosts = [
asset for asset in assets
if asset.has_protocol(system_user.protocol)
]
if not hosts:
msg = _("No assets matched related system user protocol, stop task")
logger.info(msg)
return hosts
@shared_task @shared_task
def set_assets_hardware_info(assets, result, **kwargs): def set_assets_hardware_info(assets, result, **kwargs):
""" """
...@@ -96,7 +108,7 @@ def set_assets_hardware_info(assets, result, **kwargs): ...@@ -96,7 +108,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
___disk_total = '%s %s' % sum_capacity(disk_info.values()) ___disk_total = '%s %s' % sum_capacity(disk_info.values())
___disk_info = json.dumps(disk_info) ___disk_info = json.dumps(disk_info)
___platform = info.get('ansible_system', 'Unknown') # ___platform = info.get('ansible_system', 'Unknown')
___os = info.get('ansible_distribution', 'Unknown') ___os = info.get('ansible_distribution', 'Unknown')
___os_version = info.get('ansible_distribution_version', 'Unknown') ___os_version = info.get('ansible_distribution_version', 'Unknown')
___os_arch = info.get('ansible_architecture', 'Unknown') ___os_arch = info.get('ansible_architecture', 'Unknown')
...@@ -163,25 +175,57 @@ def test_asset_connectivity_util(assets, task_name=None): ...@@ -163,25 +175,57 @@ def test_asset_connectivity_util(assets, task_name=None):
if task_name is None: if task_name is None:
task_name = _("Test assets connectivity") task_name = _("Test assets connectivity")
hosts = clean_hosts(assets) hosts = clean_hosts(assets)
if not hosts: if not hosts:
return {} return {}
tasks = const.TEST_ADMIN_USER_CONN_TASKS
created_by = assets[0].org_id hosts_category = {
task, created = update_or_create_ansible_task( 'linux': {
task_name=task_name, hosts=hosts, tasks=tasks, pattern='all', 'hosts': [],
options=const.TASK_OPTIONS, run_as_admin=True, created_by=created_by, 'tasks': const.TEST_ADMIN_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
) )
result = task.run() created_by = assets[0].org_id
summary = result[1] for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by,
)
result = task.run()
summary = result[1]
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for asset in assets: for asset in assets:
if asset.hostname in summary.get('dark', {}): if asset.hostname in results_summary.get('dark', {}):
asset.connectivity = asset.UNREACHABLE asset.connectivity = asset.UNREACHABLE
elif asset.hostname in summary.get('contacted', []): elif asset.hostname in results_summary.get('contacted', []):
asset.connectivity = asset.REACHABLE asset.connectivity = asset.REACHABLE
else: else:
asset.connectivity = asset.UNKNOWN asset.connectivity = asset.UNKNOWN
return summary
return results_summary
@shared_task @shared_task
...@@ -243,8 +287,7 @@ def test_admin_user_connectivity_manual(admin_user): ...@@ -243,8 +287,7 @@ def test_admin_user_connectivity_manual(admin_user):
## System user connective ## ## System user connective ##
@shared_task @shared_task
def set_system_user_connectivity_info(system_user, result): def set_system_user_connectivity_info(system_user, summary):
summary = result[1]
system_user.connectivity = summary system_user.connectivity = summary
...@@ -258,18 +301,53 @@ def test_system_user_connectivity_util(system_user, assets, task_name): ...@@ -258,18 +301,53 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
tasks = const.TEST_SYSTEM_USER_CONN_TASKS
hosts = clean_hosts(assets) hosts = clean_hosts(assets)
if not hosts: if not hosts:
return {} return {}
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, pattern='all', hosts = clean_hosts_by_protocol(system_user, hosts)
options=const.TASK_OPTIONS, if not hosts:
run_as=system_user.username, created_by=system_user.org_id, return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
}
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
) )
result = task.run() for k, value in hosts_category.items():
set_system_user_connectivity_info(system_user, result) if not value['hosts']:
return result continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
)
result = task.run()
summary = result[1]
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
set_system_user_connectivity_info(system_user, results_summary)
return results_summary
@shared_task @shared_task
...@@ -301,11 +379,7 @@ def test_system_user_connectivity_period(): ...@@ -301,11 +379,7 @@ def test_system_user_connectivity_period():
#### Push system user tasks #### #### Push system user tasks ####
def get_push_system_user_tasks(system_user): def get_push_linux_system_user_tasks(system_user):
# Set root as system user is dangerous
if system_user.username == "root":
return []
tasks = [] tasks = []
if system_user.password: if system_user.password:
tasks.append({ tasks.append({
...@@ -320,12 +394,12 @@ def get_push_system_user_tasks(system_user): ...@@ -320,12 +394,12 @@ def get_push_system_user_tasks(system_user):
}) })
tasks.extend([ tasks.extend([
{ {
'name': 'Check home dir exists', 'name': 'Check home dir exists',
'action': { 'action': {
'module': 'stat', 'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username) 'args': 'path=/home/{}'.format(system_user.username)
}, },
'register': 'home_existed' 'register': 'home_existed'
}, },
{ {
'name': "Set home dir permission", 'name': "Set home dir permission",
...@@ -364,6 +438,46 @@ def get_push_system_user_tasks(system_user): ...@@ -364,6 +438,46 @@ def get_push_system_user_tasks(system_user):
) )
} }
}) })
return tasks
def get_push_windows_system_user_tasks(system_user):
tasks = []
if system_user.password:
tasks.append({
'name': 'Add user {}'.format(system_user.username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
'name={} '
'password={} '
'state=present '
'update_password=always '
'password_expired=no '
'password_never_expires=yes '
'groups="Users,Remote Desktop Users" '
'groups_action=add '
''.format(system_user.name,
system_user.username,
system_user.password),
}
})
return tasks
def get_push_system_user_tasks(host, system_user):
if host.is_unixlike():
tasks = get_push_linux_system_user_tasks(system_user)
elif host.is_windows():
tasks = get_push_windows_system_user_tasks(system_user)
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(host.hostname, host.platform)
)
logger.info(msg)
tasks = []
return tasks return tasks
...@@ -372,16 +486,29 @@ def push_system_user_util(system_user, assets, task_name): ...@@ -372,16 +486,29 @@ def push_system_user_util(system_user, assets, task_name):
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
if not system_user.is_need_push(): if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or " msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh: {}").format(system_user.name) "protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg) logger.info(msg)
return return {}
# Set root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return {}
hosts = clean_hosts(assets) hosts = clean_hosts(assets)
if not hosts: if not hosts:
return {} return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
for host in hosts: for host in hosts:
system_user.load_specific_asset_auth(host) system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(system_user) tasks = get_push_system_user_tasks(host, system_user)
if not tasks:
continue
task, created = update_or_create_ansible_task( task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all', task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True, options=const.TASK_OPTIONS, run_as_admin=True,
...@@ -423,41 +550,74 @@ def test_admin_user_connectability_period(): ...@@ -423,41 +550,74 @@ def test_admin_user_connectability_period():
pass pass
#### Test Asset user connectivity task ####
def get_test_asset_user_connectivity_tasks(asset):
if asset.is_unixlike():
tasks = const.TEST_ASSET_USER_CONN_TASKS
elif asset.is_windows():
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(asset.hostname, asset.platform)
)
logger.info(msg)
tasks = []
return tasks
@shared_task @shared_task
def set_asset_user_connectivity_info(asset_user, result): def set_asset_user_connectivity_info(asset_user, result):
summary = result[1] summary = result[1]
asset_user.connectivity = summary if summary.get('contacted'):
connectivity = 1
elif summary.get("dark"):
connectivity = 0
else:
connectivity = 3
asset_user.connectivity = connectivity
@shared_task @shared_task
def test_asset_user_connectivity_util(asset_user, task_name): def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
""" """
:param asset_user: <AuthBook>对象 :param asset_user: <AuthBook>对象
:param task_name: :param task_name:
:return: :return:
""" """
from ops.utils import update_or_create_ansible_task from ops.utils import update_or_create_ansible_task
tasks = const.TEST_ASSET_USER_CONN_TASKS
if not check_asset_can_run_ansible(asset_user.asset): if not check_asset_can_run_ansible(asset_user.asset):
return return
task, created = update_or_create_ansible_task( tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
task_name, hosts=[asset_user.asset], tasks=tasks, pattern='all', if not tasks:
options=const.TASK_OPTIONS, return
run_as=asset_user.username, created_by=asset_user.org_id
) args = (task_name,)
kwargs = {
'hosts': [asset_user.asset], 'tasks': tasks,
'pattern': 'all', 'options': const.TASK_OPTIONS,
'created_by': asset_user.org_id,
}
if run_as_admin:
kwargs["run_as_admin"] = True
else:
kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs)
result = task.run() result = task.run()
set_asset_user_connectivity_info(asset_user, result) set_asset_user_connectivity_info(asset_user, result)
@shared_task @shared_task
def test_asset_users_connectivity_manual(asset_users): def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
""" """
:param asset_users: <AuthBook>对象 :param asset_users: <AuthBook>对象
""" """
for asset_user in asset_users: for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user) task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name) test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
# @shared_task # @shared_task
......
...@@ -76,6 +76,7 @@ function initTable2() { ...@@ -76,6 +76,7 @@ function initTable2() {
function onNodeSelected2(event, treeNode) { function onNodeSelected2(event, treeNode) {
var url = asset_table2.ajax.url(); var url = asset_table2.ajax.url();
url = setUrlParam(url, "node_id", treeNode.meta.node.id); url = setUrlParam(url, "node_id", treeNode.meta.node.id);
url = setUrlParam(url, "show_current_asset", "");
asset_table2.ajax.url(url); asset_table2.ajax.url(url);
asset_table2.ajax.reload(); 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 @@ ...@@ -10,17 +10,7 @@
} }
</style> </style>
<form class="form-horizontal" action="" style="padding-top: 20px"> <form class="form-horizontal" action="" style="padding-top: 20px">
<div class="form-group mfa-field"> <div class="auth-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="form-group"> <div class="form-group">
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label> <label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
<div class="col-sm-8"> <div class="col-sm-8">
...@@ -48,10 +38,11 @@ ...@@ -48,10 +38,11 @@
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script> <script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
<script> <script>
var showPassword = false; var showPassword = false;
var lastMFATime = "{{ request.session.OTP_LAST_VERIFY_TIME }}";
var asset_id = ""; var authAssetId = "";
var host = ""; var authHostname = "";
var username = ""; var authUsername = "";
var mfaFor = "";
function initClipboard() { function initClipboard() {
var clipboard = new Clipboard('.btn-copy-password', { var clipboard = new Clipboard('.btn-copy-password', {
...@@ -65,12 +56,12 @@ function initClipboard() { ...@@ -65,12 +56,12 @@ function initClipboard() {
} }
function showAuth() { function showAuth() {
$(".mfa-field").hide(); var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + authAssetId + "&username=" + authUsername;
$(".auth-field").show(); if (prefer) {
url = setUrlParam(url, 'prefer', prefer)
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + asset_id + "&username=" + username; }
$("#id_username_view").html(username); $("#id_username_view").html(authUsername);
$("#id_hostname_view").html(host); $("#id_hostname_view").html(authHostname);
var success = function (data) { var success = function (data) {
var password = data.password; var password = data.password;
$("#id_password_view").val(password); $("#id_password_view").val(password);
...@@ -88,11 +79,6 @@ function showAuth() { ...@@ -88,11 +79,6 @@ function showAuth() {
}) })
} }
function showMFA() {
$(".mfa-field").show();
$(".auth-field").hide();
}
$(document).ready(function () { $(document).ready(function () {
initClipboard(); initClipboard();
}).on("click", ".btn-show-password", function () { }).on("click", ".btn-show-password", function () {
...@@ -103,35 +89,7 @@ $(document).ready(function () { ...@@ -103,35 +89,7 @@ $(document).ready(function () {
$("#id_password_view").attr("type", "password") $("#id_password_view").attr("type", "password")
} }
}).on("show.bs.modal", "#asset_user_auth_view", function () { }).on("show.bs.modal", "#asset_user_auth_view", function () {
var now = new Date(); showAuth();
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
})
}) })
</script> </script>
{% endblock %} {% 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 }}'; ...@@ -95,41 +95,97 @@ var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}'; var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}'; var shell_id = '#' + '{{ form.shell.id_for_label }}';
var need_change_field = [ function autoLoginModeProtocol() {
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id // 协议+自动登录模式字段控制
]; $('#auth_title_id').removeClass('hidden');
var need_change_field_login_mode = [
auto_generate_key, private_key_id, auto_push_id, password_id
];
function protocolChange() {
var protocol = $(protocol_id + " option:selected").text(); 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'); $('.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'); $('#command-filter-block').addClass('hidden');
$.each(need_change_field, function (index, value) { $(sudo_id).closest('.form-group').addClass('hidden');
$(value).closest('.form-group').addClass('hidden') $(shell_id).closest('.form-group').addClass('hidden');
});
} }
else if (protocol === 'telnet (beta)') { else if (protocol === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden'); $('.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'); $('#command-filter-block').removeClass('hidden');
$.each(need_change_field, function (index, value) { $(sudo_id).closest('.form-group').addClass('hidden');
$(value).closest('.form-group').addClass('hidden') $(shell_id).closest('.form-group').addClass('hidden');
});
} }
else { else {
if($(login_mode_id).val() === 'manual'){
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
return
}
authFieldsDisplay(); 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'); $('#command-filter-block').removeClass('hidden');
$.each(need_change_field, function (index, value) { $(sudo_id).closest('.form-group').addClass('hidden');
$(value).closest('.form-group').removeClass('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() { function authFieldsDisplay() {
...@@ -139,34 +195,29 @@ function authFieldsDisplay() { ...@@ -139,34 +195,29 @@ function authFieldsDisplay() {
$('.auth-fields').removeClass('hidden'); $('.auth-fields').removeClass('hidden');
} }
} }
function loginModeChange(){ function fieldDisplay(){
if ($(login_mode_id).val() === 'manual'){ var login_mode = $(login_mode_id).val();
$('#auth_title_id').addClass('hidden'); if (login_mode === 'manual'){
$.each(need_change_field_login_mode, function(index, value){ manualLoginModeProtocol();
$(value).closest('.form-group').addClass('hidden')
})
} }
else if($(login_mode_id).val() === 'auto'){ else if(login_mode === 'auto'){
$('#auth_title_id').removeClass('hidden'); autoLoginModeProtocol();
$(password_id).closest('.form-group').removeClass('hidden')
protocolChange();
} }
} }
$(document).ready(function () { $(document).ready(function () {
$('.select2').select2(); $('.select2').select2();
authFieldsDisplay(); authFieldsDisplay();
protocolChange(); fieldDisplay();
loginModeChange();
})
.on('change', protocol_id, function(){
protocolChange();
}) })
.on('change', auto_generate_key, function(){ .on('change', auto_generate_key, function(){
authFieldsDisplay(); authFieldsDisplay();
}) })
.on('change', login_mode_id, function(){ .on('change', login_mode_id, function(){
loginModeChange(); fieldDisplay();
})
.on('change', protocol_id, function(){
fieldDisplay();
}) })
</script> </script>
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
</ul> </ul>
</div> </div>
<div class="tab-content"> <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 float-e-margins">
<div class="ibox-title"> <div class="ibox-title">
<span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span> <span style="float: left">{% trans 'Asset list of ' %} <b>{{ admin_user.name }}</b></span>
...@@ -42,23 +42,11 @@ ...@@ -42,23 +42,11 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<table class="table table-striped table-bordered table-hover" id="asset_list_table"> {% include 'assets/_asset_user_list.html' %}
<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>
</div> </div>
</div> </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 panel-primary">
<div class="panel-heading"> <div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %} <i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
...@@ -84,64 +72,14 @@ ...@@ -84,64 +72,14 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% include 'assets/_asset_user_view_auth_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <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 () { $(document).ready(function () {
initTable(); assetUserListUrl = setUrlParam(assetUserListUrl, "admin_user_id", "{{ admin_user.id }}");
prefer = "admin_user";
initAssetUserTable();
}) })
.on('click', '.btn-test-asset', function () { .on('click', '.btn-test-asset', function () {
var asset_id = $(this).data('uid'); var asset_id = $(this).data('uid');
...@@ -173,37 +111,10 @@ $(document).ready(function () { ...@@ -173,37 +111,10 @@ $(document).ready(function () {
}); });
}) })
.on('click', '.btn-update-asset-user-auth', function() { .on('click', '.btn-update-asset-user-auth', function() {
assetId = $(this).data('aid'); asset_id = $(this).data('aid');
var hostname = $(this).data('hostname'); hostname = $(this).data('hostname');
var username = '{{ admin_user.username }}'; username = '{{ admin_user.username }}';
initAssetUserAuthModalForm(hostname, username); $("#asset_user_auth_update_modal").modal();
$("#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');
}
}) })
.on("click", ".btn-view-auth", function (evt) { .on("click", ".btn-view-auth", function (evt) {
asset_id = $(this).data("aid") ; asset_id = $(this).data("aid") ;
......
...@@ -37,20 +37,7 @@ ...@@ -37,20 +37,7 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<table class="table table-hover" id="asset_user_list"> {% include 'assets/_asset_user_list.html' %}
<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>
</div> </div>
</div> </div>
</div> </div>
...@@ -62,7 +49,7 @@ ...@@ -62,7 +49,7 @@
<div class="panel-body"> <div class="panel-body">
<table class="table"> <table class="table">
<tbody> <tbody>
{% if asset.protocol == 'ssh' %} {% if asset.is_support_ansible %}
<tr class="no-borders-tr"> <tr class="no-borders-tr">
<td>{% trans 'Test connective' %}:</td> <td>{% trans 'Test connective' %}:</td>
<td> <td>
...@@ -82,110 +69,14 @@ ...@@ -82,110 +69,14 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% include 'assets/_asset_user_view_auth_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <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 () { $(document).ready(function () {
initAssetUserTable(); assetUserListUrl = setUrlParam(assetUserListUrl, "asset_id", "{{ asset.id }}");
}) 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
});
}) })
.on('click', '#btn-bulk-test-connective', function () { .on('click', '#btn-bulk-test-connective', function () {
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}"; var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id={{ asset.id }}";
var success = function (data) { var success = function (data) {
...@@ -200,11 +91,5 @@ $(document).ready(function () { ...@@ -200,11 +91,5 @@ $(document).ready(function () {
flash_message: false 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> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -16,12 +16,24 @@ ...@@ -16,12 +16,24 @@
<h3>{% trans 'Basic' %}</h3> <h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %} {% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip 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.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %} {% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain 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> <div class="hr-line-dashed"></div>
<h3>{% trans 'Auth' %}</h3> <h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.admin_user layout="horizontal" %} {% bootstrap_field form.admin_user layout="horizontal" %}
...@@ -55,6 +67,8 @@ ...@@ -55,6 +67,8 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% block extra %}
{% endblock %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3> <h3>{% trans 'Other' %}</h3>
...@@ -73,11 +87,25 @@ ...@@ -73,11 +87,25 @@
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
var instanceId = "{{ object.id }}";
var protocolLen = 0;
function format(item) { function format(item) {
var group = item.element.parentElement.label; var group = item.element.parentElement.label;
return group + ':' + item.text; 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 () { $(document).ready(function () {
$('.select2').select2({ $('.select2').select2({
allowClear: true allowClear: true
...@@ -89,20 +117,131 @@ $(document).ready(function () { ...@@ -89,20 +117,131 @@ $(document).ready(function () {
$('#id_nodes.select2').select2({ $('#id_nodes.select2').select2({
closeOnSelect: false closeOnSelect: false
}); });
$("#id_protocol").change(function (){ protocolBtnShow()
var protocol = $("#id_protocol option:selected").text(); })
var port = 22; .on("change", "#id_platform", function () {
if(protocol === 'rdp'){ if (instanceId !== "") {
port = 3389; 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; port = 23;
} break;
else if(protocol === 'vnc'){ case "vnc":
port = 5901; 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> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -69,12 +69,12 @@ ...@@ -69,12 +69,12 @@
<td><b>{{ asset.public_ip|default:"" }}</b></td> <td><b>{{ asset.public_ip|default:"" }}</b></td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Port' %}:</td> <td>{% trans 'Protocol' %}</td>
<td><b>{{ asset.port }}</b></td> <td>
</tr> {% for protocol in asset.protocols.all %}
<tr> <b>{{ protocol }}</b>
<td>{% trans 'Protocol' %}:</td> {% endfor %}
<td><b>{{ asset.protocol }}</b></td> </td>
</tr> </tr>
<tr> <tr>
<td>{% trans 'Admin user' %}:</td> <td>{% trans 'Admin user' %}:</td>
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
</tr> </tr>
<tr> <tr>
<td>{% trans 'CPU' %}:</td> <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>
<tr> <tr>
<td>{% trans 'Memory' %}:</td> <td>{% trans 'Memory' %}:</td>
...@@ -166,7 +166,7 @@ ...@@ -166,7 +166,7 @@
</span> </span>
</td> </td>
</tr> </tr>
{% if asset.protocol == 'ssh' %} {% if asset.is_support_ansible %}
<tr> <tr>
<td>{% trans 'Refresh hardware' %}:</td> <td>{% trans 'Refresh hardware' %}:</td>
<td> <td>
......
{% extends '_base_create_update.html' %} {% extends 'assets/asset_create.html' %}
{% load static %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% 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> <div class="hr-line-dashed"></div>
<h3>{% trans 'Configuration' %}</h3> <h3>{% trans 'Configuration' %}</h3>
{% bootstrap_field form.number layout="horizontal" %} {% 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 %} {% endblock %}
{% block custom_foot_js %} {% block formUrl %}
<script> var the_url = '{% url 'api-assets:asset-detail' pk=object.id %}';
function format(item) { var redirect_to = '{% url "assets:asset-list" %}';
var group = item.element.parentElement.label; var method = 'PUT';
return group + ':' + item.text;
}
$(document).ready(function () {
$('.select2').select2({
allowClear: true
});
$(".labels").select2({
allowClear: true,
templateSelection: format
});
})
</script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -44,19 +44,7 @@ ...@@ -44,19 +44,7 @@
</div> </div>
</div> </div>
<div class="ibox-content"> <div class="ibox-content">
<table class="table table-hover" id="system_user_list"> {% include 'assets/_asset_user_list.html' %}
<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>
</div> </div>
</div> </div>
</div> </div>
...@@ -132,50 +120,9 @@ ...@@ -132,50 +120,9 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'assets/_asset_user_auth_modal.html' %}
{% include 'assets/_asset_user_view_auth_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <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) { function updateSystemUserNode(nodes) {
var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}"; var the_url = "{% url 'api-assets:system-user-detail' pk=system_user.id %}";
...@@ -207,15 +154,6 @@ function updateSystemUserNode(nodes) { ...@@ -207,15 +154,6 @@ function updateSystemUserNode(nodes) {
} }
jumpserver.nodes_selected = {}; 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 () { $(document).ready(function () {
$('.select2').select2() $('.select2').select2()
.on('select2:select', function(evt) { .on('select2:select', function(evt) {
...@@ -226,7 +164,10 @@ $(document).ready(function () { ...@@ -226,7 +164,10 @@ $(document).ready(function () {
var data = evt.params.data; var data = evt.params.data;
delete jumpserver.nodes_selected[data.id]; delete jumpserver.nodes_selected[data.id];
}); });
initAssetsTable(); assetUserListUrl = setUrlParam(assetUserListUrl, "system_user_id", "{{ system_user.id }}");
needPush = true;
initAssetUserTable();
}) })
.on('click', '.btn-push', function () { .on('click', '.btn-push', function () {
var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}"; var the_url = "{% url 'api-assets:system-user-push' pk=system_user.id %}";
...@@ -289,9 +230,9 @@ $(document).ready(function () { ...@@ -289,9 +230,9 @@ $(document).ready(function () {
}); });
updateSystemUserNode(nodes); updateSystemUserNode(nodes);
}) })
.on('click', '.btn-push-asset', function () { .on('click', '.btn-push-auth', function () {
var $this = $(this); 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 %}"; 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); the_url = the_url.replace("{{ DEFAULT_PK }}", asset_id);
var success = function (data) { var success = function (data) {
...@@ -309,64 +250,7 @@ $(document).ready(function () { ...@@ -309,64 +250,7 @@ $(document).ready(function () {
error: error 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> </script>
{% endblock %} {% endblock %}
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
<div class="panel-body"> <div class="panel-body">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr class="only-ssh"> <tr class="only-ssh-rdp">
<td width="50%">{% trans 'Auto push' %}:</td> <td width="50%">{% trans 'Auto push' %}:</td>
<td> <td>
<span class="pull-right"> <span class="pull-right">
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
</td> </td>
</tr> </tr>
{% if system_user.auto_push %} {% if system_user.auto_push %}
<tr class="only-ssh"> <tr class="only-ssh-rdp">
<td width="50%">{% trans 'Push system user now' %}:</td> <td width="50%">{% trans 'Push system user now' %}:</td>
<td> <td>
<span style="float: right"> <span style="float: right">
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
<tr class="only-ssh"> <tr>
<td width="50%">{% trans 'Test assets connective' %}:</td> <td width="50%">{% trans 'Test assets connective' %}:</td>
<td> <td>
<span style="float: right"> <span style="float: right">
...@@ -219,8 +219,12 @@ function updateCommandFilters(command_filters) { ...@@ -219,8 +219,12 @@ function updateCommandFilters(command_filters) {
}); });
} }
$(document).ready(function () { $(document).ready(function () {
if($('#id_protocol_type').text() === 'rdp'){ var protocol = $('#id_protocol_type').text();
$('.only-ssh').addClass('hidden') 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'); $(".panel-body .table tr:visible:first").addClass('no-borders-tr');
$('.select2').select2() $('.select2').select2()
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
{# 目前还不支持Windows的自动推送#} {# 目前还不支持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 '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 '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> </div>
{% endblock %} {% endblock %}
......
...@@ -18,6 +18,7 @@ router.register(r'domain', api.DomainViewSet, 'domain') ...@@ -18,6 +18,7 @@ router.register(r'domain', api.DomainViewSet, 'domain')
router.register(r'gateway', api.GatewayViewSet, 'gateway') router.register(r'gateway', api.GatewayViewSet, 'gateway')
router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter') router.register(r'cmd-filter', api.CommandFilterViewSet, 'cmd-filter')
router.register(r'asset-user', api.AssetUserViewSet, 'asset-user') 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 = routers.NestedDefaultRouter(router, r'cmd-filter', lookup='filter')
cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule') cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-rule')
......
...@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin ...@@ -11,7 +11,7 @@ from django.views.generic.detail import DetailView, SingleObjectMixin
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from .. import forms from .. import forms
from ..models import AdminUser, Node from ..models import AdminUser, Node
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
__all__ = [ __all__ = [
'AdminUserCreateView', 'AdminUserDetailView', 'AdminUserCreateView', 'AdminUserDetailView',
...@@ -20,9 +20,10 @@ __all__ = [ ...@@ -20,9 +20,10 @@ __all__ = [
] ]
class AdminUserListView(AdminUserRequiredMixin, TemplateView): class AdminUserListView(PermissionsMixin, TemplateView):
model = AdminUser model = AdminUser
template_name = 'assets/admin_user_list.html' template_name = 'assets/admin_user_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -33,7 +34,7 @@ class AdminUserListView(AdminUserRequiredMixin, TemplateView): ...@@ -33,7 +34,7 @@ class AdminUserListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdminUserCreateView(AdminUserRequiredMixin, class AdminUserCreateView(PermissionsMixin,
SuccessMessageMixin, SuccessMessageMixin,
CreateView): CreateView):
model = AdminUser model = AdminUser
...@@ -41,6 +42,7 @@ class AdminUserCreateView(AdminUserRequiredMixin, ...@@ -41,6 +42,7 @@ class AdminUserCreateView(AdminUserRequiredMixin,
template_name = 'assets/admin_user_create_update.html' template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list') success_url = reverse_lazy('assets:admin-user-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -51,12 +53,13 @@ class AdminUserCreateView(AdminUserRequiredMixin, ...@@ -51,12 +53,13 @@ class AdminUserCreateView(AdminUserRequiredMixin,
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class AdminUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
model = AdminUser model = AdminUser
form_class = forms.AdminUserForm form_class = forms.AdminUserForm
template_name = 'assets/admin_user_create_update.html' template_name = 'assets/admin_user_create_update.html'
success_url = reverse_lazy('assets:admin-user-list') success_url = reverse_lazy('assets:admin-user-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -67,11 +70,12 @@ class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie ...@@ -67,11 +70,12 @@ class AdminUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdminUserDetailView(AdminUserRequiredMixin, DetailView): class AdminUserDetailView(PermissionsMixin, DetailView):
model = AdminUser model = AdminUser
template_name = 'assets/admin_user_detail.html' template_name = 'assets/admin_user_detail.html'
context_object_name = 'admin_user' context_object_name = 'admin_user'
object = None object = None
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -83,18 +87,19 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView): ...@@ -83,18 +87,19 @@ class AdminUserDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): class AdminUserAssetsView(PermissionsMixin, SingleObjectMixin, ListView):
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
template_name = 'assets/admin_user_assets.html' template_name = 'assets/admin_user_assets.html'
context_object_name = 'admin_user' context_object_name = 'admin_user'
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AdminUser.objects.all()) self.object = self.get_object(queryset=AdminUser.objects.all())
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
self.queryset = self.object.asset_set.all() self.queryset = self.object.assets.all()
return self.queryset return self.queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
...@@ -108,9 +113,10 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView): ...@@ -108,9 +113,10 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdminUserDeleteView(AdminUserRequiredMixin, DeleteView): class AdminUserDeleteView(PermissionsMixin, DeleteView):
model = AdminUser model = AdminUser
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:admin-user-list') success_url = reverse_lazy('assets:admin-user-list')
permission_classes = [IsOrgAdmin]
...@@ -20,18 +20,16 @@ from django.views.decorators.csrf import csrf_exempt ...@@ -20,18 +20,16 @@ from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.core.cache import cache from django.core.cache import cache
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.forms.formsets import formset_factory
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none, get_logger 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 ( from common.const import (
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID 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 .. import forms
from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain
...@@ -44,8 +42,9 @@ __all__ = [ ...@@ -44,8 +42,9 @@ __all__ = [
logger = get_logger(__file__) logger = get_logger(__file__)
class AssetListView(AdminUserRequiredMixin, TemplateView): class AssetListView(PermissionsMixin, TemplateView):
template_name = 'assets/asset_list.html' template_name = 'assets/asset_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
Node.root() Node.root()
...@@ -59,10 +58,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView): ...@@ -59,10 +58,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetUserListView(AdminUserRequiredMixin, DetailView): class AssetUserListView(PermissionsMixin, DetailView):
model = Asset model = Asset
context_object_name = 'asset' context_object_name = 'asset'
template_name = 'assets/asset_asset_user_list.html' template_name = 'assets/asset_asset_user_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -73,8 +73,9 @@ class AssetUserListView(AdminUserRequiredMixin, DetailView): ...@@ -73,8 +73,9 @@ class AssetUserListView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserAssetListView(LoginRequiredMixin, TemplateView): class UserAssetListView(PermissionsMixin, TemplateView):
template_name = 'assets/user_asset_list.html' template_name = 'assets/user_asset_list.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -86,11 +87,12 @@ class UserAssetListView(LoginRequiredMixin, TemplateView): ...@@ -86,11 +87,12 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class AssetCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
model = Asset model = Asset
form_class = forms.AssetCreateForm form_class = forms.AssetCreateForm
template_name = 'assets/asset_create.html' template_name = 'assets/asset_create.html'
success_url = reverse_lazy('assets:asset-list') success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin]
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class=form_class) form = super().get_form(form_class=form_class)
...@@ -102,10 +104,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -102,10 +104,30 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
form["nodes"].initial = node form["nodes"].initial = node
return form 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): def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Create asset'), 'action': _('Create asset'),
'formset': formset,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -114,7 +136,7 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -114,7 +136,7 @@ class AssetCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
return create_success_msg % ({"name": cleaned_data["hostname"]}) return create_success_msg % ({"name": cleaned_data["hostname"]})
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): class AssetBulkUpdateView(PermissionsMixin, ListView):
model = Asset model = Asset
form_class = forms.AssetBulkUpdateForm form_class = forms.AssetBulkUpdateForm
template_name = 'assets/asset_bulk_update.html' template_name = 'assets/asset_bulk_update.html'
...@@ -122,6 +144,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): ...@@ -122,6 +144,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
success_message = _("Bulk update asset success") success_message = _("Bulk update asset success")
id_list = None id_list = None
form = None form = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
...@@ -154,16 +177,28 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): ...@@ -154,16 +177,28 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class AssetUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
model = Asset model = Asset
form_class = forms.AssetUpdateForm form_class = forms.AssetUpdateForm
template_name = 'assets/asset_update.html' template_name = 'assets/asset_update.html'
success_url = reverse_lazy('assets:asset-list') 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): def get_context_data(self, **kwargs):
formset = self.get_protocol_formset()
context = { context = {
'app': _('Assets'), 'app': _('Assets'),
'action': _('Update asset'), 'action': _('Update asset'),
'formset': formset,
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
...@@ -172,16 +207,18 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -172,16 +207,18 @@ class AssetUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
return update_success_msg % ({"name": cleaned_data["hostname"]}) return update_success_msg % ({"name": cleaned_data["hostname"]})
class AssetDeleteView(AdminUserRequiredMixin, DeleteView): class AssetDeleteView(PermissionsMixin, DeleteView):
model = Asset model = Asset
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:asset-list') success_url = reverse_lazy('assets:asset-list')
permission_classes = [IsOrgAdmin]
class AssetDetailView(LoginRequiredMixin, DetailView): class AssetDetailView(PermissionsMixin, DetailView):
model = Asset model = Asset
context_object_name = 'asset' context_object_name = 'asset'
template_name = 'assets/asset_detail.html' template_name = 'assets/asset_detail.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = Node.objects.exclude(assets=self.object) nodes_remain = Node.objects.exclude(assets=self.object)
...@@ -195,7 +232,9 @@ class AssetDetailView(LoginRequiredMixin, DetailView): ...@@ -195,7 +232,9 @@ class AssetDetailView(LoginRequiredMixin, DetailView):
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(LoginRequiredMixin, View): class AssetExportView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request): def get(self, request):
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [] assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else []
...@@ -242,8 +281,9 @@ class AssetExportView(LoginRequiredMixin, View): ...@@ -242,8 +281,9 @@ class AssetExportView(LoginRequiredMixin, View):
return JsonResponse({'redirect': url}) return JsonResponse({'redirect': url})
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView): class BulkImportAssetView(PermissionsMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm form_class = forms.FileForm
permission_classes = [IsOrgAdmin]
def form_valid(self, form): def form_valid(self, form):
node_id = self.request.GET.get("node_id") node_id = self.request.GET.get("node_id")
......
...@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, reverse 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 common.const import create_success_msg, update_success_msg
from ..models import CommandFilter, CommandFilterRule, SystemUser from ..models import CommandFilter, CommandFilterRule, SystemUser
from ..forms import CommandFilterForm, CommandFilterRuleForm from ..forms import CommandFilterForm, CommandFilterRuleForm
...@@ -22,8 +22,9 @@ __all__ = ( ...@@ -22,8 +22,9 @@ __all__ = (
) )
class CommandFilterListView(AdminUserRequiredMixin, TemplateView): class CommandFilterListView(PermissionsMixin, TemplateView):
template_name = 'assets/cmd_filter_list.html' template_name = 'assets/cmd_filter_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -34,12 +35,13 @@ class CommandFilterListView(AdminUserRequiredMixin, TemplateView): ...@@ -34,12 +35,13 @@ class CommandFilterListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterCreateView(AdminUserRequiredMixin, CreateView): class CommandFilterCreateView(PermissionsMixin, CreateView):
model = CommandFilter model = CommandFilter
template_name = 'assets/cmd_filter_create_update.html' template_name = 'assets/cmd_filter_create_update.html'
form_class = CommandFilterForm form_class = CommandFilterForm
success_url = reverse_lazy('assets:cmd-filter-list') success_url = reverse_lazy('assets:cmd-filter-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -50,12 +52,13 @@ class CommandFilterCreateView(AdminUserRequiredMixin, CreateView): ...@@ -50,12 +52,13 @@ class CommandFilterCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView): class CommandFilterUpdateView(PermissionsMixin, UpdateView):
model = CommandFilter model = CommandFilter
template_name = 'assets/cmd_filter_create_update.html' template_name = 'assets/cmd_filter_create_update.html'
form_class = CommandFilterForm form_class = CommandFilterForm
success_url = reverse_lazy('assets:cmd-filter-list') success_url = reverse_lazy('assets:cmd-filter-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -66,9 +69,10 @@ class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -66,9 +69,10 @@ class CommandFilterUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterDetailView(AdminUserRequiredMixin, DetailView): class CommandFilterDetailView(PermissionsMixin, DetailView):
model = CommandFilter model = CommandFilter
template_name = 'assets/cmd_filter_detail.html' template_name = 'assets/cmd_filter_detail.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
system_users_remain = SystemUser.objects\ system_users_remain = SystemUser.objects\
...@@ -83,10 +87,11 @@ class CommandFilterDetailView(AdminUserRequiredMixin, DetailView): ...@@ -83,10 +87,11 @@ class CommandFilterDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateView): class CommandFilterRuleListView(PermissionsMixin, SingleObjectMixin, TemplateView):
template_name = 'assets/cmd_filter_rule_list.html' template_name = 'assets/cmd_filter_rule_list.html'
model = CommandFilter model = CommandFilter
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self.model.objects.all()) self.object = self.get_object(queryset=self.model.objects.all())
...@@ -102,12 +107,13 @@ class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, Templ ...@@ -102,12 +107,13 @@ class CommandFilterRuleListView(AdminUserRequiredMixin, SingleObjectMixin, Templ
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView): class CommandFilterRuleCreateView(PermissionsMixin, CreateView):
template_name = 'assets/cmd_filter_rule_create_update.html' template_name = 'assets/cmd_filter_rule_create_update.html'
model = CommandFilterRule model = CommandFilterRule
form_class = CommandFilterRuleForm form_class = CommandFilterRuleForm
success_message = create_success_msg success_message = create_success_msg
cmd_filter = None cmd_filter = None
permission_classes = [IsOrgAdmin]
def get_success_url(self): def get_success_url(self):
return reverse('assets:cmd-filter-rule-list', kwargs={ return reverse('assets:cmd-filter-rule-list', kwargs={
...@@ -135,12 +141,13 @@ class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView): ...@@ -135,12 +141,13 @@ class CommandFilterRuleCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandFilterRuleUpdateView(AdminUserRequiredMixin, UpdateView): class CommandFilterRuleUpdateView(PermissionsMixin, UpdateView):
template_name = 'assets/cmd_filter_rule_create_update.html' template_name = 'assets/cmd_filter_rule_create_update.html'
model = CommandFilterRule model = CommandFilterRule
form_class = CommandFilterRuleForm form_class = CommandFilterRuleForm
success_message = create_success_msg success_message = create_success_msg
cmd_filter = None cmd_filter = None
permission_classes = [IsOrgAdmin]
def get_success_url(self): def get_success_url(self):
return reverse('assets:cmd-filter-rule-list', kwargs={ return reverse('assets:cmd-filter-rule-list', kwargs={
......
...@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin ...@@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy, reverse 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.const import create_success_msg, update_success_msg
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import Domain, Gateway from ..models import Domain, Gateway
...@@ -21,8 +21,9 @@ __all__ = ( ...@@ -21,8 +21,9 @@ __all__ = (
) )
class DomainListView(AdminUserRequiredMixin, TemplateView): class DomainListView(PermissionsMixin, TemplateView):
template_name = 'assets/domain_list.html' template_name = 'assets/domain_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -33,12 +34,13 @@ class DomainListView(AdminUserRequiredMixin, TemplateView): ...@@ -33,12 +34,13 @@ class DomainListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainCreateView(AdminUserRequiredMixin, CreateView): class DomainCreateView(PermissionsMixin, CreateView):
model = Domain model = Domain
template_name = 'assets/domain_create_update.html' template_name = 'assets/domain_create_update.html'
form_class = DomainForm form_class = DomainForm
success_url = reverse_lazy('assets:domain-list') success_url = reverse_lazy('assets:domain-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -49,12 +51,13 @@ class DomainCreateView(AdminUserRequiredMixin, CreateView): ...@@ -49,12 +51,13 @@ class DomainCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainUpdateView(AdminUserRequiredMixin, UpdateView): class DomainUpdateView(PermissionsMixin, UpdateView):
model = Domain model = Domain
template_name = 'assets/domain_create_update.html' template_name = 'assets/domain_create_update.html'
form_class = DomainForm form_class = DomainForm
success_url = reverse_lazy('assets:domain-list') success_url = reverse_lazy('assets:domain-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -65,9 +68,10 @@ class DomainUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -65,9 +68,10 @@ class DomainUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainDetailView(AdminUserRequiredMixin, DetailView): class DomainDetailView(PermissionsMixin, DetailView):
model = Domain model = Domain
template_name = 'assets/domain_detail.html' template_name = 'assets/domain_detail.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -78,16 +82,18 @@ class DomainDetailView(AdminUserRequiredMixin, DetailView): ...@@ -78,16 +82,18 @@ class DomainDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainDeleteView(AdminUserRequiredMixin, DeleteView): class DomainDeleteView(PermissionsMixin, DeleteView):
model = Domain model = Domain
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:domain-list') 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' template_name = 'assets/domain_gateway_list.html'
model = Domain model = Domain
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self.model.objects.all()) self.object = self.get_object(queryset=self.model.objects.all())
...@@ -103,11 +109,12 @@ class DomainGatewayListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateV ...@@ -103,11 +109,12 @@ class DomainGatewayListView(AdminUserRequiredMixin, SingleObjectMixin, TemplateV
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainGatewayCreateView(AdminUserRequiredMixin, CreateView): class DomainGatewayCreateView(PermissionsMixin, CreateView):
model = Gateway model = Gateway
template_name = 'assets/gateway_create_update.html' template_name = 'assets/gateway_create_update.html'
form_class = GatewayForm form_class = GatewayForm
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_success_url(self): def get_success_url(self):
domain = self.object.domain domain = self.object.domain
...@@ -130,11 +137,12 @@ class DomainGatewayCreateView(AdminUserRequiredMixin, CreateView): ...@@ -130,11 +137,12 @@ class DomainGatewayCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView): class DomainGatewayUpdateView(PermissionsMixin, UpdateView):
model = Gateway model = Gateway
template_name = 'assets/gateway_create_update.html' template_name = 'assets/gateway_create_update.html'
form_class = GatewayForm form_class = GatewayForm
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_success_url(self): def get_success_url(self):
domain = self.object.domain domain = self.object.domain
......
...@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \ ...@@ -6,7 +6,7 @@ from django.views.generic import TemplateView, CreateView, \
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse_lazy 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 common.const import create_success_msg, update_success_msg
from ..models import Label from ..models import Label
from ..forms import LabelForm from ..forms import LabelForm
...@@ -18,8 +18,9 @@ __all__ = ( ...@@ -18,8 +18,9 @@ __all__ = (
) )
class LabelListView(AdminUserRequiredMixin, TemplateView): class LabelListView(PermissionsMixin, TemplateView):
template_name = 'assets/label_list.html' template_name = 'assets/label_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -30,13 +31,14 @@ class LabelListView(AdminUserRequiredMixin, TemplateView): ...@@ -30,13 +31,14 @@ class LabelListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class LabelCreateView(AdminUserRequiredMixin, CreateView): class LabelCreateView(PermissionsMixin, CreateView):
model = Label model = Label
template_name = 'assets/label_create_update.html' template_name = 'assets/label_create_update.html'
form_class = LabelForm form_class = LabelForm
success_url = reverse_lazy('assets:label-list') success_url = reverse_lazy('assets:label-list')
success_message = create_success_msg success_message = create_success_msg
disable_name = ['draw', 'search', 'limit', 'offset', '_'] disable_name = ['draw', 'search', 'limit', 'offset', '_']
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -57,12 +59,13 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView): ...@@ -57,12 +59,13 @@ class LabelCreateView(AdminUserRequiredMixin, CreateView):
return super().form_valid(form) return super().form_valid(form)
class LabelUpdateView(AdminUserRequiredMixin, UpdateView): class LabelUpdateView(PermissionsMixin, UpdateView):
model = Label model = Label
template_name = 'assets/label_create_update.html' template_name = 'assets/label_create_update.html'
form_class = LabelForm form_class = LabelForm
success_url = reverse_lazy('assets:label-list') success_url = reverse_lazy('assets:label-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -73,11 +76,12 @@ class LabelUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -73,11 +76,12 @@ class LabelUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class LabelDetailView(AdminUserRequiredMixin, DetailView): class LabelDetailView(PermissionsMixin, DetailView):
pass pass
class LabelDeleteView(AdminUserRequiredMixin, DeleteView): class LabelDeleteView(PermissionsMixin, DeleteView):
model = Label model = Label
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:label-list') success_url = reverse_lazy('assets:label-list')
permission_classes = [IsOrgAdmin]
...@@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView ...@@ -10,7 +10,7 @@ from django.views.generic.detail import DetailView
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from ..forms import SystemUserForm from ..forms import SystemUserForm
from ..models import SystemUser, Node, CommandFilter from ..models import SystemUser, Node, CommandFilter
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
__all__ = [ __all__ = [
...@@ -20,8 +20,9 @@ __all__ = [ ...@@ -20,8 +20,9 @@ __all__ = [
] ]
class SystemUserListView(AdminUserRequiredMixin, TemplateView): class SystemUserListView(PermissionsMixin, TemplateView):
template_name = 'assets/system_user_list.html' template_name = 'assets/system_user_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -32,12 +33,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView): ...@@ -32,12 +33,13 @@ class SystemUserListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class SystemUserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
model = SystemUser model = SystemUser
form_class = SystemUserForm form_class = SystemUserForm
template_name = 'assets/system_user_create.html' template_name = 'assets/system_user_create.html'
success_url = reverse_lazy('assets:system-user-list') success_url = reverse_lazy('assets:system-user-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -48,12 +50,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi ...@@ -48,12 +50,13 @@ class SystemUserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVi
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class SystemUserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
model = SystemUser model = SystemUser
form_class = SystemUserForm form_class = SystemUserForm
template_name = 'assets/system_user_update.html' template_name = 'assets/system_user_update.html'
success_url = reverse_lazy('assets:system-user-list') success_url = reverse_lazy('assets:system-user-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -64,10 +67,11 @@ class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVi ...@@ -64,10 +67,11 @@ class SystemUserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVi
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SystemUserDetailView(AdminUserRequiredMixin, DetailView): class SystemUserDetailView(PermissionsMixin, DetailView):
template_name = 'assets/system_user_detail.html' template_name = 'assets/system_user_detail.html'
context_object_name = 'system_user' context_object_name = 'system_user'
model = SystemUser model = SystemUser
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -79,16 +83,18 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView): ...@@ -79,16 +83,18 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SystemUserDeleteView(AdminUserRequiredMixin, DeleteView): class SystemUserDeleteView(PermissionsMixin, DeleteView):
model = SystemUser model = SystemUser
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('assets:system-user-list') success_url = reverse_lazy('assets:system-user-list')
permission_classes = [IsOrgAdmin]
class SystemUserAssetView(AdminUserRequiredMixin, DetailView): class SystemUserAssetView(PermissionsMixin, DetailView):
model = SystemUser model = SystemUser
template_name = 'assets/system_user_asset.html' template_name = 'assets/system_user_asset.html'
context_object_name = 'system_user' context_object_name = 'system_user'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True) nodes_remain = sorted(Node.objects.exclude(systemuser=self.object), reverse=True)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from rest_framework import viewsets from rest_framework import viewsets
from common.permissions import IsOrgAdminOrAppUser from common.permissions import IsOrgAdminOrAppUser, IsAuditor
from .models import FTPLog from .models import FTPLog
from .serializers import FTPLogSerializer from .serializers import FTPLogSerializer
...@@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer ...@@ -11,4 +11,4 @@ from .serializers import FTPLogSerializer
class FTPLogViewSet(viewsets.ModelViewSet): class FTPLogViewSet(viewsets.ModelViewSet):
queryset = FTPLog.objects.all() queryset = FTPLog.objects.all()
serializer_class = FTPLogSerializer serializer_class = FTPLogSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
...@@ -14,12 +14,11 @@ from django.views import View ...@@ -14,12 +14,11 @@ from django.views import View
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ListView from django.views.generic import ListView
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q from django.db.models import Q
from audits.utils import get_excel_response, write_content_to_excel from audits.utils import get_excel_response, write_content_to_excel
from common.mixins import DatetimeSearchMixin 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 orgs.utils import current_org
from ops.views import CommandExecutionListView as UserCommandExecutionListView from ops.views import CommandExecutionListView as UserCommandExecutionListView
...@@ -42,12 +41,13 @@ def get_resource_type_list(): ...@@ -42,12 +41,13 @@ def get_resource_type_list():
return [model._meta.verbose_name for model in models] return [model._meta.verbose_name for model in models]
class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class FTPLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
model = FTPLog model = FTPLog
template_name = 'audits/ftp_log_list.html' template_name = 'audits/ftp_log_list.html'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = asset = system_user = filename = '' user = asset = system_user = filename = ''
date_from = date_to = None date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get_queryset(self): def get_queryset(self):
self.queryset = super().get_queryset() self.queryset = super().get_queryset()
...@@ -89,13 +89,14 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -89,13 +89,14 @@ class FTPLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class OperateLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
model = OperateLog model = OperateLog
template_name = 'audits/operate_log_list.html' template_name = 'audits/operate_log_list.html'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = action = resource_type = '' user = action = resource_type = ''
date_from = date_to = None date_from = date_to = None
actions_dict = dict(OperateLog.ACTION_CHOICES) actions_dict = dict(OperateLog.ACTION_CHOICES)
permission_classes = [IsOrgAdmin | IsAuditor]
def get_queryset(self): def get_queryset(self):
self.queryset = super().get_queryset() self.queryset = super().get_queryset()
...@@ -124,7 +125,6 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -124,7 +125,6 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
'date_from': self.date_from, 'date_from': self.date_from,
'date_to': self.date_to, 'date_to': self.date_to,
'user': self.user, 'user': self.user,
'action': self.action,
'resource_type': self.resource_type, 'resource_type': self.resource_type,
"app": _("Audits"), "app": _("Audits"),
"action": _("Operate log"), "action": _("Operate log"),
...@@ -133,12 +133,13 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -133,12 +133,13 @@ class OperateLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class PasswordChangeLogList(PermissionsMixin, DatetimeSearchMixin, ListView):
model = PasswordChangeLog model = PasswordChangeLog
template_name = 'audits/password_change_log_list.html' template_name = 'audits/password_change_log_list.html'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = '' user = ''
date_from = date_to = None date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get_queryset(self): def get_queryset(self):
users = current_org.get_org_users() users = current_org.get_org_users()
...@@ -169,12 +170,13 @@ class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListVie ...@@ -169,12 +170,13 @@ class PasswordChangeLogList(AdminUserRequiredMixin, DatetimeSearchMixin, ListVie
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class LoginLogListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class LoginLogListView(PermissionsMixin, DatetimeSearchMixin, ListView):
template_name = 'audits/login_log_list.html' template_name = 'audits/login_log_list.html'
model = UserLoginLog model = UserLoginLog
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = keyword = "" user = keyword = ""
date_to = date_from = None date_to = date_from = None
permission_classes = [IsOrgAdmin | IsAuditor]
@staticmethod @staticmethod
def get_org_users(): def get_org_users():
...@@ -246,11 +248,12 @@ class CommandExecutionListView(UserCommandExecutionListView): ...@@ -246,11 +248,12 @@ class CommandExecutionListView(UserCommandExecutionListView):
'keyword': self.keyword, 'keyword': self.keyword,
'user_id': self.user_id, 'user_id': self.user_id,
}) })
return super().get_context_data(**context) return context
@method_decorator(csrf_exempt, name='dispatch') @method_decorator(csrf_exempt, name='dispatch')
class LoginLogExportView(LoginRequiredMixin, View): class LoginLogExportView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request): def get(self, request):
fields = [ fields = [
......
...@@ -147,7 +147,23 @@ class PrivateTokenAuthentication(authentication.TokenAuthentication): ...@@ -147,7 +147,23 @@ class PrivateTokenAuthentication(authentication.TokenAuthentication):
class SessionAuthentication(authentication.SessionAuthentication): class SessionAuthentication(authentication.SessionAuthentication):
def enforce_csrf(self, request): def authenticate(self, request):
reason = CSRFCheck().process_view(request, None, (), {}) """
if reason: Returns a `User` if the request session currently has a logged in user.
raise exceptions.AuthenticationFailed(reason) 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): ...@@ -48,7 +48,7 @@ class LogTailApi(generics.RetrieveAPIView):
return line return line
def read_from_file(self): 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) offset = cache.get(self.mark, 0)
f.seek(offset) f.seek(offset)
data = f.read(self.buff_size).replace('\n', '\r\n') data = f.read(self.buff_size).replace('\n', '\r\n')
...@@ -79,7 +79,6 @@ class LogTailApi(generics.RetrieveAPIView): ...@@ -79,7 +79,6 @@ class LogTailApi(generics.RetrieveAPIView):
class ResourcesIDCacheApi(APIView): class ResourcesIDCacheApi(APIView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
spm = str(uuid.uuid4()) spm = str(uuid.uuid4())
resources_id = request.data.get('resources') 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 ...@@ -5,6 +5,7 @@ from django.http import JsonResponse
from django.utils import timezone from django.utils import timezone
from django.core.cache import cache from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from rest_framework.utils import html from rest_framework.utils import html
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
...@@ -203,3 +204,31 @@ class DatetimeSearchMixin: ...@@ -203,3 +204,31 @@ class DatetimeSearchMixin:
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.get_date_range() self.get_date_range()
return super().get(request, *args, **kwargs) 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): ...@@ -27,6 +27,12 @@ class IsAppUser(IsValidUser):
and request.user.is_app 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): class IsSuperUser(IsValidUser):
def has_permission(self, request, view): def has_permission(self, request, view):
return super(IsSuperUser, self).has_permission(request, view) \ return super(IsSuperUser, self).has_permission(request, view) \
...@@ -115,3 +121,14 @@ class WithBootstrapToken(permissions.BasePermission): ...@@ -115,3 +121,14 @@ class WithBootstrapToken(permissions.BasePermission):
return False return False
request_bootstrap_token = authorization.split()[-1] request_bootstrap_token = authorization.split()[-1]
return settings.BOOTSTRAP_TOKEN == request_bootstrap_token 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 @@ ...@@ -3,5 +3,22 @@
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _ 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): ...@@ -287,7 +287,10 @@ class Config(dict):
return v return v
try: try:
v = tp(v) if tp in [list, dict]:
v = json.loads(v)
else:
v = tp(v)
except Exception: except Exception:
pass pass
return v return v
......
...@@ -358,7 +358,7 @@ EMAIL_USE_SSL = False ...@@ -358,7 +358,7 @@ EMAIL_USE_SSL = False
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
EMAIL_SUBJECT_PREFIX = '[JMS] ' EMAIL_SUBJECT_PREFIX = '[JMS] '
#Email custom content # Email custom content
EMAIL_CUSTOM_USER_CREATED_SUBJECT = '' EMAIL_CUSTOM_USER_CREATED_SUBJECT = ''
EMAIL_CUSTOM_USER_CREATED_HONORIFIC = '' EMAIL_CUSTOM_USER_CREATED_HONORIFIC = ''
EMAIL_CUSTOM_USER_CREATED_BODY = '' EMAIL_CUSTOM_USER_CREATED_BODY = ''
......
...@@ -8,7 +8,6 @@ from django.utils import timezone ...@@ -8,7 +8,6 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Count from django.db.models import Count
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework.response import Response from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse from django.http import HttpResponse
...@@ -18,10 +17,12 @@ from users.models import User ...@@ -18,10 +17,12 @@ from users.models import User
from assets.models import Asset from assets.models import Asset
from terminal.models import Session from terminal.models import Session
from orgs.utils import current_org from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser
class IndexView(LoginRequiredMixin, TemplateView): class IndexView(PermissionsMixin, TemplateView):
template_name = 'index.html' template_name = 'index.html'
permission_classes = [IsValidUser]
session_week = None session_week = None
session_month = None session_month = None
...@@ -31,6 +32,8 @@ class IndexView(LoginRequiredMixin, TemplateView): ...@@ -31,6 +32,8 @@ class IndexView(LoginRequiredMixin, TemplateView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self.handle_no_permission() 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: if not request.user.is_org_admin:
return redirect('assets:user-asset-list') return redirect('assets:user-asset-list')
if not current_org or not current_org.can_admin_by(request.user): if not current_org or not current_org.can_admin_by(request.user):
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-27 15:53+0800\n" "POT-Creation-Date: 2019-06-20 16:30+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -17,58 +17,58 @@ msgstr "" ...@@ -17,58 +17,58 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: static/js/jumpserver.js:249 #: static/js/jumpserver.js:263
msgid "Update is successful!" msgid "Update is successful!"
msgstr "更新成功" msgstr "更新成功"
#: static/js/jumpserver.js:251 #: static/js/jumpserver.js:265
msgid "An unknown error occurred while updating.." msgid "An unknown error occurred while updating.."
msgstr "更新时发生未知错误" msgstr "更新时发生未知错误"
#: static/js/jumpserver.js:315 static/js/jumpserver.js:352 #: static/js/jumpserver.js:329 static/js/jumpserver.js:366
#: static/js/jumpserver.js:355 #: static/js/jumpserver.js:369
msgid "Error" msgid "Error"
msgstr "错误" msgstr "错误"
#: static/js/jumpserver.js:315 #: static/js/jumpserver.js:329
msgid "Being used by the asset, please unbind the asset first." msgid "Being used by the asset, please unbind the asset first."
msgstr "正在被资产使用中,请先解除资产绑定" msgstr "正在被资产使用中,请先解除资产绑定"
#: static/js/jumpserver.js:321 static/js/jumpserver.js:362 #: static/js/jumpserver.js:335 static/js/jumpserver.js:376
msgid "Delete the success" msgid "Delete the success"
msgstr "删除成功" msgstr "删除成功"
#: static/js/jumpserver.js:327 #: static/js/jumpserver.js:341
msgid "Are you sure about deleting it?" msgid "Are you sure about deleting it?"
msgstr "你确定删除吗 ?" msgstr "你确定删除吗 ?"
#: static/js/jumpserver.js:331 static/js/jumpserver.js:372 #: static/js/jumpserver.js:345 static/js/jumpserver.js:386
msgid "Cancel" msgid "Cancel"
msgstr "取消" msgstr "取消"
#: static/js/jumpserver.js:333 static/js/jumpserver.js:374 #: static/js/jumpserver.js:347 static/js/jumpserver.js:388
msgid "Confirm" msgid "Confirm"
msgstr "确认" msgstr "确认"
#: static/js/jumpserver.js:352 #: static/js/jumpserver.js:366
msgid "" msgid ""
"The organization contains undeleted information. Please try again after " "The organization contains undeleted information. Please try again after "
"deleting" "deleting"
msgstr "组织中包含未删除信息,请删除后重试" msgstr "组织中包含未删除信息,请删除后重试"
#: static/js/jumpserver.js:355 #: static/js/jumpserver.js:369
msgid "" msgid ""
"Do not perform this operation under this organization. Try again after " "Do not perform this operation under this organization. Try again after "
"switching to another organization" "switching to another organization"
msgstr "请勿在此组织下执行此操作,切换到其他组织后重试" msgstr "请勿在此组织下执行此操作,切换到其他组织后重试"
#: static/js/jumpserver.js:368 #: static/js/jumpserver.js:382
msgid "" msgid ""
"Please ensure that the following information in the organization has been " "Please ensure that the following information in the organization has been "
"deleted" "deleted"
msgstr "请确保组织内的以下信息已删除" msgstr "请确保组织内的以下信息已删除"
#: static/js/jumpserver.js:369 #: static/js/jumpserver.js:383
msgid "" msgid ""
"User list、User group、Asset list、Domain list、Admin user、System user、" "User list、User group、Asset list、Domain list、Admin user、System user、"
"Labels、Asset permission" "Labels、Asset permission"
...@@ -76,76 +76,80 @@ msgstr "" ...@@ -76,76 +76,80 @@ msgstr ""
"用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权" "用户列表、用户组、资产列表、网域列表、管理用户、系统用户、标签管理、资产授权"
"规则" "规则"
#: static/js/jumpserver.js:408 #: static/js/jumpserver.js:422
msgid "Loading ..." msgid "Loading ..."
msgstr "加载中 ..." msgstr "加载中 ..."
#: static/js/jumpserver.js:409 #: static/js/jumpserver.js:423
msgid "Search" msgid "Search"
msgstr "搜索" msgstr "搜索"
#: static/js/jumpserver.js:412 #: static/js/jumpserver.js:426
#, javascript-format #, javascript-format
msgid "Selected item %d" msgid "Selected item %d"
msgstr "选中 %d 项" msgstr "选中 %d 项"
#: static/js/jumpserver.js:416 #: static/js/jumpserver.js:430
msgid "Per page _MENU_" msgid "Per page _MENU_"
msgstr "每页 _MENU_" msgstr "每页 _MENU_"
#: static/js/jumpserver.js:417 #: static/js/jumpserver.js:431
msgid "" msgid ""
"Displays the results of items _START_ to _END_; A total of _TOTAL_ entries" "Displays the results of items _START_ to _END_; A total of _TOTAL_ entries"
msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项" msgstr "显示第 _START_ 至 _END_ 项结果; 总共 _TOTAL_ 项"
#: static/js/jumpserver.js:420 #: static/js/jumpserver.js:434
msgid "No match" msgid "No match"
msgstr "没有匹配项" msgstr "没有匹配项"
#: static/js/jumpserver.js:421 #: static/js/jumpserver.js:435
msgid "No record" msgid "No record"
msgstr "没有记录" msgstr "没有记录"
#: static/js/jumpserver.js:563 #: static/js/jumpserver.js:577
msgid "Unknown error occur" msgid "Unknown error occur"
msgstr "" msgstr ""
#: static/js/jumpserver.js:800 #: static/js/jumpserver.js:816
msgid "Password minimum length {N} bits" msgid "Password minimum length {N} bits"
msgstr "密码最小长度 {N} 位" msgstr "密码最小长度 {N} 位"
#: static/js/jumpserver.js:801 #: static/js/jumpserver.js:817
msgid "Must contain capital letters" msgid "Must contain capital letters"
msgstr "必须包含大写字母" msgstr "必须包含大写字母"
#: static/js/jumpserver.js:802 #: static/js/jumpserver.js:818
msgid "Must contain lowercase letters" msgid "Must contain lowercase letters"
msgstr "必须包含小写字母" msgstr "必须包含小写字母"
#: static/js/jumpserver.js:803 #: static/js/jumpserver.js:819
msgid "Must contain numeric characters" msgid "Must contain numeric characters"
msgstr "必须包含数字字符" msgstr "必须包含数字字符"
#: static/js/jumpserver.js:804 #: static/js/jumpserver.js:820
msgid "Must contain special characters" msgid "Must contain special characters"
msgstr "必须包含特殊字符" msgstr "必须包含特殊字符"
#: static/js/jumpserver.js:976 #: static/js/jumpserver.js:995
msgid "Export failed" msgid "Export failed"
msgstr "导出失败" msgstr "导出失败"
#: static/js/jumpserver.js:993 #: static/js/jumpserver.js:1012
msgid "Import Success" msgid "Import Success"
msgstr "导入成功" msgstr "导入成功"
#: static/js/jumpserver.js:998 #: static/js/jumpserver.js:1017
msgid "Update Success" msgid "Update Success"
msgstr "更新成功" msgstr "更新成功"
#: static/js/jumpserver.js:1028 #: static/js/jumpserver.js:1018
msgid "Count"
msgstr "数量"
#: static/js/jumpserver.js:1047
msgid "Import failed" msgid "Import failed"
msgstr "导入失败" msgstr "导入失败"
#: static/js/jumpserver.js:1033 #: static/js/jumpserver.js:1052
msgid "Update failed" msgid "Update failed"
msgstr "更新失败" msgstr "更新失败"
...@@ -21,7 +21,7 @@ class JMSBaseInventory(BaseInventory): ...@@ -21,7 +21,7 @@ class JMSBaseInventory(BaseInventory):
'id': asset.id, 'id': asset.id,
'hostname': asset.hostname, 'hostname': asset.hostname,
'ip': asset.ip, 'ip': asset.ip,
'port': asset.port, 'port': asset.ssh_port,
'vars': dict(), 'vars': dict(),
'groups': [], 'groups': [],
} }
...@@ -29,8 +29,15 @@ class JMSBaseInventory(BaseInventory): ...@@ -29,8 +29,15 @@ class JMSBaseInventory(BaseInventory):
info["vars"].update(self.make_proxy_command(asset)) info["vars"].update(self.make_proxy_command(asset))
if run_as_admin: if run_as_admin:
info.update(asset.get_auth_info()) info.update(asset.get_auth_info())
if asset.is_unixlike():
info["become"] = asset.admin_user.become_info
for node in asset.nodes.all(): for node in asset.nodes.all():
info["groups"].append(node.value) info["groups"].append(node.value)
if asset.is_windows():
info["vars"].update({
"ansible_connection": "ssh",
"ansible_shell_type": "cmd",
})
for label in asset.labels.all(): for label in asset.labels.all():
info["vars"].update({ info["vars"].update({
label.name: label.value label.name: label.value
...@@ -73,7 +80,7 @@ class JMSInventory(JMSBaseInventory): ...@@ -73,7 +80,7 @@ class JMSInventory(JMSBaseInventory):
""" """
def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None): def __init__(self, assets, run_as_admin=False, run_as=None, become_info=None):
""" """
:param host_id_list: ["test1", ] :param assets: assets
:param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同 :param run_as_admin: True 是否使用管理用户去执行, 每台服务器的管理用户可能不同
:param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username) :param run_as: 用户名(添加了统一的资产用户管理器之后AssetUserManager加上之后修改为username)
:param become_info: 是否become成某个用户去执行 :param become_info: 是否become成某个用户去执行
...@@ -86,28 +93,26 @@ class JMSInventory(JMSBaseInventory): ...@@ -86,28 +93,26 @@ class JMSInventory(JMSBaseInventory):
host_list = [] host_list = []
for asset in assets: for asset in assets:
info = self.convert_to_ansible(asset, run_as_admin=run_as_admin) host = self.convert_to_ansible(asset, run_as_admin=run_as_admin)
host_list.append(info) if run_as:
if run_as:
for host in host_list:
run_user_info = self.get_run_user_info(host) run_user_info = self.get_run_user_info(host)
host.update(run_user_info) host.update(run_user_info)
if become_info and asset.is_unixlike():
if become_info:
for host in host_list:
host.update(become_info) host.update(become_info)
host_list.append(host)
super().__init__(host_list=host_list) super().__init__(host_list=host_list)
def get_run_user_info(self, host): def get_run_user_info(self, host):
from assets.backends.multi import AssetUserManager from assets.backends import AssetUserManager
if not self.run_as: if not self.run_as:
return {} return {}
try: try:
asset = self.assets.get(id=host.get('id')) asset = self.assets.get(id=host.get('id'))
run_user = AssetUserManager.get(self.run_as, asset) manager = AssetUserManager()
run_user = manager.get(self.run_as, asset)
except Exception as e: except Exception as e:
logger.error(e, exc_info=True) logger.error(e, exc_info=True)
return {} return {}
...@@ -133,12 +138,10 @@ class JMSCustomInventory(JMSBaseInventory): ...@@ -133,12 +138,10 @@ class JMSCustomInventory(JMSBaseInventory):
host_list = [] host_list = []
for asset in assets: for asset in assets:
info = self.convert_to_ansible(asset) host = self.convert_to_ansible(asset)
host_list.append(info)
for host in host_list:
run_user_info = self.get_run_user_info() run_user_info = self.get_run_user_info()
host.update(run_user_info) host.update(run_user_info)
host_list.append(host)
super().__init__(host_list=host_list) super().__init__(host_list=host_list)
......
...@@ -220,6 +220,7 @@ class AdHoc(models.Model): ...@@ -220,6 +220,7 @@ class AdHoc(models.Model):
time_start = time.time() time_start = time.time()
try: try:
date_start = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') 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)) print(_("{} Start task: {}").format(date_start, self.task.name))
raw, summary = self._run_only() raw, summary = self._run_only()
date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') date_end = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
......
# coding: utf-8 # coding: utf-8
import os import os
import subprocess import subprocess
import datetime
from django.conf import settings from django.conf import settings
from celery import shared_task, subtask from celery import shared_task, subtask
...@@ -104,6 +105,13 @@ def hello(name, callback=None): ...@@ -104,6 +105,13 @@ def hello(name, callback=None):
print("Hello {}".format(name)) 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 @shared_task
def hello_callback(result): def hello_callback(result):
print(result) print(result)
......
...@@ -134,8 +134,11 @@ function getSelectedAssetsNode() { ...@@ -134,8 +134,11 @@ function getSelectedAssetsNode() {
var assetsNodeId = []; var assetsNodeId = [];
var assetsNode = []; var assetsNode = [];
nodes.forEach(function (node) { nodes.forEach(function (node) {
if (node.meta.type === 'asset' && !node.isHidden && node.meta.asset.protocol === 'ssh') { if (node.meta.type === 'asset' && !node.isHidden) {
if (assetsNodeId.indexOf(node.id) === -1) { 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); assetsNodeId.push(node.id);
assetsNode.push(node) assetsNode.push(node)
} }
......
...@@ -18,7 +18,7 @@ def update_or_create_ansible_task( ...@@ -18,7 +18,7 @@ def update_or_create_ansible_task(
run_as_admin=False, run_as=None, become_info=None, run_as_admin=False, run_as=None, become_info=None,
): ):
if not hosts or not tasks or not task_name: if not hosts or not tasks or not task_name:
return return None, None
set_to_root_org() set_to_root_org()
defaults = { defaults = {
'name': task_name, 'name': task_name,
......
...@@ -5,7 +5,7 @@ from django.conf import settings ...@@ -5,7 +5,7 @@ from django.conf import settings
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
from orgs.utils import current_org from orgs.utils import current_org
from ..models import Task, AdHoc, AdHocRunHistory from ..models import Task, AdHoc, AdHocRunHistory
...@@ -17,13 +17,14 @@ __all__ = [ ...@@ -17,13 +17,14 @@ __all__ = [
] ]
class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class TaskListView(PermissionsMixin, DatetimeSearchMixin, ListView):
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
model = Task model = Task
ordering = ('-date_created',) ordering = ('-date_created',)
context_object_name = 'task_list' context_object_name = 'task_list'
template_name = 'ops/task_list.html' template_name = 'ops/task_list.html'
keyword = '' keyword = ''
permission_classes = [IsOrgAdmin]
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
...@@ -51,9 +52,10 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): ...@@ -51,9 +52,10 @@ class TaskListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskDetailView(AdminUserRequiredMixin, DetailView): class TaskDetailView(PermissionsMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_detail.html' template_name = 'ops/task_detail.html'
permission_classes = [IsOrgAdmin]
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
...@@ -73,9 +75,10 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView): ...@@ -73,9 +75,10 @@ class TaskDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskAdhocView(AdminUserRequiredMixin, DetailView): class TaskAdhocView(PermissionsMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_adhoc.html' template_name = 'ops/task_adhoc.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -86,9 +89,10 @@ class TaskAdhocView(AdminUserRequiredMixin, DetailView): ...@@ -86,9 +89,10 @@ class TaskAdhocView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class TaskHistoryView(AdminUserRequiredMixin, DetailView): class TaskHistoryView(PermissionsMixin, DetailView):
model = Task model = Task
template_name = 'ops/task_history.html' template_name = 'ops/task_history.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -99,9 +103,10 @@ class TaskHistoryView(AdminUserRequiredMixin, DetailView): ...@@ -99,9 +103,10 @@ class TaskHistoryView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocDetailView(AdminUserRequiredMixin, DetailView): class AdHocDetailView(PermissionsMixin, DetailView):
model = AdHoc model = AdHoc
template_name = 'ops/adhoc_detail.html' template_name = 'ops/adhoc_detail.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -112,9 +117,10 @@ class AdHocDetailView(AdminUserRequiredMixin, DetailView): ...@@ -112,9 +117,10 @@ class AdHocDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocHistoryView(AdminUserRequiredMixin, DetailView): class AdHocHistoryView(PermissionsMixin, DetailView):
model = AdHoc model = AdHoc
template_name = 'ops/adhoc_history.html' template_name = 'ops/adhoc_history.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -125,9 +131,10 @@ class AdHocHistoryView(AdminUserRequiredMixin, DetailView): ...@@ -125,9 +131,10 @@ class AdHocHistoryView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AdHocHistoryDetailView(AdminUserRequiredMixin, DetailView): class AdHocHistoryDetailView(PermissionsMixin, DetailView):
model = AdHocRunHistory model = AdHocRunHistory
template_name = 'ops/adhoc_history_detail.html' template_name = 'ops/adhoc_history_detail.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -2,14 +2,15 @@ ...@@ -2,14 +2,15 @@
# #
from django.views.generic import TemplateView from django.views.generic import TemplateView
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
__all__ = ['CeleryTaskLogView'] __all__ = ['CeleryTaskLogView']
class CeleryTaskLogView(AdminUserRequiredMixin, TemplateView): class CeleryTaskLogView(PermissionsMixin, TemplateView):
template_name = 'ops/celery_task_log.html' template_name = 'ops/celery_task_log.html'
permission_classes = [IsOrgAdmin | IsAuditor]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
......
...@@ -5,7 +5,9 @@ from django.utils.translation import ugettext as _ ...@@ -5,7 +5,9 @@ from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from common.permissions import AdminUserRequiredMixin, LoginRequiredMixin from common.permissions import (
PermissionsMixin, IsOrgAdmin, IsAuditor, IsValidUser
)
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from ..models import CommandExecution from ..models import CommandExecution
from ..forms import CommandExecutionForm from ..forms import CommandExecutionForm
...@@ -16,13 +18,14 @@ __all__ = [ ...@@ -16,13 +18,14 @@ __all__ = [
] ]
class CommandExecutionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class CommandExecutionListView(PermissionsMixin, DatetimeSearchMixin, ListView):
template_name = 'ops/command_execution_list.html' template_name = 'ops/command_execution_list.html'
model = CommandExecution model = CommandExecution
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
ordering = ('-date_created',) ordering = ('-date_created',)
context_object_name = 'task_list' context_object_name = 'task_list'
keyword = '' keyword = ''
permission_classes = [IsOrgAdmin | IsAuditor]
def _get_queryset(self): def _get_queryset(self):
self.keyword = self.request.GET.get('keyword', '') self.keyword = self.request.GET.get('keyword', '')
...@@ -51,9 +54,10 @@ class CommandExecutionListView(AdminUserRequiredMixin, DatetimeSearchMixin, List ...@@ -51,9 +54,10 @@ class CommandExecutionListView(AdminUserRequiredMixin, DatetimeSearchMixin, List
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandExecutionStartView(LoginRequiredMixin, TemplateView): class CommandExecutionStartView(PermissionsMixin, TemplateView):
template_name = 'ops/command_execution_create.html' template_name = 'ops/command_execution_create.html'
form_class = CommandExecutionForm form_class = CommandExecutionForm
permission_classes = [IsValidUser]
def get_user_system_users(self): def get_user_system_users(self):
from perms.utils import AssetPermissionUtil from perms.utils import AssetPermissionUtil
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .utils import current_org, get_current_org from .utils import current_org, get_org_from_request
from .models import Organization from .models import Organization
def org_processor(request): def org_processor(request):
context = { context = {
'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user), 'ADMIN_ORGS': Organization.get_user_admin_orgs(request.user),
'CURRENT_ORG': get_current_org(), 'CURRENT_ORG': get_org_from_request(request),
'HAS_ORG_PERM': current_org.can_admin_by(request.user), 'HAS_ORG_PERM': current_org.can_admin_by(request.user),
} }
return context return context
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .models import Organization
from .utils import get_org_from_request, set_current_org from .utils import get_org_from_request, set_current_org
...@@ -8,7 +9,21 @@ class OrgMiddleware: ...@@ -8,7 +9,21 @@ class OrgMiddleware:
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
@staticmethod
def set_permed_org_if_need(request):
if request.path.startswith('/api'):
return
if not (request.user.is_authenticated and request.user.is_org_admin):
return
org = get_org_from_request(request)
if org.can_admin_by(request.user):
return
admin_orgs = Organization.get_user_admin_orgs(request.user)
if admin_orgs:
request.session['oid'] = str(admin_orgs[0].id)
def __call__(self, request): def __call__(self, request):
self.set_permed_org_if_need(request)
org = get_org_from_request(request) org = get_org_from_request(request)
request.current_org = org request.current_org = org
set_current_org(org) set_current_org(org)
......
...@@ -9,8 +9,11 @@ from django.forms import ModelForm ...@@ -9,8 +9,11 @@ from django.forms import ModelForm
from django.http.response import HttpResponseForbidden from django.http.response import HttpResponseForbidden
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from rest_framework import serializers from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from common.utils import get_logger from common.utils import get_logger
from common.validators import ProjectUniqueValidator
from common.mixins import BulkSerializerMixin
from .utils import ( from .utils import (
current_org, set_current_org, set_to_root_org, get_current_org_id current_org, set_current_org, set_to_root_org, get_current_org_id
) )
...@@ -23,6 +26,7 @@ __all__ = [ ...@@ -23,6 +26,7 @@ __all__ = [
'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm', 'OrgManager', 'OrgViewGenericMixin', 'OrgModelMixin', 'OrgModelForm',
'RootOrgViewMixin', 'OrgMembershipSerializerMixin', 'RootOrgViewMixin', 'OrgMembershipSerializerMixin',
'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin', 'OrgMembershipModelViewSetMixin', 'OrgResourceSerializerMixin',
'BulkOrgResourceSerializerMixin', 'BulkOrgResourceModelSerializer',
] ]
...@@ -215,4 +219,29 @@ class OrgResourceSerializerMixin(serializers.Serializer): ...@@ -215,4 +219,29 @@ class OrgResourceSerializerMixin(serializers.Serializer):
由于HiddenField字段不可读,API获取资产信息时获取不到org_id, 由于HiddenField字段不可读,API获取资产信息时获取不到org_id,
但是coco需要资产的org_id字段,所以修改为CharField类型 但是coco需要资产的org_id字段,所以修改为CharField类型
""" """
org_id = serializers.CharField(default=get_current_org_id) org_id = serializers.ReadOnlyField(default=get_current_org_id, label=_("Organization"))
org_name = serializers.ReadOnlyField(label=_("Org name"))
def get_validators(self):
_validators = super().get_validators()
validators = []
for v in _validators:
if isinstance(v, UniqueTogetherValidator) \
and "org_id" in v.fields:
v = ProjectUniqueValidator(v.queryset, v.fields)
validators.append(v)
return validators
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(["org_id", "org_name"])
return fields
class BulkOrgResourceSerializerMixin(BulkSerializerMixin, OrgResourceSerializerMixin):
pass
class BulkOrgResourceModelSerializer(BulkOrgResourceSerializerMixin, serializers.ModelSerializer):
pass
...@@ -96,7 +96,7 @@ class Organization(models.Model): ...@@ -96,7 +96,7 @@ class Organization(models.Model):
admin_orgs = [] admin_orgs = []
if user.is_anonymous: if user.is_anonymous:
return admin_orgs return admin_orgs
elif user.is_superuser: elif user.is_superuser or user.is_auditor:
admin_orgs = list(cls.objects.all()) admin_orgs = list(cls.objects.all())
admin_orgs.append(cls.default()) admin_orgs.append(cls.default())
elif user.is_org_admin: elif user.is_org_admin:
......
...@@ -14,7 +14,6 @@ from rest_framework.pagination import LimitOffsetPagination ...@@ -14,7 +14,6 @@ from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsValidUser, IsOrgAdminOrAppUser from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.tree import TreeNodeSerializer from common.tree import TreeNodeSerializer
from common.utils import get_logger from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..utils import ( from ..utils import (
AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node,
check_system_user_action, RemoteAppPermissionUtil, check_system_user_action, RemoteAppPermissionUtil,
...@@ -26,7 +25,7 @@ from ..hands import ( ...@@ -26,7 +25,7 @@ from ..hands import (
) )
from .. import serializers, const from .. import serializers, const
from ..mixins import ( from ..mixins import (
AssetsFilterMixin, RemoteAppFilterMixin, ChangeOrgIfNeedMixin AssetsFilterMixin, RemoteAppFilterMixin
) )
from ..models import Action from ..models import Action
...@@ -48,14 +47,6 @@ class UserPermissionCacheMixin: ...@@ -48,14 +47,6 @@ class UserPermissionCacheMixin:
CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME CACHE_TIME = settings.ASSETS_PERM_CACHE_TIME
_object = None _object = None
@staticmethod
def change_org_if_need(request, kwargs):
if request.user.is_authenticated and \
request.user.is_superuser or \
request.user.is_app or \
kwargs.get('pk') is None:
set_to_root_org()
def get_object(self): def get_object(self):
return None return None
...@@ -115,7 +106,6 @@ class UserPermissionCacheMixin: ...@@ -115,7 +106,6 @@ class UserPermissionCacheMixin:
cache.set(key, response.data, self.CACHE_TIME) cache.set(key, response.data, self.CACHE_TIME)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.change_org_if_need(request, kwargs)
self.cache_policy = request.GET.get('cache_policy', '0') self.cache_policy = request.GET.get('cache_policy', '0')
obj = self._get_object() obj = self._get_object()
...@@ -156,7 +146,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV ...@@ -156,7 +146,7 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
util = AssetPermissionUtil(user, cache_policy=self.cache_policy) util = AssetPermissionUtil(user, cache_policy=self.cache_policy)
assets = util.get_assets() assets = util.get_assets()
for k, v in assets.items(): for k, v in assets.items():
system_users_granted = [s for s in v if s.protocol == k.protocol] system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
k.system_users_granted = system_users_granted k.system_users_granted = system_users_granted
queryset.append(k) queryset.append(k)
return queryset return queryset
...@@ -217,8 +207,7 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ...@@ -217,8 +207,7 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
for node, _assets in nodes.items(): for node, _assets in nodes.items():
assets = _assets.keys() assets = _assets.keys()
for k, v in _assets.items(): for k, v in _assets.items():
system_users_granted = [s for s in v if system_users_granted = [s for s in v if k.has_protocol(s.protocol)]
s.protocol == k.protocol]
k.system_users_granted = system_users_granted k.system_users_granted = system_users_granted
node.assets_granted = assets node.assets_granted = assets
queryset.append(node) queryset.append(node)
...@@ -366,7 +355,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView): ...@@ -366,7 +355,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
for asset, system_users in nodes_granted[node].items(): for asset, system_users in nodes_granted[node].items():
fake_node = asset.as_node() fake_node = asset.as_node()
fake_node.assets_amount = 0 fake_node.assets_amount = 0
system_users = [s for s in system_users if s.protocol == asset.protocol] system_users = [s for s in system_users if asset.has_protocol(s.protocol)]
fake_node.asset.system_users_granted = system_users fake_node.asset.system_users_granted = system_users
fake_node.key = node.key + ':0' fake_node.key = node.key + ':0'
fake_nodes.append(fake_node) fake_nodes.append(fake_node)
...@@ -391,7 +380,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView): ...@@ -391,7 +380,7 @@ class UserGrantedNodeChildrenApi(UserPermissionCacheMixin, ListAPIView):
fake_node = asset.as_node() fake_node = asset.as_node()
fake_node.assets_amount = 0 fake_node.assets_amount = 0
system_users = [s for s in system_users if system_users = [s for s in system_users if
s.protocol == asset.protocol] asset.has_protocol(s.protocol)]
fake_node.asset.system_users_granted = system_users fake_node.asset.system_users_granted = system_users
fake_node.key = node.key + ':0' fake_node.key = node.key + ':0'
matched_assets.append(fake_node) matched_assets.append(fake_node)
...@@ -462,7 +451,7 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): ...@@ -462,7 +451,7 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView):
# RemoteApp permission # RemoteApp permission
class UserGrantedRemoteAppsApi(ChangeOrgIfNeedMixin, RemoteAppFilterMixin, ListAPIView): class UserGrantedRemoteAppsApi(RemoteAppFilterMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = RemoteAppSerializer serializer_class = RemoteAppSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
...@@ -487,7 +476,7 @@ class UserGrantedRemoteAppsApi(ChangeOrgIfNeedMixin, RemoteAppFilterMixin, ListA ...@@ -487,7 +476,7 @@ class UserGrantedRemoteAppsApi(ChangeOrgIfNeedMixin, RemoteAppFilterMixin, ListA
return super().get_permissions() return super().get_permissions()
class UserGrantedRemoteAppsAsTreeApi(ChangeOrgIfNeedMixin, ListAPIView): class UserGrantedRemoteAppsAsTreeApi(ListAPIView):
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
...@@ -519,7 +508,7 @@ class UserGrantedRemoteAppsAsTreeApi(ChangeOrgIfNeedMixin, ListAPIView): ...@@ -519,7 +508,7 @@ class UserGrantedRemoteAppsAsTreeApi(ChangeOrgIfNeedMixin, ListAPIView):
return super().get_permissions() return super().get_permissions()
class ValidateUserRemoteAppPermissionApi(ChangeOrgIfNeedMixin, APIView): class ValidateUserRemoteAppPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
...@@ -533,5 +522,4 @@ class ValidateUserRemoteAppPermissionApi(ChangeOrgIfNeedMixin, APIView): ...@@ -533,5 +522,4 @@ class ValidateUserRemoteAppPermissionApi(ChangeOrgIfNeedMixin, APIView):
remote_apps = util.get_remote_apps() remote_apps = util.get_remote_apps()
if remote_app not in remote_apps: if remote_app not in remote_apps:
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
# #
from common.permissions import AdminUserRequiredMixin
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import Asset, SystemUser, Node, RemoteApp from assets.models import Asset, SystemUser, Node
from assets.serializers import ( from assets.serializers import (
AssetGrantedSerializer, NodeSerializer AssetGrantedSerializer, NodeSerializer
) )
from applications.serializers import RemoteAppSerializer from applications.serializers import RemoteAppSerializer
from applications.models import RemoteApp
...@@ -4,15 +4,15 @@ ...@@ -4,15 +4,15 @@
from rest_framework import serializers from rest_framework import serializers
from common.fields import StringManyToManyField from common.fields import StringManyToManyField
from orgs.mixins import BulkOrgResourceModelSerializer
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
from assets.models import Node, Asset, SystemUser from assets.models import Node
from assets.serializers import AssetGrantedSerializer from assets.serializers import AssetGrantedSerializer
__all__ = [ __all__ = [
'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer', 'AssetPermissionCreateUpdateSerializer', 'AssetPermissionListSerializer',
'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer',
'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer',
'GrantedAssetSerializer', 'GrantedSystemUserSerializer',
'ActionSerializer', 'NodeGrantedSerializer', 'ActionSerializer', 'NodeGrantedSerializer',
] ]
...@@ -23,13 +23,13 @@ class ActionSerializer(serializers.ModelSerializer): ...@@ -23,13 +23,13 @@ class ActionSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer): class AssetPermissionCreateUpdateSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = AssetPermission model = AssetPermission
exclude = ('created_by', 'date_created') exclude = ('created_by', 'date_created')
class AssetPermissionListSerializer(serializers.ModelSerializer): class AssetPermissionListSerializer(BulkOrgResourceModelSerializer):
users = StringManyToManyField(many=True, read_only=True) users = StringManyToManyField(many=True, read_only=True)
user_groups = StringManyToManyField(many=True, read_only=True) user_groups = StringManyToManyField(many=True, read_only=True)
assets = StringManyToManyField(many=True, read_only=True) assets = StringManyToManyField(many=True, read_only=True)
...@@ -122,19 +122,21 @@ class GrantedNodeSerializer(serializers.ModelSerializer): ...@@ -122,19 +122,21 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
] ]
class GrantedAssetSerializer(serializers.ModelSerializer): # class GrantedAssetSerializer(serializers.ModelSerializer):
class Meta: # protocols = ProtocolSerializer(many=True)
model = Asset #
fields = [ # class Meta:
'id', 'hostname', 'ip', 'port', 'protocol', 'platform', # model = Asset
'domain', 'is_active', 'comment' # fields = [
] # 'id', 'hostname', 'ip', 'protocols', 'port', 'protocol',
# 'platform', 'domain', 'is_active', 'comment'
# ]
class GrantedSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser # class GrantedSystemUserSerializer(serializers.ModelSerializer):
fields = [ # class Meta:
'id', 'name', 'username', 'protocol', 'priority', # model = SystemUser
'login_mode', 'comment' # fields = [
] # 'id', 'name', 'username', 'protocol', 'priority',
# 'login_mode', 'comment'
# ]
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
from rest_framework import serializers from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import RemoteAppPermission from ..models import RemoteAppPermission
...@@ -13,13 +15,14 @@ __all__ = [ ...@@ -13,13 +15,14 @@ __all__ = [
] ]
class RemoteAppPermissionSerializer(serializers.ModelSerializer): class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer):
class Meta: class Meta:
model = RemoteAppPermission model = RemoteAppPermission
list_serializer_class = AdaptedBulkListSerializer
fields = [ fields = [
'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment', 'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment',
'is_active', 'date_start', 'date_expired', 'is_valid', 'is_active', 'date_start', 'date_expired', 'is_valid',
'created_by', 'date_created', 'org_id' 'created_by', 'date_created',
] ]
read_only_fields = ['created_by', 'date_created'] read_only_fields = ['created_by', 'date_created']
......
...@@ -11,6 +11,7 @@ from django.core.cache import cache ...@@ -11,6 +11,7 @@ from django.core.cache import cache
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from orgs.utils import set_to_root_org
from common.utils import get_logger from common.utils import get_logger
from common.tree import TreeNode from common.tree import TreeNode
from .. import const from .. import const
...@@ -162,6 +163,11 @@ class AssetPermissionUtil: ...@@ -162,6 +163,11 @@ class AssetPermissionUtil:
self._filter_id = 'None' # 当通过filter更改 permission是标记 self._filter_id = 'None' # 当通过filter更改 permission是标记
self.cache_policy = cache_policy self.cache_policy = cache_policy
self.tree = GenerateTree() self.tree = GenerateTree()
self.change_org_if_need()
@staticmethod
def change_org_if_need():
set_to_root_org()
@classmethod @classmethod
def is_not_using_cache(cls, cache_policy): def is_not_using_cache(cls, cache_policy):
...@@ -238,7 +244,7 @@ class AssetPermissionUtil: ...@@ -238,7 +244,7 @@ class AssetPermissionUtil:
for perm in permissions: for perm in permissions:
actions = perm.actions.all() actions = perm.actions.all()
for asset in perm.assets.all().valid().prefetch_related('nodes'): for asset in perm.assets.all().valid().prefetch_related('nodes'):
system_users = perm.system_users.filter(protocol=asset.protocol) system_users = perm.system_users.filter(protocol__in=asset.protocols_name)
system_users = self._structured_system_user(system_users, actions) system_users = self._structured_system_user(system_users, actions)
assets[asset].update(system_users) assets[asset].update(system_users)
return assets return assets
...@@ -255,7 +261,7 @@ class AssetPermissionUtil: ...@@ -255,7 +261,7 @@ class AssetPermissionUtil:
_assets = node.get_all_assets().valid().prefetch_related('nodes') _assets = node.get_all_assets().valid().prefetch_related('nodes')
for asset in _assets: for asset in _assets:
for system_user, attr_dict in system_users.items(): for system_user, attr_dict in system_users.items():
if system_user.protocol != asset.protocol: if not asset.has_protocol(system_user.protocol):
continue continue
if system_user in assets[asset]: if system_user in assets[asset]:
actions = assets[asset][system_user]['actions'] actions = assets[asset][system_user]['actions']
...@@ -279,15 +285,12 @@ class AssetPermissionUtil: ...@@ -279,15 +285,12 @@ class AssetPermissionUtil:
resource=resource resource=resource
) )
@property
def node_key(self): def node_key(self):
return self.get_cache_key('NODES_WITH_ASSETS') return self.get_cache_key('NODES_WITH_ASSETS')
@property
def asset_key(self): def asset_key(self):
return self.get_cache_key('ASSETS') return self.get_cache_key('ASSETS')
@property
def system_key(self): def system_key(self):
return self.get_cache_key('SYSTEM_USER') return self.get_cache_key('SYSTEM_USER')
...@@ -457,7 +460,7 @@ def parse_node_to_tree_node(node): ...@@ -457,7 +460,7 @@ def parse_node_to_tree_node(node):
def parse_asset_to_tree_node(node, asset, system_users): def parse_asset_to_tree_node(node, asset, system_users):
system_users_protocol_matched = [s for s in system_users if s.protocol == asset.protocol] system_users_protocol_matched = [s for s in system_users if asset.has_protocol(s.protocol)]
icon_skin = 'file' icon_skin = 'file'
if asset.platform.lower() == 'windows': if asset.platform.lower() == 'windows':
icon_skin = 'windows' icon_skin = 'windows'
...@@ -490,8 +493,8 @@ def parse_asset_to_tree_node(node, asset, system_users): ...@@ -490,8 +493,8 @@ def parse_asset_to_tree_node(node, asset, system_users):
'id': asset.id, 'id': asset.id,
'hostname': asset.hostname, 'hostname': asset.hostname,
'ip': asset.ip, 'ip': asset.ip,
'port': asset.port, 'protocols': [{"name": p.name, "port": p.port}
'protocol': asset.protocol, for p in asset.protocols.all()],
'platform': asset.platform, 'platform': asset.platform,
'domain': None if not asset.domain else asset.domain.id, 'domain': None if not asset.domain else asset.domain.id,
'is_active': asset.is_active, 'is_active': asset.is_active,
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
from django.db.models import Q from django.db.models import Q
from common.tree import TreeNode from common.tree import TreeNode
from orgs.utils import set_to_root_org
from ..models import RemoteAppPermission from ..models import RemoteAppPermission
...@@ -38,6 +39,11 @@ class RemoteAppPermissionUtil: ...@@ -38,6 +39,11 @@ class RemoteAppPermissionUtil:
def __init__(self, obj): def __init__(self, obj):
self.object = obj self.object = obj
self.change_org_if_need()
@staticmethod
def change_org_if_need():
set_to_root_org()
@property @property
def permissions(self): def permissions(self):
......
...@@ -8,7 +8,7 @@ from django.views.generic.edit import DeleteView, SingleObjectMixin ...@@ -8,7 +8,7 @@ from django.views.generic.edit import DeleteView, SingleObjectMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
from orgs.utils import current_org from orgs.utils import current_org
from perms.hands import Node, Asset, SystemUser, User, UserGroup from perms.hands import Node, Asset, SystemUser, User, UserGroup
from perms.models import AssetPermission, Action from perms.models import AssetPermission, Action
...@@ -25,8 +25,9 @@ __all__ = [ ...@@ -25,8 +25,9 @@ __all__ = [
] ]
class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): class AssetPermissionListView(PermissionsMixin, TemplateView):
template_name = 'perms/asset_permission_list.html' template_name = 'perms/asset_permission_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -37,11 +38,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): ...@@ -37,11 +38,12 @@ class AssetPermissionListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView): class AssetPermissionCreateView(PermissionsMixin, CreateView):
model = AssetPermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
permission_classes = [IsOrgAdmin]
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class=form_class) form = super().get_form(form_class=form_class)
...@@ -69,11 +71,12 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView): ...@@ -69,11 +71,12 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): class AssetPermissionUpdateView(PermissionsMixin, UpdateView):
model = AssetPermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_create_update.html' template_name = 'perms/asset_permission_create_update.html'
success_url = reverse_lazy("perms:asset-permission-list") success_url = reverse_lazy("perms:asset-permission-list")
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -84,11 +87,12 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -84,11 +87,12 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): class AssetPermissionDetailView(PermissionsMixin, DetailView):
model = AssetPermission model = AssetPermission
form_class = AssetPermissionForm form_class = AssetPermissionForm
template_name = 'perms/asset_permission_detail.html' template_name = 'perms/asset_permission_detail.html'
success_url = reverse_lazy("perms:asset-permission-list") success_url = reverse_lazy("perms:asset-permission-list")
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -102,19 +106,21 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView): ...@@ -102,19 +106,21 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionDeleteView(AdminUserRequiredMixin, DeleteView): class AssetPermissionDeleteView(PermissionsMixin, DeleteView):
model = AssetPermission model = AssetPermission
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('perms:asset-permission-list') success_url = reverse_lazy('perms:asset-permission-list')
permission_classes = [IsOrgAdmin]
class AssetPermissionUserView(AdminUserRequiredMixin, class AssetPermissionUserView(PermissionsMixin,
SingleObjectMixin, SingleObjectMixin,
ListView): ListView):
template_name = 'perms/asset_permission_user.html' template_name = 'perms/asset_permission_user.html'
context_object_name = 'asset_permission' context_object_name = 'asset_permission'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=AssetPermission.objects.all()) self.object = self.get_object(queryset=AssetPermission.objects.all())
...@@ -140,13 +146,14 @@ class AssetPermissionUserView(AdminUserRequiredMixin, ...@@ -140,13 +146,14 @@ class AssetPermissionUserView(AdminUserRequiredMixin,
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class AssetPermissionAssetView(AdminUserRequiredMixin, class AssetPermissionAssetView(PermissionsMixin,
SingleObjectMixin, SingleObjectMixin,
ListView): ListView):
template_name = 'perms/asset_permission_asset.html' template_name = 'perms/asset_permission_asset.html'
context_object_name = 'asset_permission' context_object_name = 'asset_permission'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset = AssetPermission.objects.all()) self.object = self.get_object(queryset = AssetPermission.objects.all())
......
...@@ -9,11 +9,10 @@ from django.views.generic import ( ...@@ -9,11 +9,10 @@ from django.views.generic import (
from django.views.generic.edit import SingleObjectMixin from django.views.generic.edit import SingleObjectMixin
from django.conf import settings from django.conf import settings
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
from orgs.utils import current_org from orgs.utils import current_org
from users.models import UserGroup
from assets.models import RemoteApp
from ..hands import RemoteApp, UserGroup
from ..models import RemoteAppPermission from ..models import RemoteAppPermission
from ..forms import RemoteAppPermissionCreateUpdateForm from ..forms import RemoteAppPermissionCreateUpdateForm
...@@ -25,8 +24,9 @@ __all__ = [ ...@@ -25,8 +24,9 @@ __all__ = [
] ]
class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView): class RemoteAppPermissionListView(PermissionsMixin, TemplateView):
template_name = 'perms/remote_app_permission_list.html' template_name = 'perms/remote_app_permission_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -37,11 +37,12 @@ class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView): ...@@ -37,11 +37,12 @@ class RemoteAppPermissionListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView): class RemoteAppPermissionCreateView(PermissionsMixin, CreateView):
template_name = 'perms/remote_app_permission_create_update.html' template_name = 'perms/remote_app_permission_create_update.html'
model = RemoteAppPermission model = RemoteAppPermission
form_class = RemoteAppPermissionCreateUpdateForm form_class = RemoteAppPermissionCreateUpdateForm
success_url = reverse_lazy('perms:remote-app-permission-list') success_url = reverse_lazy('perms:remote-app-permission-list')
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -52,11 +53,12 @@ class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView): ...@@ -52,11 +53,12 @@ class RemoteAppPermissionCreateView(AdminUserRequiredMixin, CreateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView): class RemoteAppPermissionUpdateView(PermissionsMixin, UpdateView):
template_name = 'perms/remote_app_permission_create_update.html' template_name = 'perms/remote_app_permission_create_update.html'
model = RemoteAppPermission model = RemoteAppPermission
form_class = RemoteAppPermissionCreateUpdateForm form_class = RemoteAppPermissionCreateUpdateForm
success_url = reverse_lazy('perms:remote-app-permission-list') success_url = reverse_lazy('perms:remote-app-permission-list')
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -67,9 +69,10 @@ class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView): ...@@ -67,9 +69,10 @@ class RemoteAppPermissionUpdateView(AdminUserRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView): class RemoteAppPermissionDetailView(PermissionsMixin, DetailView):
template_name = 'perms/remote_app_permission_detail.html' template_name = 'perms/remote_app_permission_detail.html'
model = RemoteAppPermission model = RemoteAppPermission
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -80,13 +83,14 @@ class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView): ...@@ -80,13 +83,14 @@ class RemoteAppPermissionDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppPermissionUserView(AdminUserRequiredMixin, class RemoteAppPermissionUserView(PermissionsMixin,
SingleObjectMixin, SingleObjectMixin,
ListView): ListView):
template_name = 'perms/remote_app_permission_user.html' template_name = 'perms/remote_app_permission_user.html'
context_object_name = 'remote_app_permission' context_object_name = 'remote_app_permission'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object( self.object = self.get_object(
...@@ -112,13 +116,14 @@ class RemoteAppPermissionUserView(AdminUserRequiredMixin, ...@@ -112,13 +116,14 @@ class RemoteAppPermissionUserView(AdminUserRequiredMixin,
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class RemoteAppPermissionRemoteAppView(AdminUserRequiredMixin, class RemoteAppPermissionRemoteAppView(PermissionsMixin,
SingleObjectMixin, SingleObjectMixin,
ListView): ListView):
template_name = 'perms/remote_app_permission_remote_app.html' template_name = 'perms/remote_app_permission_remote_app.html'
context_object_name = 'remote_app_permission' context_object_name = 'remote_app_permission'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
object = None object = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object( self.object = self.get_object(
......
...@@ -3,15 +3,16 @@ from django.shortcuts import render, redirect ...@@ -3,15 +3,16 @@ from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from common.permissions import SuperUserRequiredMixin from common.permissions import PermissionsMixin, IsSuperUser
from common import utils from common import utils
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm TerminalSettingForm, SecuritySettingForm, EmailContentSettingForm
class BasicSettingView(SuperUserRequiredMixin, TemplateView): class BasicSettingView(PermissionsMixin, TemplateView):
form_class = BasicSettingForm form_class = BasicSettingForm
template_name = "settings/basic_setting.html" template_name = "settings/basic_setting.html"
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -35,9 +36,10 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -35,9 +36,10 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class EmailSettingView(SuperUserRequiredMixin, TemplateView): class EmailSettingView(PermissionsMixin, TemplateView):
form_class = EmailSettingForm form_class = EmailSettingForm
template_name = "settings/email_setting.html" template_name = "settings/email_setting.html"
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -61,9 +63,10 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -61,9 +63,10 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class LDAPSettingView(SuperUserRequiredMixin, TemplateView): class LDAPSettingView(PermissionsMixin, TemplateView):
form_class = LDAPSettingForm form_class = LDAPSettingForm
template_name = "settings/ldap_setting.html" template_name = "settings/ldap_setting.html"
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -87,9 +90,10 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -87,9 +90,10 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class TerminalSettingView(SuperUserRequiredMixin, TemplateView): class TerminalSettingView(PermissionsMixin, TemplateView):
form_class = TerminalSettingForm form_class = TerminalSettingForm
template_name = "settings/terminal_setting.html" template_name = "settings/terminal_setting.html"
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
command_storage = utils.get_command_storage_setting() command_storage = utils.get_command_storage_setting()
...@@ -118,8 +122,9 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -118,8 +122,9 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView): class ReplayStorageCreateView(PermissionsMixin, TemplateView):
template_name = 'settings/replay_storage_create.html' template_name = 'settings/replay_storage_create.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -130,8 +135,9 @@ class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView): ...@@ -130,8 +135,9 @@ class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView): class CommandStorageCreateView(PermissionsMixin, TemplateView):
template_name = 'settings/command_storage_create.html' template_name = 'settings/command_storage_create.html'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -142,9 +148,10 @@ class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView): ...@@ -142,9 +148,10 @@ class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SecuritySettingView(SuperUserRequiredMixin, TemplateView): class SecuritySettingView(PermissionsMixin, TemplateView):
form_class = SecuritySettingForm form_class = SecuritySettingForm
template_name = "settings/security_setting.html" template_name = "settings/security_setting.html"
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -168,9 +175,10 @@ class SecuritySettingView(SuperUserRequiredMixin, TemplateView): ...@@ -168,9 +175,10 @@ class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class EmailContentSettingView(SuperUserRequiredMixin, TemplateView): class EmailContentSettingView(PermissionsMixin, TemplateView):
template_name = "settings/email_content_setting.html" template_name = "settings/email_content_setting.html"
form_class = EmailContentSettingForm form_class = EmailContentSettingForm
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -453,4 +453,16 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -453,4 +453,16 @@ div.dataTables_wrapper div.dataTables_filter {
#tree-refresh .fa-refresh { #tree-refresh .fa-refresh {
font: normal normal normal 14px/1 FontAwesome !important; font: normal normal normal 14px/1 FontAwesome !important;
} }
\ No newline at end of file
.select2-selection__rendered span.select2-selection, .select2-container .select2-selection--single, .select2-selection__arrow {
height: 34px !important;
}
.select2-selection {
border-radius: 0 !important;
}
span.select2-selection__placeholder {
line-height: 34px !important;
}
...@@ -165,11 +165,13 @@ function formSubmit(props) { ...@@ -165,11 +165,13 @@ function formSubmit(props) {
/* /*
{ {
"form": $("form"), "form": $("form"),
"data": {},
"url": "", "url": "",
"method": "POST", "method": "POST",
"redirect_to": "", "redirect_to": "",
"success": function(data, textStatue, jqXHR){}, "success": function(data, textStatue, jqXHR){},
"error": function(jqXHR, textStatus, errorThrown) {} "error": function(jqXHR, textStatus, errorThrown) {},
"message": "",
} }
*/ */
props = props || {}; props = props || {};
...@@ -183,6 +185,10 @@ function formSubmit(props) { ...@@ -183,6 +185,10 @@ function formSubmit(props) {
dataType: props.data_type || "json" dataType: props.data_type || "json"
}).done(function (data, textState, jqXHR) { }).done(function (data, textState, jqXHR) {
if (redirect_to) { if (redirect_to) {
if (props.message) {
var messages="ed65330a45559c87345a0eb6ac7812d18d0d8976$[[\"__json_message\"\0540\05425\054\"asdfasdf \\u521b\\u5efa\\u6210\\u529f\"]]"
setCookie("messages", messages)
}
location.href = redirect_to; location.href = redirect_to;
} else if (typeof props.success === 'function') { } else if (typeof props.success === 'function') {
return props.success(data, textState, jqXHR); return props.success(data, textState, jqXHR);
...@@ -230,7 +236,15 @@ function formSubmit(props) { ...@@ -230,7 +236,15 @@ function formSubmit(props) {
var help_msg = v.join("<br/>") ; var help_msg = v.join("<br/>") ;
helpBlockRef.html(help_msg); helpBlockRef.html(help_msg);
} else { } else {
noneFieldErrorMsg += v + '<br/>'; $.each(v, function (kk, vv) {
if (typeof errors === "object") {
$.each(vv, function (kkk, vvv) {
noneFieldErrorMsg += " " + vvv + '<br/>';
})
} else{
noneFieldErrorMsg += vv + '<br/>';
}
})
} }
}); });
if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') { if (noneFieldErrorRef.length === 1 && noneFieldErrorMsg !== '') {
...@@ -632,6 +646,8 @@ jumpserver.initServerSideDataTable = function (options) { ...@@ -632,6 +646,8 @@ jumpserver.initServerSideDataTable = function (options) {
$.each(rows, function (id, row) { $.each(rows, function (id, row) {
table.selected_rows.push(row); table.selected_rows.push(row);
if (row.id && $.inArray(row.id, table.selected) === -1){ if (row.id && $.inArray(row.id, table.selected) === -1){
console.log(table)
console.log(table.selected);
table.selected.push(row.id) table.selected.push(row.id)
} }
}) })
...@@ -913,8 +929,11 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul ...@@ -913,8 +929,11 @@ function initPopover($container, $progress, $idPassword, $el, password_check_rul
} }
// 解决input框中的资产和弹出表格中资产的显示不一致 // 解决input框中的资产和弹出表格中资产的显示不一致
function initSelectedAssets2Table(){ function initSelectedAssets2Table(id){
var inputAssets = $('#id_assets').val(); if (!id) {
id = "#id_assets"
}
var inputAssets = $(id).val();
var selectedAssets = asset_table2.selected.concat(); var selectedAssets = asset_table2.selected.concat();
// input assets无,table assets选中,则取消勾选(再次click) // input assets无,table assets选中,则取消勾选(再次click)
...@@ -996,7 +1015,7 @@ function APIImportData(props){ ...@@ -996,7 +1015,7 @@ function APIImportData(props){
$('#updated_failed').html(''); $('#updated_failed').html('');
$('#updated_failed_detail').html(''); $('#updated_failed_detail').html('');
$('#success_updated').html(gettext("Update Success")); $('#success_updated').html(gettext("Update Success"));
$('#success_updated_detail').html("Count" + ": " + data.length); $('#success_updated_detail').html(gettext("Count") + ": " + data.length);
} }
props.data_table.ajax.reload() props.data_table.ajax.reload()
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
{% include '_user_profile.html' %} {% include '_user_profile.html' %}
{% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %} {% if request.user.is_org_admin and request.COOKIES.IN_ADMIN_PAGE != "No" %}
{% include '_nav.html' %} {% include '_nav.html' %}
{% elif request.user.is_auditor %}
{% include '_nav_audits.html' %}
{% else %} {% else %}
{% include '_nav_user.html' %} {% include '_nav_user.html' %}
{% endif %} {% endif %}
......
{% load i18n %}
<li id="index">
<a href="{% url 'index' %}">
<i class="fa fa-dashboard" style="width: 14px"></i> <span class="nav-label">{% trans 'Dashboard' %}</span>
<span class="label label-info pull-right"></span>
</a>
</li>
<li id="terminal">
<a>
<i class="fa fa-rocket" style="width: 14px"></i> <span class="nav-label">{% trans 'Sessions' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="session-online"><a href="{% url 'terminal:session-online-list' %}">{% trans 'Session online' %}</a></li>
<li id="session-offline"><a href="{% url 'terminal:session-offline-list' %}">{% trans 'Session offline' %}</a></li>
<li id="command"><a href="{% url 'terminal:command-list' %}">{% trans 'Commands' %}</a></li>
</ul>
</li>
<li id="audits">
<a>
<i class="fa fa-history" style="width: 14px"></i> <span class="nav-label">{% trans 'Audits' %}</span><span class="fa arrow"></span>
</a>
<ul class="nav nav-second-level">
<li id="login-log"><a href="{% url 'audits:login-log-list' %}">{% trans 'Login log' %}</a></li>
<li id="ftp-log"><a href="{% url 'audits:ftp-log-list' %}">{% trans 'FTP log' %}</a></li>
<li id="operate-log"><a href="{% url 'audits:operate-log-list' %}">{% trans 'Operate log' %}</a></li>
<li id="password-change-log"><a href="{% url 'audits:password-change-log-list' %}">{% trans 'Password change log' %}</a></li>
<li id="command-execution-log"><a href="{% url 'audits:command-execution-log-list' %}">{% trans 'Batch command' %}</a></li>
</ul>
</li>
\ No newline at end of file
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
<i class="fa fa-files-o" style="width: 14px"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span> <i class="fa fa-files-o" style="width: 14px"></i><span class="nav-label">{% trans 'My assets' %}</span><span class="label label-info pull-right"></span>
</a> </a>
</li> </li>
{% if LICENSE_VALID %}
<li id="applications"> <li id="applications">
<a> <a>
<i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'My Applications' %}</span><span class="fa arrow"></span> <i class="fa fa-th" style="width: 14px"></i> <span class="nav-label">{% trans 'My Applications' %}</span><span class="fa arrow"></span>
...@@ -16,6 +18,8 @@ ...@@ -16,6 +18,8 @@
</li> </li>
</ul> </ul>
</li> </li>
{% endif %}
{% if SECURITY_COMMAND_EXECUTION %} {% if SECURITY_COMMAND_EXECUTION %}
<li id="ops"> <li id="ops">
<a href="{% url 'ops:command-execution-start' %}"> <a href="{% url 'ops:command-execution-start' %}">
......
...@@ -15,7 +15,7 @@ import jms_storage ...@@ -15,7 +15,7 @@ import jms_storage
from common.utils import is_uuid from common.utils import is_uuid
from common.permissions import IsOrgAdminOrAppUser from common.permissions import IsOrgAdminOrAppUser, IsAuditor
from ..hands import SystemUser from ..hands import SystemUser
from ..models import Terminal, Session from ..models import Terminal, Session
from .. import serializers from .. import serializers
...@@ -30,7 +30,7 @@ class SessionViewSet(BulkModelViewSet): ...@@ -30,7 +30,7 @@ class SessionViewSet(BulkModelViewSet):
queryset = Session.objects.all() queryset = Session.objects.all()
serializer_class = serializers.SessionSerializer serializer_class = serializers.SessionSerializer
pagination_class = LimitOffsetPagination pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser | IsAuditor, )
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
...@@ -68,7 +68,7 @@ class CommandViewSet(viewsets.ViewSet): ...@@ -68,7 +68,7 @@ class CommandViewSet(viewsets.ViewSet):
""" """
command_store = get_command_storage() command_store = get_command_storage()
serializer_class = SessionCommandSerializer serializer_class = SessionCommandSerializer
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser | IsAuditor,)
def get_queryset(self): def get_queryset(self):
self.command_store.filter(**dict(self.request.query_params)) self.command_store.filter(**dict(self.request.query_params))
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
{% if session.is_finished %} {% if session.is_finished %}
<a {% if not session.can_replay %} disabled="" {% endif %} onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a> <a {% if not session.can_replay %} disabled="" {% endif %} onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>
{% else %} {% else %}
{% if session.protocol == 'ssh' %} {% if session.protocol == 'ssh' and request.user.is_org_admin%}
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a> <a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
{% else %} {% else %}
<a class="btn btn-xs btn-danger btn-term" disabled value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a> <a class="btn btn-xs btn-danger btn-term" disabled value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
...@@ -115,6 +115,7 @@ ...@@ -115,6 +115,7 @@
{% endblock %} {% endblock %}
{% block content_bottom_left %} {% block content_bottom_left %}
{% if request.user.is_org_admin %}
<div id="actions" {% if type != "online" %} style="display: none" {% endif %}> <div id="actions" {% if type != "online" %} style="display: none" {% endif %}>
<div class="input-group"> <div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update"> <select class="form-control m-b" style="width: auto" id="slct_bulk_update">
...@@ -128,6 +129,7 @@ ...@@ -128,6 +129,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
......
...@@ -9,7 +9,7 @@ from django.template import loader ...@@ -9,7 +9,7 @@ from django.template import loader
import time import time
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
from ..models import Command from ..models import Command
from .. import utils from .. import utils
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage
...@@ -18,13 +18,14 @@ __all__ = ['CommandListView', 'CommandExportView'] ...@@ -18,13 +18,14 @@ __all__ = ['CommandListView', 'CommandExportView']
common_storage = get_multi_command_storage() common_storage = get_multi_command_storage()
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): class CommandListView(DatetimeSearchMixin, PermissionsMixin, ListView):
model = Command model = Command
template_name = "terminal/command_list.html" template_name = "terminal/command_list.html"
context_object_name = 'command_list' context_object_name = 'command_list'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
command = user = asset = system_user = "" command = user = asset = system_user = ""
date_from = date_to = None date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get_queryset(self): def get_queryset(self):
self.command = self.request.GET.get('command', '') self.command = self.request.GET.get('command', '')
...@@ -47,7 +48,7 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): ...@@ -47,7 +48,7 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Command list'), 'action': _('Command list'),
'user_list': utils.get_session_user_list(), 'user_list': utils.get_session_user_list(),
'asset_list': utils.get_session_asset_list(), 'asset_list': utils.get_session_asset_list(),
...@@ -63,10 +64,11 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): ...@@ -63,10 +64,11 @@ class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class CommandExportView(DatetimeSearchMixin, AdminUserRequiredMixin, View): class CommandExportView(DatetimeSearchMixin, PermissionsMixin, View):
model = Command model = Command
command = user = asset = system_user = action = '' command = user = asset = system_user = action = ''
date_from = date_to = None date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
queryset = self.get_queryset() queryset = self.get_queryset()
......
...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _ ...@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsAuditor
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal from ..models import Session, Command, Terminal
from ..backends import get_multi_command_storage from ..backends import get_multi_command_storage
...@@ -20,14 +20,14 @@ __all__ = [ ...@@ -20,14 +20,14 @@ __all__ = [
] ]
class SessionListView(PermissionsMixin, DatetimeSearchMixin, ListView):
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
model = Session model = Session
template_name = 'terminal/session_list.html' template_name = 'terminal/session_list.html'
context_object_name = 'session_list' context_object_name = 'session_list'
paginate_by = settings.DISPLAY_PER_PAGE paginate_by = settings.DISPLAY_PER_PAGE
user = asset = system_user = '' user = asset = system_user = ''
date_from = date_to = None date_from = date_to = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get_queryset(self): def get_queryset(self):
self.queryset = super().get_queryset() self.queryset = super().get_queryset()
...@@ -71,7 +71,7 @@ class SessionOnlineListView(SessionListView): ...@@ -71,7 +71,7 @@ class SessionOnlineListView(SessionListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Session online list'), 'action': _('Session online list'),
'type': 'online', 'type': 'online',
'now': timezone.now(), 'now': timezone.now(),
...@@ -89,18 +89,19 @@ class SessionOfflineListView(SessionListView): ...@@ -89,18 +89,19 @@ class SessionOfflineListView(SessionListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Session offline list'), 'action': _('Session offline'),
'now': timezone.now(), 'now': timezone.now(),
} }
kwargs.update(context) kwargs.update(context)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView): class SessionDetailView(SingleObjectMixin, PermissionsMixin, ListView):
template_name = 'terminal/session_detail.html' template_name = 'terminal/session_detail.html'
model = Session model = Session
object = None object = None
permission_classes = [IsOrgAdmin | IsAuditor]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=self.model.objects.all()) self.object = self.get_object(queryset=self.model.objects.all())
...@@ -112,7 +113,7 @@ class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView): ...@@ -112,7 +113,7 @@ class SessionDetailView(SingleObjectMixin, AdminUserRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Session detail'), 'action': _('Session detail'),
} }
kwargs.update(context) kwargs.update(context)
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
# #
from django.views.generic import ListView, UpdateView, DeleteView, \ from django.views.generic import ListView, UpdateView, DeleteView, \
DetailView, View DetailView, View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
...@@ -10,7 +9,7 @@ from django.urls import reverse_lazy, reverse ...@@ -10,7 +9,7 @@ from django.urls import reverse_lazy, reverse
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from ..models import Terminal from ..models import Terminal
from ..forms import TerminalForm from ..forms import TerminalForm
from common.permissions import SuperUserRequiredMixin from common.permissions import PermissionsMixin, IsSuperUser
__all__ = [ __all__ = [
...@@ -20,57 +19,62 @@ __all__ = [ ...@@ -20,57 +19,62 @@ __all__ = [
] ]
class TerminalListView(SuperUserRequiredMixin, ListView): class TerminalListView(PermissionsMixin, ListView):
model = Terminal model = Terminal
template_name = 'terminal/terminal_list.html' template_name = 'terminal/terminal_list.html'
form_class = TerminalForm form_class = TerminalForm
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TerminalListView, self).get_context_data(**kwargs) context = super(TerminalListView, self).get_context_data(**kwargs)
context.update({ context.update({
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Terminal list'), 'action': _('Terminal list'),
'form': self.form_class() 'form': self.form_class()
}) })
return context return context
class TerminalUpdateView(SuperUserRequiredMixin, UpdateView): class TerminalUpdateView(PermissionsMixin, UpdateView):
model = Terminal model = Terminal
form_class = TerminalForm form_class = TerminalForm
template_name = 'terminal/terminal_update.html' template_name = 'terminal/terminal_update.html'
success_url = reverse_lazy('terminal:terminal-list') success_url = reverse_lazy('terminal:terminal-list')
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TerminalUpdateView, self).get_context_data(**kwargs) context = super(TerminalUpdateView, self).get_context_data(**kwargs)
context.update({'app': _('Terminal'), 'action': _('Update terminal')}) context.update({'app': _('Sessions'), 'action': _('Update terminal')})
return context return context
class TerminalDetailView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView): class TerminalDetailView(PermissionsMixin, DetailView):
model = Terminal model = Terminal
template_name = 'terminal/terminal_detail.html' template_name = 'terminal/terminal_detail.html'
context_object_name = 'terminal' context_object_name = 'terminal'
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(TerminalDetailView, self).get_context_data(**kwargs) context = super(TerminalDetailView, self).get_context_data(**kwargs)
context.update({ context.update({
'app': _('Terminal'), 'app': _('Sessions'),
'action': _('Terminal detail') 'action': _('Terminal detail')
}) })
return context return context
class TerminalDeleteView(SuperUserRequiredMixin, DeleteView): class TerminalDeleteView(PermissionsMixin, DeleteView):
model = Terminal model = Terminal
template_name = 'delete_confirm.html' template_name = 'delete_confirm.html'
success_url = reverse_lazy('terminal:terminal-list') success_url = reverse_lazy('terminal:terminal-list')
permission_classes = [IsSuperUser]
class TerminalAcceptView(SuperUserRequiredMixin, JSONResponseMixin, UpdateView): class TerminalAcceptView(PermissionsMixin, JSONResponseMixin, UpdateView):
model = Terminal model = Terminal
form_class = TerminalForm form_class = TerminalForm
template_name = 'terminal/terminal_modal_accept.html' template_name = 'terminal/terminal_modal_accept.html'
permission_classes = [IsSuperUser]
def form_valid(self, form): def form_valid(self, form):
terminal = form.save() terminal = form.save()
...@@ -92,12 +96,13 @@ class TerminalAcceptView(SuperUserRequiredMixin, JSONResponseMixin, UpdateView): ...@@ -92,12 +96,13 @@ class TerminalAcceptView(SuperUserRequiredMixin, JSONResponseMixin, UpdateView):
return self.render_json_response(data) return self.render_json_response(data)
class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView): class TerminalConnectView(PermissionsMixin, DetailView):
""" """
Abandon Abandon
""" """
template_name = 'flash_message_standalone.html' template_name = 'flash_message_standalone.html'
model = Terminal model = Terminal
permission_classes = [IsSuperUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
if self.object.type == 'Web': if self.object.type == 'Web':
...@@ -121,11 +126,11 @@ class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView ...@@ -121,11 +126,11 @@ class TerminalConnectView(LoginRequiredMixin, SuperUserRequiredMixin, DetailView
return super(TerminalConnectView, self).get_context_data(**kwargs) return super(TerminalConnectView, self).get_context_data(**kwargs)
class WebTerminalView(LoginRequiredMixin, View): class WebTerminalView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return redirect('/luna/?' + request.GET.urlencode()) return redirect('/luna/?' + request.GET.urlencode())
class WebSFTPView(LoginRequiredMixin, View): class WebSFTPView(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return redirect('/coco/elfinder/sftp/?' + request.GET.urlencode()) return redirect('/coco/elfinder/sftp/?' + request.GET.urlencode())
...@@ -62,6 +62,7 @@ class UserCreateUpdateFormMixin(OrgModelForm): ...@@ -62,6 +62,7 @@ class UserCreateUpdateFormMixin(OrgModelForm):
if self.request.user.is_superuser: if self.request.user.is_superuser:
roles.append((User.ROLE_ADMIN, dict(User.ROLE_CHOICES).get(User.ROLE_ADMIN))) roles.append((User.ROLE_ADMIN, dict(User.ROLE_CHOICES).get(User.ROLE_ADMIN)))
roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER))) roles.append((User.ROLE_USER, dict(User.ROLE_CHOICES).get(User.ROLE_USER)))
roles.append((User.ROLE_AUDITOR, dict(User.ROLE_CHOICES).get(User.ROLE_AUDITOR)))
# Org admin user # Org admin user
else: else:
......
# Generated by Django 2.1.7 on 2019-06-12 10:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0019_auto_20190304_1459'),
]
operations = [
migrations.AlterField(
model_name='user',
name='role',
field=models.CharField(blank=True, choices=[('Admin', 'Administrator'), ('User', 'User'), ('App', 'Application'), ('Auditor', 'Auditor')], default='User', max_length=10, verbose_name='Role'),
),
]
...@@ -30,11 +30,13 @@ class User(AbstractUser): ...@@ -30,11 +30,13 @@ class User(AbstractUser):
ROLE_ADMIN = 'Admin' ROLE_ADMIN = 'Admin'
ROLE_USER = 'User' ROLE_USER = 'User'
ROLE_APP = 'App' ROLE_APP = 'App'
ROLE_AUDITOR = 'Auditor'
ROLE_CHOICES = ( ROLE_CHOICES = (
(ROLE_ADMIN, _('Administrator')), (ROLE_ADMIN, _('Administrator')),
(ROLE_USER, _('User')), (ROLE_USER, _('User')),
(ROLE_APP, _('Application')) (ROLE_APP, _('Application')),
(ROLE_AUDITOR, _("Auditor"))
) )
OTP_LEVEL_CHOICES = ( OTP_LEVEL_CHOICES = (
(0, _('Disable')), (0, _('Disable')),
...@@ -243,6 +245,10 @@ class User(AbstractUser): ...@@ -243,6 +245,10 @@ class User(AbstractUser):
else: else:
return False return False
@property
def is_auditor(self):
return self.role == 'Auditor'
@property @property
def is_app(self): def is_app(self):
return self.role == 'App' return self.role == 'App'
......
...@@ -7,6 +7,7 @@ from rest_framework import serializers ...@@ -7,6 +7,7 @@ from rest_framework import serializers
from common.utils import get_signer, validate_ssh_public_key from common.utils import get_signer, validate_ssh_public_key
from common.mixins import BulkSerializerMixin from common.mixins import BulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import User, UserGroup from ..models import User, UserGroup
signer = get_signer() signer = get_signer()
...@@ -56,7 +57,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer): ...@@ -56,7 +57,7 @@ class UserUpdateGroupSerializer(serializers.ModelSerializer):
fields = ['id', 'groups'] fields = ['id', 'groups']
class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): class UserGroupSerializer(BulkOrgResourceModelSerializer):
users = serializers.PrimaryKeyRelatedField( users = serializers.PrimaryKeyRelatedField(
required=False, many=True, queryset=User.objects.all(), label=_('User') required=False, many=True, queryset=User.objects.all(), label=_('User')
) )
......
...@@ -24,16 +24,6 @@ from .models import User ...@@ -24,16 +24,6 @@ from .models import User
logger = logging.getLogger('jumpserver') logger = logging.getLogger('jumpserver')
class AdminUserRequiredMixin(UserPassesTestMixin):
def test_func(self):
if not self.request.user.is_authenticated:
return False
elif not self.request.user.is_superuser:
self.raise_exception = True
return False
return True
def construct_user_created_email_body(user): def construct_user_created_email_body(user):
default_body = _(""" default_body = _("""
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
......
...@@ -9,7 +9,7 @@ from django.contrib.messages.views import SuccessMessageMixin ...@@ -9,7 +9,7 @@ from django.contrib.messages.views import SuccessMessageMixin
from common.utils import get_logger from common.utils import get_logger
from common.const import create_success_msg, update_success_msg from common.const import create_success_msg, update_success_msg
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin
from orgs.utils import current_org from orgs.utils import current_org
from ..models import User, UserGroup from ..models import User, UserGroup
from .. import forms from .. import forms
...@@ -19,8 +19,9 @@ __all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView', ...@@ -19,8 +19,9 @@ __all__ = ['UserGroupListView', 'UserGroupCreateView', 'UserGroupDetailView',
logger = get_logger(__name__) logger = get_logger(__name__)
class UserGroupListView(AdminUserRequiredMixin, TemplateView): class UserGroupListView(PermissionsMixin, TemplateView):
template_name = 'users/user_group_list.html' template_name = 'users/user_group_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -31,12 +32,13 @@ class UserGroupListView(AdminUserRequiredMixin, TemplateView): ...@@ -31,12 +32,13 @@ class UserGroupListView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class UserGroupCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
model = UserGroup model = UserGroup
form_class = forms.UserGroupForm form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html' template_name = 'users/user_group_create_update.html'
success_url = reverse_lazy('users:user-group-list') success_url = reverse_lazy('users:user-group-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -47,12 +49,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie ...@@ -47,12 +49,13 @@ class UserGroupCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateVie
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class UserGroupUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
model = UserGroup model = UserGroup
form_class = forms.UserGroupForm form_class = forms.UserGroupForm
template_name = 'users/user_group_create_update.html' template_name = 'users/user_group_create_update.html'
success_url = reverse_lazy('users:user-group-list') success_url = reverse_lazy('users:user-group-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -64,10 +67,11 @@ class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie ...@@ -64,10 +67,11 @@ class UserGroupUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateVie
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserGroupDetailView(AdminUserRequiredMixin, DetailView): class UserGroupDetailView(PermissionsMixin, DetailView):
model = UserGroup model = UserGroup
context_object_name = 'user_group' context_object_name = 'user_group'
template_name = 'users/user_group_detail.html' template_name = 'users/user_group_detail.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
users = current_org.get_org_users().exclude(id__in=self.object.users.all()) users = current_org.get_org_users().exclude(id__in=self.object.users.all())
...@@ -80,11 +84,12 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView): ...@@ -80,11 +84,12 @@ class UserGroupDetailView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserGroupGrantedAssetView(AdminUserRequiredMixin, DetailView): class UserGroupGrantedAssetView(PermissionsMixin, DetailView):
model = UserGroup model = UserGroup
template_name = 'users/user_group_granted_asset.html' template_name = 'users/user_group_granted_asset.html'
context_object_name = 'user_group' context_object_name = 'user_group'
object = None object = None
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.cache import cache
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
...@@ -15,6 +13,7 @@ from django.urls import reverse_lazy ...@@ -15,6 +13,7 @@ from django.urls import reverse_lazy
from formtools.wizard.views import SessionWizardView from formtools.wizard.views import SessionWizardView
from common.utils import get_object_or_none from common.utils import get_object_or_none
from common.permissions import PermissionsMixin, IsValidUser
from ..models import User from ..models import User
from ..utils import ( from ..utils import (
send_reset_password_mail, get_password_check_rules, check_password_rules send_reset_password_mail, get_password_check_rules, check_password_rules
...@@ -120,8 +119,9 @@ class UserResetPasswordView(TemplateView): ...@@ -120,8 +119,9 @@ class UserResetPasswordView(TemplateView):
return HttpResponseRedirect(reverse('users:reset-password-success')) return HttpResponseRedirect(reverse('users:reset-password-success'))
class UserFirstLoginView(LoginRequiredMixin, SessionWizardView): class UserFirstLoginView(PermissionsMixin, SessionWizardView):
template_name = 'users/first_login.html' template_name = 'users/first_login.html'
permission_classes = [IsValidUser]
form_list = [ form_list = [
forms.UserProfileForm, forms.UserProfileForm,
forms.UserPublicKeyForm, forms.UserPublicKeyForm,
......
...@@ -10,7 +10,6 @@ import chardet ...@@ -10,7 +10,6 @@ import chardet
from io import StringIO from io import StringIO
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import authenticate, login as auth_login from django.contrib.auth import authenticate, login as auth_login
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache from django.core.cache import cache
...@@ -36,7 +35,7 @@ from common.const import ( ...@@ -36,7 +35,7 @@ from common.const import (
) )
from common.mixins import JSONResponseMixin from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.permissions import AdminUserRequiredMixin from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
from orgs.utils import current_org from orgs.utils import current_org
from .. import forms from .. import forms
from ..models import User, UserGroup from ..models import User, UserGroup
...@@ -61,8 +60,9 @@ __all__ = [ ...@@ -61,8 +60,9 @@ __all__ = [
logger = get_logger(__name__) logger = get_logger(__name__)
class UserListView(AdminUserRequiredMixin, TemplateView): class UserListView(PermissionsMixin, TemplateView):
template_name = 'users/user_list.html' template_name = 'users/user_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
...@@ -73,12 +73,13 @@ class UserListView(AdminUserRequiredMixin, TemplateView): ...@@ -73,12 +73,13 @@ class UserListView(AdminUserRequiredMixin, TemplateView):
return context return context
class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): class UserCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
model = User model = User
form_class = forms.UserCreateForm form_class = forms.UserCreateForm
template_name = 'users/user_create.html' template_name = 'users/user_create.html'
success_url = reverse_lazy('users:user-list') success_url = reverse_lazy('users:user-list')
success_message = create_success_msg success_message = create_success_msg
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
check_rules = get_password_check_rules() check_rules = get_password_check_rules()
...@@ -106,13 +107,14 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView): ...@@ -106,13 +107,14 @@ class UserCreateView(AdminUserRequiredMixin, SuccessMessageMixin, CreateView):
return kwargs return kwargs
class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): class UserUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
model = User model = User
form_class = forms.UserUpdateForm form_class = forms.UserUpdateForm
template_name = 'users/user_update.html' template_name = 'users/user_update.html'
context_object_name = 'user_object' context_object_name = 'user_object'
success_url = reverse_lazy('users:user-list') success_url = reverse_lazy('users:user-list')
success_message = update_success_msg success_message = update_success_msg
permission_classes = [IsOrgAdmin]
def _deny_permission(self): def _deny_permission(self):
obj = self.get_object() obj = self.get_object()
...@@ -153,7 +155,7 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): ...@@ -153,7 +155,7 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
return kwargs return kwargs
class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView): class UserBulkUpdateView(PermissionsMixin, TemplateView):
model = User model = User
form_class = forms.UserBulkUpdateForm form_class = forms.UserBulkUpdateForm
template_name = 'users/user_bulk_update.html' template_name = 'users/user_bulk_update.html'
...@@ -161,6 +163,7 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView): ...@@ -161,6 +163,7 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
success_message = _("Bulk update user success") success_message = _("Bulk update user success")
form = None form = None
id_list = None id_list = None
permission_classes = [IsOrgAdmin]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
spm = request.GET.get('spm', '') spm = request.GET.get('spm', '')
...@@ -193,11 +196,12 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView): ...@@ -193,11 +196,12 @@ class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserDetailView(AdminUserRequiredMixin, DetailView): class UserDetailView(PermissionsMixin, DetailView):
model = User model = User
template_name = 'users/user_detail.html' template_name = 'users/user_detail.html'
context_object_name = "user_object" context_object_name = "user_object"
key_prefix_block = "_LOGIN_BLOCK_{}" key_prefix_block = "_LOGIN_BLOCK_{}"
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
user = self.get_object() user = self.get_object()
...@@ -263,8 +267,9 @@ class UserExportView(View): ...@@ -263,8 +267,9 @@ class UserExportView(View):
return JsonResponse({'redirect': url}) return JsonResponse({'redirect': url})
class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView): class UserBulkImportView(PermissionsMixin, JSONResponseMixin, FormView):
form_class = forms.FileForm form_class = forms.FileForm
permission_classes = [IsOrgAdmin]
def form_invalid(self, form): def form_invalid(self, form):
try: try:
...@@ -359,9 +364,10 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView): ...@@ -359,9 +364,10 @@ class UserBulkImportView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
return self.render_json_response(data) return self.render_json_response(data)
class UserGrantedAssetView(AdminUserRequiredMixin, DetailView): class UserGrantedAssetView(PermissionsMixin, DetailView):
model = User model = User
template_name = 'users/user_granted_asset.html' template_name = 'users/user_granted_asset.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -372,8 +378,9 @@ class UserGrantedAssetView(AdminUserRequiredMixin, DetailView): ...@@ -372,8 +378,9 @@ class UserGrantedAssetView(AdminUserRequiredMixin, DetailView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserProfileView(LoginRequiredMixin, TemplateView): class UserProfileView(PermissionsMixin, TemplateView):
template_name = 'users/user_profile.html' template_name = 'users/user_profile.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
mfa_setting = settings.SECURITY_MFA_AUTH mfa_setting = settings.SECURITY_MFA_AUTH
...@@ -385,9 +392,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView): ...@@ -385,9 +392,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserProfileUpdateView(LoginRequiredMixin, UpdateView): class UserProfileUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_profile_update.html' template_name = 'users/user_profile_update.html'
model = User model = User
permission_classes = [IsValidUser]
form_class = forms.UserProfileForm form_class = forms.UserProfileForm
success_url = reverse_lazy('users:user-profile') success_url = reverse_lazy('users:user-profile')
...@@ -403,7 +411,7 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView): ...@@ -403,7 +411,7 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): class UserPasswordUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_password_update.html' template_name = 'users/user_password_update.html'
model = User model = User
form_class = forms.UserPasswordForm form_class = forms.UserPasswordForm
...@@ -444,10 +452,11 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView): ...@@ -444,10 +452,11 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return super().form_valid(form) return super().form_valid(form)
class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView): class UserPublicKeyUpdateView(PermissionsMixin, UpdateView):
template_name = 'users/user_pubkey_update.html' template_name = 'users/user_pubkey_update.html'
model = User model = User
form_class = forms.UserPublicKeyForm form_class = forms.UserPublicKeyForm
permission_classes = [IsValidUser]
success_url = reverse_lazy('users:user-profile') success_url = reverse_lazy('users:user-profile')
def get_object(self, queryset=None): def get_object(self, queryset=None):
...@@ -462,7 +471,8 @@ class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView): ...@@ -462,7 +471,8 @@ class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserPublicKeyGenerateView(LoginRequiredMixin, View): class UserPublicKeyGenerateView(PermissionsMixin, View):
permission_classes = [IsValidUser]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver') private, public = ssh_key_gen(username=request.user.username, hostname='jumpserver')
......
...@@ -28,7 +28,6 @@ djangorestframework==3.9.4 ...@@ -28,7 +28,6 @@ djangorestframework==3.9.4
djangorestframework-bulk==0.2.1 djangorestframework-bulk==0.2.1
docutils==0.14 docutils==0.14
ecdsa==0.13 ecdsa==0.13
elasticsearch==6.1.1
enum-compat==0.0.2 enum-compat==0.0.2
ephem==3.7.6.0 ephem==3.7.6.0
eventlet==0.24.1 eventlet==0.24.1
......
#!/bin/bash
#
function disable_auth_ldap() {
python3 ../apps/manage.py shell << EOF
import sys
from settings.models import Setting
auth_ldap = Setting.objects.get(name='AUTH_LDAP')
if not auth_ldap:
print("No AUTH_LDAP config")
sys.exit(1)
if auth_ldap.value == 'false':
print("AUTH_LDAP is already disabled")
sys.exit(1)
auth_ldap.value = 'false'
auth_ldap.save()
auth_ldap.refresh_setting()
print("Disable AUTH_LDAP success!")
EOF
}
disable_auth_ldap
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