mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-30 08:42:04 +00:00
合并冲突解决
This commit is contained in:
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">
|
||||||
|
87
apps/common/templates/common/security_setting.html
Normal file
87
apps/common/templates/common/security_setting.html
Normal file
@ -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
|
||||||
|
7
apps/common/validators.py
Normal file
7
apps/common/validators.py
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
2261
apps/static/js/plugins/xterm/xterm.css
Normal file
2261
apps/static/js/plugins/xterm/xterm.css
Normal file
File diff suppressed because it is too large
Load Diff
5132
apps/static/js/plugins/xterm/xterm.js
Normal file
5132
apps/static/js/plugins/xterm/xterm.js
Normal file
File diff suppressed because it is too large
Load Diff
1
apps/static/js/plugins/xterm/xterm.js.map
Normal file
1
apps/static/js/plugins/xterm/xterm.js.map
Normal file
File diff suppressed because one or more lines are too long
976
apps/static/js/pwstrength-bootstrap.js
Executable file
976
apps/static/js/pwstrength-bootstrap.js
Executable file
@ -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
Block a user