mirror of https://github.com/jumpserver/jumpserver
合并冲突解决
commit
5d63d2369f
14
README.md
14
README.md
|
@ -19,20 +19,14 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
|
||||||
----
|
----
|
||||||
|
|
||||||
### 功能
|
### 功能
|
||||||
- 统一认证
|
|
||||||
- 资产管理
|

|
||||||
- 统一授权
|
|
||||||
- 审计
|
|
||||||
- 支持LDAP认证
|
|
||||||
- Web terminal
|
|
||||||
- SSH Server
|
|
||||||
- 支持Windows RDP
|
|
||||||
|
|
||||||
### 开始使用
|
### 开始使用
|
||||||
|
|
||||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/latest/quickstart.html)
|
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||||
|
|
||||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/latest/step_by_step.html)
|
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||||
|
|
||||||
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
__version__ = "1.2.1"
|
__version__ = "1.3.2"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
@ -11,9 +13,8 @@ from django.db.models import Q
|
||||||
|
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
|
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||||
NodePermissionUtil
|
from ..models import Asset, SystemUser, AdminUser, Node
|
||||||
from ..models import Asset, SystemUser, AdminUser, Node
|
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
from ..tasks import update_asset_hardware_info_manual, \
|
from ..tasks import update_asset_hardware_info_manual, \
|
||||||
test_asset_connectability_manual
|
test_asset_connectability_manual
|
||||||
|
@ -22,8 +23,9 @@ from ..utils import LabelFilter
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
|
'AssetViewSet', 'AssetListUpdateApi',
|
||||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
|
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||||
|
'AssetGatewayApi'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,32 +42,34 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()\
|
||||||
|
.prefetch_related('labels', 'nodes')\
|
||||||
|
.select_related('admin_user')
|
||||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||||
node_id = self.request.query_params.get("node_id")
|
node_id = self.request.query_params.get("node_id")
|
||||||
|
show_current_asset = self.request.query_params.get("show_current_asset")
|
||||||
|
|
||||||
if admin_user_id:
|
if admin_user_id:
|
||||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||||
queryset = queryset.filter(admin_user=admin_user)
|
queryset = queryset.filter(admin_user=admin_user)
|
||||||
if node_id:
|
|
||||||
|
if node_id and show_current_asset:
|
||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
if not node.is_root():
|
if node.is_root():
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
|
Q(nodes=node_id) | Q(nodes__isnull=True)
|
||||||
).distinct()
|
).distinct()
|
||||||
return queryset
|
else:
|
||||||
|
queryset = queryset.filter(nodes=node).distinct()
|
||||||
|
|
||||||
|
if node_id and not show_current_asset:
|
||||||
class UserAssetListView(generics.ListAPIView):
|
node = get_object_or_404(Node, id=node_id)
|
||||||
queryset = Asset.objects.all()
|
if node.is_root():
|
||||||
serializer_class = serializers.AssetSerializer
|
queryset = Asset.objects.all()
|
||||||
permission_classes = (IsValidUser,)
|
else:
|
||||||
|
queryset = queryset.filter(
|
||||||
def get_queryset(self):
|
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||||
assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
|
).distinct()
|
||||||
queryset = self.queryset.filter(
|
|
||||||
id__in=[asset.id for asset in assets_granted]
|
|
||||||
)
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,3 +109,20 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
task = test_asset_connectability_manual.delay(asset)
|
task = test_asset_connectability_manual.delay(asset)
|
||||||
return Response({"task": task.id})
|
return Response({"task": task.id})
|
||||||
|
|
||||||
|
|
||||||
|
class AssetGatewayApi(generics.RetrieveAPIView):
|
||||||
|
queryset = Asset.objects.all()
|
||||||
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
asset_id = kwargs.get('pk')
|
||||||
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
|
|
||||||
|
if asset.domain and \
|
||||||
|
asset.domain.gateways.filter(protocol=asset.protocol).exists():
|
||||||
|
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
|
||||||
|
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
|
||||||
|
return Response(serializer.data)
|
||||||
|
else:
|
||||||
|
return Response({"msg": "Not have gateway"}, status=404)
|
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from rest_framework import generics, mixins
|
from rest_framework import generics, mixins
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
|
@ -30,7 +31,7 @@ from .. import serializers
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'NodeViewSet', 'NodeChildrenApi',
|
'NodeViewSet', 'NodeChildrenApi',
|
||||||
'NodeAssetsApi', 'NodeWithAssetsApi',
|
'NodeAssetsApi',
|
||||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
|
||||||
'NodeReplaceAssetsApi',
|
'NodeReplaceAssetsApi',
|
||||||
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
|
||||||
|
@ -49,32 +50,32 @@ class NodeViewSet(BulkModelViewSet):
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|
||||||
|
|
||||||
class NodeWithAssetsApi(generics.ListAPIView):
|
# class NodeWithAssetsApi(generics.ListAPIView):
|
||||||
permission_classes = (IsSuperUser,)
|
# permission_classes = (IsSuperUser,)
|
||||||
serializers = serializers.NodeSerializer
|
# serializers = serializers.NodeSerializer
|
||||||
|
#
|
||||||
def get_node(self):
|
# def get_node(self):
|
||||||
pk = self.kwargs.get('pk') or self.request.query_params.get('node')
|
# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
|
||||||
if not pk:
|
# if not pk:
|
||||||
node = Node.root()
|
# node = Node.root()
|
||||||
else:
|
# else:
|
||||||
node = get_object_or_404(Node, pk)
|
# node = get_object_or_404(Node, pk)
|
||||||
return node
|
# return node
|
||||||
|
#
|
||||||
def get_queryset(self):
|
# def get_queryset(self):
|
||||||
queryset = []
|
# queryset = []
|
||||||
node = self.get_node()
|
# node = self.get_node()
|
||||||
children = node.get_children()
|
# children = node.get_children()
|
||||||
assets = node.get_assets()
|
# assets = node.get_assets()
|
||||||
queryset.extend(list(children))
|
# queryset.extend(list(children))
|
||||||
|
#
|
||||||
for asset in assets:
|
# for asset in assets:
|
||||||
node = Node()
|
# node = Node()
|
||||||
node.id = asset.id
|
# node.id = asset.id
|
||||||
node.parent = node.id
|
# node.parent = node.id
|
||||||
node.value = asset.hostname
|
# node.value = asset.hostname
|
||||||
queryset.append(node)
|
# queryset.append(node)
|
||||||
return queryset
|
# return queryset
|
||||||
|
|
||||||
|
|
||||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
|
@ -83,16 +84,29 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
serializer_class = serializers.NodeSerializer
|
serializer_class = serializers.NodeSerializer
|
||||||
instance = None
|
instance = None
|
||||||
|
|
||||||
|
def counter(self):
|
||||||
|
values = [
|
||||||
|
child.value[child.value.rfind(' '):]
|
||||||
|
for child in self.get_object().get_children()
|
||||||
|
if child.value.startswith("新节点 ")
|
||||||
|
]
|
||||||
|
values = [int(value) for value in values if value.strip().isdigit()]
|
||||||
|
count = max(values)+1 if values else 1
|
||||||
|
return count
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not request.data.get("value"):
|
if not request.data.get("value"):
|
||||||
request.data["value"] = _("New node {}").format(
|
request.data["value"] = _("New node {}").format(self.counter())
|
||||||
Node.root().get_next_child_key().split(":")[-1]
|
|
||||||
)
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
value = request.data.get("value")
|
value = request.data.get("value")
|
||||||
|
values = [child.value for child in instance.get_children()]
|
||||||
|
if value in values:
|
||||||
|
raise ValidationError(
|
||||||
|
'The same level node name cannot be the same'
|
||||||
|
)
|
||||||
node = instance.create_child(value=value)
|
node = instance.create_child(value=value)
|
||||||
return Response(
|
return Response(
|
||||||
{"id": node.id, "key": node.key, "value": node.value},
|
{"id": node.id, "key": node.key, "value": node.value},
|
||||||
|
@ -102,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
||||||
if not pk:
|
if not pk:
|
||||||
node = Node.root()
|
node = None
|
||||||
else:
|
else:
|
||||||
node = get_object_or_404(Node, pk=pk)
|
node = get_object_or_404(Node, pk=pk)
|
||||||
return node
|
return node
|
||||||
|
@ -112,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
query_all = self.request.query_params.get("all")
|
query_all = self.request.query_params.get("all")
|
||||||
query_assets = self.request.query_params.get('assets')
|
query_assets = self.request.query_params.get('assets')
|
||||||
node = self.get_object()
|
node = self.get_object()
|
||||||
if node == Node.root():
|
if node is None:
|
||||||
|
node = Node.root()
|
||||||
queryset.append(node)
|
queryset.append(node)
|
||||||
if query_all:
|
if query_all:
|
||||||
children = node.get_all_children()
|
children = node.get_all_children()
|
||||||
|
@ -125,10 +140,11 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
node_fake = Node()
|
node_fake = Node()
|
||||||
node_fake.id = asset.id
|
node_fake.id = asset.id
|
||||||
node_fake.parent = node
|
node_fake.is_node = False
|
||||||
|
node_fake.parent_id = node.id
|
||||||
node_fake.value = asset.hostname
|
node_fake.value = asset.hostname
|
||||||
node_fake.is_asset = True
|
|
||||||
queryset.append(node_fake)
|
queryset.append(node_fake)
|
||||||
|
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -163,7 +179,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||||
if not node:
|
if not node:
|
||||||
continue
|
continue
|
||||||
node.parent = instance
|
node.parent = instance
|
||||||
node.save()
|
|
||||||
return Response("OK")
|
return Response("OK")
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,6 +205,9 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
if instance != Node.root():
|
if instance != Node.root():
|
||||||
instance.assets.remove(*tuple(assets))
|
instance.assets.remove(*tuple(assets))
|
||||||
|
else:
|
||||||
|
assets = [asset for asset in assets if asset.nodes.count() > 1]
|
||||||
|
instance.assets.remove(*tuple(assets))
|
||||||
|
|
||||||
|
|
||||||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||||
|
|
|
@ -40,7 +40,7 @@ class SystemUserViewSet(BulkModelViewSet):
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Get system user auth info
|
Get system user auth info
|
||||||
"""
|
"""
|
||||||
|
@ -48,6 +48,11 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
serializer_class = serializers.SystemUserAuthSerializer
|
serializer_class = serializers.SystemUserAuthSerializer
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
instance.clear_auth()
|
||||||
|
return Response(status=204)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
|
@ -58,6 +63,9 @@ class SystemUserPushApi(generics.RetrieveAPIView):
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
system_user = self.get_object()
|
system_user = self.get_object()
|
||||||
|
nodes = system_user.nodes.all()
|
||||||
|
for node in nodes:
|
||||||
|
system_user.assets.add(*tuple(node.get_all_assets()))
|
||||||
task = push_system_user_to_assets_manual.delay(system_user)
|
task = push_system_user_to_assets_manual.delay(system_user)
|
||||||
return Response({"task": task.id})
|
return Response({"task": task.id})
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class AssetCreateForm(forms.ModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||||
'domain',
|
'domain', 'protocol',
|
||||||
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
|
@ -56,7 +56,7 @@ class AssetUpdateForm(forms.ModelForm):
|
||||||
fields = [
|
fields = [
|
||||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||||
'domain',
|
'domain', 'protocol',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'nodes': forms.SelectMultiple(attrs={
|
'nodes': forms.SelectMultiple(attrs={
|
||||||
|
|
|
@ -93,14 +93,21 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||||
# Because we define custom field, so we need rewrite :method: `save`
|
# Because we define custom field, so we need rewrite :method: `save`
|
||||||
system_user = super().save()
|
system_user = super().save()
|
||||||
password = self.cleaned_data.get('password', '') or None
|
password = self.cleaned_data.get('password', '') or None
|
||||||
|
login_mode = self.cleaned_data.get('login_mode', '') or None
|
||||||
|
protocol = self.cleaned_data.get('protocol') or None
|
||||||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||||
private_key, public_key = super().gen_keys()
|
private_key, public_key = super().gen_keys()
|
||||||
|
|
||||||
|
if login_mode == SystemUser.MANUAL_LOGIN or protocol == SystemUser.TELNET_PROTOCOL:
|
||||||
|
system_user.auto_push = 0
|
||||||
|
system_user.save()
|
||||||
|
|
||||||
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()
|
system_user.auto_gen_auth()
|
||||||
else:
|
else:
|
||||||
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
|
||||||
|
|
||||||
return system_user
|
return system_user
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -109,12 +116,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||||
if not self.instance and not auto_generate:
|
if not self.instance and not auto_generate:
|
||||||
super().validate_password_key()
|
super().validate_password_key()
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
validated = super().is_valid()
|
||||||
|
username = self.cleaned_data.get('username')
|
||||||
|
login_mode = self.cleaned_data.get('login_mode')
|
||||||
|
if login_mode == SystemUser.AUTO_LOGIN and not username:
|
||||||
|
self.add_error(
|
||||||
|
"username", _('* Automatic login mode,'
|
||||||
|
' must fill in the username.')
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return validated
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'protocol', 'auto_generate_key',
|
'name', 'username', 'protocol', 'auto_generate_key',
|
||||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||||
'comment', 'shell', 'priority',
|
'comment', 'shell', 'priority', 'login_mode',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||||
|
@ -124,5 +143,8 @@ class SystemUserForm(PasswordAndKeyAuthForm):
|
||||||
'name': '* required',
|
'name': '* required',
|
||||||
'username': '* required',
|
'username': '* required',
|
||||||
'auto_push': _('Auto push system user to asset'),
|
'auto_push': _('Auto push system user to asset'),
|
||||||
'priority': _('High level will be using login asset as default, if user was granted more than 2 system user'),
|
'priority': _('High level will be using login asset as default, '
|
||||||
|
'if user was granted more than 2 system user'),
|
||||||
|
'login_mode': _('If you choose manual login mode, you do not '
|
||||||
|
'need to fill in the username and password.')
|
||||||
}
|
}
|
|
@ -14,4 +14,3 @@
|
||||||
from common.mixins import AdminUserRequiredMixin
|
from common.mixins import AdminUserRequiredMixin
|
||||||
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
from common.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
|
||||||
from users.models import User, UserGroup
|
from users.models import User, UserGroup
|
||||||
from perms.utils import NodePermissionUtil
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -35,6 +36,19 @@ def default_node():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class AssetQuerySet(models.QuerySet):
|
||||||
|
def active(self):
|
||||||
|
return self.filter(is_active=True)
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
return self.active()
|
||||||
|
|
||||||
|
|
||||||
|
class AssetManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return AssetQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
|
||||||
class Asset(models.Model):
|
class Asset(models.Model):
|
||||||
# Important
|
# Important
|
||||||
PLATFORM_CHOICES = (
|
PLATFORM_CHOICES = (
|
||||||
|
@ -43,45 +57,89 @@ class Asset(models.Model):
|
||||||
('MacOS', 'MacOS'),
|
('MacOS', 'MacOS'),
|
||||||
('BSD', 'BSD'),
|
('BSD', 'BSD'),
|
||||||
('Windows', 'Windows'),
|
('Windows', 'Windows'),
|
||||||
|
('Windows2016', 'Windows(2016)'),
|
||||||
('Other', 'Other'),
|
('Other', 'Other'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SSH_PROTOCOL = 'ssh'
|
||||||
|
RDP_PROTOCOL = 'rdp'
|
||||||
|
TELNET_PROTOCOL = 'telnet'
|
||||||
|
PROTOCOL_CHOICES = (
|
||||||
|
(SSH_PROTOCOL, 'ssh'),
|
||||||
|
(RDP_PROTOCOL, 'rdp'),
|
||||||
|
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||||
|
)
|
||||||
|
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
|
||||||
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
|
db_index=True)
|
||||||
|
hostname = models.CharField(max_length=128, unique=True,
|
||||||
|
verbose_name=_('Hostname'))
|
||||||
|
protocol = models.CharField(max_length=128, default=SSH_PROTOCOL,
|
||||||
|
choices=PROTOCOL_CHOICES,
|
||||||
|
verbose_name=_('Protocol'))
|
||||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
|
||||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
default='Linux', verbose_name=_('Platform'))
|
||||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
|
||||||
|
related_name='assets', verbose_name=_("Domain"),
|
||||||
|
on_delete=models.SET_NULL)
|
||||||
|
nodes = models.ManyToManyField('assets.Node', default=default_node,
|
||||||
|
related_name='assets',
|
||||||
|
verbose_name=_("Nodes"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
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"))
|
||||||
|
|
||||||
# Some information
|
# Some information
|
||||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
|
||||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
null=True,
|
||||||
|
verbose_name=_('Public IP'))
|
||||||
|
number = models.CharField(max_length=32, null=True, blank=True,
|
||||||
|
verbose_name=_('Asset number'))
|
||||||
|
|
||||||
# Collect
|
# Collect
|
||||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
vendor = models.CharField(max_length=64, null=True, blank=True,
|
||||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
verbose_name=_('Vendor'))
|
||||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
model = models.CharField(max_length=54, null=True, blank=True,
|
||||||
|
verbose_name=_('Model'))
|
||||||
|
sn = models.CharField(max_length=128, null=True, blank=True,
|
||||||
|
verbose_name=_('Serial number'))
|
||||||
|
|
||||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
cpu_model = models.CharField(max_length=64, null=True, blank=True,
|
||||||
|
verbose_name=_('CPU model'))
|
||||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
memory = models.CharField(max_length=64, null=True, blank=True,
|
||||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
verbose_name=_('Memory'))
|
||||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
disk_total = models.CharField(max_length=1024, null=True, blank=True,
|
||||||
|
verbose_name=_('Disk total'))
|
||||||
|
disk_info = models.CharField(max_length=1024, null=True, blank=True,
|
||||||
|
verbose_name=_('Disk info'))
|
||||||
|
|
||||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
os = models.CharField(max_length=128, null=True, blank=True,
|
||||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
verbose_name=_('OS'))
|
||||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
os_version = models.CharField(max_length=16, null=True, blank=True,
|
||||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
verbose_name=_('OS version'))
|
||||||
|
os_arch = models.CharField(max_length=16, blank=True, null=True,
|
||||||
|
verbose_name=_('OS arch'))
|
||||||
|
hostname_raw = models.CharField(max_length=128, blank=True, null=True,
|
||||||
|
verbose_name=_('Hostname raw'))
|
||||||
|
|
||||||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
labels = models.ManyToManyField('assets.Label', blank=True,
|
||||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
related_name='assets',
|
||||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
verbose_name=_("Labels"))
|
||||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
created_by = models.CharField(max_length=32, null=True, blank=True,
|
||||||
|
verbose_name=_('Created by'))
|
||||||
|
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Date created'))
|
||||||
|
comment = models.TextField(max_length=128, default='', blank=True,
|
||||||
|
verbose_name=_('Comment'))
|
||||||
|
|
||||||
|
objects = AssetManager()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.hostname}({0.ip})'.format(self)
|
return '{0.hostname}({0.ip})'.format(self)
|
||||||
|
@ -103,7 +161,17 @@ class Asset(models.Model):
|
||||||
|
|
||||||
def get_nodes(self):
|
def get_nodes(self):
|
||||||
from .node import Node
|
from .node import Node
|
||||||
return self.nodes.all() or [Node.root()]
|
nodes = self.nodes.all() or [Node.root()]
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def get_all_nodes(self, flat=False):
|
||||||
|
nodes = []
|
||||||
|
for node in self.get_nodes():
|
||||||
|
_nodes = node.get_ancestor(with_self=True)
|
||||||
|
_nodes.append(_nodes)
|
||||||
|
if flat:
|
||||||
|
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||||
|
return nodes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hardware_info(self):
|
def hardware_info(self):
|
||||||
|
@ -176,7 +244,8 @@ class Asset(models.Model):
|
||||||
|
|
||||||
seed()
|
seed()
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
asset = cls(ip='%s.%s.%s.%s' % (i, i, i, i),
|
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||||
|
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,
|
port=22,
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||||
|
from common.validators import alphanumeric
|
||||||
from .utils import private_key_validator
|
from .utils import private_key_validator
|
||||||
|
|
||||||
signer = get_signer()
|
signer = get_signer()
|
||||||
|
@ -18,7 +19,7 @@ signer = get_signer()
|
||||||
class AssetUser(models.Model):
|
class AssetUser(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
username = models.CharField(max_length=128, verbose_name=_('Username'))
|
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||||
_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'))
|
||||||
|
@ -103,10 +104,16 @@ class AssetUser(models.Model):
|
||||||
if update_fields:
|
if update_fields:
|
||||||
self.save(update_fields=update_fields)
|
self.save(update_fields=update_fields)
|
||||||
|
|
||||||
|
def clear_auth(self):
|
||||||
|
self._password = ''
|
||||||
|
self._private_key = ''
|
||||||
|
self._public_key = ''
|
||||||
|
self.save()
|
||||||
|
|
||||||
def auto_gen_auth(self):
|
def auto_gen_auth(self):
|
||||||
password = str(uuid.uuid4())
|
password = str(uuid.uuid4())
|
||||||
private_key, public_key = ssh_key_gen(
|
private_key, public_key = ssh_key_gen(
|
||||||
username=self.username, password=password
|
username=self.username
|
||||||
)
|
)
|
||||||
self.set_auth(password=password,
|
self.set_auth(password=password,
|
||||||
private_key=private_key,
|
private_key=private_key,
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common.utils import with_cache
|
||||||
|
|
||||||
__all__ = ['Node']
|
__all__ = ['Node']
|
||||||
|
|
||||||
|
@ -12,25 +13,40 @@ __all__ = ['Node']
|
||||||
class Node(models.Model):
|
class Node(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||||
value = models.CharField(max_length=128, unique=True, verbose_name=_("Value"))
|
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||||
child_mark = models.IntegerField(default=0)
|
child_mark = models.IntegerField(default=0)
|
||||||
date_create = models.DateTimeField(auto_now_add=True)
|
date_create = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
is_asset = False
|
is_node = True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_value
|
return self.full_value
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.key == other.key
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if self.is_root():
|
||||||
|
return True
|
||||||
|
self_key = [int(k) for k in self.key.split(':')]
|
||||||
|
other_key = [int(k) for k in other.key.split(':')]
|
||||||
|
if len(self_key) < len(other_key):
|
||||||
|
return True
|
||||||
|
elif len(self_key) > len(other_key):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self_key[-1] < other_key[-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_value(self):
|
def full_value(self):
|
||||||
if self == self.__class__.root():
|
ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
||||||
|
if self.is_root():
|
||||||
return self.value
|
return self.value
|
||||||
else:
|
return ' / '.join(ancestor)
|
||||||
return '{} / {}'.format(self.parent.full_value, self.value)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def level(self):
|
def level(self):
|
||||||
|
@ -43,77 +59,106 @@ class Node(models.Model):
|
||||||
return "{}:{}".format(self.key, mark)
|
return "{}:{}".format(self.key, mark)
|
||||||
|
|
||||||
def create_child(self, value):
|
def create_child(self, value):
|
||||||
child_key = self.get_next_child_key()
|
with transaction.atomic():
|
||||||
child = self.__class__.objects.create(key=child_key, value=value)
|
child_key = self.get_next_child_key()
|
||||||
return child
|
child = self.__class__.objects.create(key=child_key, value=value)
|
||||||
|
return child
|
||||||
|
|
||||||
def get_children(self):
|
def get_children(self, with_self=False):
|
||||||
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
|
pattern = r'^{0}$|^{}:[0-9]+$' if with_self else r'^{}:[0-9]+$'
|
||||||
|
return self.__class__.objects.filter(
|
||||||
|
key__regex=pattern.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
def get_all_children(self):
|
def get_all_children(self, with_self=False):
|
||||||
return self.__class__.objects.filter(key__startswith='{}:'.format(self.key))
|
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
|
||||||
|
return self.__class__.objects.filter(
|
||||||
|
key__regex=pattern.format(self.key)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_sibling(self, with_self=False):
|
||||||
|
key = ':'.join(self.key.split(':')[:-1])
|
||||||
|
pattern = r'^{}:[0-9]+$'.format(key)
|
||||||
|
sibling = self.__class__.objects.filter(
|
||||||
|
key__regex=pattern.format(self.key)
|
||||||
|
)
|
||||||
|
if not with_self:
|
||||||
|
sibling = sibling.exclude(key=self.key)
|
||||||
|
return sibling
|
||||||
|
|
||||||
def get_family(self):
|
def get_family(self):
|
||||||
children = list(self.get_all_children())
|
ancestor = self.get_ancestor()
|
||||||
children.append(self)
|
children = self.get_all_children()
|
||||||
return children
|
return [*tuple(ancestor), self, *tuple(children)]
|
||||||
|
|
||||||
def get_assets(self):
|
def get_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
assets = Asset.objects.filter(nodes__id=self.id)
|
if self.is_root():
|
||||||
|
assets = Asset.objects.filter(
|
||||||
|
Q(nodes__id=self.id) | Q(nodes__isnull=True)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assets = self.assets.all()
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def get_active_assets(self):
|
def get_valid_assets(self):
|
||||||
return self.get_assets().filter(is_active=True)
|
return self.get_assets().valid()
|
||||||
|
|
||||||
def get_all_assets(self):
|
def get_all_assets(self):
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
assets = Asset.objects.all()
|
assets = Asset.objects.all()
|
||||||
else:
|
else:
|
||||||
nodes = self.get_family()
|
pattern = r'^{0}$|^{0}:'.format(self.key)
|
||||||
assets = Asset.objects.filter(nodes__in=nodes).distinct()
|
assets = Asset.objects.filter(nodes__key__regex=pattern)
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
def has_assets(self):
|
def get_all_valid_assets(self):
|
||||||
return self.get_all_assets()
|
return self.get_all_assets().valid()
|
||||||
|
|
||||||
def get_all_active_assets(self):
|
|
||||||
return self.get_all_assets().filter(is_active=True)
|
|
||||||
|
|
||||||
def is_root(self):
|
def is_root(self):
|
||||||
return self.key == '0'
|
return self.key == '0'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
if self.key == "0":
|
if self.key == "0" or not self.key.startswith("0"):
|
||||||
return self.__class__.root()
|
return self.__class__.root()
|
||||||
elif not self.key.startswith("0"):
|
|
||||||
return self.__class__.root()
|
|
||||||
|
|
||||||
parent_key = ":".join(self.key.split(":")[:-1])
|
parent_key = ":".join(self.key.split(":")[:-1])
|
||||||
try:
|
try:
|
||||||
parent = self.__class__.objects.get(key=parent_key)
|
parent = self.__class__.objects.get(key=parent_key)
|
||||||
|
return parent
|
||||||
except Node.DoesNotExist:
|
except Node.DoesNotExist:
|
||||||
return self.__class__.root()
|
return self.__class__.root()
|
||||||
else:
|
|
||||||
return parent
|
|
||||||
|
|
||||||
@parent.setter
|
@parent.setter
|
||||||
def parent(self, parent):
|
def parent(self, parent):
|
||||||
self.key = parent.get_next_child_key()
|
if self.is_node:
|
||||||
|
children = self.get_all_children()
|
||||||
@property
|
old_key = self.key
|
||||||
def ancestor(self):
|
with transaction.atomic():
|
||||||
if self.parent == self.__class__.root():
|
self.key = parent.get_next_child_key()
|
||||||
return [self.__class__.root()]
|
for child in children:
|
||||||
|
child.key = child.key.replace(old_key, self.key, 1)
|
||||||
|
child.save()
|
||||||
|
self.save()
|
||||||
else:
|
else:
|
||||||
return [self.parent, *tuple(self.parent.ancestor)]
|
self.key = parent.key+':fake'
|
||||||
|
|
||||||
@property
|
def get_ancestor(self, with_self=False):
|
||||||
def ancestor_with_node(self):
|
if self.is_root():
|
||||||
ancestor = self.ancestor
|
ancestor = self.__class__.objects.filter(key='0')
|
||||||
ancestor.insert(0, self)
|
return ancestor
|
||||||
|
|
||||||
|
_key = self.key.split(':')
|
||||||
|
if not with_self:
|
||||||
|
_key.pop()
|
||||||
|
ancestor_keys = []
|
||||||
|
for i in range(len(_key)):
|
||||||
|
ancestor_keys.append(':'.join(_key))
|
||||||
|
_key.pop()
|
||||||
|
ancestor = self.__class__.objects.filter(
|
||||||
|
key__in=ancestor_keys
|
||||||
|
).order_by('key')
|
||||||
return ancestor
|
return ancestor
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -121,4 +166,6 @@ class Node(models.Model):
|
||||||
obj, created = cls.objects.get_or_create(
|
obj, created = cls.objects.get_or_create(
|
||||||
key='0', defaults={"key": '0', 'value': "ROOT"}
|
key='0', defaults={"key": '0', 'value': "ROOT"}
|
||||||
)
|
)
|
||||||
|
print(obj)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
|
@ -95,9 +95,18 @@ class AdminUser(AssetUser):
|
||||||
class SystemUser(AssetUser):
|
class SystemUser(AssetUser):
|
||||||
SSH_PROTOCOL = 'ssh'
|
SSH_PROTOCOL = 'ssh'
|
||||||
RDP_PROTOCOL = 'rdp'
|
RDP_PROTOCOL = 'rdp'
|
||||||
|
TELNET_PROTOCOL = 'telnet'
|
||||||
PROTOCOL_CHOICES = (
|
PROTOCOL_CHOICES = (
|
||||||
(SSH_PROTOCOL, 'ssh'),
|
(SSH_PROTOCOL, 'ssh'),
|
||||||
(RDP_PROTOCOL, 'rdp'),
|
(RDP_PROTOCOL, 'rdp'),
|
||||||
|
(TELNET_PROTOCOL, 'telnet (beta)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTO_LOGIN = 'auto'
|
||||||
|
MANUAL_LOGIN = 'manual'
|
||||||
|
LOGIN_MODE_CHOICES = (
|
||||||
|
(AUTO_LOGIN, _('Automatic login')),
|
||||||
|
(MANUAL_LOGIN, _('Manually login'))
|
||||||
)
|
)
|
||||||
|
|
||||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||||
|
@ -107,6 +116,7 @@ class SystemUser(AssetUser):
|
||||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||||
|
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=AUTO_LOGIN, max_length=10, verbose_name=_('Login mode'))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.name}({0.username})'.format(self)
|
return '{0.name}({0.username})'.format(self)
|
||||||
|
|
|
@ -12,36 +12,10 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class NodeTMPSerializer(serializers.ModelSerializer):
|
|
||||||
parent = serializers.SerializerMethodField()
|
|
||||||
assets_amount = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Node
|
|
||||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount',
|
|
||||||
'is_asset']
|
|
||||||
list_serializer_class = BulkListSerializer
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parent(obj):
|
|
||||||
return obj.parent.id
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_assets_amount(obj):
|
|
||||||
return obj.get_all_assets().count()
|
|
||||||
|
|
||||||
def get_fields(self):
|
|
||||||
fields = super().get_fields()
|
|
||||||
field = fields["key"]
|
|
||||||
field.required = False
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
资产的数据结构
|
资产的数据结构
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
|
@ -62,14 +36,14 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Asset
|
model = Asset
|
||||||
fields = (
|
fields = (
|
||||||
"id", "hostname", "ip", "port", "system_users_granted",
|
"id", "hostname", "ip", "port", "system_users_granted",
|
||||||
"is_active", "system_users_join", "os", 'domain', "nodes",
|
"is_active", "system_users_join", "os", 'domain',
|
||||||
"platform", "comment"
|
"platform", "comment", "protocol",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -48,16 +48,27 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Node
|
model = Node
|
||||||
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_asset']
|
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
value = data.get('value')
|
||||||
|
instance = self.instance if self.instance else Node.root()
|
||||||
|
children = instance.parent.get_children().exclude(key=instance.key)
|
||||||
|
values = [child.value for child in children]
|
||||||
|
if value in values:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
'The same level node name cannot be the same'
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_parent(obj):
|
def get_parent(obj):
|
||||||
return obj.parent.id
|
return obj.parent.id if obj.is_node else obj.parent_id
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_assets_amount(obj):
|
def get_assets_amount(obj):
|
||||||
return obj.get_all_assets().count()
|
return obj.get_all_assets().count() if obj.is_node else 0
|
||||||
|
|
||||||
def get_fields(self):
|
def get_fields(self):
|
||||||
fields = super().get_fields()
|
fields = super().get_fields()
|
||||||
|
|
|
@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
exclude = ('_password', '_private_key', '_public_key')
|
exclude = ('_password', '_private_key', '_public_key')
|
||||||
|
|
||||||
|
def get_field_names(self, declared_fields, info):
|
||||||
|
fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info)
|
||||||
|
fields.extend([
|
||||||
|
'get_login_mode_display',
|
||||||
|
])
|
||||||
|
return fields
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_unreachable_assets(obj):
|
def get_unreachable_assets(obj):
|
||||||
return obj.unreachable_assets
|
return obj.unreachable_assets
|
||||||
|
@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SystemUser
|
model = SystemUser
|
||||||
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
|
fields = (
|
||||||
|
'id', 'name', 'username', 'priority',
|
||||||
|
'protocol', 'comment', 'login_mode'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
class SystemUserSimpleSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -63,22 +63,26 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||||
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
|
if isinstance(instance, Asset):
|
||||||
logger.debug("Asset node change signal received")
|
if kwargs['action'] == 'post_add':
|
||||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
logger.debug("Asset node change signal received")
|
||||||
system_users_assets = defaultdict(set)
|
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
system_users_assets = defaultdict(set)
|
||||||
for system_user in system_users:
|
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||||
system_users_assets[system_user].update({instance})
|
# 清理节点缓存
|
||||||
for system_user, assets in system_users_assets.items():
|
for system_user in system_users:
|
||||||
system_user.assets.add(*tuple(assets))
|
system_users_assets[system_user].update({instance})
|
||||||
|
for system_user, assets in system_users_assets.items():
|
||||||
|
system_user.assets.add(*tuple(assets))
|
||||||
|
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
def on_node_assets_changed(sender, instance=None, **kwargs):
|
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||||
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
|
if isinstance(instance, Node):
|
||||||
logger.debug("Node assets change signal received")
|
|
||||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
system_users = SystemUser.objects.filter(nodes=instance)
|
if kwargs['action'] == 'post_add':
|
||||||
for system_user in system_users:
|
logger.debug("Node assets change signal received")
|
||||||
system_user.assets.add(*tuple(assets))
|
# 重新关联系统用户和资产的关系
|
||||||
|
system_users = SystemUser.objects.filter(nodes=instance)
|
||||||
|
for system_user in system_users:
|
||||||
|
system_user.assets.add(*tuple(assets))
|
||||||
|
|
|
@ -22,7 +22,7 @@ TIMEOUT = 60
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
CACHE_MAX_TIME = 60*60*60
|
CACHE_MAX_TIME = 60*60*60
|
||||||
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
disk_pattern = re.compile(r'^hd|sd|xvd|vd')
|
||||||
PERIOD_TASK = os.environ.get("PERIOD_TASK", "on")
|
PERIOD_TASK = os.environ.get("PERIOD_TASK", "off")
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
|
|
@ -59,7 +59,7 @@ var zTree2, asset_table2 = 0;
|
||||||
function initTable2() {
|
function initTable2() {
|
||||||
var options = {
|
var options = {
|
||||||
ele: $('#asset_list_modal_table'),
|
ele: $('#asset_list_modal_table'),
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "hostname" }, {data: "ip" }
|
{data: "id"}, {data: "hostname" }, {data: "ip" }
|
||||||
],
|
],
|
||||||
|
@ -98,7 +98,10 @@ function initTree2() {
|
||||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["open"] = true;
|
{#value["open"] = true;#}
|
||||||
|
if (value["key"] === "0") {
|
||||||
|
value["open"] = true;
|
||||||
|
}
|
||||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||||
value['value'] = value['value'];
|
value['value'] = value['value'];
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,12 +36,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3>{% trans 'Basic' %}</h3>
|
<h3>{% trans 'Basic' %}</h3>
|
||||||
{% bootstrap_field form.name layout="horizontal" %}
|
{% bootstrap_field form.name layout="horizontal" %}
|
||||||
|
{% bootstrap_field form.login_mode layout="horizontal" %}
|
||||||
{% bootstrap_field form.username layout="horizontal" %}
|
{% bootstrap_field form.username layout="horizontal" %}
|
||||||
{% bootstrap_field form.priority layout="horizontal" %}
|
{% bootstrap_field form.priority layout="horizontal" %}
|
||||||
{% bootstrap_field form.protocol layout="horizontal" %}
|
{% bootstrap_field form.protocol layout="horizontal" %}
|
||||||
|
|
||||||
|
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
|
||||||
{% block auth %}
|
{% block auth %}
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
|
||||||
<div class="auto-generate">
|
<div class="auto-generate">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
|
||||||
|
@ -55,7 +56,7 @@
|
||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="{{ form.as_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
<label for="{{ form.auto_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{{ form.auto_push}}
|
{{ form.auto_push}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,43 +80,86 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||||
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
|
||||||
var password_id = '#' + '{{ form.password.id_for_label }}';
|
|
||||||
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
|
||||||
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
|
||||||
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
|
||||||
|
|
||||||
var need_change_field = [auto_generate_key, private_key_id, sudo_id, shell_id] ;
|
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
|
||||||
|
var password_id = '#' + '{{ form.password.id_for_label }}';
|
||||||
|
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||||
|
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
|
||||||
|
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
|
||||||
|
var shell_id = '#' + '{{ form.shell.id_for_label }}';
|
||||||
|
|
||||||
function authFieldsDisplay() {
|
var need_change_field = [
|
||||||
if ($(auto_generate_key).prop('checked')) {
|
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
|
||||||
$('.auth-fields').addClass('hidden');
|
];
|
||||||
} else {
|
var need_change_field_login_mode = [
|
||||||
$('.auth-fields').removeClass('hidden');
|
auto_generate_key, private_key_id, auto_push_id, password_id
|
||||||
}
|
];
|
||||||
|
|
||||||
|
function protocolChange() {
|
||||||
|
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||||
|
$('.auth-fields').removeClass('hidden');
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).closest('.form-group').addClass('hidden')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
|
||||||
|
$('.auth-fields').removeClass('hidden');
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).closest('.form-group').addClass('hidden')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if($(login_mode_id).val() === 'manual'){
|
||||||
|
$(sudo_id).closest('.form-group').removeClass('hidden');
|
||||||
|
$(shell_id).closest('.form-group').removeClass('hidden');
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
authFieldsDisplay();
|
||||||
|
$.each(need_change_field, function (index, value) {
|
||||||
|
$(value).closest('.form-group').removeClass('hidden')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function protocolChange() {
|
function authFieldsDisplay() {
|
||||||
if ($(protocol_id).attr('value') === 'rdp') {
|
if ($(auto_generate_key).prop('checked')) {
|
||||||
$.each(need_change_field, function (index, value) {
|
$('.auth-fields').addClass('hidden');
|
||||||
$(value).addClass('hidden')
|
} else {
|
||||||
});
|
$('.auth-fields').removeClass('hidden');
|
||||||
$(password_id).removeClass('hidden')
|
}
|
||||||
} else {
|
}
|
||||||
$.each(need_change_field, function (index, value) {
|
function loginModeChange(){
|
||||||
$(value).removeClass('hidden')
|
if ($(login_mode_id).val() === 'manual'){
|
||||||
});
|
$('#auth_title_id').addClass('hidden');
|
||||||
}
|
$.each(need_change_field_login_mode, function(index, value){
|
||||||
}
|
$(value).closest('.form-group').addClass('hidden')
|
||||||
$(document).ready(function () {
|
|
||||||
$('.select2').select2();
|
|
||||||
authFieldsDisplay();
|
|
||||||
protocolChange();
|
|
||||||
$(auto_generate_key).change(function () {
|
|
||||||
authFieldsDisplay();
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
</script>
|
}
|
||||||
|
else if($(login_mode_id).val() === 'auto'){
|
||||||
|
$('#auth_title_id').removeClass('hidden');
|
||||||
|
$(password_id).closest('.form-group').removeClass('hidden')
|
||||||
|
protocolChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.select2').select2();
|
||||||
|
authFieldsDisplay();
|
||||||
|
protocolChange();
|
||||||
|
loginModeChange();
|
||||||
|
})
|
||||||
|
.on('change', protocol_id, function(){
|
||||||
|
protocolChange();
|
||||||
|
})
|
||||||
|
.on('change', auto_generate_key, function(){
|
||||||
|
authFieldsDisplay();
|
||||||
|
})
|
||||||
|
.on('change', login_mode_id, function(){
|
||||||
|
loginModeChange();
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -124,7 +124,7 @@ $(document).ready(function () {
|
||||||
var success = function (data) {
|
var success = function (data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
{% block help_message %}
|
{% block help_message %}
|
||||||
<div class="alert alert-info help-message">
|
<div class="alert alert-info help-message">
|
||||||
管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
|
||||||
Windows或其它硬件可以随意设置一个
|
Windows或其它硬件可以随意设置一个
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -107,6 +107,3 @@ $(document).ready(function(){
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
{% bootstrap_field form.hostname layout="horizontal" %}
|
{% bootstrap_field form.hostname layout="horizontal" %}
|
||||||
{% bootstrap_field form.platform layout="horizontal" %}
|
{% bootstrap_field form.platform 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.port 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" %}
|
||||||
|
@ -85,14 +86,14 @@ $(document).ready(function () {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
templateSelection: format
|
templateSelection: format
|
||||||
});
|
});
|
||||||
$("#id_platform").change(function (){
|
$("#id_protocol").change(function (){
|
||||||
var platform = $("#id_platform option:selected").text();
|
var protocol = $("#id_protocol option:selected").text();
|
||||||
var port = 22;
|
var port = 22;
|
||||||
if(platform === 'Windows'){
|
if(protocol === 'rdp'){
|
||||||
port = 3389;
|
port = 3389;
|
||||||
}
|
}
|
||||||
if(platform === 'Other'){
|
if(protocol === 'telnet (beta)'){
|
||||||
port = null;
|
port = 23;
|
||||||
}
|
}
|
||||||
$("#id_port").val(port);
|
$("#id_port").val(port);
|
||||||
});
|
});
|
||||||
|
|
|
@ -190,7 +190,7 @@
|
||||||
<td colspan="2" class="no-borders">
|
<td colspan="2" class="no-borders">
|
||||||
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
|
||||||
{% for node in nodes_remain %}
|
{% for node in nodes_remain %}
|
||||||
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node.name }}</option>
|
<option value="{{ node.id }}" id="opt_{{ node.id }}" >{{ node }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
|
@ -204,7 +204,7 @@
|
||||||
|
|
||||||
{% for node in asset.nodes.all %}
|
{% for node in asset.nodes.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node.name }}</b></td>
|
<td ><b class="bdg_node" data-gid={{ node.id }}>{{ node }}</b></td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -17,20 +17,21 @@
|
||||||
position:absolute;
|
position:absolute;
|
||||||
visibility:hidden;
|
visibility:hidden;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
top: 100%;
|
{#top: 100%;#}
|
||||||
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
float: left;
|
{#float: left;#}
|
||||||
padding: 5px 0;
|
padding: 0 0;
|
||||||
margin: 2px 0 0;
|
margin: 2px 0 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
}
|
}
|
||||||
div#rMenu li{
|
div#rMenu li{
|
||||||
margin: 1px 0;
|
margin: 1px 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
{#list-style: none outside none;#}
|
list-style: none outside none;
|
||||||
}
|
}
|
||||||
.dropdown a:hover {
|
.dropdown a:hover {
|
||||||
background-color: #f1f1f1
|
background-color: #f1f1f1
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,6 @@
|
||||||
<div class="file-manager ">
|
<div class="file-manager ">
|
||||||
<div id="assetTree" class="ztree">
|
<div id="assetTree" class="ztree">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
<th class="text-center">{% trans 'IP' %}</th>
|
<th class="text-center">{% trans 'IP' %}</th>
|
||||||
<th class="text-center">{% trans 'Hardware' %}</th>
|
<th class="text-center">{% trans 'Hardware' %}</th>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
{# <th class="text-center">{% trans 'Reachable' %}</th>#}
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -127,6 +127,9 @@
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
<li id="menu_refresh_hardware_info" class="btn-refresh-hardware" tabindex="-1"><a><i class="fa fa-refresh"></i> {% trans 'Refresh node hardware info' %}</a></li>
|
||||||
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
<li id="menu_test_connective" class="btn-test-connective" tabindex="-1"><a><i class="fa fa-chain"></i> {% trans 'Test node connective' %}</a></li>
|
||||||
|
<li class="divider"></li>
|
||||||
|
<li id="show_current_asset" class="btn-show-current-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-hand-o-up"></i> {% trans 'Display only current node assets' %}</a></li>
|
||||||
|
<li id="show_all_asset" class="btn-show-all-asset" style="display: none;" tabindex="-1"><a><i class="fa fa-th"></i> {% trans 'Displays all child node assets' %}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -157,26 +160,35 @@ function initTable() {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
|
||||||
if (cellData === 'Unknown'){
|
{#{targets: 5, createdCell: function (td, cellData) {#}
|
||||||
$(td).html('<i class="fa fa-circle text-warning"></i>')
|
{# if (cellData === 'Unknown'){#}
|
||||||
} else if (!cellData) {
|
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
|
||||||
$(td).html('<i class="fa fa-circle text-danger"></i>')
|
{# } else if (!cellData) {#}
|
||||||
} else {
|
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
|
||||||
$(td).html('<i class="fa fa-circle text-navy"></i>')
|
{# } else {#}
|
||||||
}
|
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
|
||||||
}},
|
{# }#}
|
||||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
{# }},#}
|
||||||
|
|
||||||
|
{targets: 5, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
var update_btn = '<a href="{% url "assets:asset-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
$(td).html(update_btn + del_btn)
|
$(td).html(update_btn + del_btn)
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
ajax_url: '{% url "api-assets:asset-list" %}',
|
ajax_url: '{% url "api-assets:asset-list" %}',
|
||||||
|
|
||||||
|
{#columns: [#}
|
||||||
|
{# {data: "id"}, {data: "hostname" }, {data: "ip" },#}
|
||||||
|
{# {data: "cpu_cores"}, {data: "is_active", orderable: false },#}
|
||||||
|
{# {data: "is_connective", orderable: false}, {data: "id", orderable: false }#}
|
||||||
|
{#],#}
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
{data: "id"}, {data: "hostname" }, {data: "ip" },
|
||||||
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
{data: "cpu_cores"}, {data: "is_active", orderable: false },
|
||||||
{data: "is_connective", orderable: false}, {data: "id", orderable: false }
|
{data: "id", orderable: false }
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
|
@ -200,6 +212,8 @@ function addTreeNode() {
|
||||||
};
|
};
|
||||||
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
newNode.checked = zTree.getSelectedNodes()[0].checked;
|
||||||
zTree.addNodes(parentNode, 0, newNode);
|
zTree.addNodes(parentNode, 0, newNode);
|
||||||
|
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
|
||||||
|
zTree.editName(node);
|
||||||
} else {
|
} else {
|
||||||
alert("{% trans 'Create node failed' %}")
|
alert("{% trans 'Create node failed' %}")
|
||||||
}
|
}
|
||||||
|
@ -230,9 +244,9 @@ function removeTreeNode() {
|
||||||
|
|
||||||
function editTreeNode() {
|
function editTreeNode() {
|
||||||
hideRMenu();
|
hideRMenu();
|
||||||
var current_node = zTree.getSelectedNodes()[0];
|
var current_node = zTree.getSelectedNodes()[0];
|
||||||
if (!current_node){
|
if (!current_node){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (current_node.value) {
|
if (current_node.value) {
|
||||||
current_node.name = current_node.value;
|
current_node.name = current_node.value;
|
||||||
|
@ -253,6 +267,8 @@ function OnRightClick(event, treeId, treeNode) {
|
||||||
function showRMenu(type, x, y) {
|
function showRMenu(type, x, y) {
|
||||||
$("#rMenu ul").show();
|
$("#rMenu ul").show();
|
||||||
x -= 220;
|
x -= 220;
|
||||||
|
x += document.body.scrollLeft;
|
||||||
|
y += document.body.scrollTop+document.documentElement.scrollTop;
|
||||||
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
|
||||||
|
|
||||||
$("body").bind("mousedown", onBodyMouseDown);
|
$("body").bind("mousedown", onBodyMouseDown);
|
||||||
|
@ -290,6 +306,7 @@ function onRename(event, treeId, treeNode, isCancel){
|
||||||
function onSelected(event, treeNode) {
|
function onSelected(event, treeNode) {
|
||||||
var url = asset_table.ajax.url();
|
var url = asset_table.ajax.url();
|
||||||
url = setUrlParam(url, "node_id", treeNode.id);
|
url = setUrlParam(url, "node_id", treeNode.id);
|
||||||
|
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
asset_table.ajax.url(url);
|
asset_table.ajax.url(url);
|
||||||
asset_table.ajax.reload();
|
asset_table.ajax.reload();
|
||||||
|
@ -385,9 +402,9 @@ function initTree() {
|
||||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
{#if (value["key"] === "0") {#}
|
if (value["key"] === "0") {
|
||||||
value["open"] = true;
|
value["open"] = true;
|
||||||
{# }#}
|
}
|
||||||
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
|
||||||
value['value'] = value['value'];
|
value['value'] = value['value'];
|
||||||
});
|
});
|
||||||
|
@ -417,6 +434,13 @@ function toggle() {
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initTable();
|
initTable();
|
||||||
initTree();
|
initTree();
|
||||||
|
|
||||||
|
if(getCookie('show_current_asset') === '1'){
|
||||||
|
$('#show_all_asset').css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#show_current_asset').css('display', 'inline-block');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.on('click', '.labels li', function () {
|
.on('click', '.labels li', function () {
|
||||||
var val = $(this).text();
|
var val = $(this).text();
|
||||||
|
@ -535,6 +559,20 @@ $(document).ready(function(){
|
||||||
flash_message: false
|
flash_message: false
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.on('click', '.btn-show-current-asset', function(){
|
||||||
|
hideRMenu();
|
||||||
|
$(this).css('display', 'none');
|
||||||
|
$('#show_all_asset').css('display', 'inline-block');
|
||||||
|
setCookie('show_current_asset', '1');
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.on('click', '.btn-show-all-asset', function(){
|
||||||
|
hideRMenu();
|
||||||
|
$(this).css('display', 'none');
|
||||||
|
$('#show_current_asset').css('display', 'inline-block');
|
||||||
|
setCookie('show_current_asset', '');
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
.on('click', '.btn_asset_delete', function () {
|
.on('click', '.btn_asset_delete', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var $data_table = $("#asset_list_table").DataTable();
|
var $data_table = $("#asset_list_table").DataTable();
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<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.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" %}
|
||||||
|
|
|
@ -85,6 +85,9 @@ function initTable() {
|
||||||
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var update_btn = '<a href="{% url "assets:domain-gateway-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
if(rowData.protocol === 'rdp'){
|
||||||
|
test_btn = '<a class="btn btn-xs btn-warning m-l-xs btn-test" disabled data-uid="{{ DEFAULT_PK }}">{% trans "Test connection" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
|
}
|
||||||
$(td).html(update_btn + test_btn + del_btn)
|
$(td).html(update_btn + test_btn + del_btn)
|
||||||
}}
|
}}
|
||||||
],
|
],
|
||||||
|
@ -120,7 +123,6 @@ $(document).ready(function(){
|
||||||
success_message: "可连接",
|
success_message: "可连接",
|
||||||
fail_message: "连接失败"
|
fail_message: "连接失败"
|
||||||
})
|
})
|
||||||
|
});
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
{% extends '_base_list.html' %}
|
{% extends '_base_list.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
{% block table_search %}{% endblock %}
|
{% block table_search %}{% endblock %}
|
||||||
|
|
||||||
|
{% block help_message %}
|
||||||
|
<div class="alert alert-info help-message">
|
||||||
|
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
|
||||||
|
录。
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block table_container %}
|
{% block table_container %}
|
||||||
<div class="uc pull-left m-r-5">
|
<div class="uc pull-left m-r-5">
|
||||||
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
|
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
|
||||||
|
@ -69,6 +77,3 @@ $(document).ready(function(){
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,3 +66,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
|
||||||
|
var private_key_id = '#' + '{{ form.private_key_file.id_for_label }}';
|
||||||
|
var port = '#' + '{{ form.port.id_for_label }}';
|
||||||
|
|
||||||
|
function protocolChange() {
|
||||||
|
if ($(protocol_id + " option:selected").text() === 'rdp') {
|
||||||
|
{#$(port).val(3389);#}
|
||||||
|
$(private_key_id).closest('.form-group').addClass('hidden')
|
||||||
|
} else {
|
||||||
|
{#$(port).val(22);#}
|
||||||
|
$(private_key_id).closest('.form-group').removeClass('hidden')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
protocolChange();
|
||||||
|
})
|
||||||
|
.on('change', protocol_id, function(){
|
||||||
|
protocolChange();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -63,15 +63,19 @@
|
||||||
<td><b>{{ system_user.username }}</b></td>
|
<td><b>{{ system_user.username }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Protocol' %}:</td>
|
<td>{% trans 'Login mode' %}:</td>
|
||||||
<td><b>{{ system_user.protocol }}</b></td>
|
<td><b>{{ system_user.get_login_mode_display }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{% trans 'Protocol' %}:</td>
|
||||||
|
<td><b id="id_protocol_type">{{ system_user.protocol }}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="only-ssh">
|
||||||
<td>{% trans 'Sudo' %}:</td>
|
<td>{% trans 'Sudo' %}:</td>
|
||||||
<td><b>{{ system_user.sudo }}</b></td>
|
<td><b>{{ system_user.sudo }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if system_user.shell %}
|
{% if system_user.shell %}
|
||||||
<tr>
|
<tr class="only-ssh">
|
||||||
<td>{% trans 'Shell' %}:</td>
|
<td>{% trans 'Shell' %}:</td>
|
||||||
<td><b>{{ system_user.shell }}</b></td>
|
<td><b>{{ system_user.shell }}</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -107,14 +111,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary ">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="no-borders-tr">
|
<tr class="only-ssh">
|
||||||
<td width="50%">{% trans 'Auto push' %}:</td>
|
<td width="50%">{% trans 'Auto push' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
|
@ -130,8 +134,8 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="no-borders-tr">
|
|
||||||
{% if system_user.auto_push %}
|
{% if system_user.auto_push %}
|
||||||
|
<tr class="only-ssh">
|
||||||
<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">
|
||||||
|
@ -139,8 +143,8 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<tr class="only-ssh">
|
||||||
<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">
|
||||||
|
@ -149,6 +153,15 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="50%">{% trans 'Clear auth' %}:</td>
|
||||||
|
<td>
|
||||||
|
<span style="float: right">
|
||||||
|
<button type="button" class="btn btn-primary btn-xs btn-clear-auth" style="width: 54px">{% trans 'Clear' %}</button>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{# <tr>#}
|
{# <tr>#}
|
||||||
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
|
||||||
{# <td>#}
|
{# <td>#}
|
||||||
|
@ -236,6 +249,10 @@ function updateSystemUserNode(nodes) {
|
||||||
}
|
}
|
||||||
jumpserver.nodes_selected = {};
|
jumpserver.nodes_selected = {};
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
if($('#id_protocol_type').text() === 'rdp'){
|
||||||
|
$('.only-ssh').addClass('hidden')
|
||||||
|
}
|
||||||
|
$(".panel-body .table tr:visible:first").addClass('no-borders-tr');
|
||||||
$('.select2').select2()
|
$('.select2').select2()
|
||||||
.on('select2:select', function(evt) {
|
.on('select2:select', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
|
@ -296,7 +313,7 @@ $(document).ready(function () {
|
||||||
var success = function (data) {
|
var success = function (data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -318,6 +335,25 @@ $(document).ready(function () {
|
||||||
success: success,
|
success: success,
|
||||||
flash_message: false
|
flash_message: false
|
||||||
});
|
});
|
||||||
|
}).on('click', '.btn-clear-auth', function () {
|
||||||
|
var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}';
|
||||||
|
var name = '{{ system_user.name }}';
|
||||||
|
swal({
|
||||||
|
title: '你确定清除该系统用户的认证信息吗 ?',
|
||||||
|
text: " [" + name + "] ",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
confirmButtonColor: "#ed5565",
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
closeOnConfirm: true
|
||||||
|
}, function () {
|
||||||
|
APIUpdateAttr({
|
||||||
|
url: the_url,
|
||||||
|
method: 'DELETE',
|
||||||
|
success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}"
|
||||||
|
});
|
||||||
|
});
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<th class="text-center">{% trans 'Name' %}</th>
|
<th class="text-center">{% trans 'Name' %}</th>
|
||||||
<th class="text-center">{% trans 'Username' %}</th>
|
<th class="text-center">{% trans 'Username' %}</th>
|
||||||
<th class="text-center">{% trans 'Protocol' %}</th>
|
<th class="text-center">{% trans 'Protocol' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Login mode' %}</th>
|
||||||
<th class="text-center">{% trans 'Asset' %}</th>
|
<th class="text-center">{% trans 'Asset' %}</th>
|
||||||
<th class="text-center">{% trans 'Reachable' %}</th>
|
<th class="text-center">{% trans 'Reachable' %}</th>
|
||||||
<th class="text-center">{% trans 'Unreachable' %}</th>
|
<th class="text-center">{% trans 'Unreachable' %}</th>
|
||||||
|
@ -48,7 +49,7 @@ function initTable() {
|
||||||
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
var detail_btn = '<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
||||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
{targets: 6, createdCell: function (td, cellData) {
|
||||||
var innerHtml = "";
|
var innerHtml = "";
|
||||||
if (cellData !== 0) {
|
if (cellData !== 0) {
|
||||||
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
innerHtml = "<span class='text-navy'>" + cellData + "</span>";
|
||||||
|
@ -57,7 +58,7 @@ function initTable() {
|
||||||
}
|
}
|
||||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData +'">' + innerHtml + '</span>');
|
||||||
}},
|
}},
|
||||||
{targets: 6, createdCell: function (td, cellData) {
|
{targets: 7, createdCell: function (td, cellData) {
|
||||||
var innerHtml = "";
|
var innerHtml = "";
|
||||||
if (cellData !== 0) {
|
if (cellData !== 0) {
|
||||||
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
innerHtml = "<span class='text-danger'>" + cellData + "</span>";
|
||||||
|
@ -66,7 +67,7 @@ function initTable() {
|
||||||
}
|
}
|
||||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
}},
|
}},
|
||||||
{targets: 7, createdCell: function (td, cellData, rowData) {
|
{targets: 8, createdCell: function (td, cellData, rowData) {
|
||||||
var val = 0;
|
var val = 0;
|
||||||
var innerHtml = "";
|
var innerHtml = "";
|
||||||
var total = rowData.assets_amount;
|
var total = rowData.assets_amount;
|
||||||
|
@ -84,14 +85,14 @@ function initTable() {
|
||||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
|
|
||||||
}},
|
}},
|
||||||
{targets: 9, createdCell: function (td, cellData, rowData) {
|
{targets: 10, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var update_btn = '<a href="{% url "assets:system-user-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||||
$(td).html(update_btn + del_btn)
|
$(td).html(update_btn + del_btn)
|
||||||
}}],
|
}}],
|
||||||
ajax_url: '{% url "api-assets:system-user-list" %}',
|
ajax_url: '{% url "api-assets:system-user-list" %}',
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "assets_amount" },
|
{data: "id" }, {data: "name" }, {data: "username" }, {data: "protocol"}, {data: "get_login_mode_display"}, {data: "assets_amount" },
|
||||||
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
|
{data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
{% load bootstrap3 %}
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
{% block auth %}
|
{% block auth %}
|
||||||
<h3>{% trans 'Auth' %}</h3>
|
|
||||||
{% bootstrap_field form.password layout="horizontal" %}
|
{% bootstrap_field form.password layout="horizontal" %}
|
||||||
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
{% bootstrap_field form.private_key_file layout="horizontal" %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -15,10 +14,3 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
|
||||||
<script>
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('.select2').select2();
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -23,8 +23,8 @@ urlpatterns = [
|
||||||
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
|
||||||
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
|
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
|
||||||
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
|
||||||
url(r'^v1/assets/user-assets/$',
|
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$',
|
||||||
api.UserAssetListView.as_view(), name='user-asset-list'),
|
api.AssetGatewayApi.as_view(), name='asset-gateway'),
|
||||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
|
||||||
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
|
||||||
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
|
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/auth/$',
|
||||||
|
@ -35,17 +35,26 @@ urlpatterns = [
|
||||||
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
api.SystemUserPushApi.as_view(), name='system-user-push'),
|
||||||
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/connective/$',
|
||||||
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
api.SystemUserTestConnectiveApi.as_view(), name='system-user-connective'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$', api.NodeChildrenApi.as_view(), name='node-children'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/$',
|
||||||
|
api.NodeChildrenApi.as_view(), name='node-children'),
|
||||||
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
url(r'^v1/nodes/children/$', api.NodeChildrenApi.as_view(), name='node-children-2'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$', api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/children/add/$',
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'),
|
api.NodeAddChildrenApi.as_view(), name='node-add-children'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$', api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/$',
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$', api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
api.NodeAssetsApi.as_view(), name='node-assets'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$', api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/add/$',
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$', api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
api.NodeAddAssetsApi.as_view(), name='node-add-assets'),
|
||||||
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/replace/$',
|
||||||
|
api.NodeReplaceAssetsApi.as_view(), name='node-replace-assets'),
|
||||||
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/assets/remove/$',
|
||||||
|
api.NodeRemoveAssetsApi.as_view(), name='node-remove-assets'),
|
||||||
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/refresh-hardware-info/$',
|
||||||
|
api.RefreshNodeHardwareInfoApi.as_view(), name='node-refresh-hardware-info'),
|
||||||
|
url(r'^v1/nodes/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||||
|
api.TestNodeConnectiveApi.as_view(), name='node-test-connective'),
|
||||||
|
|
||||||
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$', api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
url(r'^v1/gateway/(?P<pk>[0-9a-zA-Z\-]{36})/test-connective/$',
|
||||||
|
api.GatewayTestConnectionApi.as_view(), name='test-gateway-connective'),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
#
|
#
|
||||||
|
import os
|
||||||
import paramiko
|
import paramiko
|
||||||
|
from paramiko.ssh_exception import SSHException
|
||||||
|
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from .models import Asset, SystemUser, Label
|
from .models import Asset, SystemUser, Label
|
||||||
|
@ -49,22 +50,23 @@ def test_gateway_connectability(gateway):
|
||||||
"""
|
"""
|
||||||
client = paramiko.SSHClient()
|
client = paramiko.SSHClient()
|
||||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
proxy = paramiko.SSHClient()
|
||||||
proxy_command = [
|
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
"ssh", "{}@{}".format(gateway.username, gateway.ip),
|
|
||||||
"-p", str(gateway.port), "-W", "127.0.0.1:{}".format(gateway.port),
|
|
||||||
]
|
|
||||||
|
|
||||||
if gateway.password:
|
|
||||||
proxy_command.insert(0, "sshpass -p '{}'".format(gateway.password))
|
|
||||||
if gateway.private_key:
|
|
||||||
proxy_command.append("-i {}".format(gateway.private_key_file))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock = paramiko.ProxyCommand(" ".join(proxy_command))
|
proxy.connect(gateway.ip, gateway.port,
|
||||||
except paramiko.ProxyCommandFailure as e:
|
username=gateway.username,
|
||||||
|
password=gateway.password,
|
||||||
|
pkey=gateway.private_key_obj)
|
||||||
|
except(paramiko.AuthenticationException,
|
||||||
|
paramiko.BadAuthenticationType,
|
||||||
|
SSHException) as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
|
sock = proxy.get_transport().open_channel(
|
||||||
|
'direct-tcpip', ('127.0.0.1', gateway.port), ('127.0.0.1', 0)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client.connect("127.0.0.1", port=gateway.port,
|
client.connect("127.0.0.1", port=gateway.port,
|
||||||
username=gateway.username,
|
username=gateway.username,
|
||||||
|
|
|
@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
|
||||||
domain = self.object.domain
|
domain = self.object.domain
|
||||||
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
|
return reverse('assets:domain-gateway-list', kwargs={"pk": domain.id})
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
response = super().form_valid(form)
|
|
||||||
print(form.cleaned_data)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = {
|
context = {
|
||||||
'app': _('Assets'),
|
'app': _('Assets'),
|
||||||
|
|
|
@ -21,23 +21,13 @@ class MailTestingAPI(APIView):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
|
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
|
||||||
kwargs = {
|
for k, v in serializer.validated_data.items():
|
||||||
"host": serializer.validated_data["EMAIL_HOST"],
|
if k.startswith('EMAIL'):
|
||||||
"port": serializer.validated_data["EMAIL_PORT"],
|
setattr(settings, k, v)
|
||||||
"username": serializer.validated_data["EMAIL_HOST_USER"],
|
|
||||||
"password": serializer.validated_data["EMAIL_HOST_PASSWORD"],
|
|
||||||
"use_ssl": serializer.validated_data["EMAIL_USE_SSL"],
|
|
||||||
"use_tls": serializer.validated_data["EMAIL_USE_TLS"]
|
|
||||||
}
|
|
||||||
connection = get_connection(timeout=5, **kwargs)
|
|
||||||
try:
|
try:
|
||||||
connection.open()
|
subject = "Test"
|
||||||
except Exception as e:
|
message = "Test smtp setting"
|
||||||
return Response({"error": str(e)}, status=401)
|
send_mail(subject, message, email_host_user, [email_host_user])
|
||||||
|
|
||||||
try:
|
|
||||||
send_mail("Test", "Test smtp setting", email_host_user,
|
|
||||||
[email_host_user], connection=connection)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return Response({"error": str(e)}, status=401)
|
return Response({"error": str(e)}, status=401)
|
||||||
|
|
||||||
|
@ -96,14 +86,7 @@ class LDAPTestingAPI(APIView):
|
||||||
|
|
||||||
class DjangoSettingsAPI(APIView):
|
class DjangoSettingsAPI(APIView):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if not settings.DEBUG:
|
return Response('Danger, Close now')
|
||||||
return Response('Only debug mode support')
|
|
||||||
|
|
||||||
configs = {}
|
|
||||||
for i in dir(settings):
|
|
||||||
if i.isupper():
|
|
||||||
configs[i] = str(getattr(settings, i))
|
|
||||||
return Response(configs)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -168,3 +168,63 @@ class TerminalSettingForm(BaseForm):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SecuritySettingForm(BaseForm):
|
||||||
|
# MFA global setting
|
||||||
|
SECURITY_MFA_AUTH = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("MFA Secondary certification"),
|
||||||
|
help_text=_(
|
||||||
|
'After opening, the user login must use MFA secondary '
|
||||||
|
'authentication (valid for all users, including administrators)'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# limit login count
|
||||||
|
SECURITY_LOGIN_LIMIT_COUNT = forms.IntegerField(
|
||||||
|
initial=3, min_value=3,
|
||||||
|
label=_("Limit the number of login failures")
|
||||||
|
)
|
||||||
|
# limit login time
|
||||||
|
SECURITY_LOGIN_LIMIT_TIME = forms.IntegerField(
|
||||||
|
initial=30, min_value=5,
|
||||||
|
label=_("No logon interval"),
|
||||||
|
help_text=_(
|
||||||
|
"Tip :(unit/minute) if the user has failed to log in for a limited "
|
||||||
|
"number of times, no login is allowed during this time interval."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# min length
|
||||||
|
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
|
||||||
|
initial=6, label=_("Password minimum length"),
|
||||||
|
min_value=6
|
||||||
|
)
|
||||||
|
# upper case
|
||||||
|
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
|
||||||
|
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain capital letters"),
|
||||||
|
help_text=_(
|
||||||
|
'After opening, the user password changes '
|
||||||
|
'and resets must contain uppercase letters')
|
||||||
|
)
|
||||||
|
# lower case
|
||||||
|
SECURITY_PASSWORD_LOWER_CASE = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain lowercase letters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain lowercase letters')
|
||||||
|
)
|
||||||
|
# number
|
||||||
|
SECURITY_PASSWORD_NUMBER = forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain numeric characters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain numeric characters')
|
||||||
|
)
|
||||||
|
# special char
|
||||||
|
SECURITY_PASSWORD_SPECIAL_CHAR= forms.BooleanField(
|
||||||
|
initial=False, required=False,
|
||||||
|
label=_("Must contain special characters"),
|
||||||
|
help_text=_('After opening, the user password changes '
|
||||||
|
'and resets must contain special characters')
|
||||||
|
)
|
||||||
|
|
|
@ -34,7 +34,7 @@ def refresh_all_settings_on_django_ready(sender, **kwargs):
|
||||||
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
|
def ldap_auth_on_changed(sender, enabled=True, **kwargs):
|
||||||
if enabled:
|
if enabled:
|
||||||
logger.debug("Enable LDAP auth")
|
logger.debug("Enable LDAP auth")
|
||||||
if settings.AUTH_LDAP_BACKEND not in settings.AUTH_LDAP_BACKEND:
|
if settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
|
||||||
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.core.mail import send_mail
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from .utils import get_logger
|
from .utils import get_logger
|
||||||
|
from .models import Setting
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
@ -21,6 +22,10 @@ def send_mail_async(*args, **kwargs):
|
||||||
Example:
|
Example:
|
||||||
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
|
send_mail_sync.delay(subject, message, recipient_list, fail_silently=False, html_message=None)
|
||||||
"""
|
"""
|
||||||
|
configs = Setting.objects.filter(name__startswith='EMAIL')
|
||||||
|
for config in configs:
|
||||||
|
setattr(settings, config.name, config.cleaned_value)
|
||||||
|
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
args = list(args)
|
args = list(args)
|
||||||
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load common_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="wrapper wrapper-content animated fadeInRight">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="ibox float-e-margins">
|
||||||
|
<div class="panel-options">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
|
</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="col-sm-12" style="padding-left:0">
|
||||||
|
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
|
||||||
|
<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 "User login settings" %}</h3>
|
||||||
|
{% for field in form %}
|
||||||
|
{% if forloop.counter == 4 %}
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
<h3>{% trans "Password check rule" %}</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not field.field|is_bool_field %}
|
||||||
|
{% bootstrap_field field layout="horizontal" %}
|
||||||
|
{% else %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="col-sm-1">
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<span class="help-block" >{{ field.help_text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-4 col-sm-offset-2">
|
||||||
|
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||||
|
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block custom_foot_js %}
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -27,6 +27,9 @@
|
||||||
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
|
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i
|
||||||
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings:security-setting' %}" class="text-center"><i class="fa fa-lock"></i> {% trans 'Security setting' %} </a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
|
@ -39,6 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<h3>{% trans "Basic setting" %}</h3>
|
<h3>{% trans "Basic setting" %}</h3>
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if not field.field|is_bool_field %}
|
{% if not field.field|is_bool_field %}
|
||||||
|
@ -60,6 +64,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
|
||||||
<h3>{% trans "Command storage" %}</h3>
|
<h3>{% trans "Command storage" %}</h3>
|
||||||
<table class="table table-hover " id="task-history-list-table">
|
<table class="table table-hover " id="task-history-list-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -11,4 +11,5 @@ urlpatterns = [
|
||||||
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
|
||||||
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
|
||||||
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
|
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
|
||||||
|
url(r'^security/$', views.SecuritySettingView.as_view(), name='security-setting'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,6 +16,7 @@ import calendar
|
||||||
import threading
|
import threading
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import uuid
|
import uuid
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
|
@ -395,3 +396,17 @@ class TeeObj:
|
||||||
def close(self):
|
def close(self):
|
||||||
self.file_obj.close()
|
self.file_obj.close()
|
||||||
|
|
||||||
|
|
||||||
|
def with_cache(func):
|
||||||
|
cache = {}
|
||||||
|
key = "_{}.{}".format(func.__module__, func.__name__)
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
cached = cache.get(key)
|
||||||
|
if cached:
|
||||||
|
return cached
|
||||||
|
res = func(*args, **kwargs)
|
||||||
|
cache[key] = res
|
||||||
|
return res
|
||||||
|
return wrapper
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
alphanumeric = RegexValidator(r'^[0-9a-zA-Z_@\-\.]*$', _('Special char not allowed'))
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
|
||||||
TerminalSettingForm
|
TerminalSettingForm, SecuritySettingForm
|
||||||
from .mixins import AdminUserRequiredMixin
|
from .mixins import AdminUserRequiredMixin
|
||||||
from .signals import ldap_auth_enable
|
from .signals import ldap_auth_enable
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
if "AUTH_LDAP" in form.cleaned_data:
|
if "AUTH_LDAP" in form.cleaned_data:
|
||||||
ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
|
ldap_auth_enable.send(sender=self.__class__, enabled=form.cleaned_data["AUTH_LDAP"])
|
||||||
msg = _("Update setting successfully, please restart program")
|
msg = _("Update setting successfully, please restart program")
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return redirect('settings:ldap-setting')
|
return redirect('settings:ldap-setting')
|
||||||
|
@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
|
||||||
return render(request, self.template_name, context)
|
return render(request, self.template_name, context)
|
||||||
|
|
||||||
|
|
||||||
|
class SecuritySettingView(AdminUserRequiredMixin, TemplateView):
|
||||||
|
form_class = SecuritySettingForm
|
||||||
|
template_name = "common/security_setting.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'app': _('Settings'),
|
||||||
|
'action': _('Security setting'),
|
||||||
|
'form': self.form_class(),
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
form = self.form_class(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
msg = _("Update setting successfully, please restart program")
|
||||||
|
messages.success(request, msg)
|
||||||
|
return redirect('settings:security-setting')
|
||||||
|
else:
|
||||||
|
context = self.get_context_data()
|
||||||
|
context.update({"form": form})
|
||||||
|
return render(request, self.template_name, context)
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -229,7 +229,11 @@ LOGGING = {
|
||||||
'django_auth_ldap': {
|
'django_auth_ldap': {
|
||||||
'handlers': ['console', 'ansible_logs'],
|
'handlers': ['console', 'ansible_logs'],
|
||||||
'level': "INFO",
|
'level': "INFO",
|
||||||
}
|
},
|
||||||
|
# 'django.db': {
|
||||||
|
# 'handlers': ['console', 'file'],
|
||||||
|
# 'level': 'DEBUG'
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,6 +333,9 @@ AUTH_LDAP_GROUP_SEARCH_FILTER = CONFIG.AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||||
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
AUTH_LDAP_GROUP_SEARCH_OU, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER
|
||||||
)
|
)
|
||||||
|
AUTH_LDAP_CONNECTION_OPTIONS = {
|
||||||
|
ldap.OPT_TIMEOUT: 5
|
||||||
|
}
|
||||||
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||||
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
AUTH_LDAP_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
|
||||||
|
|
||||||
|
@ -336,10 +343,11 @@ if AUTH_LDAP:
|
||||||
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
|
||||||
|
|
||||||
# Celery using redis as broker
|
# Celery using redis as broker
|
||||||
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/3' % {
|
CELERY_BROKER_URL = 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
||||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
||||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||||
'port': CONFIG.REDIS_PORT or 6379,
|
'port': CONFIG.REDIS_PORT or 6379,
|
||||||
|
'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
|
||||||
}
|
}
|
||||||
CELERY_TASK_SERIALIZER = 'pickle'
|
CELERY_TASK_SERIALIZER = 'pickle'
|
||||||
CELERY_RESULT_SERIALIZER = 'pickle'
|
CELERY_RESULT_SERIALIZER = 'pickle'
|
||||||
|
@ -360,10 +368,11 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'redis_cache.RedisCache',
|
'BACKEND': 'redis_cache.RedisCache',
|
||||||
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/4' % {
|
'LOCATION': 'redis://:%(password)s@%(host)s:%(port)s/%(db)s' % {
|
||||||
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
'password': CONFIG.REDIS_PASSWORD if CONFIG.REDIS_PASSWORD else '',
|
||||||
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
'host': CONFIG.REDIS_HOST or '127.0.0.1',
|
||||||
'port': CONFIG.REDIS_PORT or 6379,
|
'port': CONFIG.REDIS_PORT or 6379,
|
||||||
|
'db':CONFIG.REDIS_DB_CACHE or 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,6 +403,11 @@ TERMINAL_REPLAY_STORAGE = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_PASSWORD_MIN_LENGTH = 6
|
||||||
|
DEFAULT_LOGIN_LIMIT_COUNT = 3
|
||||||
|
DEFAULT_LOGIN_LIMIT_TIME = 30
|
||||||
|
|
||||||
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
|
||||||
BOOTSTRAP3 = {
|
BOOTSTRAP3 = {
|
||||||
'horizontal_label_class': 'col-md-2',
|
'horizontal_label_class': 'col-md-2',
|
||||||
|
|
|
@ -72,7 +72,7 @@ class CeleryTaskLogApi(generics.RetrieveAPIView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
mark = request.query_params.get("mark") or str(uuid.uuid4())
|
||||||
task = super().get_object()
|
task = self.get_object()
|
||||||
log_path = task.full_log_path
|
log_path = task.full_log_path
|
||||||
|
|
||||||
if not log_path or not os.path.isfile(log_path):
|
if not log_path or not os.path.isfile(log_path):
|
||||||
|
|
|
@ -86,13 +86,14 @@ class JMSInventory(BaseInventory):
|
||||||
gateway = asset.domain.random_gateway()
|
gateway = asset.domain.random_gateway()
|
||||||
proxy_command_list = [
|
proxy_command_list = [
|
||||||
"ssh", "-p", str(gateway.port),
|
"ssh", "-p", str(gateway.port),
|
||||||
|
"-o", "StrictHostKeyChecking=no",
|
||||||
"{}@{}".format(gateway.username, gateway.ip),
|
"{}@{}".format(gateway.username, gateway.ip),
|
||||||
"-W", "%h:%p", "-q",
|
"-W", "%h:%p", "-q",
|
||||||
]
|
]
|
||||||
|
|
||||||
if gateway.password:
|
if gateway.password:
|
||||||
proxy_command_list.insert(
|
proxy_command_list.insert(
|
||||||
0, "sshpass -p {}".format(gateway.password)
|
0, "sshpass -p '{}'".format(gateway.password)
|
||||||
)
|
)
|
||||||
if gateway.private_key:
|
if gateway.private_key:
|
||||||
proxy_command_list.append("-i {}".format(gateway.private_key_file))
|
proxy_command_list.append("-i {}".format(gateway.private_key_file))
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<a href="{% url 'ops:adhoc-history-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history detail' %} </a>
|
<a href="{% url 'ops:adhoc-history-detail' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history detail' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,38 +2,25 @@
|
||||||
<head>
|
<head>
|
||||||
<title>term.js</title>
|
<title>term.js</title>
|
||||||
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
<script src="{% static 'js/jquery-2.1.1.js' %}"></script>
|
||||||
|
<script src="{% static 'js/plugins/xterm/xterm.js' %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/xterm/xterm.css' %}" />
|
||||||
<style>
|
<style>
|
||||||
html {
|
body {
|
||||||
background: #000;
|
background-color: black;
|
||||||
}
|
}
|
||||||
h1 {
|
.xterm-rows {
|
||||||
margin-bottom: 20px;
|
{#padding: 15px;#}
|
||||||
font: 20px/1.5 sans-serif;
|
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
|
||||||
}
|
font-size: 13px;
|
||||||
.terminal {
|
}
|
||||||
float: left;
|
|
||||||
font-family: 'Monaco', 'Consolas', "DejaVu Sans Mono", "Liberation Mono", monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #f0f0f0;
|
|
||||||
background-color: #555;
|
|
||||||
padding: 20px 20px 20px;
|
|
||||||
}
|
|
||||||
.terminal-cursor {
|
|
||||||
color: #000;
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<div class="container">
|
<div id="term" style="height: 100%;width: 100%">
|
||||||
<div id="term">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="{% static 'js/term.js' %}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
var rowHeight = 1;
|
var rowHeight = 18;
|
||||||
var colWidth = 1;
|
var colWidth = 10;
|
||||||
var mark = '';
|
var mark = '';
|
||||||
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
|
||||||
var term;
|
var term;
|
||||||
|
@ -42,13 +29,16 @@
|
||||||
var interval = 200;
|
var interval = 200;
|
||||||
|
|
||||||
function calWinSize() {
|
function calWinSize() {
|
||||||
var t = $('.terminal');
|
var t = $('#marker');
|
||||||
rowHeight = 1.00 * t.height() / 24;
|
{#rowHeight = 1.00 * t.height();#}
|
||||||
colWidth = 1.00 * t.width() / 80;
|
{#colWidth = 1.00 * t.width() / 6;#}
|
||||||
}
|
}
|
||||||
function resize() {
|
function resize() {
|
||||||
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
|
{#console.log(rowHeight, window.innerHeight);#}
|
||||||
var cols = Math.floor(window.innerWidth / colWidth) - 10;
|
{#console.log(colWidth, window.innerWidth);#}
|
||||||
|
var rows = Math.floor(window.innerHeight / rowHeight) - 1;
|
||||||
|
var cols = Math.floor(window.innerWidth / colWidth) - 2;
|
||||||
|
console.log(rows, cols);
|
||||||
term.resize(cols, rows);
|
term.resize(cols, rows);
|
||||||
}
|
}
|
||||||
function requestAndWrite() {
|
function requestAndWrite() {
|
||||||
|
@ -74,21 +64,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
term = new Terminal({
|
term = new Terminal();
|
||||||
cols: 80,
|
term.open(document.getElementById('term'));
|
||||||
rows: 24,
|
term.resize(80, 24);
|
||||||
useStyle: true,
|
|
||||||
screenKeys: false,
|
|
||||||
convertEol: false,
|
|
||||||
cursorBlink: false
|
|
||||||
});
|
|
||||||
term.open();
|
|
||||||
term.on('data', function (data) {
|
|
||||||
term.write(data.replace('\r', '\r\n'))
|
|
||||||
});
|
|
||||||
calWinSize();
|
|
||||||
resize();
|
resize();
|
||||||
$('.terminal').detach().appendTo('#term');
|
term.on('data', function (data) {
|
||||||
|
{#term.write(data.replace('\r', '\r\n'))#}
|
||||||
|
term.write(data);
|
||||||
|
});
|
||||||
|
window.onresize = function () {
|
||||||
|
resize()
|
||||||
|
};
|
||||||
|
{#$('.terminal').detach().appendTo('#term');#}
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
requestAndWrite()
|
requestAndWrite()
|
||||||
}, interval)
|
}, interval)
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
<a href="{% url 'ops:task-history' pk=object.pk %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Run history' %} </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
<a class="text-center celery-task-log" onclick="window.open('{% url 'ops:celery-task-log' pk=object.latest_history.pk %}','', 'width=800,height=600,left=400,top=400')"><i class="fa fa-laptop"></i> {% trans 'Last run output' %} </a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -115,7 +115,7 @@ $(document).ready(function() {
|
||||||
var success = function(data) {
|
var success = function(data) {
|
||||||
var task_id = data.task;
|
var task_id = data.task;
|
||||||
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
|
||||||
window.open(url, '', 'width=800,height=600')
|
window.open(url, '', 'width=800,height=600,left=400,top=400')
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
|
|
@ -6,7 +6,7 @@ from rest_framework.views import APIView, Response
|
||||||
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from common.utils import set_or_append_attr_bulk
|
from common.utils import set_or_append_attr_bulk, get_object_or_none
|
||||||
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser
|
||||||
from .utils import AssetPermissionUtil
|
from .utils import AssetPermissionUtil
|
||||||
from .models import AssetPermission
|
from .models import AssetPermission
|
||||||
|
@ -41,11 +41,11 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
|
||||||
asset = get_object_or_404(Asset, pk=asset_id)
|
asset = get_object_or_404(Asset, pk=asset_id)
|
||||||
permissions = set(queryset.filter(assets=asset))
|
permissions = set(queryset.filter(assets=asset))
|
||||||
for node in asset.nodes.all():
|
for node in asset.nodes.all():
|
||||||
inherit_nodes.update(set(node.ancestor_with_node))
|
inherit_nodes.update(set(node.get_ancestor(with_self=True)))
|
||||||
elif node_id:
|
elif node_id:
|
||||||
node = get_object_or_404(Node, pk=node_id)
|
node = get_object_or_404(Node, pk=node_id)
|
||||||
permissions = set(queryset.filter(nodes=node))
|
permissions = set(queryset.filter(nodes=node))
|
||||||
inherit_nodes = node.ancestor
|
inherit_nodes = node.get_ancestor()
|
||||||
|
|
||||||
for n in inherit_nodes:
|
for n in inherit_nodes:
|
||||||
_permissions = queryset.filter(nodes=n)
|
_permissions = queryset.filter(nodes=n)
|
||||||
|
@ -70,11 +70,12 @@ class UserGrantedAssetsApi(ListAPIView):
|
||||||
else:
|
else:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
|
||||||
for k, v in AssetPermissionUtil.get_user_assets(user).items():
|
util = AssetPermissionUtil(user)
|
||||||
|
for k, v in util.get_assets().items():
|
||||||
if k.is_unixlike():
|
if k.is_unixlike():
|
||||||
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']]
|
||||||
else:
|
else:
|
||||||
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']]
|
||||||
k.system_users_granted = system_users_granted
|
k.system_users_granted = system_users_granted
|
||||||
queryset.append(k)
|
queryset.append(k)
|
||||||
return queryset
|
return queryset
|
||||||
|
@ -95,7 +96,8 @@ class UserGrantedNodesApi(ListAPIView):
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
else:
|
else:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
|
util = AssetPermissionUtil(user)
|
||||||
|
nodes = util.get_nodes_with_assets()
|
||||||
return nodes.keys()
|
return nodes.keys()
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
@ -116,14 +118,15 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
|
||||||
else:
|
else:
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
|
|
||||||
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
|
util = AssetPermissionUtil(user)
|
||||||
|
nodes = util.get_nodes_with_assets()
|
||||||
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():
|
||||||
if k.is_unixlike():
|
if k.is_unixlike():
|
||||||
system_users_granted = [s for s in v if s.protocol == 'ssh']
|
system_users_granted = [s for s in v if s.protocol in ['ssh', 'telnet']]
|
||||||
else:
|
else:
|
||||||
system_users_granted = [s for s in v if s.protocol == 'rdp']
|
system_users_granted = [s for s in v if s.protocol in ['rdp', 'telnet']]
|
||||||
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)
|
||||||
|
@ -147,8 +150,9 @@ class UserGrantedNodeAssetsApi(ListAPIView):
|
||||||
user = get_object_or_404(User, id=user_id)
|
user = get_object_or_404(User, id=user_id)
|
||||||
else:
|
else:
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
util = AssetPermissionUtil(user)
|
||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
|
nodes = util.get_nodes_with_assets()
|
||||||
assets = nodes.get(node, [])
|
assets = nodes.get(node, [])
|
||||||
for asset, system_users in assets.items():
|
for asset, system_users in assets.items():
|
||||||
asset.system_users_granted = system_users
|
asset.system_users_granted = system_users
|
||||||
|
@ -172,7 +176,8 @@ class UserGroupGrantedAssetsApi(ListAPIView):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
assets = AssetPermissionUtil.get_user_group_assets(user_group)
|
util = AssetPermissionUtil(user_group)
|
||||||
|
assets = util.get_assets()
|
||||||
for k, v in assets.items():
|
for k, v in assets.items():
|
||||||
k.system_users_granted = v
|
k.system_users_granted = v
|
||||||
queryset.append(k)
|
queryset.append(k)
|
||||||
|
@ -189,7 +194,8 @@ class UserGroupGrantedNodesApi(ListAPIView):
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
group = get_object_or_404(UserGroup, id=group_id)
|
group = get_object_or_404(UserGroup, id=group_id)
|
||||||
nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(group)
|
util = AssetPermissionUtil(group)
|
||||||
|
nodes = util.get_nodes_with_assets()
|
||||||
return nodes.keys()
|
return nodes.keys()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -206,7 +212,8 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(user_group)
|
util = AssetPermissionUtil(user_group)
|
||||||
|
nodes = util.get_nodes_with_assets()
|
||||||
for node, _assets in nodes.items():
|
for node, _assets in nodes.items():
|
||||||
assets = _assets.keys()
|
assets = _assets.keys()
|
||||||
for asset, system_users in _assets.items():
|
for asset, system_users in _assets.items():
|
||||||
|
@ -226,7 +233,8 @@ class UserGroupGrantedNodeAssetsApi(ListAPIView):
|
||||||
|
|
||||||
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
user_group = get_object_or_404(UserGroup, id=user_group_id)
|
||||||
node = get_object_or_404(Node, id=node_id)
|
node = get_object_or_404(Node, id=node_id)
|
||||||
nodes = AssetPermissionUtil.get_user_group_nodes_with_assets(user_group)
|
util = AssetPermissionUtil(user_group)
|
||||||
|
nodes = util.get_nodes_with_assets()
|
||||||
assets = nodes.get(node, [])
|
assets = nodes.get(node, [])
|
||||||
for asset, system_users in assets.items():
|
for asset, system_users in assets.items():
|
||||||
asset.system_users_granted = system_users
|
asset.system_users_granted = system_users
|
||||||
|
@ -246,7 +254,8 @@ class ValidateUserAssetPermissionView(APIView):
|
||||||
asset = get_object_or_404(Asset, id=asset_id)
|
asset = get_object_or_404(Asset, id=asset_id)
|
||||||
system_user = get_object_or_404(SystemUser, id=system_id)
|
system_user = get_object_or_404(SystemUser, id=system_id)
|
||||||
|
|
||||||
assets_granted = AssetPermissionUtil.get_user_assets(user)
|
util = AssetPermissionUtil(user)
|
||||||
|
assets_granted = util.get_assets()
|
||||||
if system_user in assets_granted.get(asset, []):
|
if system_user in assets_granted.get(asset, []):
|
||||||
return Response({'msg': True}, status=200)
|
return Response({'msg': True}, status=200)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -7,13 +7,23 @@ from django.utils import timezone
|
||||||
from common.utils import date_expired_default, set_or_append_attr_bulk
|
from common.utils import date_expired_default, set_or_append_attr_bulk
|
||||||
|
|
||||||
|
|
||||||
class ValidManager(models.Manager):
|
class AssetPermissionQuerySet(models.QuerySet):
|
||||||
def get_queryset(self):
|
def active(self):
|
||||||
return super().get_queryset().filter(is_active=True) \
|
return self.filter(is_active=True)
|
||||||
.filter(date_start__lt=timezone.now())\
|
|
||||||
|
def valid(self):
|
||||||
|
return self.active().filter(date_start__lt=timezone.now())\
|
||||||
.filter(date_expired__gt=timezone.now())
|
.filter(date_expired__gt=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermissionManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return AssetPermissionQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
def valid(self):
|
||||||
|
return self.get_queryset().valid()
|
||||||
|
|
||||||
|
|
||||||
class AssetPermission(models.Model):
|
class AssetPermission(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
|
@ -23,14 +33,13 @@ class AssetPermission(models.Model):
|
||||||
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes"))
|
||||||
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
|
||||||
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
|
||||||
date_start = models.DateTimeField(default=timezone.now, verbose_name=_("Date start"))
|
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
|
||||||
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
|
date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired'))
|
||||||
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
|
created_by = models.CharField(max_length=128, blank=True, verbose_name=_('Created by'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
|
||||||
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
comment = models.TextField(verbose_name=_('Comment'), blank=True)
|
||||||
|
|
||||||
objects = models.Manager()
|
objects = AssetPermissionManager()
|
||||||
valid = ValidManager()
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -9,7 +9,7 @@ from common.fields import StringManyToManyField
|
||||||
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssetPermission
|
model = AssetPermission
|
||||||
exclude = ('id', 'created_by', 'date_created')
|
exclude = ('created_by', 'date_created')
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
class AssetPermissionListSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
|
<div class="form-group {% if form.date_expired.errors or form.date_start.errors %} has-error {% endif %}" id="date_5">
|
||||||
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{{ form.date_expired.label }}</label>
|
<label for="{{ form.date_expired.id_for_label }}" class="col-sm-2 control-label">{% trans 'Validity period' %}</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-daterange input-group" id="datepicker">
|
<div class="input-daterange input-group" id="datepicker">
|
||||||
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
<span class="input-group-addon"><i class="fa fa-calendar"></i></span>
|
||||||
|
|
|
@ -78,12 +78,12 @@ var zTree, table, show = 0;
|
||||||
function onSelected(event, treeNode) {
|
function onSelected(event, treeNode) {
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
var url = table.ajax.url();
|
var url = table.ajax.url();
|
||||||
if (treeNode.is_asset) {
|
if (treeNode.is_node) {
|
||||||
url = setUrlParam(url, 'node', "");
|
|
||||||
url = setUrlParam(url, 'asset', treeNode.id)
|
|
||||||
} else {
|
|
||||||
url = setUrlParam(url, 'asset', "");
|
url = setUrlParam(url, 'asset', "");
|
||||||
url = setUrlParam(url, 'node', treeNode.id)
|
url = setUrlParam(url, 'node', treeNode.id)
|
||||||
|
} else {
|
||||||
|
url = setUrlParam(url, 'node', "");
|
||||||
|
url = setUrlParam(url, 'asset', treeNode.id)
|
||||||
}
|
}
|
||||||
setCookie('node_selected', treeNode.id);
|
setCookie('node_selected', treeNode.id);
|
||||||
table.ajax.url(url);
|
table.ajax.url(url);
|
||||||
|
@ -113,14 +113,14 @@ function filter(treeId, parentNode, childNodes) {
|
||||||
$.each(childNodes, function (index, value) {
|
$.each(childNodes, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["name"] = value["value"];
|
value["name"] = value["value"];
|
||||||
value["isParent"] = value["assets_amount"] !== 0;
|
value["isParent"] = value["is_node"];
|
||||||
value["iconSkin"] = value["is_asset"] ? "file" : null;
|
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||||
});
|
});
|
||||||
return childNodes;
|
return childNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeAsync(treeId, treeNode) {
|
function beforeAsync(treeId, treeNode) {
|
||||||
return true;
|
return treeNode.is_node
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLabel(data) {
|
function makeLabel(data) {
|
||||||
|
@ -226,7 +226,7 @@ function initTree() {
|
||||||
},
|
},
|
||||||
async: {
|
async: {
|
||||||
enable: true,
|
enable: true,
|
||||||
url: "{% url 'api-assets:node-children-2' %}?assets=1",
|
url: "{% url 'api-assets:node-children-2' %}?assets=1&all=",
|
||||||
autoParam:["id", "name=n", "level=lv"],
|
autoParam:["id", "name=n", "level=lv"],
|
||||||
dataFilter: filter,
|
dataFilter: filter,
|
||||||
type: 'get'
|
type: 'get'
|
||||||
|
@ -238,18 +238,19 @@ function initTree() {
|
||||||
};
|
};
|
||||||
|
|
||||||
var zNodes = [];
|
var zNodes = [];
|
||||||
$.get("{% url 'api-assets:node-children-2' %}", function(data, status){
|
$.get("{% url 'api-assets:node-children-2' %}?assets=1&all=", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["pId"] = value["parent"];
|
value["pId"] = value["parent"];
|
||||||
value["isParent"] = value["assets_amount"] !== 0;
|
|
||||||
value["name"] = value["value"];
|
value["name"] = value["value"];
|
||||||
value["open"] = value["key"] === "0";
|
value["open"] = value["key"] === "0";
|
||||||
|
value["isParent"] = value["is_node"];
|
||||||
|
value["iconSkin"] = value["is_node"] ? null : 'file';
|
||||||
});
|
});
|
||||||
zNodes = data;
|
zNodes = data;
|
||||||
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
{#$.fn.zTree.init($("#assetTree"), setting);#}
|
||||||
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
$.fn.zTree.init($("#assetTree"), setting, zNodes);
|
||||||
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
zTree = $.fn.zTree.getZTreeObj("assetTree");
|
||||||
selectQueryNode();
|
{#selectQueryNode();#}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,10 +287,10 @@ $(document).ready(function(){
|
||||||
var _nodes = [];
|
var _nodes = [];
|
||||||
var _assets = [];
|
var _assets = [];
|
||||||
$.each(nodes, function (id, node) {
|
$.each(nodes, function (id, node) {
|
||||||
if (node.is_asset) {
|
if (node.is_node) {
|
||||||
_assets.push(node.id)
|
|
||||||
} else {
|
|
||||||
_nodes.push(node.id)
|
_nodes.push(node.id)
|
||||||
|
} else {
|
||||||
|
_assets.push(node.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
|
||||||
|
@ -303,6 +304,7 @@ $(document).ready(function(){
|
||||||
|
|
||||||
if (row.child.isShown()) {
|
if (row.child.isShown()) {
|
||||||
tr.removeClass('details');
|
tr.removeClass('details');
|
||||||
|
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
|
||||||
row.child.hide();
|
row.child.hide();
|
||||||
|
|
||||||
// Remove from the 'open' array
|
// Remove from the 'open' array
|
||||||
|
@ -310,7 +312,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tr.addClass('details');
|
tr.addClass('details');
|
||||||
$('.toggle i').removeClass('fa-angle-right').addClass('fa-angle-down');
|
$(this).children('i:first-child').removeClass('fa-angle-right').addClass('fa-angle-down');
|
||||||
row.child(format(row.data())).show();
|
row.child(format(row.data())).show();
|
||||||
// Add to the 'open' array
|
// Add to the 'open' array
|
||||||
if ( idx === -1 ) {
|
if ( idx === -1 ) {
|
||||||
|
|
|
@ -1,300 +1,160 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
import collections
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from django.utils import timezone
|
from django.db.models import Q
|
||||||
import copy
|
|
||||||
|
|
||||||
from common.utils import set_or_append_attr_bulk, get_logger
|
from common.utils import get_logger
|
||||||
from .models import AssetPermission
|
from .models import AssetPermission
|
||||||
|
from .hands import Node
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class AssetPermissionUtil:
|
class Tree:
|
||||||
|
def __init__(self):
|
||||||
|
self.__all_nodes = list(Node.objects.all().prefetch_related('assets'))
|
||||||
|
self.__node_asset_map = defaultdict(set)
|
||||||
|
self.nodes = defaultdict(dict)
|
||||||
|
self.root = Node.root()
|
||||||
|
self.init_node_asset_map()
|
||||||
|
|
||||||
@staticmethod
|
def init_node_asset_map(self):
|
||||||
def get_user_permissions(user):
|
for node in self.__all_nodes:
|
||||||
return AssetPermission.valid.all().filter(users=user)
|
assets = node.get_assets().values_list('id', flat=True)
|
||||||
|
for asset in assets:
|
||||||
|
self.__node_asset_map[str(asset)].add(node)
|
||||||
|
|
||||||
@staticmethod
|
def add_asset(self, asset, system_users):
|
||||||
def get_user_group_permissions(user_group):
|
nodes = self.__node_asset_map.get(str(asset.id), [])
|
||||||
return AssetPermission.valid.all().filter(user_groups=user_group)
|
self.add_nodes(nodes)
|
||||||
|
for node in nodes:
|
||||||
|
self.nodes[node][asset].update(system_users)
|
||||||
|
|
||||||
@staticmethod
|
def add_node(self, node):
|
||||||
def get_asset_permissions(asset):
|
if node in self.nodes:
|
||||||
return AssetPermission.valid.all().filter(assets=asset)
|
return
|
||||||
|
else:
|
||||||
|
self.nodes[node] = defaultdict(set)
|
||||||
|
if node.key == self.root.key:
|
||||||
|
return
|
||||||
|
parent_key = ':'.join(node.key.split(':')[:-1])
|
||||||
|
for n in self.__all_nodes:
|
||||||
|
if n.key == parent_key:
|
||||||
|
self.add_node(n)
|
||||||
|
break
|
||||||
|
|
||||||
@staticmethod
|
def add_nodes(self, nodes):
|
||||||
def get_node_permissions(node):
|
for node in nodes:
|
||||||
return AssetPermission.valid.all().filter(nodes=node)
|
self.add_node(node)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_system_user_permissions(system_user):
|
|
||||||
return AssetPermission.objects.all().filter(system_users=system_user)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_nodes(cls, group):
|
|
||||||
nodes = defaultdict(set)
|
|
||||||
permissions = cls.get_user_group_permissions(group)
|
|
||||||
for perm in permissions:
|
|
||||||
_nodes = perm.nodes.all()
|
|
||||||
_system_users = perm.system_users.all()
|
|
||||||
set_or_append_attr_bulk(_nodes, 'permission', perm.id)
|
|
||||||
for node in _nodes:
|
|
||||||
nodes[node].update(set(_system_users))
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_assets_direct(cls, group):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
permissions = cls.get_user_group_permissions(group)
|
|
||||||
for perm in permissions:
|
|
||||||
_assets = perm.assets.all()
|
|
||||||
_system_users = perm.system_users.all()
|
|
||||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
|
||||||
for asset in _assets:
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_nodes_assets(cls, group):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
nodes = cls.get_user_group_nodes(group)
|
|
||||||
for node, _system_users in nodes.items():
|
|
||||||
_assets = node.get_all_assets()
|
|
||||||
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
|
||||||
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
|
||||||
for asset in _assets:
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_assets(cls, group):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
_assets = cls.get_user_group_assets_direct(group)
|
|
||||||
_nodes_assets = cls.get_user_group_nodes_assets(group)
|
|
||||||
for asset, _system_users in _assets.items():
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
for asset, _system_users in _nodes_assets.items():
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_nodes_with_assets(cls, user):
|
|
||||||
"""
|
|
||||||
:param user:
|
|
||||||
:return: {node: {asset: set(su1, su2)}}
|
|
||||||
"""
|
|
||||||
nodes = defaultdict(dict)
|
|
||||||
_assets = cls.get_user_group_assets(user)
|
|
||||||
for asset, _system_users in _assets.items():
|
|
||||||
_nodes = asset.get_nodes()
|
|
||||||
for node in _nodes:
|
|
||||||
if asset in nodes[node]:
|
|
||||||
nodes[node][asset].update(_system_users)
|
|
||||||
else:
|
|
||||||
nodes[node][asset] = _system_users
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_assets_direct(cls, user):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
permissions = list(cls.get_user_permissions(user))
|
|
||||||
for perm in permissions:
|
|
||||||
_assets = perm.assets.all()
|
|
||||||
_system_users = perm.system_users.all()
|
|
||||||
set_or_append_attr_bulk(_assets, 'permission', perm.id)
|
|
||||||
for asset in _assets:
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_nodes_direct(cls, user):
|
|
||||||
nodes = defaultdict(set)
|
|
||||||
permissions = cls.get_user_permissions(user)
|
|
||||||
for perm in permissions:
|
|
||||||
_nodes = perm.nodes.all()
|
|
||||||
_system_users = perm.system_users.all()
|
|
||||||
set_or_append_attr_bulk(_nodes, 'permission', perm.id)
|
|
||||||
for node in _nodes:
|
|
||||||
nodes[node].update(set(_system_users))
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_nodes_assets_direct(cls, user):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
nodes = cls.get_user_nodes_direct(user)
|
|
||||||
for node, _system_users in nodes.items():
|
|
||||||
_assets = node.get_all_assets()
|
|
||||||
set_or_append_attr_bulk(_assets, 'inherit_node', node.id)
|
|
||||||
set_or_append_attr_bulk(_assets, 'permission', getattr(node, 'permission', None))
|
|
||||||
for asset in _assets:
|
|
||||||
assets[asset].update(set(_system_users))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_assets_inherit_group(cls, user):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
for group in user.groups.all():
|
|
||||||
_assets = cls.get_user_group_assets(group)
|
|
||||||
set_or_append_attr_bulk(_assets, 'inherit_group', group.id)
|
|
||||||
for asset, _system_users in _assets.items():
|
|
||||||
assets[asset].update(_system_users)
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_assets(cls, user):
|
|
||||||
assets = defaultdict(set)
|
|
||||||
_assets_direct = cls.get_user_assets_direct(user)
|
|
||||||
_nodes_assets_direct = cls.get_user_nodes_assets_direct(user)
|
|
||||||
_assets_inherit_group = cls.get_user_assets_inherit_group(user)
|
|
||||||
for asset, _system_users in _assets_direct.items():
|
|
||||||
assets[asset].update(_system_users)
|
|
||||||
for asset, _system_users in _nodes_assets_direct.items():
|
|
||||||
assets[asset].update(_system_users)
|
|
||||||
for asset, _system_users in _assets_inherit_group.items():
|
|
||||||
assets[asset].update(_system_users)
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_nodes_with_assets(cls, user):
|
|
||||||
"""
|
|
||||||
:param user:
|
|
||||||
:return: {node: {asset: set(su1, su2)}}
|
|
||||||
"""
|
|
||||||
nodes = defaultdict(dict)
|
|
||||||
_assets = cls.get_user_assets(user)
|
|
||||||
for asset, _system_users in _assets.items():
|
|
||||||
_nodes = asset.get_nodes()
|
|
||||||
for node in _nodes:
|
|
||||||
if asset in nodes[node]:
|
|
||||||
nodes[node][asset].update(_system_users)
|
|
||||||
else:
|
|
||||||
nodes[node][asset] = _system_users
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_system_user_assets(cls, system_user):
|
|
||||||
assets = set()
|
|
||||||
permissions = cls.get_system_user_permissions(system_user)
|
|
||||||
for perm in permissions:
|
|
||||||
assets.update(set(perm.assets.all()))
|
|
||||||
nodes = perm.nodes.all()
|
|
||||||
for node in nodes:
|
|
||||||
assets.update(set(node.get_all_assets()))
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_node_system_users(cls, node):
|
|
||||||
system_users = set()
|
|
||||||
permissions = cls.get_node_permissions(node)
|
|
||||||
for perm in permissions:
|
|
||||||
system_users.update(perm.system_users.all())
|
|
||||||
return system_users
|
|
||||||
|
|
||||||
|
|
||||||
# Abandon
|
def get_user_permissions(user, include_group=True):
|
||||||
class NodePermissionUtil:
|
if include_group:
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_user_group_permissions(user_group):
|
|
||||||
return user_group.nodepermission_set.all() \
|
|
||||||
.filter(is_active=True) \
|
|
||||||
.filter(date_expired__gt=timezone.now())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_system_user_permissions(system_user):
|
|
||||||
return system_user.nodepermission_set.all() \
|
|
||||||
.filter(is_active=True) \
|
|
||||||
.filter(date_expired__gt=timezone.now())
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_nodes(cls, user_group):
|
|
||||||
"""
|
|
||||||
获取用户组授权的node和系统用户
|
|
||||||
:param user_group:
|
|
||||||
:return: {"node": set(systemuser1, systemuser2), ..}
|
|
||||||
"""
|
|
||||||
permissions = cls.get_user_group_permissions(user_group)
|
|
||||||
nodes_directed = collections.defaultdict(set)
|
|
||||||
|
|
||||||
for perm in permissions:
|
|
||||||
nodes_directed[perm.node].add(perm.system_user)
|
|
||||||
|
|
||||||
nodes = copy.deepcopy(nodes_directed)
|
|
||||||
for node, system_users in nodes_directed.items():
|
|
||||||
for child in node.get_family():
|
|
||||||
nodes[child].update(system_users)
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_nodes_with_assets(cls, user_group):
|
|
||||||
"""
|
|
||||||
获取用户组授权的节点和系统用户,节点下带有资产
|
|
||||||
:param user_group:
|
|
||||||
:return: {"node": {"assets": "", "system_user": ""}, {}}
|
|
||||||
"""
|
|
||||||
nodes = cls.get_user_group_nodes(user_group)
|
|
||||||
nodes_with_assets = dict()
|
|
||||||
for node, system_users in nodes.items():
|
|
||||||
nodes_with_assets[node] = {
|
|
||||||
'assets': node.get_active_assets(),
|
|
||||||
'system_users': system_users
|
|
||||||
}
|
|
||||||
return nodes_with_assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_group_assets(cls, user_group):
|
|
||||||
assets = collections.defaultdict(set)
|
|
||||||
permissions = cls.get_user_group_permissions(user_group)
|
|
||||||
|
|
||||||
for perm in permissions:
|
|
||||||
for asset in perm.node.get_all_assets():
|
|
||||||
assets[asset].add(perm.system_user)
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_nodes(cls, user):
|
|
||||||
nodes = collections.defaultdict(set)
|
|
||||||
groups = user.groups.all()
|
groups = user.groups.all()
|
||||||
for group in groups:
|
arg = Q(users=user) | Q(user_groups__in=groups)
|
||||||
group_nodes = cls.get_user_group_nodes(group)
|
else:
|
||||||
for node, system_users in group_nodes.items():
|
arg = Q(users=user)
|
||||||
nodes[node].update(system_users)
|
return AssetPermission.objects.all().valid().filter(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_group_permissions(user_group):
|
||||||
|
return AssetPermission.objects.all().valid().filter(
|
||||||
|
user_groups=user_group
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_permissions(asset, include_node=True):
|
||||||
|
if include_node:
|
||||||
|
nodes = asset.get_all_nodes(flat=True)
|
||||||
|
arg = Q(assets=asset) | Q(nodes__in=nodes)
|
||||||
|
else:
|
||||||
|
arg = Q(assets=asset)
|
||||||
|
return AssetPermission.objects.all().valid().filter(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_node_permissions(node):
|
||||||
|
return AssetPermission.objects.all().valid().filter(nodes=node)
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_user_permissions(system_user):
|
||||||
|
return AssetPermission.objects.valid().all().filter(
|
||||||
|
system_users=system_user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AssetPermissionUtil:
|
||||||
|
get_permissions_map = {
|
||||||
|
"User": get_user_permissions,
|
||||||
|
"UserGroup": get_user_group_permissions,
|
||||||
|
"Asset": get_asset_permissions,
|
||||||
|
"Node": get_node_permissions,
|
||||||
|
"SystemUser": get_node_permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
self.object = obj
|
||||||
|
self._permissions = None
|
||||||
|
self._assets = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permissions(self):
|
||||||
|
if self._permissions:
|
||||||
|
return self._permissions
|
||||||
|
object_cls = self.object.__class__.__name__
|
||||||
|
func = self.get_permissions_map[object_cls]
|
||||||
|
permissions = func(self.object)
|
||||||
|
self._permissions = permissions
|
||||||
|
return permissions
|
||||||
|
|
||||||
|
def get_nodes_direct(self):
|
||||||
|
"""
|
||||||
|
返回用户/组授权规则直接关联的节点
|
||||||
|
:return: {node1: set(system_user1,)}
|
||||||
|
"""
|
||||||
|
nodes = defaultdict(set)
|
||||||
|
permissions = self.permissions.prefetch_related('nodes', 'system_users')
|
||||||
|
for perm in permissions:
|
||||||
|
for node in perm.nodes.all():
|
||||||
|
nodes[node].update(perm.system_users.all())
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
@classmethod
|
def get_assets_direct(self):
|
||||||
def get_user_nodes_with_assets(cls, user):
|
"""
|
||||||
nodes = cls.get_user_nodes(user)
|
返回用户授权规则直接关联的资产
|
||||||
nodes_with_assets = dict()
|
:return: {asset1: set(system_user1,)}
|
||||||
for node, system_users in nodes.items():
|
"""
|
||||||
nodes_with_assets[node] = {
|
assets = defaultdict(set)
|
||||||
'assets': node.get_active_assets(),
|
permissions = self.permissions.prefetch_related('assets', 'system_users')
|
||||||
'system_users': system_users
|
|
||||||
}
|
|
||||||
return nodes_with_assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_user_assets(cls, user):
|
|
||||||
assets = collections.defaultdict(set)
|
|
||||||
nodes_with_assets = cls.get_user_nodes_with_assets(user)
|
|
||||||
|
|
||||||
for v in nodes_with_assets.values():
|
|
||||||
for asset in v['assets']:
|
|
||||||
assets[asset].update(v['system_users'])
|
|
||||||
return assets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_system_user_assets(cls, system_user):
|
|
||||||
assets = set()
|
|
||||||
permissions = cls.get_system_user_permissions(system_user)
|
|
||||||
|
|
||||||
for perm in permissions:
|
for perm in permissions:
|
||||||
assets.update(perm.node.get_all_assets())
|
for asset in perm.assets.all().valid().prefetch_related('nodes'):
|
||||||
|
assets[asset].update(perm.system_users.all())
|
||||||
return assets
|
return assets
|
||||||
|
|
||||||
|
def get_assets(self):
|
||||||
|
if self._assets:
|
||||||
|
return self._assets
|
||||||
|
assets = self.get_assets_direct()
|
||||||
|
nodes = self.get_nodes_direct()
|
||||||
|
for node, system_users in nodes.items():
|
||||||
|
_assets = node.get_all_assets().valid().prefetch_related('nodes')
|
||||||
|
for asset in _assets:
|
||||||
|
if isinstance(asset, Node):
|
||||||
|
print(_assets)
|
||||||
|
assets[asset].update(system_users)
|
||||||
|
self._assets = assets
|
||||||
|
return self._assets
|
||||||
|
|
||||||
|
def get_nodes_with_assets(self):
|
||||||
|
"""
|
||||||
|
返回节点并且包含资产
|
||||||
|
{"node": {"assets": set("system_user")}}
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
assets = self.get_assets()
|
||||||
|
tree = Tree()
|
||||||
|
for asset, system_users in assets.items():
|
||||||
|
tree.add_asset(asset, system_users)
|
||||||
|
return tree.nodes
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
|
||||||
|
|
||||||
if nodes_id:
|
if nodes_id:
|
||||||
nodes_id = nodes_id.split(",")
|
nodes_id = nodes_id.split(",")
|
||||||
nodes = Node.objects.filter(id__in=nodes_id)
|
nodes = Node.objects.filter(id__in=nodes_id).exclude(id=Node.root().id)
|
||||||
form['nodes'].initial = nodes
|
form['nodes'].initial = nodes
|
||||||
if assets_id:
|
if assets_id:
|
||||||
assets_id = assets_id.split(",")
|
assets_id = assets_id.split(",")
|
||||||
|
|
|
@ -198,7 +198,8 @@ function objectDelete(obj, name, url, redirectTo) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var fail = function() {
|
var fail = function() {
|
||||||
swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
|
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
|
||||||
|
swal("错误", "[ "+name+" ]"+"正在被资产使用中,请先解除资产绑定", "error");
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -272,7 +273,7 @@ jumpserver.initDataTable = function (options) {
|
||||||
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
|
$(td).html('<input type="checkbox" class="text-center ipt_check" id=99991937>'.replace('99991937', cellData));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{className: 'text-center', targets: '_all'}
|
{className: 'text-center', render: $.fn.dataTable.render.text(), targets: '_all'}
|
||||||
];
|
];
|
||||||
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
|
||||||
var select = {
|
var select = {
|
||||||
|
@ -609,3 +610,91 @@ function setUrlParam(url, name, value) {
|
||||||
}
|
}
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 校验密码-改变规则颜色
|
||||||
|
function checkPasswordRules(password, minLength) {
|
||||||
|
if (wordMinLength(password, minLength)) {
|
||||||
|
$('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', 'green')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#rule_SECURITY_PASSWORD_MIN_LENGTH').css('color', '#908a8a')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordUpperCase(password)) {
|
||||||
|
$('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', 'green');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#rule_SECURITY_PASSWORD_UPPER_CASE').css('color', '#908a8a')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordLowerCase(password)) {
|
||||||
|
$('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', 'green')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#rule_SECURITY_PASSWORD_LOWER_CASE').css('color', '#908a8a')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordNumber(password)) {
|
||||||
|
$('#rule_SECURITY_PASSWORD_NUMBER').css('color', 'green')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#rule_SECURITY_PASSWORD_NUMBER').css('color', '#908a8a')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordSpecialChar(password)) {
|
||||||
|
$('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', 'green')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#rule_SECURITY_PASSWORD_SPECIAL_CHAR').css('color', '#908a8a')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最小长度
|
||||||
|
function wordMinLength(word, minLength) {
|
||||||
|
//var minLength = {{ min_length }};
|
||||||
|
var re = new RegExp("^(.{" + minLength + ",})$");
|
||||||
|
return word.match(re)
|
||||||
|
}
|
||||||
|
// 大写字母
|
||||||
|
function wordUpperCase(word) {
|
||||||
|
return word.match(/([A-Z]+)/)
|
||||||
|
}
|
||||||
|
// 小写字母
|
||||||
|
function wordLowerCase(word) {
|
||||||
|
return word.match(/([a-z]+)/)
|
||||||
|
}
|
||||||
|
// 数字字符
|
||||||
|
function wordNumber(word) {
|
||||||
|
return word.match(/([\d]+)/)
|
||||||
|
}
|
||||||
|
// 特殊字符
|
||||||
|
function wordSpecialChar(word) {
|
||||||
|
return word.match(/[`,~,!,@,#,\$,%,\^,&,\*,\(,\),\-,_,=,\+,\{,\},\[,\],\|,\\,;,',:,",\,,\.,<,>,\/,\?]+/)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示弹窗密码规则
|
||||||
|
function popoverPasswordRules(password_check_rules, $el) {
|
||||||
|
var message = "";
|
||||||
|
jQuery.each(password_check_rules, function (idx, rules) {
|
||||||
|
message += "<li id=" + rules.id + " style='list-style-type:none;'> <i class='fa fa-check-circle-o' style='margin-right:10px;' ></i>" + rules.label + "</li>";
|
||||||
|
});
|
||||||
|
//$('#id_password_rules').html(message);
|
||||||
|
$el.html(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化弹窗popover
|
||||||
|
function initPopover($container, $progress, $idPassword, $el, password_check_rules){
|
||||||
|
options = {};
|
||||||
|
// User Interface
|
||||||
|
options.ui = {
|
||||||
|
container: $container,
|
||||||
|
viewports: {
|
||||||
|
progress: $progress
|
||||||
|
//errors: $('.popover-content')
|
||||||
|
},
|
||||||
|
showProgressbar: true,
|
||||||
|
showVerdictsInsideProgressBar: true
|
||||||
|
};
|
||||||
|
$idPassword.pwstrength(options);
|
||||||
|
popoverPasswordRules(password_check_rules, $el);
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,976 @@
|
||||||
|
/*!
|
||||||
|
* jQuery Password Strength plugin for Twitter Bootstrap
|
||||||
|
* Version: 2.2.1
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008-2013 Tane Piper
|
||||||
|
* Copyright (c) 2013 Alejandro Blanco
|
||||||
|
* Dual licensed under the MIT and GPL licenses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (jQuery) {
|
||||||
|
// Source: src/i18n.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var i18n = {};
|
||||||
|
|
||||||
|
(function (i18n, i18next) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
i18n.fallback = {
|
||||||
|
"wordMinLength": "Your password is too short",
|
||||||
|
"wordMaxLength": "Your password is too long",
|
||||||
|
"wordInvalidChar": "Your password contains an invalid character",
|
||||||
|
"wordNotEmail": "Do not use your email as your password",
|
||||||
|
"wordSimilarToUsername": "Your password cannot contain your username",
|
||||||
|
"wordTwoCharacterClasses": "Use different character classes",
|
||||||
|
"wordRepetitions": "Too many repetitions",
|
||||||
|
"wordSequences": "Your password contains sequences",
|
||||||
|
"errorList": "Errors:",
|
||||||
|
"veryWeak": "Very Weak",
|
||||||
|
"weak": "Weak",
|
||||||
|
"normal": "Normal",
|
||||||
|
"medium": "Medium",
|
||||||
|
"strong": "Strong",
|
||||||
|
"veryStrong": "Very Strong"
|
||||||
|
};
|
||||||
|
|
||||||
|
i18n.t = function (key) {
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
// Try to use i18next.com
|
||||||
|
if (i18next) {
|
||||||
|
result = i18next.t(key);
|
||||||
|
} else {
|
||||||
|
// Fallback to english
|
||||||
|
result = i18n.fallback[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result === key ? '' : result;
|
||||||
|
};
|
||||||
|
}(i18n, window.i18next));
|
||||||
|
|
||||||
|
// Source: src/rules.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var rulesEngine = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!jQuery && module && module.exports) {
|
||||||
|
var jQuery = require("jquery"),
|
||||||
|
jsdom = require("jsdom").jsdom;
|
||||||
|
jQuery = jQuery(jsdom().defaultView);
|
||||||
|
}
|
||||||
|
} catch (ignore) {}
|
||||||
|
|
||||||
|
(function ($, rulesEngine) {
|
||||||
|
"use strict";
|
||||||
|
var validation = {};
|
||||||
|
|
||||||
|
rulesEngine.forbiddenSequences = [
|
||||||
|
"0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
|
||||||
|
"zxcvbnm", "!@#$%^&*()_+"
|
||||||
|
];
|
||||||
|
|
||||||
|
validation.wordNotEmail = function (options, word, score) {
|
||||||
|
if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordMinLength = function (options, word, score) {
|
||||||
|
var wordlen = word.length,
|
||||||
|
lenScore = Math.pow(wordlen, options.rules.raisePower);
|
||||||
|
if (wordlen < options.common.minChar) {
|
||||||
|
lenScore = (lenScore + score);
|
||||||
|
}
|
||||||
|
return lenScore;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordMaxLength = function (options, word, score) {
|
||||||
|
var wordlen = word.length,
|
||||||
|
lenScore = Math.pow(wordlen, options.rules.raisePower);
|
||||||
|
if (wordlen > options.common.maxChar) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return lenScore;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordInvalidChar = function (options, word, score) {
|
||||||
|
if (options.common.invalidCharsRegExp.test(word)) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordMinLengthStaticScore = function (options, word, score) {
|
||||||
|
return word.length < options.common.minChar ? 0 : score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordMaxLengthStaticScore = function (options, word, score) {
|
||||||
|
return word.length > options.common.maxChar ? 0 : score;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
validation.wordSimilarToUsername = function (options, word, score) {
|
||||||
|
var username = $(options.common.usernameField).val();
|
||||||
|
if (username && word.toLowerCase().match(username.replace(/[\-\[\]\/\{\}\(\)\*\+\=\?\:\.\\\^\$\|\!\,]/g, "\\$&").toLowerCase())) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordTwoCharacterClasses = function (options, word, score) {
|
||||||
|
if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
|
||||||
|
(word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
|
||||||
|
(word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordRepetitions = function (options, word, score) {
|
||||||
|
if (word.match(/(.)\1\1/)) { return score; }
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordSequences = function (options, word, score) {
|
||||||
|
var found = false,
|
||||||
|
j;
|
||||||
|
if (word.length > 2) {
|
||||||
|
$.each(rulesEngine.forbiddenSequences, function (idx, seq) {
|
||||||
|
if (found) { return; }
|
||||||
|
var sequences = [seq, seq.split('').reverse().join('')];
|
||||||
|
$.each(sequences, function (idx, sequence) {
|
||||||
|
for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
|
||||||
|
if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (found) { return score; }
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordLowercase = function (options, word, score) {
|
||||||
|
return word.match(/[a-z]/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordUppercase = function (options, word, score) {
|
||||||
|
return word.match(/[A-Z]/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordOneNumber = function (options, word, score) {
|
||||||
|
return word.match(/\d+/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordThreeNumbers = function (options, word, score) {
|
||||||
|
return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordOneSpecialChar = function (options, word, score) {
|
||||||
|
return word.match(/[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordTwoSpecialChar = function (options, word, score) {
|
||||||
|
return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordUpperLowerCombo = function (options, word, score) {
|
||||||
|
return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordLetterNumberCombo = function (options, word, score) {
|
||||||
|
return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordLetterNumberCharCombo = function (options, word, score) {
|
||||||
|
return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
|
||||||
|
};
|
||||||
|
|
||||||
|
validation.wordIsACommonPassword = function (options, word, score) {
|
||||||
|
if ($.inArray(word, options.rules.commonPasswords) >= 0) {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
rulesEngine.validation = validation;
|
||||||
|
|
||||||
|
rulesEngine.executeRules = function (options, word) {
|
||||||
|
var totalScore = 0;
|
||||||
|
|
||||||
|
$.each(options.rules.activated, function (rule, active) {
|
||||||
|
if (active) {
|
||||||
|
var score = options.rules.scores[rule],
|
||||||
|
funct = rulesEngine.validation[rule],
|
||||||
|
result,
|
||||||
|
errorMessage;
|
||||||
|
|
||||||
|
if (!$.isFunction(funct)) {
|
||||||
|
funct = options.rules.extra[rule];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($.isFunction(funct)) {
|
||||||
|
result = funct(options, word, score);
|
||||||
|
if (result) {
|
||||||
|
totalScore += result;
|
||||||
|
}
|
||||||
|
if (result < 0 || (!$.isNumeric(result) && !result)) {
|
||||||
|
errorMessage = options.ui.spanError(options, rule);
|
||||||
|
if (errorMessage.length > 0) {
|
||||||
|
options.instances.errors.push(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return totalScore;
|
||||||
|
};
|
||||||
|
}(jQuery, rulesEngine));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (module && module.exports) {
|
||||||
|
module.exports = rulesEngine;
|
||||||
|
}
|
||||||
|
} catch (ignore) {}
|
||||||
|
|
||||||
|
// Source: src/options.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var defaultOptions = {};
|
||||||
|
|
||||||
|
defaultOptions.common = {};
|
||||||
|
defaultOptions.common.minChar = 6;
|
||||||
|
defaultOptions.common.maxChar = 20;
|
||||||
|
defaultOptions.common.usernameField = "#username";
|
||||||
|
defaultOptions.common.invalidCharsRegExp = new RegExp(/[\s,'"]/);
|
||||||
|
defaultOptions.common.userInputs = [
|
||||||
|
// Selectors for input fields with user input
|
||||||
|
];
|
||||||
|
defaultOptions.common.onLoad = undefined;
|
||||||
|
defaultOptions.common.onKeyUp = undefined;
|
||||||
|
defaultOptions.common.onScore = undefined;
|
||||||
|
defaultOptions.common.zxcvbn = false;
|
||||||
|
defaultOptions.common.zxcvbnTerms = [
|
||||||
|
// List of disrecommended words
|
||||||
|
];
|
||||||
|
defaultOptions.common.events = ["keyup", "change", "paste"];
|
||||||
|
defaultOptions.common.debug = false;
|
||||||
|
|
||||||
|
defaultOptions.rules = {};
|
||||||
|
defaultOptions.rules.extra = {};
|
||||||
|
defaultOptions.rules.scores = {
|
||||||
|
wordNotEmail: -100,
|
||||||
|
wordMinLength: -50,
|
||||||
|
wordMaxLength: -50,
|
||||||
|
wordInvalidChar: -100,
|
||||||
|
wordSimilarToUsername: -100,
|
||||||
|
wordSequences: -20,
|
||||||
|
wordTwoCharacterClasses: 2,
|
||||||
|
wordRepetitions: -25,
|
||||||
|
wordLowercase: 1,
|
||||||
|
wordUppercase: 3,
|
||||||
|
wordOneNumber: 3,
|
||||||
|
wordThreeNumbers: 5,
|
||||||
|
wordOneSpecialChar: 3,
|
||||||
|
wordTwoSpecialChar: 5,
|
||||||
|
wordUpperLowerCombo: 2,
|
||||||
|
wordLetterNumberCombo: 2,
|
||||||
|
wordLetterNumberCharCombo: 2,
|
||||||
|
wordIsACommonPassword: -100
|
||||||
|
};
|
||||||
|
defaultOptions.rules.activated = {
|
||||||
|
wordNotEmail: true,
|
||||||
|
wordMinLength: true,
|
||||||
|
wordMaxLength: false,
|
||||||
|
wordInvalidChar: false,
|
||||||
|
wordSimilarToUsername: true,
|
||||||
|
wordSequences: true,
|
||||||
|
wordTwoCharacterClasses: true,
|
||||||
|
wordRepetitions: true,
|
||||||
|
wordLowercase: true,
|
||||||
|
wordUppercase: true,
|
||||||
|
wordOneNumber: true,
|
||||||
|
wordThreeNumbers: true,
|
||||||
|
wordOneSpecialChar: true,
|
||||||
|
wordTwoSpecialChar: true,
|
||||||
|
wordUpperLowerCombo: true,
|
||||||
|
wordLetterNumberCombo: true,
|
||||||
|
wordLetterNumberCharCombo: true,
|
||||||
|
wordIsACommonPassword: true
|
||||||
|
};
|
||||||
|
defaultOptions.rules.raisePower = 1.4;
|
||||||
|
// List taken from https://github.com/danielmiessler/SecLists (MIT License)
|
||||||
|
defaultOptions.rules.commonPasswords = [
|
||||||
|
'123456',
|
||||||
|
'password',
|
||||||
|
'12345678',
|
||||||
|
'qwerty',
|
||||||
|
'123456789',
|
||||||
|
'12345',
|
||||||
|
'1234',
|
||||||
|
'111111',
|
||||||
|
'1234567',
|
||||||
|
'dragon',
|
||||||
|
'123123',
|
||||||
|
'baseball',
|
||||||
|
'abc123',
|
||||||
|
'football',
|
||||||
|
'monkey',
|
||||||
|
'letmein',
|
||||||
|
'696969',
|
||||||
|
'shadow',
|
||||||
|
'master',
|
||||||
|
'666666',
|
||||||
|
'qwertyuiop',
|
||||||
|
'123321',
|
||||||
|
'mustang',
|
||||||
|
'1234567890',
|
||||||
|
'michael',
|
||||||
|
'654321',
|
||||||
|
'pussy',
|
||||||
|
'superman',
|
||||||
|
'1qaz2wsx',
|
||||||
|
'7777777',
|
||||||
|
'fuckyou',
|
||||||
|
'121212',
|
||||||
|
'000000',
|
||||||
|
'qazwsx',
|
||||||
|
'123qwe',
|
||||||
|
'killer',
|
||||||
|
'trustno1',
|
||||||
|
'jordan',
|
||||||
|
'jennifer',
|
||||||
|
'zxcvbnm',
|
||||||
|
'asdfgh',
|
||||||
|
'hunter',
|
||||||
|
'buster',
|
||||||
|
'soccer',
|
||||||
|
'harley',
|
||||||
|
'batman',
|
||||||
|
'andrew',
|
||||||
|
'tigger',
|
||||||
|
'sunshine',
|
||||||
|
'iloveyou',
|
||||||
|
'fuckme',
|
||||||
|
'2000',
|
||||||
|
'charlie',
|
||||||
|
'robert',
|
||||||
|
'thomas',
|
||||||
|
'hockey',
|
||||||
|
'ranger',
|
||||||
|
'daniel',
|
||||||
|
'starwars',
|
||||||
|
'klaster',
|
||||||
|
'112233',
|
||||||
|
'george',
|
||||||
|
'asshole',
|
||||||
|
'computer',
|
||||||
|
'michelle',
|
||||||
|
'jessica',
|
||||||
|
'pepper',
|
||||||
|
'1111',
|
||||||
|
'zxcvbn',
|
||||||
|
'555555',
|
||||||
|
'11111111',
|
||||||
|
'131313',
|
||||||
|
'freedom',
|
||||||
|
'777777',
|
||||||
|
'pass',
|
||||||
|
'fuck',
|
||||||
|
'maggie',
|
||||||
|
'159753',
|
||||||
|
'aaaaaa',
|
||||||
|
'ginger',
|
||||||
|
'princess',
|
||||||
|
'joshua',
|
||||||
|
'cheese',
|
||||||
|
'amanda',
|
||||||
|
'summer',
|
||||||
|
'love',
|
||||||
|
'ashley',
|
||||||
|
'6969',
|
||||||
|
'nicole',
|
||||||
|
'chelsea',
|
||||||
|
'biteme',
|
||||||
|
'matthew',
|
||||||
|
'access',
|
||||||
|
'yankees',
|
||||||
|
'987654321',
|
||||||
|
'dallas',
|
||||||
|
'austin',
|
||||||
|
'thunder',
|
||||||
|
'taylor',
|
||||||
|
'matrix'
|
||||||
|
];
|
||||||
|
|
||||||
|
defaultOptions.ui = {};
|
||||||
|
defaultOptions.ui.bootstrap2 = false;
|
||||||
|
defaultOptions.ui.bootstrap4 = false;
|
||||||
|
defaultOptions.ui.colorClasses = [
|
||||||
|
"danger", "danger", "danger", "warning", "warning", "success"
|
||||||
|
];
|
||||||
|
defaultOptions.ui.showProgressBar = true;
|
||||||
|
defaultOptions.ui.progressBarEmptyPercentage = 1;
|
||||||
|
defaultOptions.ui.progressBarMinPercentage = 1;
|
||||||
|
defaultOptions.ui.progressExtraCssClasses = '';
|
||||||
|
defaultOptions.ui.progressBarExtraCssClasses = '';
|
||||||
|
defaultOptions.ui.showPopover = false;
|
||||||
|
defaultOptions.ui.popoverPlacement = "bottom";
|
||||||
|
defaultOptions.ui.showStatus = false;
|
||||||
|
defaultOptions.ui.spanError = function (options, key) {
|
||||||
|
"use strict";
|
||||||
|
var text = options.i18n.t(key);
|
||||||
|
if (!text) { return ''; }
|
||||||
|
return '<span style="color: #d52929">' + text + '</span>';
|
||||||
|
};
|
||||||
|
defaultOptions.ui.popoverError = function (options) {
|
||||||
|
"use strict";
|
||||||
|
var errors = options.instances.errors,
|
||||||
|
errorsTitle = options.i18n.t("errorList"),
|
||||||
|
message = "<div>" + errorsTitle + "<ul class='error-list' style='margin-bottom: 0;'>";
|
||||||
|
|
||||||
|
jQuery.each(errors, function (idx, err) {
|
||||||
|
message += "<li>" + err + "</li>";
|
||||||
|
});
|
||||||
|
message += "</ul></div>";
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
defaultOptions.ui.showVerdicts = true;
|
||||||
|
defaultOptions.ui.showVerdictsInsideProgressBar = false;
|
||||||
|
defaultOptions.ui.useVerdictCssClass = false;
|
||||||
|
defaultOptions.ui.showErrors = false;
|
||||||
|
defaultOptions.ui.showScore = false;
|
||||||
|
defaultOptions.ui.container = undefined;
|
||||||
|
defaultOptions.ui.viewports = {
|
||||||
|
progress: undefined,
|
||||||
|
verdict: undefined,
|
||||||
|
errors: undefined,
|
||||||
|
score: undefined
|
||||||
|
};
|
||||||
|
defaultOptions.ui.scores = [0, 14, 26, 38, 50];
|
||||||
|
|
||||||
|
defaultOptions.i18n = {};
|
||||||
|
defaultOptions.i18n.t = i18n.t;
|
||||||
|
|
||||||
|
// Source: src/ui.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var ui = {};
|
||||||
|
|
||||||
|
(function ($, ui) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var statusClasses = ["error", "warning", "success"],
|
||||||
|
verdictKeys = [
|
||||||
|
"veryWeak", "weak", "normal", "medium", "strong", "veryStrong"
|
||||||
|
];
|
||||||
|
|
||||||
|
ui.getContainer = function (options, $el) {
|
||||||
|
var $container;
|
||||||
|
|
||||||
|
$container = $(options.ui.container);
|
||||||
|
if (!($container && $container.length === 1)) {
|
||||||
|
$container = $el.parent();
|
||||||
|
}
|
||||||
|
return $container;
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.findElement = function ($container, viewport, cssSelector) {
|
||||||
|
if (viewport) {
|
||||||
|
return $container.find(viewport).find(cssSelector);
|
||||||
|
}
|
||||||
|
return $container.find(cssSelector);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.getUIElements = function (options, $el) {
|
||||||
|
var $container, result;
|
||||||
|
|
||||||
|
if (options.instances.viewports) {
|
||||||
|
return options.instances.viewports;
|
||||||
|
}
|
||||||
|
|
||||||
|
$container = ui.getContainer(options, $el);
|
||||||
|
|
||||||
|
result = {};
|
||||||
|
result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
|
||||||
|
if (options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
result.$verdict = result.$progressbar.find("span.password-verdict");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.ui.showPopover) {
|
||||||
|
if (!options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
|
||||||
|
}
|
||||||
|
result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
|
||||||
|
}
|
||||||
|
result.$score = ui.findElement($container, options.ui.viewports.score,
|
||||||
|
"span.password-score");
|
||||||
|
|
||||||
|
options.instances.viewports = result;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initProgressBar = function (options, $el) {
|
||||||
|
var $container = ui.getContainer(options, $el),
|
||||||
|
progressbar = "<div class='progress ";
|
||||||
|
|
||||||
|
if (options.ui.bootstrap2) {
|
||||||
|
// Boostrap 2
|
||||||
|
progressbar += options.ui.progressBarExtraCssClasses +
|
||||||
|
"'><div class='";
|
||||||
|
} else {
|
||||||
|
// Bootstrap 3 & 4
|
||||||
|
progressbar += options.ui.progressExtraCssClasses + "'><div class='" +
|
||||||
|
options.ui.progressBarExtraCssClasses + " progress-";
|
||||||
|
}
|
||||||
|
progressbar += "bar'>";
|
||||||
|
|
||||||
|
if (options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
progressbar += "<span class='password-verdict'></span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
progressbar += "</div></div>";
|
||||||
|
|
||||||
|
if (options.ui.viewports.progress) {
|
||||||
|
$container.find(options.ui.viewports.progress).append(progressbar);
|
||||||
|
} else {
|
||||||
|
$(progressbar).insertAfter($el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initHelper = function (options, $el, html, viewport) {
|
||||||
|
var $container = ui.getContainer(options, $el);
|
||||||
|
if (viewport) {
|
||||||
|
$container.find(viewport).append(html);
|
||||||
|
} else {
|
||||||
|
$(html).insertAfter($el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initVerdict = function (options, $el) {
|
||||||
|
ui.initHelper(options, $el, "<span class='password-verdict'></span>",
|
||||||
|
options.ui.viewports.verdict);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initErrorList = function (options, $el) {
|
||||||
|
ui.initHelper(options, $el, "<ul class='error-list'></ul >",
|
||||||
|
options.ui.viewports.errors);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initScore = function (options, $el) {
|
||||||
|
ui.initHelper(options, $el, "<span class='password-score'></span>",
|
||||||
|
options.ui.viewports.score);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initPopover = function (options, $el) {
|
||||||
|
$el.popover("destroy");
|
||||||
|
$el.popover({
|
||||||
|
html: true,
|
||||||
|
placement: options.ui.popoverPlacement,
|
||||||
|
trigger: "manual",
|
||||||
|
content: " "
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.initUI = function (options, $el) {
|
||||||
|
if (options.ui.showPopover) {
|
||||||
|
ui.initPopover(options, $el);
|
||||||
|
} else {
|
||||||
|
if (options.ui.showErrors) { ui.initErrorList(options, $el); }
|
||||||
|
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
ui.initVerdict(options, $el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.ui.showProgressBar) {
|
||||||
|
ui.initProgressBar(options, $el);
|
||||||
|
}
|
||||||
|
if (options.ui.showScore) {
|
||||||
|
ui.initScore(options, $el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateProgressBar = function (options, $el, cssClass, percentage) {
|
||||||
|
var $progressbar = ui.getUIElements(options, $el).$progressbar,
|
||||||
|
$bar = $progressbar.find(".progress-bar"),
|
||||||
|
cssPrefix = "progress-";
|
||||||
|
|
||||||
|
if (options.ui.bootstrap2) {
|
||||||
|
$bar = $progressbar.find(".bar");
|
||||||
|
cssPrefix = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(options.ui.colorClasses, function (idx, value) {
|
||||||
|
if (options.ui.bootstrap4) {
|
||||||
|
$bar.removeClass("bg-" + value);
|
||||||
|
} else {
|
||||||
|
$bar.removeClass(cssPrefix + "bar-" + value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (options.ui.bootstrap4) {
|
||||||
|
$bar.addClass("bg-" + options.ui.colorClasses[cssClass]);
|
||||||
|
} else {
|
||||||
|
$bar.addClass(cssPrefix + "bar-" + options.ui.colorClasses[cssClass]);
|
||||||
|
}
|
||||||
|
$bar.css("width", percentage + '%');
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateVerdict = function (options, $el, cssClass, text) {
|
||||||
|
var $verdict = ui.getUIElements(options, $el).$verdict;
|
||||||
|
$verdict.removeClass(options.ui.colorClasses.join(' '));
|
||||||
|
if (cssClass > -1) {
|
||||||
|
$verdict.addClass(options.ui.colorClasses[cssClass]);
|
||||||
|
}
|
||||||
|
if (options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
$verdict.css('white-space', 'nowrap');
|
||||||
|
}
|
||||||
|
$verdict.html(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateErrors = function (options, $el, remove) {
|
||||||
|
var $errors = ui.getUIElements(options, $el).$errors,
|
||||||
|
html = "";
|
||||||
|
|
||||||
|
if (!remove) {
|
||||||
|
$.each(options.instances.errors, function (idx, err) {
|
||||||
|
html += "<li style='list-style-type:none;'>" + err + "</li>";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$errors.html(html);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateScore = function (options, $el, score, remove) {
|
||||||
|
var $score = ui.getUIElements(options, $el).$score,
|
||||||
|
html = "";
|
||||||
|
|
||||||
|
if (!remove) { html = score.toFixed(2); }
|
||||||
|
$score.html(html);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updatePopover = function (options, $el, verdictText, remove) {
|
||||||
|
var popover = $el.data("bs.popover"),
|
||||||
|
html = "",
|
||||||
|
hide = true;
|
||||||
|
|
||||||
|
if (options.ui.showVerdicts &&
|
||||||
|
!options.ui.showVerdictsInsideProgressBar &&
|
||||||
|
verdictText.length > 0) {
|
||||||
|
html = "<h5><span class='password-verdict'>" + verdictText +
|
||||||
|
"</span></h5>";
|
||||||
|
hide = false;
|
||||||
|
}
|
||||||
|
if (options.ui.showErrors) {
|
||||||
|
if (options.instances.errors.length > 0) {
|
||||||
|
hide = false;
|
||||||
|
}
|
||||||
|
html += options.ui.popoverError(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hide || remove) {
|
||||||
|
$el.popover("hide");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ui.bootstrap2) { popover = $el.data("popover"); }
|
||||||
|
|
||||||
|
if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
|
||||||
|
$el.find("+ .popover .popover-content").html(html);
|
||||||
|
} else {
|
||||||
|
// It's hidden
|
||||||
|
popover.options.content = html;
|
||||||
|
$el.popover("show");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateFieldStatus = function (options, $el, cssClass, remove) {
|
||||||
|
var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
|
||||||
|
$container = $el.parents(targetClass).first();
|
||||||
|
|
||||||
|
$.each(statusClasses, function (idx, css) {
|
||||||
|
if (!options.ui.bootstrap2) { css = "has-" + css; }
|
||||||
|
$container.removeClass(css);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (remove) { return; }
|
||||||
|
|
||||||
|
cssClass = statusClasses[Math.floor(cssClass / 2)];
|
||||||
|
if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
|
||||||
|
$container.addClass(cssClass);
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.percentage = function (options, score, maximun) {
|
||||||
|
var result = Math.floor(100 * score / maximun),
|
||||||
|
min = options.ui.progressBarMinPercentage;
|
||||||
|
|
||||||
|
result = result <= min ? min : result;
|
||||||
|
result = result > 100 ? 100 : result;
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.getVerdictAndCssClass = function (options, score) {
|
||||||
|
var level, verdict;
|
||||||
|
|
||||||
|
if (score === undefined) { return ['', 0]; }
|
||||||
|
|
||||||
|
if (score <= options.ui.scores[0]) {
|
||||||
|
level = 0;
|
||||||
|
} else if (score < options.ui.scores[1]) {
|
||||||
|
level = 1;
|
||||||
|
} else if (score < options.ui.scores[2]) {
|
||||||
|
level = 2;
|
||||||
|
} else if (score < options.ui.scores[3]) {
|
||||||
|
level = 3;
|
||||||
|
} else if (score < options.ui.scores[4]) {
|
||||||
|
level = 4;
|
||||||
|
} else {
|
||||||
|
level = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
verdict = verdictKeys[level];
|
||||||
|
|
||||||
|
return [options.i18n.t(verdict), level];
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.updateUI = function (options, $el, score) {
|
||||||
|
var cssClass, barPercentage, verdictText, verdictCssClass;
|
||||||
|
|
||||||
|
cssClass = ui.getVerdictAndCssClass(options, score);
|
||||||
|
verdictText = score === 0 ? '' : cssClass[0];
|
||||||
|
cssClass = cssClass[1];
|
||||||
|
verdictCssClass = options.ui.useVerdictCssClass ? cssClass : -1;
|
||||||
|
|
||||||
|
if (options.ui.showProgressBar) {
|
||||||
|
if (score === undefined) {
|
||||||
|
barPercentage = options.ui.progressBarEmptyPercentage;
|
||||||
|
} else {
|
||||||
|
barPercentage = ui.percentage(options, score, options.ui.scores[4]);
|
||||||
|
}
|
||||||
|
ui.updateProgressBar(options, $el, cssClass, barPercentage);
|
||||||
|
if (options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
ui.updateVerdict(options, $el, verdictCssClass, verdictText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ui.showStatus) {
|
||||||
|
ui.updateFieldStatus(options, $el, cssClass, score === undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ui.showPopover) {
|
||||||
|
ui.updatePopover(options, $el, verdictText, score === undefined);
|
||||||
|
} else {
|
||||||
|
if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
|
||||||
|
ui.updateVerdict(options, $el, verdictCssClass, verdictText);
|
||||||
|
}
|
||||||
|
if (options.ui.showErrors) {
|
||||||
|
ui.updateErrors(options, $el, score === undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ui.showScore) {
|
||||||
|
ui.updateScore(options, $el, score, score === undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}(jQuery, ui));
|
||||||
|
|
||||||
|
// Source: src/methods.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var methods = {};
|
||||||
|
|
||||||
|
(function ($, methods) {
|
||||||
|
"use strict";
|
||||||
|
var onKeyUp, onPaste, applyToAll;
|
||||||
|
|
||||||
|
onKeyUp = function (event) {
|
||||||
|
var $el = $(event.target),
|
||||||
|
options = $el.data("pwstrength-bootstrap"),
|
||||||
|
word = $el.val(),
|
||||||
|
userInputs,
|
||||||
|
verdictText,
|
||||||
|
verdictLevel,
|
||||||
|
score;
|
||||||
|
|
||||||
|
if (options === undefined) { return; }
|
||||||
|
|
||||||
|
options.instances.errors = [];
|
||||||
|
if (word.length === 0) {
|
||||||
|
score = undefined;
|
||||||
|
} else {
|
||||||
|
if (options.common.zxcvbn) {
|
||||||
|
userInputs = [];
|
||||||
|
$.each(options.common.userInputs.concat([options.common.usernameField]), function (idx, selector) {
|
||||||
|
var value = $(selector).val();
|
||||||
|
if (value) { userInputs.push(value); }
|
||||||
|
});
|
||||||
|
userInputs = userInputs.concat(options.common.zxcvbnTerms);
|
||||||
|
score = zxcvbn(word, userInputs).guesses;
|
||||||
|
score = Math.log(score) * Math.LOG2E;
|
||||||
|
} else {
|
||||||
|
score = rulesEngine.executeRules(options, word);
|
||||||
|
}
|
||||||
|
if ($.isFunction(options.common.onScore)) {
|
||||||
|
score = options.common.onScore(options, word, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.updateUI(options, $el, score);
|
||||||
|
verdictText = ui.getVerdictAndCssClass(options, score);
|
||||||
|
verdictLevel = verdictText[1];
|
||||||
|
verdictText = verdictText[0];
|
||||||
|
|
||||||
|
if (options.common.debug) {
|
||||||
|
console.log(score + ' - ' + verdictText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($.isFunction(options.common.onKeyUp)) {
|
||||||
|
options.common.onKeyUp(event, {
|
||||||
|
score: score,
|
||||||
|
verdictText: verdictText,
|
||||||
|
verdictLevel: verdictLevel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onPaste = function (event) {
|
||||||
|
// This handler is necessary because the paste event fires before the
|
||||||
|
// content is actually in the input, so we cannot read its value right
|
||||||
|
// away. Therefore, the timeouts.
|
||||||
|
var $el = $(event.target),
|
||||||
|
word = $el.val(),
|
||||||
|
tries = 0,
|
||||||
|
callback;
|
||||||
|
|
||||||
|
callback = function () {
|
||||||
|
var newWord = $el.val();
|
||||||
|
|
||||||
|
if (newWord !== word) {
|
||||||
|
onKeyUp(event);
|
||||||
|
} else if (tries < 3) {
|
||||||
|
tries += 1;
|
||||||
|
setTimeout(callback, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(callback, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.init = function (settings) {
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
// Make it deep extend (first param) so it extends also the
|
||||||
|
// rules and other inside objects
|
||||||
|
var clonedDefaults = $.extend(true, {}, defaultOptions),
|
||||||
|
localOptions = $.extend(true, clonedDefaults, settings),
|
||||||
|
$el = $(el);
|
||||||
|
|
||||||
|
localOptions.instances = {};
|
||||||
|
$el.data("pwstrength-bootstrap", localOptions);
|
||||||
|
|
||||||
|
$.each(localOptions.common.events, function (idx, eventName) {
|
||||||
|
var handler = eventName === "paste" ? onPaste : onKeyUp;
|
||||||
|
$el.on(eventName, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.initUI(localOptions, $el);
|
||||||
|
$el.trigger("keyup");
|
||||||
|
|
||||||
|
if ($.isFunction(localOptions.common.onLoad)) {
|
||||||
|
localOptions.common.onLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.destroy = function () {
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
var $el = $(el),
|
||||||
|
options = $el.data("pwstrength-bootstrap"),
|
||||||
|
elements = ui.getUIElements(options, $el);
|
||||||
|
elements.$progressbar.remove();
|
||||||
|
elements.$verdict.remove();
|
||||||
|
elements.$errors.remove();
|
||||||
|
$el.removeData("pwstrength-bootstrap");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.forceUpdate = function () {
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
var event = { target: el };
|
||||||
|
onKeyUp(event);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.addRule = function (name, method, score, active) {
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
var options = $(el).data("pwstrength-bootstrap");
|
||||||
|
|
||||||
|
options.rules.activated[name] = active;
|
||||||
|
options.rules.scores[name] = score;
|
||||||
|
options.rules.extra[name] = method;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
applyToAll = function (rule, prop, value) {
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
$(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.changeScore = function (rule, score) {
|
||||||
|
applyToAll.call(this, rule, "scores", score);
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.ruleActive = function (rule, active) {
|
||||||
|
applyToAll.call(this, rule, "activated", active);
|
||||||
|
};
|
||||||
|
|
||||||
|
methods.ruleIsMet = function (rule) {
|
||||||
|
if ($.isFunction(rulesEngine.validation[rule])) {
|
||||||
|
if (rule === "wordMinLength") {
|
||||||
|
rule = "wordMinLengthStaticScore";
|
||||||
|
} else if (rule === "wordMaxLength") {
|
||||||
|
rule = "wordMaxLengthStaticScore";
|
||||||
|
}
|
||||||
|
|
||||||
|
var rulesMetCnt = 0;
|
||||||
|
|
||||||
|
this.each(function (idx, el) {
|
||||||
|
var options = $(el).data("pwstrength-bootstrap");
|
||||||
|
|
||||||
|
rulesMetCnt += rulesEngine.validation[rule](options, $(el).val(), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (rulesMetCnt === this.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.error("Rule " + rule + " does not exist on jQuery.pwstrength-bootstrap.validation");
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.pwstrength = function (method) {
|
||||||
|
var result;
|
||||||
|
|
||||||
|
if (methods[method]) {
|
||||||
|
result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
|
||||||
|
} else if (typeof method === "object" || !method) {
|
||||||
|
result = methods.init.apply(this, arguments);
|
||||||
|
} else {
|
||||||
|
$.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}(jQuery, methods));
|
||||||
|
}(jQuery));
|
|
@ -5,6 +5,7 @@
|
||||||
{% block custom_head_css_js %}
|
{% block custom_head_css_js %}
|
||||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
||||||
{% block custom_head_css_js_create %} {% endblock %}
|
{% block custom_head_css_js_create %} {% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="footer fixed">
|
<div class="footer fixed">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
Version <strong>1.2.1-{% include '_build.html' %}</strong> GPLv2.
|
Version <strong>1.3.2-{% include '_build.html' %}</strong> GPLv2.
|
||||||
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
|
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Copyright</strong> 北京堆栈科技有限公司 © 2014-2018
|
<strong>Copyright</strong> 北京堆栈科技有限公司 © 2014-2018
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.core.cache import cache
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
|
from django.http.response import HttpResponseRedirectBase
|
||||||
from django.http import HttpResponseNotFound
|
from django.http import HttpResponseNotFound
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \
|
||||||
SessionSerializer, TaskSerializer, ReplaySerializer
|
SessionSerializer, TaskSerializer, ReplaySerializer
|
||||||
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
from .hands import IsSuperUserOrAppUser, IsAppUser, \
|
||||||
IsSuperUserOrAppUserOrUserReadonly
|
IsSuperUserOrAppUserOrUserReadonly
|
||||||
from .backends import get_command_store, get_multi_command_store, \
|
from .backends import get_command_storage, get_multi_command_storage, \
|
||||||
SessionCommandSerializer
|
SessionCommandSerializer
|
||||||
|
|
||||||
logger = logging.getLogger(__file__)
|
logger = logging.getLogger(__file__)
|
||||||
|
@ -108,7 +109,9 @@ class StatusViewSet(viewsets.ModelViewSet):
|
||||||
task_serializer_class = TaskSerializer
|
task_serializer_class = TaskSerializer
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
self.handle_sessions()
|
from_gua = self.request.query_params.get("from_guacamole", None)
|
||||||
|
if not from_gua:
|
||||||
|
self.handle_sessions()
|
||||||
super().create(request, *args, **kwargs)
|
super().create(request, *args, **kwargs)
|
||||||
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
|
||||||
serializer = self.task_serializer_class(tasks, many=True)
|
serializer = self.task_serializer_class(tasks, many=True)
|
||||||
|
@ -224,8 +227,8 @@ class CommandViewSet(viewsets.ViewSet):
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
command_store = get_command_store()
|
command_store = get_command_storage()
|
||||||
multi_command_storage = get_multi_command_store()
|
multi_command_storage = get_multi_command_storage()
|
||||||
serializer_class = SessionCommandSerializer
|
serializer_class = SessionCommandSerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
|
|
||||||
|
@ -255,10 +258,35 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
||||||
serializer_class = ReplaySerializer
|
serializer_class = ReplaySerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
session = None
|
session = None
|
||||||
|
upload_to = 'replay' # 仅添加到本地存储中
|
||||||
|
|
||||||
def gen_session_path(self):
|
def get_session_path(self, version=2):
|
||||||
|
"""
|
||||||
|
获取session日志的文件路径
|
||||||
|
:param version: 原来后缀是 .gz,为了统一新版本改为 .replay.gz
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
suffix = '.replay.gz'
|
||||||
|
if version == 1:
|
||||||
|
suffix = '.gz'
|
||||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
date = self.session.date_start.strftime('%Y-%m-%d')
|
||||||
return os.path.join(date, str(self.session.id) + '.gz')
|
return os.path.join(date, str(self.session.id) + suffix)
|
||||||
|
|
||||||
|
def get_local_path(self, version=2):
|
||||||
|
session_path = self.get_session_path(version=version)
|
||||||
|
if version == 2:
|
||||||
|
local_path = os.path.join(self.upload_to, session_path)
|
||||||
|
else:
|
||||||
|
local_path = session_path
|
||||||
|
return local_path
|
||||||
|
|
||||||
|
def save_to_storage(self, f):
|
||||||
|
local_path = self.get_local_path()
|
||||||
|
try:
|
||||||
|
name = default_storage.save(local_path, f)
|
||||||
|
return name, None
|
||||||
|
except OSError as e:
|
||||||
|
return None, e
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
session_id = kwargs.get('pk')
|
session_id = kwargs.get('pk')
|
||||||
|
@ -267,91 +295,64 @@ class SessionReplayViewSet(viewsets.ViewSet):
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
file = serializer.validated_data['file']
|
file = serializer.validated_data['file']
|
||||||
file_path = self.gen_session_path()
|
name, err = self.save_to_storage(file)
|
||||||
try:
|
if not name:
|
||||||
default_storage.save(file_path, file)
|
msg = "Failed save replay `{}`: {}".format(session_id, err)
|
||||||
return Response({'url': default_storage.url(file_path)},
|
logger.error(msg)
|
||||||
status=201)
|
return Response({'msg': str(err)}, status=400)
|
||||||
except IOError:
|
url = default_storage.url(name)
|
||||||
return Response("Save error", status=500)
|
return Response({'url': url}, status=201)
|
||||||
else:
|
else:
|
||||||
logger.error(
|
msg = 'Upload data invalid: {}'.format(serializer.errors)
|
||||||
'Update load data invalid: {}'.format(serializer.errors))
|
logger.error(msg)
|
||||||
return Response({'msg': serializer.errors}, status=401)
|
return Response({'msg': serializer.errors}, status=401)
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
session_id = kwargs.get('pk')
|
session_id = kwargs.get('pk')
|
||||||
self.session = get_object_or_404(Session, id=session_id)
|
self.session = get_object_or_404(Session, id=session_id)
|
||||||
path = self.gen_session_path()
|
# 新版本和老版本的文件后缀不同
|
||||||
|
session_path = self.get_session_path() # 存在外部存储上的路径
|
||||||
|
local_path = self.get_local_path()
|
||||||
|
local_path_v1 = self.get_local_path(version=1)
|
||||||
|
|
||||||
if default_storage.exists(path):
|
# 去default storage中查找
|
||||||
url = default_storage.url(path)
|
for _local_path in (local_path, local_path_v1, session_path):
|
||||||
return redirect(url)
|
if default_storage.exists(_local_path):
|
||||||
else:
|
url = default_storage.url(_local_path)
|
||||||
configs = settings.TERMINAL_REPLAY_STORAGE.items()
|
return redirect(url)
|
||||||
if not configs:
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
for name, config in configs:
|
# 去定义的外部storage查找
|
||||||
client = jms_storage.init(config)
|
configs = settings.TERMINAL_REPLAY_STORAGE
|
||||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
configs = {k: v for k, v in configs.items() if v['TYPE'] != 'server'}
|
||||||
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
|
if not configs:
|
||||||
target_path = default_storage.base_location + '/' + path
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
if client and client.has_file(file_path) and \
|
target_path = os.path.join(default_storage.base_location, local_path) # 保存到storage的路径
|
||||||
client.download_file(file_path, target_path):
|
target_dir = os.path.dirname(target_path)
|
||||||
return redirect(default_storage.url(path))
|
if not os.path.isdir(target_dir):
|
||||||
return HttpResponseNotFound()
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
storage = jms_storage.get_multi_object_storage(configs)
|
||||||
|
ok, err = storage.download(session_path, target_path)
|
||||||
|
if not ok:
|
||||||
|
logger.error("Failed download replay file: {}".format(err))
|
||||||
|
return HttpResponseNotFound()
|
||||||
|
return redirect(default_storage.url(local_path))
|
||||||
|
|
||||||
|
|
||||||
class SessionReplayV2ViewSet(viewsets.ViewSet):
|
class SessionReplayV2ViewSet(SessionReplayViewSet):
|
||||||
serializer_class = ReplaySerializer
|
serializer_class = ReplaySerializer
|
||||||
permission_classes = (IsSuperUserOrAppUser,)
|
permission_classes = (IsSuperUserOrAppUser,)
|
||||||
session = None
|
session = None
|
||||||
|
|
||||||
def gen_session_path(self):
|
|
||||||
date = self.session.date_start.strftime('%Y-%m-%d')
|
|
||||||
replay = {
|
|
||||||
"id": self.session.id,
|
|
||||||
# "width": 100,
|
|
||||||
# "heith": 100
|
|
||||||
}
|
|
||||||
if self.session.protocol == "ssh":
|
|
||||||
replay['type'] = "json"
|
|
||||||
replay['path'] = os.path.join(date, str(self.session.id) + '.gz')
|
|
||||||
return replay
|
|
||||||
elif self.session.protocol == "rdp":
|
|
||||||
replay['type'] = "mp4"
|
|
||||||
replay['path'] = os.path.join(date, str(self.session.id) + '.mp4')
|
|
||||||
return replay
|
|
||||||
else:
|
|
||||||
return replay
|
|
||||||
|
|
||||||
def retrieve(self, request, *args, **kwargs):
|
def retrieve(self, request, *args, **kwargs):
|
||||||
session_id = kwargs.get('pk')
|
response = super().retrieve(request, *args, **kwargs)
|
||||||
self.session = get_object_or_404(Session, id=session_id)
|
data = {
|
||||||
replay = self.gen_session_path()
|
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
|
||||||
|
'src': '',
|
||||||
if replay.get("path", "") == "":
|
}
|
||||||
return HttpResponseNotFound()
|
if isinstance(response, HttpResponseRedirectBase):
|
||||||
|
data['src'] = response.url
|
||||||
if default_storage.exists(replay["path"]):
|
return Response(data)
|
||||||
replay["src"] = default_storage.url(replay["path"])
|
|
||||||
return Response(replay)
|
|
||||||
else:
|
|
||||||
configs = settings.TERMINAL_REPLAY_STORAGE.items()
|
|
||||||
if not configs:
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
for name, config in configs:
|
|
||||||
client = jms_storage.init(config)
|
|
||||||
|
|
||||||
target_path = default_storage.base_location + '/' + replay["path"]
|
|
||||||
|
|
||||||
if client and client.has_file(replay["path"]) and \
|
|
||||||
client.download_file(replay["path"], target_path):
|
|
||||||
replay["src"] = default_storage.url(replay["path"])
|
|
||||||
return Response(replay)
|
|
||||||
return HttpResponseNotFound()
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_command_store():
|
def get_command_storage():
|
||||||
params = settings.COMMAND_STORAGE
|
config = settings.COMMAND_STORAGE
|
||||||
engine_class = import_module(params['ENGINE'])
|
engine_class = import_module(config['ENGINE'])
|
||||||
storage = engine_class.CommandStore(params)
|
storage = engine_class.CommandStore(config)
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
|
||||||
def get_terminal_command_store():
|
def get_terminal_command_storages():
|
||||||
storage_list = {}
|
storage_list = {}
|
||||||
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
|
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
|
||||||
tp = params['TYPE']
|
tp = params['TYPE']
|
||||||
if tp == 'server':
|
if tp == 'server':
|
||||||
storage = get_command_store()
|
storage = get_command_storage()
|
||||||
else:
|
else:
|
||||||
if not TYPE_ENGINE_MAPPING.get(tp):
|
if not TYPE_ENGINE_MAPPING.get(tp):
|
||||||
continue
|
continue
|
||||||
|
@ -29,9 +29,9 @@ def get_terminal_command_store():
|
||||||
return storage_list
|
return storage_list
|
||||||
|
|
||||||
|
|
||||||
def get_multi_command_store():
|
def get_multi_command_storage():
|
||||||
from .command.multi import CommandStore
|
from .command.multi import CommandStore
|
||||||
storage_list = get_terminal_command_store().values()
|
storage_list = get_terminal_command_storages().values()
|
||||||
storage = CommandStore(storage_list)
|
storage = CommandStore(storage_list)
|
||||||
return storage
|
return storage
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,22 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from jms_es_sdk import ESStore
|
from jms_storage.es import ESStorage
|
||||||
from .base import CommandBase
|
from .base import CommandBase
|
||||||
from .models import AbstractSessionCommand
|
from .models import AbstractSessionCommand
|
||||||
|
|
||||||
|
|
||||||
class CommandStore(CommandBase, ESStore):
|
class CommandStore(ESStorage, CommandBase):
|
||||||
def __init__(self, params):
|
def __init__(self, params):
|
||||||
hosts = params.get('HOSTS', ['http://localhost'])
|
super().__init__(params)
|
||||||
ESStore.__init__(self, hosts=hosts)
|
|
||||||
|
|
||||||
def save(self, command):
|
|
||||||
return ESStore.save(self, command)
|
|
||||||
|
|
||||||
def bulk_save(self, commands):
|
|
||||||
return ESStore.bulk_save(self, commands)
|
|
||||||
|
|
||||||
def filter(self, date_from=None, date_to=None,
|
def filter(self, date_from=None, date_to=None,
|
||||||
user=None, asset=None, system_user=None,
|
user=None, asset=None, system_user=None,
|
||||||
input=None, session=None):
|
input=None, session=None):
|
||||||
|
|
||||||
data = ESStore.filter(
|
data = super().filter(date_from=date_from, date_to=date_to,
|
||||||
self, date_from=date_from, date_to=date_to,
|
user=user, asset=asset, system_user=system_user,
|
||||||
user=user, asset=asset, system_user=system_user,
|
input=input, session=session)
|
||||||
input=input, session=session
|
|
||||||
)
|
|
||||||
return AbstractSessionCommand.from_multi_dict(
|
return AbstractSessionCommand.from_multi_dict(
|
||||||
[item["_source"] for item in data["hits"] if item]
|
[item["_source"] for item in data["hits"] if item]
|
||||||
)
|
)
|
||||||
|
|
||||||
def count(self, date_from=None, date_to=None,
|
|
||||||
user=None, asset=None, system_user=None,
|
|
||||||
input=None, session=None):
|
|
||||||
amount = ESStore.count(
|
|
||||||
self, date_from=date_from, date_to=date_to,
|
|
||||||
user=user, asset=asset, system_user=system_user,
|
|
||||||
input=input, session=session
|
|
||||||
)
|
|
||||||
return amount
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework_bulk.serializers import BulkListSerializer
|
||||||
from common.mixins import BulkSerializerMixin
|
from common.mixins import BulkSerializerMixin
|
||||||
from common.utils import get_object_or_none
|
from common.utils import get_object_or_none
|
||||||
from .models import Terminal, Status, Session, Task
|
from .models import Terminal, Status, Session, Task
|
||||||
from .backends import get_multi_command_store
|
from .backends import get_multi_command_storage
|
||||||
|
|
||||||
|
|
||||||
class TerminalSerializer(serializers.ModelSerializer):
|
class TerminalSerializer(serializers.ModelSerializer):
|
||||||
|
@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class SessionSerializer(serializers.ModelSerializer):
|
class SessionSerializer(serializers.ModelSerializer):
|
||||||
command_amount = serializers.SerializerMethodField()
|
command_amount = serializers.SerializerMethodField()
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Session
|
model = Session
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
<td>{% trans 'Replay session' %}:</td>
|
<td>{% trans 'Replay session' %}:</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
<button type="button" onclick="window.open('/luna/replay/{{ object.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Go' %}</button>
|
<button type="button" onclick="window.open('/luna/replay/{{ object.id }}','luna', 'height=600, width=800, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-primary btn-xs" id="btn_reset_password" style="width: 54px">{% trans 'Go' %}</button>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
<th class="text-center">{% trans 'System user' %}</th>
|
<th class="text-center">{% trans 'System user' %}</th>
|
||||||
<th class="text-center">{% trans 'Remote addr' %}</th>
|
<th class="text-center">{% trans 'Remote addr' %}</th>
|
||||||
<th class="text-center">{% trans 'Protocol' %}</th>
|
<th class="text-center">{% trans 'Protocol' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Login from' %}</th>
|
||||||
<th class="text-center">{% trans 'Command' %}</th>
|
<th class="text-center">{% trans 'Command' %}</th>
|
||||||
<th class="text-center">{% trans 'Date start' %}</th>
|
<th class="text-center">{% trans 'Date start' %}</th>
|
||||||
{# <th class="text-center">{% trans 'Date last active' %}</th>#}
|
{# <th class="text-center">{% trans 'Date last active' %}</th>#}
|
||||||
|
@ -92,6 +93,7 @@
|
||||||
<td class="text-center">{{ session.system_user }}</td>
|
<td class="text-center">{{ session.system_user }}</td>
|
||||||
<td class="text-center">{{ session.remote_addr|default:"" }}</td>
|
<td class="text-center">{{ session.remote_addr|default:"" }}</td>
|
||||||
<td class="text-center">{{ session.protocol }}</td>
|
<td class="text-center">{{ session.protocol }}</td>
|
||||||
|
<td class="text-center">{{ session.get_login_from_display }}</td>
|
||||||
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
|
<td class="text-center">{{ session.id | get_session_command_amount }}</td>
|
||||||
|
|
||||||
<td class="text-center">{{ session.date_start }}</td>
|
<td class="text-center">{{ session.date_start }}</td>
|
||||||
|
@ -99,10 +101,14 @@
|
||||||
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if session.is_finished %}
|
{% if session.is_finished %}
|
||||||
<a onclick="window.open('/luna/replay/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-replay" >{% trans "Replay" %}</a>
|
<a 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 %}
|
||||||
<!--<a onclick="window.open('/luna/monitor/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-monitor" >{% trans "Monitor" %}</a>-->
|
<!--<a onclick="window.open('/luna/monitor/{{ session.id }}','luna', 'height=600, width=800, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')" class="btn btn-xs btn-warning btn-monitor" >{% trans "Monitor" %}</a>-->
|
||||||
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
{% if session.protocol == 'rdp' %}
|
||||||
|
<a class="btn btn-xs btn-danger btn-term" disabled value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-xs btn-danger btn-term" value="{{ session.id }}" terminal="{{ session.terminal.id }}" >{% trans "Terminate" %}</a>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_storage
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
|
@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
|
||||||
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
|
||||||
from ..models import Command
|
from ..models import Command
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..backends import get_multi_command_store
|
from ..backends import get_multi_command_storage
|
||||||
|
|
||||||
__all__ = ['CommandListView']
|
__all__ = ['CommandListView']
|
||||||
common_storage = get_multi_command_store()
|
common_storage = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.conf import settings
|
||||||
from users.utils import AdminUserRequiredMixin
|
from users.utils import AdminUserRequiredMixin
|
||||||
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_store
|
from ..backends import get_multi_command_storage
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ __all__ = [
|
||||||
'SessionDetailView',
|
'SessionDetailView',
|
||||||
]
|
]
|
||||||
|
|
||||||
command_store = get_multi_command_store()
|
command_store = get_multi_command_storage()
|
||||||
|
|
||||||
|
|
||||||
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
|
||||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from rest_framework import generics
|
from rest_framework import generics
|
||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||||
|
@ -14,10 +15,11 @@ from .serializers import UserSerializer, UserGroupSerializer, \
|
||||||
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
|
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
|
||||||
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
|
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
|
||||||
from .tasks import write_login_log_async
|
from .tasks import write_login_log_async
|
||||||
from .models import User, UserGroup
|
from .models import User, UserGroup, LoginLog
|
||||||
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
|
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
|
||||||
IsSuperUserOrAppUser
|
IsSuperUserOrAppUser
|
||||||
from .utils import check_user_valid, generate_token, get_login_ip, check_otp_code
|
from .utils import check_user_valid, generate_token, get_login_ip, \
|
||||||
|
check_otp_code, set_user_login_failed_count_to_cache, is_block_login
|
||||||
from common.mixins import IDInFilterMixin
|
from common.mixins import IDInFilterMixin
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
|
||||||
|
@ -128,16 +130,12 @@ class UserToken(APIView):
|
||||||
return Response({'error': msg}, status=406)
|
return Response({'error': msg}, status=406)
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(APIView):
|
class UserProfile(generics.RetrieveAPIView):
|
||||||
permission_classes = (IsValidUser,)
|
permission_classes = (IsAuthenticated,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
def get(self, request):
|
def get_object(self):
|
||||||
# return Response(request.user.to_json())
|
return self.request.user
|
||||||
return Response(self.serializer_class(request.user).data)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
return Response(self.serializer_class(request.user).data)
|
|
||||||
|
|
||||||
|
|
||||||
class UserOtpAuthApi(APIView):
|
class UserOtpAuthApi(APIView):
|
||||||
|
@ -153,10 +151,23 @@ class UserOtpAuthApi(APIView):
|
||||||
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
|
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
|
||||||
|
|
||||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||||
|
data = {
|
||||||
|
'username': user.username,
|
||||||
|
'mfa': int(user.otp_enabled),
|
||||||
|
'reason': LoginLog.REASON_MFA,
|
||||||
|
'status': False
|
||||||
|
}
|
||||||
|
self.write_login_log(request, data)
|
||||||
return Response({'msg': 'MFA认证失败'}, status=401)
|
return Response({'msg': 'MFA认证失败'}, status=401)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': user.username,
|
||||||
|
'mfa': int(user.otp_enabled),
|
||||||
|
'reason': LoginLog.REASON_NOTHING,
|
||||||
|
'status': True
|
||||||
|
}
|
||||||
|
self.write_login_log(request, data)
|
||||||
token = generate_token(request, user)
|
token = generate_token(request, user)
|
||||||
self.write_login_log(request, user)
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'token': token,
|
'token': token,
|
||||||
|
@ -165,7 +176,7 @@ class UserOtpAuthApi(APIView):
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_login_log(request, user):
|
def write_login_log(request, data):
|
||||||
login_ip = request.data.get('remote_addr', None)
|
login_ip = request.data.get('remote_addr', None)
|
||||||
login_type = request.data.get('login_type', '')
|
login_type = request.data.get('login_type', '')
|
||||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||||
|
@ -173,25 +184,52 @@ class UserOtpAuthApi(APIView):
|
||||||
if not login_ip:
|
if not login_ip:
|
||||||
login_ip = get_login_ip(request)
|
login_ip = get_login_ip(request)
|
||||||
|
|
||||||
write_login_log_async.delay(
|
tmp_data = {
|
||||||
user.username, ip=login_ip,
|
'ip': login_ip,
|
||||||
type=login_type, user_agent=user_agent,
|
'type': login_type,
|
||||||
)
|
'user_agent': user_agent
|
||||||
|
}
|
||||||
|
data.update(tmp_data)
|
||||||
|
write_login_log_async.delay(**data)
|
||||||
|
|
||||||
|
|
||||||
class UserAuthApi(APIView):
|
class UserAuthApi(APIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
user, msg = self.check_user_valid(request)
|
# limit login
|
||||||
|
username = request.data.get('username')
|
||||||
|
ip = request.data.get('remote_addr', None)
|
||||||
|
ip = ip if ip else get_login_ip(request)
|
||||||
|
key_limit = self.key_prefix_limit.format(ip, username)
|
||||||
|
if is_block_login(key_limit):
|
||||||
|
msg = _("Log in frequently and try again later")
|
||||||
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
|
user, msg = self.check_user_valid(request)
|
||||||
if not user:
|
if not user:
|
||||||
|
data = {
|
||||||
|
'username': request.data.get('username', ''),
|
||||||
|
'mfa': LoginLog.MFA_UNKNOWN,
|
||||||
|
'reason': LoginLog.REASON_PASSWORD,
|
||||||
|
'status': False
|
||||||
|
}
|
||||||
|
self.write_login_log(request, data)
|
||||||
|
|
||||||
|
set_user_login_failed_count_to_cache(key_limit)
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
if not user.otp_enabled:
|
if not user.otp_enabled:
|
||||||
|
data = {
|
||||||
|
'username': user.username,
|
||||||
|
'mfa': int(user.otp_enabled),
|
||||||
|
'reason': LoginLog.REASON_NOTHING,
|
||||||
|
'status': True
|
||||||
|
}
|
||||||
|
self.write_login_log(request, data)
|
||||||
token = generate_token(request, user)
|
token = generate_token(request, user)
|
||||||
self.write_login_log(request, user)
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'token': token,
|
'token': token,
|
||||||
|
@ -208,7 +246,8 @@ class UserAuthApi(APIView):
|
||||||
'otp_url': reverse('api-users:user-otp-auth'),
|
'otp_url': reverse('api-users:user-otp-auth'),
|
||||||
'seed': seed,
|
'seed': seed,
|
||||||
'user': self.serializer_class(user).data
|
'user': self.serializer_class(user).data
|
||||||
}, status=300)
|
}, status=300
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_user_valid(request):
|
def check_user_valid(request):
|
||||||
|
@ -222,7 +261,7 @@ class UserAuthApi(APIView):
|
||||||
return user, msg
|
return user, msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_login_log(request, user):
|
def write_login_log(request, data):
|
||||||
login_ip = request.data.get('remote_addr', None)
|
login_ip = request.data.get('remote_addr', None)
|
||||||
login_type = request.data.get('login_type', '')
|
login_type = request.data.get('login_type', '')
|
||||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||||
|
@ -230,10 +269,14 @@ class UserAuthApi(APIView):
|
||||||
if not login_ip:
|
if not login_ip:
|
||||||
login_ip = get_login_ip(request)
|
login_ip = get_login_ip(request)
|
||||||
|
|
||||||
write_login_log_async.delay(
|
tmp_data = {
|
||||||
user.username, ip=login_ip,
|
'ip': login_ip,
|
||||||
type=login_type, user_agent=user_agent,
|
'type': login_type,
|
||||||
)
|
'user_agent': user_agent,
|
||||||
|
}
|
||||||
|
data.update(tmp_data)
|
||||||
|
|
||||||
|
write_login_log_async.delay(**data)
|
||||||
|
|
||||||
|
|
||||||
class UserConnectionTokenApi(APIView):
|
class UserConnectionTokenApi(APIView):
|
||||||
|
|
|
@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm):
|
||||||
max_length=128, strip=False
|
max_length=128, strip=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def confirm_login_allowed(self, user):
|
||||||
|
if not user.is_staff:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
self.error_messages['inactive'],
|
||||||
|
code='inactive',)
|
||||||
|
|
||||||
class UserLoginCaptchaForm(AuthenticationForm):
|
|
||||||
username = forms.CharField(label=_('Username'), max_length=100)
|
class UserLoginCaptchaForm(UserLoginForm):
|
||||||
password = forms.CharField(
|
|
||||||
label=_('Password'), widget=forms.PasswordInput,
|
|
||||||
max_length=128, strip=False
|
|
||||||
)
|
|
||||||
captcha = CaptchaField()
|
captcha = CaptchaField()
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm):
|
||||||
'data-placeholder': _('Join user groups')
|
'data-placeholder': _('Join user groups')
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'otp_level': forms.RadioSelect()
|
'otp_level': forms.RadioSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def clean_public_key(self):
|
def clean_public_key(self):
|
||||||
|
|
|
@ -41,12 +41,40 @@ class LoginLog(models.Model):
|
||||||
('W', 'Web'),
|
('W', 'Web'),
|
||||||
('T', 'Terminal'),
|
('T', 'Terminal'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MFA_DISABLED = 0
|
||||||
|
MFA_ENABLED = 1
|
||||||
|
MFA_UNKNOWN = 2
|
||||||
|
|
||||||
|
MFA_CHOICE = (
|
||||||
|
(MFA_DISABLED, _('Disabled')),
|
||||||
|
(MFA_ENABLED, _('Enabled')),
|
||||||
|
(MFA_UNKNOWN, _('-')),
|
||||||
|
)
|
||||||
|
|
||||||
|
REASON_NOTHING = 0
|
||||||
|
REASON_PASSWORD = 1
|
||||||
|
REASON_MFA = 2
|
||||||
|
|
||||||
|
REASON_CHOICE = (
|
||||||
|
(REASON_NOTHING, _('-')),
|
||||||
|
(REASON_PASSWORD, _('Username/password check failed')),
|
||||||
|
(REASON_MFA, _('MFA authentication failed')),
|
||||||
|
)
|
||||||
|
|
||||||
|
STATUS_CHOICE = (
|
||||||
|
(True, _('Success')),
|
||||||
|
(False, _('Failed'))
|
||||||
|
)
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
username = models.CharField(max_length=20, verbose_name=_('Username'))
|
||||||
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
|
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
|
||||||
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
|
||||||
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
city = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('Login city'))
|
||||||
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
user_agent = models.CharField(max_length=254, blank=True, null=True, verbose_name=_('User agent'))
|
||||||
|
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
||||||
|
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
||||||
|
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||||
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -4,14 +4,12 @@ import uuid
|
||||||
from django.db import models, IntegrityError
|
from django.db import models, IntegrityError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.mixins import NoDeleteModelMixin
|
|
||||||
|
|
||||||
__all__ = ['UserGroup']
|
__all__ = ['UserGroup']
|
||||||
|
|
||||||
|
|
||||||
class UserGroup(NoDeleteModelMixin):
|
class UserGroup(models.Model):
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||||
verbose_name=_('Date created'))
|
verbose_name=_('Date created'))
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django.utils import timezone
|
||||||
from django.shortcuts import reverse
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
from common.utils import get_signer, date_expired_default
|
from common.utils import get_signer, date_expired_default
|
||||||
|
from common.models import Setting
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['User']
|
__all__ = ['User']
|
||||||
|
@ -35,6 +36,12 @@ class User(AbstractUser):
|
||||||
(1, _('Enable')),
|
(1, _('Enable')),
|
||||||
(2, _("Force enable")),
|
(2, _("Force enable")),
|
||||||
)
|
)
|
||||||
|
SOURCE_LOCAL = 'local'
|
||||||
|
SOURCE_LDAP = 'ldap'
|
||||||
|
SOURCE_CHOICES = (
|
||||||
|
(SOURCE_LOCAL, 'Local'),
|
||||||
|
(SOURCE_LDAP, 'LDAP/AD'),
|
||||||
|
)
|
||||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||||
username = models.CharField(
|
username = models.CharField(
|
||||||
max_length=128, unique=True, verbose_name=_('Username')
|
max_length=128, unique=True, verbose_name=_('Username')
|
||||||
|
@ -77,11 +84,15 @@ class User(AbstractUser):
|
||||||
is_first_login = models.BooleanField(default=True)
|
is_first_login = models.BooleanField(default=True)
|
||||||
date_expired = models.DateTimeField(
|
date_expired = models.DateTimeField(
|
||||||
default=date_expired_default, blank=True, null=True,
|
default=date_expired_default, blank=True, null=True,
|
||||||
verbose_name=_('Date expired')
|
db_index=True, verbose_name=_('Date expired')
|
||||||
)
|
)
|
||||||
created_by = models.CharField(
|
created_by = models.CharField(
|
||||||
max_length=30, default='', verbose_name=_('Created by')
|
max_length=30, default='', verbose_name=_('Created by')
|
||||||
)
|
)
|
||||||
|
source = models.CharField(
|
||||||
|
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
|
||||||
|
verbose_name=_('Source')
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{0.name}({0.username})'.format(self)
|
return '{0.name}({0.username})'.format(self)
|
||||||
|
@ -248,14 +259,17 @@ class User(AbstractUser):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def otp_enabled(self):
|
def otp_enabled(self):
|
||||||
return self.otp_level > 0
|
return self.otp_force_enabled or self.otp_level > 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def otp_force_enabled(self):
|
def otp_force_enabled(self):
|
||||||
|
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
|
||||||
|
if mfa_setting and mfa_setting.cleaned_value:
|
||||||
|
return True
|
||||||
return self.otp_level == 2
|
return self.otp_level == 2
|
||||||
|
|
||||||
def enable_otp(self):
|
def enable_otp(self):
|
||||||
if not self.otp_force_enabled:
|
if not self.otp_level == 2:
|
||||||
self.otp_level = 1
|
self.otp_level = 1
|
||||||
|
|
||||||
def force_enable_otp(self):
|
def force_enable_otp(self):
|
||||||
|
@ -275,6 +289,7 @@ class User(AbstractUser):
|
||||||
'is_superuser': self.is_superuser,
|
'is_superuser': self.is_superuser,
|
||||||
'role': self.get_role_display(),
|
'role': self.get_role_display(),
|
||||||
'groups': [group.name for group in self.groups.all()],
|
'groups': [group.name for group in self.groups.all()],
|
||||||
|
'source': self.get_source_display(),
|
||||||
'wechat': self.wechat,
|
'wechat': self.wechat,
|
||||||
'phone': self.phone,
|
'phone': self.phone,
|
||||||
'otp_level': self.otp_level,
|
'otp_level': self.otp_level,
|
||||||
|
|
|
@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
def get_field_names(self, declared_fields, info):
|
def get_field_names(self, declared_fields, info):
|
||||||
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
fields = super(UserSerializer, self).get_field_names(declared_fields, info)
|
||||||
fields.extend(['groups_display', 'get_role_display', 'is_valid'])
|
fields.extend([
|
||||||
|
'groups_display', 'get_role_display',
|
||||||
|
'get_source_display', 'is_valid'
|
||||||
|
])
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django_auth_ldap.backend import populate_user
|
||||||
# from django.db.models.signals import post_save
|
# from django.db.models.signals import post_save
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -28,3 +29,11 @@ def on_user_create(sender, user=None, **kwargs):
|
||||||
logger.info(" - Sending welcome mail ...".format(user.name))
|
logger.info(" - Sending welcome mail ...".format(user.name))
|
||||||
if user.email:
|
if user.email:
|
||||||
send_user_created_mail(user)
|
send_user_created_mail(user)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(populate_user)
|
||||||
|
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
|
||||||
|
if user:
|
||||||
|
user.source = user.SOURCE_LDAP
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
<br>
|
<br>
|
||||||
<input type="checkbox" id="acceptTerms">
|
<input type="checkbox" id="acceptTerms">
|
||||||
<label for="acceptTerms" style="margin-top:20px">{% trans "I agree with the terms and conditions." %}</label>
|
<label for="acceptTerms" style="margin-top:20px">{% trans "I agree with the terms and conditions." %}</label>
|
||||||
|
<p id="noTerms" class="red-fonts" style="visibility: hidden; font-size: 10px; margin-top: 10px;">* {% trans 'Please choose the terms and conditions.' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% bootstrap_form wizard.form %}
|
{% bootstrap_form wizard.form %}
|
||||||
|
@ -99,11 +100,7 @@
|
||||||
{% if wizard.steps.prev %}
|
{% if wizard.steps.prev %}
|
||||||
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
|
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{#{% if wizard.steps.next %}#}
|
|
||||||
{#<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.next }}">{% trans "Next" %}</a></li>#}
|
|
||||||
{#{% endif %}#}
|
|
||||||
{#<li><a id="fl_submit">{% trans "Submit" %}</a></li>#}
|
|
||||||
{#将原来的下一页-替换为提交;修复 每页都提交,最后才能成功问题#}
|
|
||||||
{% if wizard.steps.next %}
|
{% if wizard.steps.next %}
|
||||||
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
|
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -124,20 +121,26 @@
|
||||||
|
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script>
|
<script>
|
||||||
$('#id_2-otp_level div').eq(2).css('display', 'none');
|
|
||||||
|
|
||||||
$(document).on('click', ".fl_goto", function(){
|
$(document).on('click', ".fl_goto", function(){
|
||||||
var $form = $('#fl_form');
|
var $form = $('#fl_form');
|
||||||
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
||||||
$form.submit();
|
$form.submit();
|
||||||
return false;
|
return false;
|
||||||
}).on('click', '#fl_submit', function(){
|
}).on('click', '#fl_submit', function(){
|
||||||
$('#fl_form').submit();
|
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
|
||||||
return false;
|
var noChecked = !$('#acceptTerms').prop('checked');
|
||||||
|
if ( isFinish && noChecked){
|
||||||
|
$('#noTerms').css('visibility', 'visible');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#fl_form').submit();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}).on('click', '#btn-reset-pubkey', function () {
|
}).on('click', '#btn-reset-pubkey', function () {
|
||||||
var the_url = '{% url "users:user-pubkey-generate" %}';
|
var the_url = '{% url "users:user-pubkey-generate" %}';
|
||||||
window.open(the_url, "_blank")
|
window.open(the_url, "_blank");
|
||||||
})
|
$('#fl_form').submit();
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -45,13 +45,17 @@
|
||||||
</div>
|
</div>
|
||||||
<form class="m-t" role="form" method="post" action="">
|
<form class="m-t" role="form" method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if form.errors %}
|
|
||||||
|
{% if block_login %}
|
||||||
|
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
|
||||||
|
{% elif form.errors %}
|
||||||
{% if 'captcha' in form.errors %}
|
{% if 'captcha' in form.errors %}
|
||||||
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
<th class="text-center">{% trans 'UA' %}</th>
|
<th class="text-center">{% trans 'UA' %}</th>
|
||||||
<th class="text-center">{% trans 'IP' %}</th>
|
<th class="text-center">{% trans 'IP' %}</th>
|
||||||
<th class="text-center">{% trans 'City' %}</th>
|
<th class="text-center">{% trans 'City' %}</th>
|
||||||
|
<th class="text-center">{% trans 'MFA' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Reason' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Status' %}</th>
|
||||||
<th class="text-center">{% trans 'Date' %}</th>
|
<th class="text-center">{% trans 'Date' %}</th>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -65,6 +68,9 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ login_log.ip }}</td>
|
<td class="text-center">{{ login_log.ip }}</td>
|
||||||
<td class="text-center">{{ login_log.city }}</td>
|
<td class="text-center">{{ login_log.city }}</td>
|
||||||
|
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||||
|
<td class="text-center">{{ login_log.get_reason_display }}</td>
|
||||||
|
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||||
<td class="text-center">{{ login_log.datetime }}</td>
|
<td class="text-center">{{ login_log.datetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
{% include '_head_css_js.html' %}
|
{% include '_head_css_js.html' %}
|
||||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -49,10 +50,20 @@
|
||||||
<p class="red-fonts">{{ errors }}</p>
|
<p class="red-fonts">{{ errors }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required="">
|
<input type="password" id="id_new_password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required="">
|
||||||
|
{# 密码popover #}
|
||||||
|
<div id="container">
|
||||||
|
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
|
||||||
|
<div class="arrow" style="left: 50%;"></div>
|
||||||
|
<h3 class="popover-title" style="display: none;"></h3>
|
||||||
|
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
|
||||||
|
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
|
||||||
|
<div class="popover-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required="">
|
<input type="password" id="id_confirm_password" class="form-control" name="password-confirm" placeholder="{% trans 'Password again' %}" required="">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
|
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
|
||||||
|
|
||||||
|
@ -79,4 +90,33 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
// 密码强度校验
|
||||||
|
var el = $('#id_password_rules'),
|
||||||
|
idPassword = $('#id_new_password'),
|
||||||
|
idPopover = $('#popover777'),
|
||||||
|
container = $('#container'),
|
||||||
|
progress = $('#id_progress'),
|
||||||
|
password_check_rules = {{ password_check_rules|safe }},
|
||||||
|
minLength = {{ min_length }},
|
||||||
|
top = 146, left = 170;
|
||||||
|
|
||||||
|
// 初始化popover
|
||||||
|
initPopover(container, progress, idPassword, el, password_check_rules);
|
||||||
|
|
||||||
|
// 监听事件
|
||||||
|
idPassword.on('focus', function () {
|
||||||
|
idPopover.css('top', top);
|
||||||
|
idPopover.css('left', left);
|
||||||
|
idPopover.css('display', 'block');
|
||||||
|
});
|
||||||
|
idPassword.on('blur', function () {
|
||||||
|
idPopover.css('display', 'none');
|
||||||
|
});
|
||||||
|
idPassword.on('keyup', function(){
|
||||||
|
var password = idPassword.val();
|
||||||
|
checkPasswordRules(password, minLength);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
|
@ -99,6 +99,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</b></td>
|
</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans 'Source' %}:</td>
|
||||||
|
<td><b>{{ user_object.get_source_display }}</b></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Date expired' %}:</td>
|
<td>{% trans 'Date expired' %}:</td>
|
||||||
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
<td><b>{{ user_object.date_expired|date:"Y-m-j H:i:s" }}</b></td>
|
||||||
|
@ -417,8 +421,8 @@ $(document).ready(function() {
|
||||||
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
|
APIUpdateAttr({ url: the_url, body: JSON.stringify(body), success: success, error: fail});
|
||||||
}).on('click', '.btn-delete-user', function () {
|
}).on('click', '.btn-delete-user', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
var name = "{{ user.name }}";
|
var name = "{{ user_object.name }}";
|
||||||
var uid = "{{ user.id }}";
|
var uid = "{{ user_object.id }}";
|
||||||
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
|
||||||
var redirect_url = "{% url 'users:user-list' %}";
|
var redirect_url = "{% url 'users:user-list' %}";
|
||||||
objectDelete($this, name, the_url, redirect_url);
|
objectDelete($this, name, the_url, redirect_url);
|
||||||
|
|
|
@ -68,7 +68,7 @@ var asset_table;
|
||||||
|
|
||||||
function initTable() {
|
function initTable() {
|
||||||
if (inited){
|
if (inited){
|
||||||
return
|
return asset_table
|
||||||
} else {
|
} else {
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,10 +64,11 @@
|
||||||
var zTree;
|
var zTree;
|
||||||
var inited = false;
|
var inited = false;
|
||||||
var url;
|
var url;
|
||||||
|
var asset_table;
|
||||||
|
|
||||||
function initTable() {
|
function initTable() {
|
||||||
if (inited){
|
if (inited){
|
||||||
return
|
return asset_table
|
||||||
} else {
|
} else {
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<th class="text-center">{% trans 'Username' %}</th>
|
<th class="text-center">{% trans 'Username' %}</th>
|
||||||
<th class="text-center">{% trans 'Role' %}</th>
|
<th class="text-center">{% trans 'Role' %}</th>
|
||||||
<th class="text-center">{% trans 'User group' %}</th>
|
<th class="text-center">{% trans 'User group' %}</th>
|
||||||
|
<th class="text-center">{% trans 'Source' %}</th>
|
||||||
<th class="text-center">{% trans 'Active' %}</th>
|
<th class="text-center">{% trans 'Active' %}</th>
|
||||||
<th class="text-center">{% trans 'Action' %}</th>
|
<th class="text-center">{% trans 'Action' %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -58,21 +59,21 @@ function initTable() {
|
||||||
ele: $('#user_list_table'),
|
ele: $('#user_list_table'),
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||||
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
|
var detail_btn = '<a href="{% url "users:user-detail" pk=DEFAULT_PK %}">' + escape(cellData) + '</a>';
|
||||||
$(td).html(detail_btn.replace("{{ DEFAULT_PK }}", rowData.id));
|
$(td).html(detail_btn.replace("{{ DEFAULT_PK }}", rowData.id));
|
||||||
}},
|
}},
|
||||||
{targets: 4, createdCell: function (td, cellData) {
|
{targets: 4, createdCell: function (td, cellData) {
|
||||||
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
|
var innerHtml = cellData.length > 20 ? cellData.substring(0, 20) + '...': cellData;
|
||||||
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
$(td).html('<span href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</span>');
|
||||||
}},
|
}},
|
||||||
{targets: 5, createdCell: function (td, cellData) {
|
{targets: 6, createdCell: function (td, cellData) {
|
||||||
if (!cellData) {
|
if (!cellData) {
|
||||||
$(td).html('<i class="fa fa-times text-danger"></i>')
|
$(td).html('<i class="fa fa-times text-danger"></i>')
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<i class="fa fa-check text-navy"></i>')
|
$(td).html('<i class="fa fa-check text-navy"></i>')
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
{targets: 7, createdCell: function (td, cellData, rowData) {
|
||||||
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
|
||||||
|
|
||||||
var del_btn = "";
|
var del_btn = "";
|
||||||
|
@ -90,7 +91,7 @@ function initTable() {
|
||||||
ajax_url: '{% url "api-users:user-list" %}',
|
ajax_url: '{% url "api-users:user-list" %}',
|
||||||
columns: [
|
columns: [
|
||||||
{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" },
|
{data: "id"}, {data: "name" }, {data: "username" }, {data: "get_role_display" },
|
||||||
{data: "groups_display" }, {data: "is_valid" }, {data: "id" }
|
{data: "groups_display" }, {data: "get_source_display" }, {data: "is_valid" }, {data: "id" }
|
||||||
],
|
],
|
||||||
op_html: $('#actions').html()
|
op_html: $('#actions').html()
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
<link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet">
|
||||||
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
|
||||||
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
<script src="{% static "js/plugins/sweetalert/sweetalert.min.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
|
||||||
|
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.crop {
|
.crop {
|
||||||
|
@ -50,6 +52,16 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_field form.old_password layout="horizontal" %}
|
{% bootstrap_field form.old_password layout="horizontal" %}
|
||||||
{% bootstrap_field form.new_password layout="horizontal" %}
|
{% bootstrap_field form.new_password layout="horizontal" %}
|
||||||
|
{# 密码popover #}
|
||||||
|
<div id="container">
|
||||||
|
<div class="popover fade bottom in" role="tooltip" id="popover777" style=" display: none; width:260px;">
|
||||||
|
<div class="arrow" style="left: 50%;"></div>
|
||||||
|
<h3 class="popover-title" style="display: none;"></h3>
|
||||||
|
<h4>{% trans 'Your password must satisfy' %}</h4><div id="id_password_rules" style="color: #908a8a; margin-left:20px; font-size:15px;"></div>
|
||||||
|
<h4 style="margin-top: 10px;">{% trans 'Password strength' %}</h4><div id="id_progress"></div>
|
||||||
|
<div class="popover-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% bootstrap_field form.confirm_password layout="horizontal" %}
|
{% bootstrap_field form.confirm_password layout="horizontal" %}
|
||||||
|
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
@ -71,5 +83,34 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
|
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
var el = $('#id_password_rules'),
|
||||||
|
idPassword = $('#id_new_password'),
|
||||||
|
idPopover = $('#popover777'),
|
||||||
|
container = $('#container'),
|
||||||
|
progress = $('#id_progress'),
|
||||||
|
password_check_rules = {{ password_check_rules|safe }},
|
||||||
|
minLength = {{ min_length }},
|
||||||
|
top = idPassword.offset().top - $('.navbar').outerHeight(true) - $('.page-heading').outerHeight(true) - 10 + 34,
|
||||||
|
left = 377;
|
||||||
|
|
||||||
|
// 初始化popover
|
||||||
|
initPopover(container, progress, idPassword, el, password_check_rules);
|
||||||
|
|
||||||
|
// 监听事件
|
||||||
|
idPassword.on('focus', function () {
|
||||||
|
idPopover.css('top', top);
|
||||||
|
idPopover.css('left', left);
|
||||||
|
idPopover.css('display', 'block');
|
||||||
|
});
|
||||||
|
idPassword.on('blur', function () {
|
||||||
|
idPopover.css('display', 'none');
|
||||||
|
});
|
||||||
|
idPassword.on('keyup', function(){
|
||||||
|
var password = idPassword.val();
|
||||||
|
checkPasswordRules(password, minLength);
|
||||||
|
});
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -91,8 +91,15 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans 'Disable' %}
|
{% trans 'Disable' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if mfa_setting %}
|
||||||
|
( {% trans 'Administrator Settings force MFA login' %} )
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-navy">{% trans 'Source' %}</td>
|
||||||
|
<td>{{ user.get_source_display }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-navy">{% trans 'Date joined' %}</td>
|
<td class="text-navy">{% trans 'Date joined' %}</td>
|
||||||
<td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td>
|
<td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td>
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2 col-lg-2" style="padding-top: 0">{% trans 'Or reset by server' %}</label>
|
<label class="control-label col-sm-2 col-lg-2" style="padding-top: 0">{% trans 'Or reset by server' %}</label>
|
||||||
<div class=" col-sm-9 col-lg-9 ">
|
<div class=" col-sm-9 col-lg-9 ">
|
||||||
<a href="{% url 'users:user-pubkey-generate' %}">{% trans 'Reset' %}</a>
|
<a id="reset_pubkey" href="{% url 'users:user-pubkey-generate' %}">{% trans 'Reset' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hr-line-dashed"></div>
|
<div class="hr-line-dashed"></div>
|
||||||
|
@ -89,5 +89,10 @@
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
|
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
}).on('click', '#reset_pubkey', function () {
|
||||||
|
var message = "{% trans 'The new public key has been set successfully, Please download the corresponding private key.' %}";
|
||||||
|
toastr.success(message)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue