Commit 5d63d236 authored by wojiushixiaobai's avatar wojiushixiaobai

合并冲突解决

parents 7c35e755 9004351a
......@@ -19,20 +19,14 @@ Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点
----
### 功能
- 统一认证
- 资产管理
- 统一授权
- 审计
- 支持LDAP认证
- Web terminal
- SSH Server
- 支持Windows RDP
![Jumpserver功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver13.jpg "Jumpserver功能")
### 开始使用
快速开始文档 [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)
......
......@@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-
#
__version__ = "1.2.1"
__version__ = "1.3.2"
# -*- coding: utf-8 -*-
#
import random
from rest_framework import generics
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
......@@ -11,8 +13,7 @@ from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \
NodePermissionUtil
from ..hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from ..models import Asset, SystemUser, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
......@@ -22,8 +23,9 @@ from ..utils import LabelFilter
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'UserAssetListView', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi'
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi'
]
......@@ -40,32 +42,34 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
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')
node_id = self.request.query_params.get("node_id")
show_current_asset = self.request.query_params.get("show_current_asset")
if admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
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)
if not node.is_root():
if node.is_root():
queryset = queryset.filter(
nodes__key__regex='{}(:[0-9]+)*$'.format(node.key),
Q(nodes=node_id) | Q(nodes__isnull=True)
).distinct()
return queryset
else:
queryset = queryset.filter(nodes=node).distinct()
class UserAssetListView(generics.ListAPIView):
if node_id and not show_current_asset:
node = get_object_or_404(Node, id=node_id)
if node.is_root():
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
permission_classes = (IsValidUser,)
def get_queryset(self):
assets_granted = NodePermissionUtil.get_user_assets(self.request.user).keys()
queryset = self.queryset.filter(
id__in=[asset.id for asset in assets_granted]
)
else:
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
).distinct()
return queryset
......@@ -105,3 +109,20 @@ class AssetAdminUserTestApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectability_manual.delay(asset)
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)
\ No newline at end of file
......@@ -14,6 +14,7 @@
# limitations under the License.
from rest_framework import generics, mixins
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
......@@ -30,7 +31,7 @@ from .. import serializers
logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi',
'NodeAssetsApi', 'NodeWithAssetsApi',
'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi',
'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
......@@ -49,32 +50,32 @@ class NodeViewSet(BulkModelViewSet):
serializer.save()
class NodeWithAssetsApi(generics.ListAPIView):
permission_classes = (IsSuperUser,)
serializers = serializers.NodeSerializer
def get_node(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('node')
if not pk:
node = Node.root()
else:
node = get_object_or_404(Node, pk)
return node
def get_queryset(self):
queryset = []
node = self.get_node()
children = node.get_children()
assets = node.get_assets()
queryset.extend(list(children))
for asset in assets:
node = Node()
node.id = asset.id
node.parent = node.id
node.value = asset.hostname
queryset.append(node)
return queryset
# class NodeWithAssetsApi(generics.ListAPIView):
# permission_classes = (IsSuperUser,)
# serializers = serializers.NodeSerializer
#
# def get_node(self):
# pk = self.kwargs.get('pk') or self.request.query_params.get('node')
# if not pk:
# node = Node.root()
# else:
# node = get_object_or_404(Node, pk)
# return node
#
# def get_queryset(self):
# queryset = []
# node = self.get_node()
# children = node.get_children()
# assets = node.get_assets()
# queryset.extend(list(children))
#
# for asset in assets:
# node = Node()
# node.id = asset.id
# node.parent = node.id
# node.value = asset.hostname
# queryset.append(node)
# return queryset
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
......@@ -83,16 +84,29 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
serializer_class = serializers.NodeSerializer
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):
if not request.data.get("value"):
request.data["value"] = _("New node {}").format(
Node.root().get_next_child_key().split(":")[-1]
)
request.data["value"] = _("New node {}").format(self.counter())
return super().post(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
instance = self.get_object()
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)
return Response(
{"id": node.id, "key": node.key, "value": node.value},
......@@ -102,7 +116,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def get_object(self):
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
if not pk:
node = Node.root()
node = None
else:
node = get_object_or_404(Node, pk=pk)
return node
......@@ -112,7 +126,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
query_all = self.request.query_params.get("all")
query_assets = self.request.query_params.get('assets')
node = self.get_object()
if node == Node.root():
if node is None:
node = Node.root()
queryset.append(node)
if query_all:
children = node.get_all_children()
......@@ -125,10 +140,11 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
for asset in assets:
node_fake = Node()
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.is_asset = True
queryset.append(node_fake)
queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True)
return queryset
def get(self, request, *args, **kwargs):
......@@ -163,7 +179,6 @@ class NodeAddChildrenApi(generics.UpdateAPIView):
if not node:
continue
node.parent = instance
node.save()
return Response("OK")
......@@ -190,6 +205,9 @@ class NodeRemoveAssetsApi(generics.UpdateAPIView):
instance = self.get_object()
if instance != Node.root():
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):
......
......@@ -40,7 +40,7 @@ class SystemUserViewSet(BulkModelViewSet):
permission_classes = (IsSuperUserOrAppUser,)
class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
Get system user auth info
"""
......@@ -48,6 +48,11 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUserOrAppUser,)
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):
"""
......@@ -58,6 +63,9 @@ class SystemUserPushApi(generics.RetrieveAPIView):
def retrieve(self, request, *args, **kwargs):
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)
return Response({"task": task.id})
......
......@@ -16,7 +16,7 @@ class AssetCreateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain',
'domain', 'protocol',
]
widgets = {
......@@ -56,7 +56,7 @@ class AssetUpdateForm(forms.ModelForm):
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
'domain', 'protocol',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
......
......@@ -93,14 +93,21 @@ class SystemUserForm(PasswordAndKeyAuthForm):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
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)
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:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key, public_key=public_key)
return system_user
def clean(self):
......@@ -109,12 +116,24 @@ class SystemUserForm(PasswordAndKeyAuthForm):
if not self.instance and not auto_generate:
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:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'comment', 'shell', 'priority',
'comment', 'shell', 'priority', 'login_mode',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
......@@ -124,5 +143,8 @@ class SystemUserForm(PasswordAndKeyAuthForm):
'name': '* required',
'username': '* required',
'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.permissions import IsAppUser, IsSuperUser, IsValidUser, IsSuperUserOrAppUser
from users.models import User, UserGroup
from perms.utils import NodePermissionUtil
......@@ -5,6 +5,7 @@
import uuid
import logging
import random
from functools import reduce
from django.db import models
from django.utils.translation import ugettext_lazy as _
......@@ -35,6 +36,19 @@ def default_node():
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):
# Important
PLATFORM_CHOICES = (
......@@ -43,45 +57,89 @@ class Asset(models.Model):
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('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)
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, unique=True, verbose_name=_('Hostname'))
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'),
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'))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES,
default='Linux', verbose_name=_('Platform'))
domain = models.ForeignKey("assets.Domain", null=True, blank=True,
related_name='assets', verbose_name=_("Domain"),
on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node,
related_name='assets',
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT,
null=True, verbose_name=_("Admin user"))
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
public_ip = models.GenericIPAddressField(max_length=32, blank=True,
null=True,
verbose_name=_('Public IP'))
number = models.CharField(max_length=32, null=True, blank=True,
verbose_name=_('Asset number'))
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
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'))
vendor = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('Vendor'))
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_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
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'))
memory = models.CharField(max_length=64, null=True, blank=True,
verbose_name=_('Memory'))
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_version = models.CharField(max_length=16, null=True, blank=True, 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'))
os = models.CharField(max_length=128, null=True, blank=True,
verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True,
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"))
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'))
labels = models.ManyToManyField('assets.Label', blank=True,
related_name='assets',
verbose_name=_("Labels"))
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):
return '{0.hostname}({0.ip})'.format(self)
......@@ -103,7 +161,17 @@ class Asset(models.Model):
def get_nodes(self):
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
def hardware_info(self):
......@@ -176,7 +244,8 @@ class Asset(models.Model):
seed()
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),
admin_user=choice(AdminUser.objects.all()),
port=22,
......
......@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
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
signer = get_signer()
......@@ -18,7 +19,7 @@ signer = get_signer()
class AssetUser(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
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'))
_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'))
......@@ -103,10 +104,16 @@ class AssetUser(models.Model):
if 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):
password = str(uuid.uuid4())
private_key, public_key = ssh_key_gen(
username=self.username, password=password
username=self.username
)
self.set_auth(password=password,
private_key=private_key,
......
......@@ -2,9 +2,10 @@
#
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 common.utils import with_cache
__all__ = ['Node']
......@@ -12,25 +13,40 @@ __all__ = ['Node']
class Node(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
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)
date_create = models.DateTimeField(auto_now_add=True)
is_asset = False
is_node = True
def __str__(self):
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
def name(self):
return self.value
@property
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
else:
return '{} / {}'.format(self.parent.full_value, self.value)
return ' / '.join(ancestor)
@property
def level(self):
......@@ -43,77 +59,106 @@ class Node(models.Model):
return "{}:{}".format(self.key, mark)
def create_child(self, value):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
return child
def get_children(self):
return self.__class__.objects.filter(key__regex=r'{}:[0-9]+$'.format(self.key))
def get_children(self, with_self=False):
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, with_self=False):
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
return self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
def get_all_children(self):
return self.__class__.objects.filter(key__startswith='{}:'.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):
children = list(self.get_all_children())
children.append(self)
return children
ancestor = self.get_ancestor()
children = self.get_all_children()
return [*tuple(ancestor), self, *tuple(children)]
def get_assets(self):
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
def get_active_assets(self):
return self.get_assets().filter(is_active=True)
def get_valid_assets(self):
return self.get_assets().valid()
def get_all_assets(self):
from .asset import Asset
if self.is_root():
assets = Asset.objects.all()
else:
nodes = self.get_family()
assets = Asset.objects.filter(nodes__in=nodes).distinct()
pattern = r'^{0}$|^{0}:'.format(self.key)
assets = Asset.objects.filter(nodes__key__regex=pattern)
return assets
def has_assets(self):
return self.get_all_assets()
def get_all_active_assets(self):
return self.get_all_assets().filter(is_active=True)
def get_all_valid_assets(self):
return self.get_all_assets().valid()
def is_root(self):
return self.key == '0'
@property
def parent(self):
if self.key == "0":
return self.__class__.root()
elif not self.key.startswith("0"):
if self.key == "0" or not self.key.startswith("0"):
return self.__class__.root()
parent_key = ":".join(self.key.split(":")[:-1])
try:
parent = self.__class__.objects.get(key=parent_key)
return parent
except Node.DoesNotExist:
return self.__class__.root()
else:
return parent
@parent.setter
def parent(self, parent):
if self.is_node:
children = self.get_all_children()
old_key = self.key
with transaction.atomic():
self.key = parent.get_next_child_key()
@property
def ancestor(self):
if self.parent == self.__class__.root():
return [self.__class__.root()]
for child in children:
child.key = child.key.replace(old_key, self.key, 1)
child.save()
self.save()
else:
return [self.parent, *tuple(self.parent.ancestor)]
self.key = parent.key+':fake'
@property
def ancestor_with_node(self):
ancestor = self.ancestor
ancestor.insert(0, self)
def get_ancestor(self, with_self=False):
if self.is_root():
ancestor = self.__class__.objects.filter(key='0')
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
@classmethod
......@@ -121,4 +166,6 @@ class Node(models.Model):
obj, created = cls.objects.get_or_create(
key='0', defaults={"key": '0', 'value': "ROOT"}
)
print(obj)
return obj
......@@ -95,9 +95,18 @@ class AdminUser(AssetUser):
class SystemUser(AssetUser):
SSH_PROTOCOL = 'ssh'
RDP_PROTOCOL = 'rdp'
TELNET_PROTOCOL = 'telnet'
PROTOCOL_CHOICES = (
(SSH_PROTOCOL, 'ssh'),
(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"))
......@@ -107,6 +116,7 @@ class SystemUser(AssetUser):
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
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):
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 Meta:
model = Asset
list_serializer_class = BulkListSerializer
......@@ -62,14 +36,14 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
"""
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField()
nodes = NodeTMPSerializer(many=True, read_only=True)
# nodes = NodeTMPSerializer(many=True, read_only=True)
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain', "nodes",
"platform", "comment"
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol",
)
@staticmethod
......
......@@ -48,16 +48,27 @@ class NodeSerializer(serializers.ModelSerializer):
class Meta:
model = Node
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_asset']
fields = ['id', 'key', 'value', 'parent', 'assets_amount', 'is_node']
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
def get_parent(obj):
return obj.parent.id
return obj.parent.id if obj.is_node else obj.parent_id
@staticmethod
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):
fields = super().get_fields()
......
......@@ -18,6 +18,13 @@ class SystemUserSerializer(serializers.ModelSerializer):
model = SystemUser
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
def get_unreachable_assets(obj):
return obj.unreachable_assets
......@@ -56,7 +63,10 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
"""
class Meta:
model = SystemUser
fields = ('id', 'name', 'username', 'priority', 'protocol', 'comment',)
fields = (
'id', 'name', 'username', 'priority',
'protocol', 'comment', 'login_mode'
)
class SystemUserSimpleSerializer(serializers.ModelSerializer):
......
......@@ -63,11 +63,13 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_node_changed(sender, instance=None, **kwargs):
if isinstance(instance, Asset) and kwargs['action'] == 'post_add':
if isinstance(instance, Asset):
if kwargs['action'] == 'post_add':
logger.debug("Asset node change signal received")
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users_assets = defaultdict(set)
system_users = SystemUser.objects.filter(nodes__in=nodes)
# 清理节点缓存
for system_user in system_users:
system_users_assets[system_user].update({instance})
for system_user, assets in system_users_assets.items():
......@@ -76,9 +78,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_node_assets_changed(sender, instance=None, **kwargs):
if isinstance(instance, Node) and kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
if isinstance(instance, Node):
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
if kwargs['action'] == 'post_add':
logger.debug("Node assets change signal received")
# 重新关联系统用户和资产的关系
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__)
CACHE_MAX_TIME = 60*60*60
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
......
......@@ -59,7 +59,7 @@ var zTree2, asset_table2 = 0;
function initTable2() {
var options = {
ele: $('#asset_list_modal_table'),
ajax_url: '{% url "api-assets:asset-list" %}',
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }
],
......@@ -98,7 +98,10 @@ function initTree2() {
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
{#value["open"] = true;#}
if (value["key"] === "0") {
value["open"] = true;
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
......
......@@ -36,12 +36,13 @@
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.login_mode layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
<div class="auto-generate">
<div class="form-group">
<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" %}
</div>
<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">
{{ form.auto_push}}
</div>
......@@ -79,43 +80,86 @@
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var protocol_id = '#' + '{{ form.protocol.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 }}';
<script>
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var login_mode_id = '#' + '{{ form.login_mode.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() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
var need_change_field = [
auto_generate_key, private_key_id, auto_push_id, sudo_id, shell_id
];
var need_change_field_login_mode = [
auto_generate_key, private_key_id, auto_push_id, password_id
];
function protocolChange() {
if ($(protocol_id + " option:selected").text() === 'rdp') {
$('.auth-fields').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).closest('.form-group').addClass('hidden')
});
}
}
function protocolChange() {
if ($(protocol_id).attr('value') === 'rdp') {
else if ($(protocol_id + " option:selected").text() === 'telnet (beta)') {
$('.auth-fields').removeClass('hidden');
$.each(need_change_field, function (index, value) {
$(value).addClass('hidden')
$(value).closest('.form-group').addClass('hidden')
});
$(password_id).removeClass('hidden')
} else {
}
else {
if($(login_mode_id).val() === 'manual'){
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
return
}
authFieldsDisplay();
$.each(need_change_field, function (index, value) {
$(value).removeClass('hidden')
$(value).closest('.form-group').removeClass('hidden')
});
}
}
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
$('.auth-fields').removeClass('hidden');
}
$(document).ready(function () {
}
function loginModeChange(){
if ($(login_mode_id).val() === 'manual'){
$('#auth_title_id').addClass('hidden');
$.each(need_change_field_login_mode, function(index, value){
$(value).closest('.form-group').addClass('hidden')
})
}
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();
$(auto_generate_key).change(function () {
loginModeChange();
})
.on('change', protocol_id, function(){
protocolChange();
})
.on('change', auto_generate_key, function(){
authFieldsDisplay();
});
})
</script>
})
.on('change', login_mode_id, function(){
loginModeChange();
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -124,7 +124,7 @@ $(document).ready(function () {
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
......
......@@ -5,7 +5,7 @@
{% block help_message %}
<div class="alert alert-info help-message">
管理用户是服务器的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。
Windows或其它硬件可以随意设置一个
</div>
{% endblock %}
......@@ -107,6 +107,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}
......@@ -17,6 +17,7 @@
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
{% bootstrap_field form.domain layout="horizontal" %}
......@@ -85,14 +86,14 @@ $(document).ready(function () {
allowClear: true,
templateSelection: format
});
$("#id_platform").change(function (){
var platform = $("#id_platform option:selected").text();
$("#id_protocol").change(function (){
var protocol = $("#id_protocol option:selected").text();
var port = 22;
if(platform === 'Windows'){
if(protocol === 'rdp'){
port = 3389;
}
if(platform === 'Other'){
port = null;
if(protocol === 'telnet (beta)'){
port = 23;
}
$("#id_port").val(port);
});
......
......@@ -190,7 +190,7 @@
<td colspan="2" class="no-borders">
<select data-placeholder="{% trans 'Nodes' %}" id="groups_selected" class="select2 groups" style="width: 100%" multiple="" tabindex="4">
{% 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 %}
</select>
</td>
......@@ -204,7 +204,7 @@
{% for node in asset.nodes.all %}
<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>
<button class="btn btn-danger pull-right btn-xs btn-leave-node" type="button"><i class="fa fa-minus"></i></button>
</td>
......
......@@ -17,11 +17,12 @@
position:absolute;
visibility:hidden;
text-align: left;
top: 100%;
{#top: 100%;#}
top: 0;
left: 0;
z-index: 1000;
float: left;
padding: 5px 0;
{#float: left;#}
padding: 0 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
......@@ -29,7 +30,7 @@
div#rMenu li{
margin: 1px 0;
cursor: pointer;
{#list-style: none outside none;#}
list-style: none outside none;
}
.dropdown a:hover {
background-color: #f1f1f1
......@@ -47,7 +48,6 @@
<div class="file-manager ">
<div id="assetTree" class="ztree">
</div>
<div class="clearfix"></div>
</div>
</div>
......@@ -87,7 +87,7 @@
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Hardware' %}</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>
</tr>
</thead>
......@@ -127,6 +127,9 @@
<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_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>
</div>
......@@ -157,26 +160,35 @@ function initTable() {
$(td).html('<i class="fa fa-check text-navy"></i>')
}
}},
{targets: 5, createdCell: function (td, cellData) {
if (cellData === 'Unknown'){
$(td).html('<i class="fa fa-circle text-warning"></i>')
} else if (!cellData) {
$(td).html('<i class="fa fa-circle text-danger"></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) {#}
{# if (cellData === 'Unknown'){#}
{# $(td).html('<i class="fa fa-circle text-warning"></i>')#}
{# } else if (!cellData) {#}
{# $(td).html('<i class="fa fa-circle text-danger"></i>')#}
{# } else {#}
{# $(td).html('<i class="fa fa-circle text-navy"></i>')#}
{# }#}
{# }},#}
{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 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)
}}
],
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: [
{data: "id"}, {data: "hostname" }, {data: "ip" },
{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()
};
......@@ -200,6 +212,8 @@ function addTreeNode() {
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.id, parentNode)
zTree.editName(node);
} else {
alert("{% trans 'Create node failed' %}")
}
......@@ -253,6 +267,8 @@ function OnRightClick(event, treeId, treeNode) {
function showRMenu(type, x, y) {
$("#rMenu ul").show();
x -= 220;
x += document.body.scrollLeft;
y += document.body.scrollTop+document.documentElement.scrollTop;
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("body").bind("mousedown", onBodyMouseDown);
......@@ -290,6 +306,7 @@ function onRename(event, treeId, treeNode, isCancel){
function onSelected(event, treeNode) {
var url = asset_table.ajax.url();
url = setUrlParam(url, "node_id", treeNode.id);
url = setUrlParam(url, "show_current_asset", getCookie('show_current_asset'));
setCookie('node_selected', treeNode.id);
asset_table.ajax.url(url);
asset_table.ajax.reload();
......@@ -385,9 +402,9 @@ function initTree() {
$.get("{% url 'api-assets:node-list' %}", function(data, status){
$.each(data, function (index, value) {
value["pId"] = value["parent"];
{#if (value["key"] === "0") {#}
if (value["key"] === "0") {
value["open"] = true;
{# }#}
}
value["name"] = value["value"] + ' (' + value['assets_amount'] + ')';
value['value'] = value['value'];
});
......@@ -417,6 +434,13 @@ function toggle() {
$(document).ready(function(){
initTable();
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 () {
var val = $(this).text();
......@@ -535,6 +559,20 @@ $(document).ready(function(){
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 () {
var $this = $(this);
var $data_table = $("#asset_list_table").DataTable();
......
......@@ -21,6 +21,7 @@
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.hostname layout="horizontal" %}
{% bootstrap_field form.ip layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.port layout="horizontal" %}
{% bootstrap_field form.platform layout="horizontal" %}
{% bootstrap_field form.public_ip layout="horizontal" %}
......
......@@ -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 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);
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)
}}
],
......@@ -120,7 +123,6 @@ $(document).ready(function(){
success_message: "可连接",
fail_message: "连接失败"
})
})
});
</script>
{% endblock %}
{% extends '_base_list.html' %}
{% load i18n static %}
{% block table_search %}{% endblock %}
{% block help_message %}
<div class="alert alert-info help-message">
网域功能是为了解决部分环境(如:混合云)无法直接连接而新增的功能,原理是通过网关服务器进行跳转登
录。
</div>
{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'assets:domain-create' %}" class="btn btn-sm btn-primary"> {% trans "Create domain" %} </a>
......@@ -69,6 +77,3 @@ $(document).ready(function(){
});
</script>
{% endblock %}
......@@ -66,3 +66,28 @@
</div>
</div>
{% 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 %}
\ No newline at end of file
......@@ -63,15 +63,19 @@
<td><b>{{ system_user.username }}</b></td>
</tr>
<tr>
<td>{% trans 'Protocol' %}:</td>
<td><b>{{ system_user.protocol }}</b></td>
<td>{% trans 'Login mode' %}:</td>
<td><b>{{ system_user.get_login_mode_display }}</b></td>
</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><b>{{ system_user.sudo }}</b></td>
</tr>
{% if system_user.shell %}
<tr>
<tr class="only-ssh">
<td>{% trans 'Shell' %}:</td>
<td><b>{{ system_user.shell }}</b></td>
</tr>
......@@ -107,14 +111,14 @@
</div>
<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">
<i class="fa fa-info-circle"></i> {% trans 'Quick update' %}
</div>
<div class="panel-body">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<tr class="only-ssh">
<td width="50%">{% trans 'Auto push' %}:</td>
<td>
<span class="pull-right">
......@@ -130,8 +134,8 @@
</span>
</td>
</tr>
<tr class="no-borders-tr">
{% if system_user.auto_push %}
<tr class="only-ssh">
<td width="50%">{% trans 'Push system user now' %}:</td>
<td>
<span style="float: right">
......@@ -139,8 +143,8 @@
</span>
</td>
</tr>
<tr>
{% endif %}
<tr class="only-ssh">
<td width="50%">{% trans 'Test assets connective' %}:</td>
<td>
<span style="float: right">
......@@ -149,6 +153,15 @@
</td>
</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>#}
{# <td width="50%">{% trans 'Change auth period' %}:</td>#}
{# <td>#}
......@@ -236,6 +249,10 @@ function updateSystemUserNode(nodes) {
}
jumpserver.nodes_selected = {};
$(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()
.on('select2:select', function(evt) {
var data = evt.params.data;
......@@ -296,7 +313,7 @@ $(document).ready(function () {
var success = function (data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
url: the_url,
......@@ -318,6 +335,25 @@ $(document).ready(function () {
success: success,
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>
{% endblock %}
......@@ -26,6 +26,7 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Username' %}</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 'Reachable' %}</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>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 5, createdCell: function (td, cellData) {
{targets: 6, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
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>');
}},
{targets: 6, createdCell: function (td, cellData) {
{targets: 7, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData !== 0) {
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>');
}},
{targets: 7, createdCell: function (td, cellData, rowData) {
{targets: 8, createdCell: function (td, cellData, rowData) {
var val = 0;
var innerHtml = "";
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>');
}},
{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 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)
}}],
ajax_url: '{% url "api-assets:system-user-list" %}',
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" }
],
op_html: $('#actions').html()
......
......@@ -4,7 +4,6 @@
{% load bootstrap3 %}
{% block auth %}
<h3>{% trans 'Auth' %}</h3>
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key_file layout="horizontal" %}
<div class="form-group">
......@@ -15,10 +14,3 @@
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
$('.select2').select2();
})
</script>
{% endblock %}
\ No newline at end of file
......@@ -23,8 +23,8 @@ urlpatterns = [
api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/alive/$',
api.AssetAdminUserTestApi.as_view(), name='asset-alive-test'),
url(r'^v1/assets/user-assets/$',
api.UserAssetListView.as_view(), name='user-asset-list'),
url(r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]{36})/gateway/$',
api.AssetGatewayApi.as_view(), name='asset-gateway'),
url(r'^v1/admin-user/(?P<pk>[0-9a-zA-Z\-]{36})/nodes/$',
api.ReplaceNodesAdminUserApi.as_view(), name='replace-nodes-admin-user'),
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'),
url(r'^v1/system-user/(?P<pk>[0-9a-zA-Z\-]{36})/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/(?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})/assets/$', api.NodeAssetsApi.as_view(), name='node-assets'),
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/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/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})/assets/$',
api.NodeAssetsApi.as_view(), name='node-assets'),
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/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
......
# ~*~ coding: utf-8 ~*~
#
import os
import paramiko
from paramiko.ssh_exception import SSHException
from common.utils import get_object_or_none
from .models import Asset, SystemUser, Label
......@@ -49,22 +50,23 @@ def test_gateway_connectability(gateway):
"""
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy_command = [
"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))
proxy = paramiko.SSHClient()
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
sock = paramiko.ProxyCommand(" ".join(proxy_command))
except paramiko.ProxyCommandFailure as e:
proxy.connect(gateway.ip, gateway.port,
username=gateway.username,
password=gateway.password,
pkey=gateway.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
SSHException) as 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:
client.connect("127.0.0.1", port=gateway.port,
username=gateway.username,
......
......@@ -140,11 +140,6 @@ class DomainGatewayUpdateView(AdminUserRequiredMixin, UpdateView):
domain = self.object.domain
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):
context = {
'app': _('Assets'),
......
......@@ -21,23 +21,13 @@ class MailTestingAPI(APIView):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
kwargs = {
"host": serializer.validated_data["EMAIL_HOST"],
"port": serializer.validated_data["EMAIL_PORT"],
"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)
for k, v in serializer.validated_data.items():
if k.startswith('EMAIL'):
setattr(settings, k, v)
try:
connection.open()
except Exception as e:
return Response({"error": str(e)}, status=401)
try:
send_mail("Test", "Test smtp setting", email_host_user,
[email_host_user], connection=connection)
subject = "Test"
message = "Test smtp setting"
send_mail(subject, message, email_host_user, [email_host_user])
except Exception as e:
return Response({"error": str(e)}, status=401)
......@@ -96,14 +86,7 @@ class LDAPTestingAPI(APIView):
class DjangoSettingsAPI(APIView):
def get(self, request):
if not settings.DEBUG:
return Response('Only debug mode support')
configs = {}
for i in dir(settings):
if i.isupper():
configs[i] = str(getattr(settings, i))
return Response(configs)
return Response('Danger, Close now')
......@@ -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):
if enabled:
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)
else:
......
......@@ -2,6 +2,7 @@ from django.core.mail import send_mail
from django.conf import settings
from celery import shared_task
from .utils import get_logger
from .models import Setting
logger = get_logger(__file__)
......@@ -21,6 +22,10 @@ def send_mail_async(*args, **kwargs):
Example:
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:
args = list(args)
args[0] = settings.EMAIL_SUBJECT_PREFIX + args[0]
......
......@@ -23,6 +23,9 @@
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<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">
......
......@@ -23,6 +23,9 @@
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<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">
......
......@@ -23,6 +23,9 @@
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<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">
......
{% 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
class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
<li>
<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">
......@@ -39,6 +42,7 @@
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans "Basic setting" %}</h3>
{% for field in form %}
{% if not field.field|is_bool_field %}
......@@ -60,6 +64,7 @@
{% endfor %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table">
<thead>
......
......@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-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
from io import StringIO
import uuid
from functools import wraps
import paramiko
import sshpubkeys
......@@ -395,3 +396,17 @@ class TeeObj:
def close(self):
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
# -*- 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'))
\ No newline at end of file
......@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm
TerminalSettingForm, SecuritySettingForm
from .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable
......@@ -82,7 +82,7 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
if form.is_valid():
form.save()
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")
messages.success(request, msg)
return redirect('settings:ldap-setting')
......@@ -122,3 +122,27 @@ class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
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)
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-23 19:51+0800\n"
"POT-Creation-Date: 2018-07-06 13:11+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
......@@ -17,51 +17,51 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: assets/api/node.py:88
#: assets/api/node.py:99
msgid "New node {}"
msgstr "新节点 {}"
#: assets/api/node.py:216
#: assets/api/node.py:234
msgid "更新节点资产硬件信息: {}"
msgstr ""
#: assets/api/node.py:229
#: assets/api/node.py:247
msgid "测试节点下资产是否可连接: {}"
msgstr ""
#: assets/forms/asset.py:24 assets/models/asset.py:54 assets/models/user.py:103
#: assets/forms/asset.py:24 assets/models/asset.py:89 assets/models/user.py:112
#: assets/templates/assets/asset_detail.html:183
#: assets/templates/assets/asset_detail.html:191
#: assets/templates/assets/system_user_detail.html:166 perms/models.py:23
#: assets/templates/assets/system_user_detail.html:179 perms/models.py:33
msgid "Nodes"
msgstr "节点管理"
#: assets/forms/asset.py:27 assets/forms/asset.py:66 assets/forms/asset.py:109
#: assets/forms/asset.py:113 assets/models/asset.py:58
#: assets/forms/asset.py:113 assets/models/asset.py:94
#: assets/models/cluster.py:19 assets/models/user.py:72
#: assets/templates/assets/asset_detail.html:73 templates/_nav.html:25
msgid "Admin user"
msgstr "管理用户"
#: assets/forms/asset.py:30 assets/forms/asset.py:69 assets/forms/asset.py:125
#: assets/templates/assets/asset_create.html:35
#: assets/templates/assets/asset_create.html:37
#: assets/templates/assets/asset_create.html:36
#: assets/templates/assets/asset_create.html:38
#: assets/templates/assets/asset_list.html:75
#: assets/templates/assets/asset_update.html:40
#: assets/templates/assets/asset_update.html:42
#: assets/templates/assets/asset_update.html:41
#: assets/templates/assets/asset_update.html:43
#: assets/templates/assets/user_asset_list.html:34
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:53
#: assets/forms/asset.py:34 assets/forms/asset.py:73 assets/models/asset.py:85
#: assets/models/domain.py:46
msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:38 assets/forms/asset.py:63 assets/forms/asset.py:77
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:29
#: assets/templates/assets/asset_update.html:34 perms/forms.py:40
#: perms/forms.py:47 perms/models.py:67
#: assets/forms/asset.py:128 assets/templates/assets/asset_create.html:30
#: assets/templates/assets/asset_update.html:35 perms/forms.py:40
#: perms/forms.py:47 perms/models.py:76
#: perms/templates/perms/asset_permission_list.html:57
#: perms/templates/perms/asset_permission_list.html:142
msgid "Node"
......@@ -90,7 +90,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
msgid "Select assets"
msgstr "选择资产"
#: assets/forms/asset.py:105 assets/models/asset.py:51
#: assets/forms/asset.py:105 assets/models/asset.py:81
#: assets/models/domain.py:44 assets/templates/assets/admin_user_assets.html:53
#: assets/templates/assets/asset_detail.html:69
#: assets/templates/assets/domain_gateway_list.html:58
......@@ -99,18 +99,18 @@ msgid "Port"
msgstr "端口"
#: assets/forms/domain.py:14 assets/forms/label.py:13
#: assets/models/asset.py:169 assets/templates/assets/admin_user_list.html:25
#: assets/models/asset.py:237 assets/templates/assets/admin_user_list.html:25
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:15
#: assets/templates/assets/label_list.html:16
#: assets/templates/assets/system_user_list.html:29 audits/models.py:11
#: assets/templates/assets/system_user_list.html:30 audits/models.py:11
#: audits/templates/audits/ftp_log_list.html:41
#: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:37
#: perms/models.py:22
#: perms/models.py:32
#: perms/templates/perms/asset_permission_create_update.html:40
#: perms/templates/perms/asset_permission_list.html:56
#: perms/templates/perms/asset_permission_list.html:139
#: terminal/backends/command/models.py:11 terminal/models.py:123
#: terminal/backends/command/models.py:11 terminal/models.py:127
#: terminal/templates/terminal/command_list.html:40
#: terminal/templates/terminal/command_list.html:73
#: terminal/templates/terminal/session_list.html:41
......@@ -118,8 +118,8 @@ msgstr "端口"
msgid "Asset"
msgstr "资产"
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:120
#: assets/models/base.py:20 assets/models/cluster.py:18
#: assets/forms/domain.py:54 assets/forms/user.py:79 assets/forms/user.py:139
#: assets/models/base.py:21 assets/models/cluster.py:18
#: assets/models/domain.py:17 assets/models/group.py:20
#: assets/models/label.py:17 assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:23
......@@ -129,15 +129,15 @@ msgstr "资产"
#: assets/templates/assets/label_list.html:14
#: assets/templates/assets/system_user_detail.html:58
#: assets/templates/assets/system_user_list.html:26 common/models.py:26
#: common/templates/common/terminal_setting.html:67
#: common/templates/common/terminal_setting.html:85 ops/models/adhoc.py:36
#: common/templates/common/terminal_setting.html:72
#: common/templates/common/terminal_setting.html:90 ops/models/adhoc.py:36
#: ops/templates/ops/task_detail.html:59 ops/templates/ops/task_list.html:35
#: perms/models.py:19 perms/templates/perms/asset_permission_detail.html:62
#: perms/models.py:29 perms/templates/perms/asset_permission_detail.html:62
#: perms/templates/perms/asset_permission_list.html:53
#: perms/templates/perms/asset_permission_user.html:54 terminal/models.py:16
#: terminal/models.py:149 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
#: users/models/user.py:42 users/templates/users/_select_user_modal.html:13
#: terminal/models.py:154 terminal/templates/terminal/terminal_detail.html:43
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:12
#: users/models/user.py:49 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_detail.html:63
#: users/templates/users/user_group_detail.html:55
#: users/templates/users/user_group_list.html:12
......@@ -147,16 +147,16 @@ msgstr "资产"
msgid "Name"
msgstr "名称"
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:121
#: assets/models/base.py:21 assets/templates/assets/admin_user_detail.html:60
#: assets/forms/domain.py:55 assets/forms/user.py:80 assets/forms/user.py:140
#: assets/models/base.py:22 assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:24
#: assets/templates/assets/domain_gateway_list.html:60
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:27
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:13
#: users/forms.py:22 users/models/authentication.py:45 users/models/user.py:40
#: users/forms.py:31 users/models/authentication.py:70 users/models/user.py:47
#: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:56
#: users/templates/users/login.html:60
#: users/templates/users/login_log_list.html:49
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24
......@@ -168,19 +168,19 @@ msgstr "用户名"
msgid "Password or private key passphrase"
msgstr "密码或密钥密码"
#: assets/forms/user.py:25 assets/models/base.py:22 common/forms.py:113
#: users/forms.py:15 users/forms.py:24 users/forms.py:36
#: users/templates/users/login.html:59
#: users/templates/users/reset_password.html:52
#: assets/forms/user.py:25 assets/models/base.py:23 common/forms.py:113
#: users/forms.py:15 users/forms.py:33 users/forms.py:45
#: users/templates/users/login.html:63
#: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:14
#: users/templates/users/user_password_update.html:40
#: users/templates/users/user_password_update.html:42
#: users/templates/users/user_profile_update.html:40
#: users/templates/users/user_pubkey_update.html:40
msgid "Password"
msgstr "密码"
#: assets/forms/user.py:28 users/models/user.py:69
#: assets/forms/user.py:28 users/models/user.py:76
msgid "Private key"
msgstr "ssh私钥"
......@@ -192,17 +192,27 @@ msgstr "ssh密钥不合法"
msgid "Password and private key file must be input one"
msgstr "密码和私钥, 必须输入一个"
#: assets/forms/user.py:126
#: assets/forms/user.py:125
msgid "* Automatic login mode, must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/forms/user.py:145
msgid "Auto push system user to asset"
msgstr "自动推送系统用户到资产"
#: assets/forms/user.py:127
#: assets/forms/user.py:146
msgid ""
"High level will be using login asset as default, if user was granted more "
"than 2 system user"
msgstr "高优先级的系统用户将会作为默认登录用户"
#: assets/models/asset.py:49 assets/models/domain.py:43
#: assets/forms/user.py:148
msgid ""
"If you choose manual login mode, you do not need to fill in the username and "
"password."
msgstr "如果选择手动登录模式,用户名和密码则不需要填写"
#: assets/models/asset.py:74 assets/models/domain.py:43
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/admin_user_assets.html:52
#: assets/templates/assets/asset_detail.html:61
......@@ -217,7 +227,7 @@ msgstr "高优先级的系统用户将会作为默认登录用户"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:50 assets/templates/assets/_asset_list_modal.html:45
#: assets/models/asset.py:77 assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/admin_user_assets.html:51
#: assets/templates/assets/asset_detail.html:57
#: assets/templates/assets/asset_list.html:86
......@@ -229,107 +239,116 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:52 assets/templates/assets/asset_detail.html:97
#: assets/models/asset.py:80 assets/models/domain.py:45
#: assets/models/user.py:115
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:28
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:83 assets/templates/assets/asset_detail.html:97
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:55 assets/models/domain.py:48
#: assets/models/asset.py:90 assets/models/domain.py:48
#: assets/models/label.py:20 assets/templates/assets/asset_detail.html:105
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:61 assets/templates/assets/asset_detail.html:65
#: assets/models/asset.py:99 assets/templates/assets/asset_detail.html:65
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:62 assets/templates/assets/asset_detail.html:113
#: assets/models/asset.py:101 assets/templates/assets/asset_detail.html:113
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:65 assets/templates/assets/asset_detail.html:77
#: assets/models/asset.py:105 assets/templates/assets/asset_detail.html:77
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:66 assets/templates/assets/asset_detail.html:81
#: assets/models/asset.py:107 assets/templates/assets/asset_detail.html:81
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:67 assets/templates/assets/asset_detail.html:109
#: assets/models/asset.py:109 assets/templates/assets/asset_detail.html:109
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:69
#: assets/models/asset.py:112
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:70
#: assets/models/asset.py:113
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:71
#: assets/models/asset.py:114
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:72 assets/templates/assets/asset_detail.html:89
#: assets/models/asset.py:116 assets/templates/assets/asset_detail.html:89
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:73
#: assets/models/asset.py:118
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:74
#: assets/models/asset.py:120
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:76 assets/templates/assets/asset_detail.html:101
#: assets/models/asset.py:123 assets/templates/assets/asset_detail.html:101
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:77
#: assets/models/asset.py:125
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:78
#: assets/models/asset.py:127
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:79
#: assets/models/asset.py:129
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:81 assets/templates/assets/asset_create.html:33
#: assets/models/asset.py:133 assets/templates/assets/asset_create.html:34
#: assets/templates/assets/asset_detail.html:220
#: assets/templates/assets/asset_update.html:38 templates/_nav.html:27
#: assets/templates/assets/asset_update.html:39 templates/_nav.html:27
msgid "Labels"
msgstr "标签管理"
#: assets/models/asset.py:82 assets/models/base.py:28
#: assets/models/asset.py:135 assets/models/base.py:29
#: assets/models/cluster.py:28 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
#: assets/templates/assets/asset_detail.html:117
#: assets/templates/assets/domain_detail.html:72
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:28 perms/models.py:72
#: assets/templates/assets/system_user_detail.html:100
#: ops/templates/ops/adhoc_detail.html:86 perms/models.py:38 perms/models.py:81
#: perms/templates/perms/asset_permission_detail.html:98
#: users/models/user.py:83 users/templates/users/user_detail.html:107
#: users/models/user.py:90 users/templates/users/user_detail.html:111
msgid "Created by"
msgstr "创建者"
#: assets/models/asset.py:83 assets/models/cluster.py:26
#: assets/models/asset.py:138 assets/models/cluster.py:26
#: assets/models/domain.py:20 assets/models/group.py:22
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:64
#: assets/templates/assets/domain_detail.html:68
#: assets/templates/assets/system_user_detail.html:92
#: assets/templates/assets/system_user_detail.html:96
#: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:63
#: perms/models.py:29 perms/models.py:73
#: perms/models.py:39 perms/models.py:82
#: perms/templates/perms/asset_permission_detail.html:94
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17
#: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:15
#: users/templates/users/user_group_detail.html:63
msgid "Date created"
msgstr "创建日期"
#: assets/models/asset.py:84 assets/models/base.py:25
#: assets/models/asset.py:140 assets/models/base.py:26
#: assets/models/cluster.py:29 assets/models/domain.py:18
#: assets/models/domain.py:47 assets/models/group.py:23
#: assets/models/label.py:21 assets/templates/assets/admin_user_detail.html:72
......@@ -338,23 +357,23 @@ msgstr "创建日期"
#: assets/templates/assets/domain_detail.html:76
#: assets/templates/assets/domain_gateway_list.html:61
#: assets/templates/assets/domain_list.html:17
#: assets/templates/assets/system_user_detail.html:100
#: assets/templates/assets/system_user_list.html:33 common/models.py:30
#: ops/models/adhoc.py:42 perms/models.py:30 perms/models.py:74
#: assets/templates/assets/system_user_detail.html:104
#: assets/templates/assets/system_user_list.html:34 common/models.py:30
#: ops/models/adhoc.py:42 perms/models.py:40 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:102 terminal/models.py:26
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
#: users/models/user.py:75 users/templates/users/user_detail.html:119
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:13
#: users/models/user.py:82 users/templates/users/user_detail.html:123
#: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:123
#: users/templates/users/user_profile.html:130
msgid "Comment"
msgstr "备注"
#: assets/models/base.py:23
#: assets/models/base.py:24
msgid "SSH private key"
msgstr "ssh密钥"
#: assets/models/base.py:24
#: assets/models/base.py:25
msgid "SSH public key"
msgstr "ssh公钥"
......@@ -366,7 +385,7 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:61
#: assets/models/cluster.py:22 users/models/user.py:68
#: users/templates/users/user_detail.html:76
msgid "Phone"
msgstr "手机"
......@@ -392,7 +411,7 @@ msgid "Default"
msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:13
#: users/models/user.py:330
#: users/models/user.py:345
msgid "System"
msgstr "系统"
......@@ -404,13 +423,6 @@ msgstr "默认Cluster"
msgid "Cluster"
msgstr "集群"
#: assets/models/domain.py:45 assets/models/user.py:106
#: assets/templates/assets/domain_gateway_list.html:59
#: assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:28
msgid "Protocol"
msgstr "协议"
#: assets/models/group.py:30
msgid "Asset group"
msgstr "资产组"
......@@ -422,22 +434,22 @@ msgstr "默认资产组"
#: assets/models/label.py:14 audits/models.py:9
#: audits/templates/audits/ftp_log_list.html:33
#: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:14
#: perms/forms.py:31 perms/models.py:20
#: perms/forms.py:31 perms/models.py:30
#: perms/templates/perms/asset_permission_create_update.html:36
#: perms/templates/perms/asset_permission_list.html:54
#: perms/templates/perms/asset_permission_list.html:133
#: terminal/backends/command/models.py:10 terminal/models.py:122
#: terminal/backends/command/models.py:10 terminal/models.py:126
#: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:273
#: users/models/user.py:30 users/models/user.py:318
#: terminal/templates/terminal/session_list.html:71 users/forms.py:282
#: users/models/user.py:31 users/models/user.py:333
#: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:339
#: users/templates/users/user_group_list.html:13 users/views/user.py:361
msgid "User"
msgstr "用户"
#: assets/models/label.py:18 assets/models/node.py:15
#: assets/models/label.py:18 assets/models/node.py:16
#: assets/templates/assets/label_list.html:15 common/models.py:27
msgid "Value"
msgstr "值"
......@@ -446,11 +458,19 @@ msgstr "值"
msgid "Category"
msgstr "分类"
#: assets/models/node.py:14
#: assets/models/node.py:15
msgid "Key"
msgstr ""
#: assets/models/user.py:104
#: assets/models/user.py:108
msgid "Automatic login"
msgstr "自动登录"
#: assets/models/user.py:109
msgid "Manually login"
msgstr "手动登录"
#: assets/models/user.py:113
#: assets/templates/assets/_asset_group_bulk_update_modal.html:11
#: assets/templates/assets/system_user_asset.html:21
#: assets/views/admin_user.py:29 assets/views/admin_user.py:47
......@@ -460,7 +480,7 @@ msgstr ""
#: assets/views/asset.py:197 assets/views/domain.py:29
#: assets/views/domain.py:45 assets/views/domain.py:61
#: assets/views/domain.py:74 assets/views/domain.py:98
#: assets/views/domain.py:126 assets/views/domain.py:150
#: assets/views/domain.py:126 assets/views/domain.py:145
#: assets/views/label.py:26 assets/views/label.py:42 assets/views/label.py:58
#: assets/views/system_user.py:28 assets/views/system_user.py:44
#: assets/views/system_user.py:60 assets/views/system_user.py:74
......@@ -468,32 +488,37 @@ msgstr ""
msgid "Assets"
msgstr "资产管理"
#: assets/models/user.py:105
#: assets/models/user.py:114
msgid "Priority"
msgstr "优先级"
#: assets/models/user.py:107 assets/templates/assets/_system_user.html:58
#: assets/templates/assets/system_user_detail.html:118
#: assets/templates/assets/system_user_update.html:11
#: assets/models/user.py:116 assets/templates/assets/_system_user.html:59
#: assets/templates/assets/system_user_detail.html:122
#: assets/templates/assets/system_user_update.html:10
msgid "Auto push"
msgstr "自动推送"
#: assets/models/user.py:108 assets/templates/assets/system_user_detail.html:70
#: assets/models/user.py:117 assets/templates/assets/system_user_detail.html:74
msgid "Sudo"
msgstr "Sudo"
#: assets/models/user.py:109 assets/templates/assets/system_user_detail.html:75
#: assets/models/user.py:118 assets/templates/assets/system_user_detail.html:79
msgid "Shell"
msgstr "Shell"
#: assets/models/user.py:149 audits/models.py:12
#: assets/models/user.py:119 assets/templates/assets/system_user_detail.html:66
#: assets/templates/assets/system_user_list.html:29
msgid "Login mode"
msgstr "登录模式"
#: assets/models/user.py:159 audits/models.py:12
#: audits/templates/audits/ftp_log_list.html:49
#: audits/templates/audits/ftp_log_list.html:73 perms/forms.py:43
#: perms/models.py:24 perms/models.py:69
#: perms/models.py:34 perms/models.py:78
#: perms/templates/perms/asset_permission_detail.html:140
#: perms/templates/perms/asset_permission_list.html:58
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:26
#: terminal/backends/command/models.py:12 terminal/models.py:124
#: terminal/backends/command/models.py:12 terminal/models.py:128
#: terminal/templates/terminal/command_list.html:48
#: terminal/templates/terminal/command_list.html:74
#: terminal/templates/terminal/session_list.html:49
......@@ -561,7 +586,6 @@ msgid "Select System Users"
msgstr "选择系统用户"
#: assets/templates/assets/_asset_group_bulk_update_modal.html:34
#, fuzzy
msgid "Enable-MFA"
msgstr "启用MFA"
......@@ -601,72 +625,73 @@ msgid "Basic"
msgstr "基本"
#: assets/templates/assets/_system_user.html:44
#: assets/templates/assets/asset_create.html:25
#: assets/templates/assets/asset_update.html:30
#: assets/templates/assets/asset_create.html:26
#: assets/templates/assets/asset_update.html:31
#: assets/templates/assets/gateway_create_update.html:45
#: assets/templates/assets/system_user_update.html:7
#: users/templates/users/_user.html:21
msgid "Auth"
msgstr "认证"
#: assets/templates/assets/_system_user.html:47
#: assets/templates/assets/_system_user.html:48
msgid "Auto generate key"
msgstr "自动生成密钥"
#: assets/templates/assets/_system_user.html:64
#: assets/templates/assets/asset_create.html:59
#: assets/templates/assets/asset_update.html:63
#: assets/templates/assets/_system_user.html:65
#: assets/templates/assets/asset_create.html:60
#: assets/templates/assets/asset_update.html:64
#: assets/templates/assets/gateway_create_update.html:53
#: perms/templates/perms/asset_permission_create_update.html:45
#: terminal/templates/terminal/terminal_update.html:42
msgid "Other"
msgstr "其它"
#: assets/templates/assets/_system_user.html:70
#: assets/templates/assets/_system_user.html:71
#: assets/templates/assets/admin_user_create_update.html:45
#: assets/templates/assets/asset_bulk_update.html:23
#: assets/templates/assets/asset_create.html:66
#: assets/templates/assets/asset_update.html:70
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/domain_create_update.html:16
#: assets/templates/assets/gateway_create_update.html:58
#: assets/templates/assets/label_create_update.html:18
#: common/templates/common/basic_setting.html:58
#: common/templates/common/email_setting.html:59
#: common/templates/common/ldap_setting.html:59
#: common/templates/common/terminal_setting.html:101
#: common/templates/common/basic_setting.html:61
#: common/templates/common/email_setting.html:62
#: common/templates/common/ldap_setting.html:62
#: common/templates/common/security_setting.html:70
#: common/templates/common/terminal_setting.html:106
#: perms/templates/perms/asset_permission_create_update.html:69
#: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_password_update.html:58
#: users/templates/users/user_profile.html:181
#: users/templates/users/user_password_update.html:70
#: users/templates/users/user_profile.html:188
#: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76
msgid "Reset"
msgstr "重置"
#: assets/templates/assets/_system_user.html:71
#: assets/templates/assets/_system_user.html:72
#: assets/templates/assets/admin_user_create_update.html:46
#: assets/templates/assets/asset_bulk_update.html:24
#: assets/templates/assets/asset_create.html:67
#: assets/templates/assets/asset_create.html:68
#: assets/templates/assets/asset_list.html:108
#: assets/templates/assets/asset_update.html:71
#: assets/templates/assets/asset_update.html:72
#: assets/templates/assets/domain_create_update.html:17
#: assets/templates/assets/gateway_create_update.html:59
#: assets/templates/assets/label_create_update.html:19
#: common/templates/common/basic_setting.html:59
#: common/templates/common/email_setting.html:60
#: common/templates/common/ldap_setting.html:60
#: common/templates/common/terminal_setting.html:103
#: common/templates/common/basic_setting.html:62
#: common/templates/common/email_setting.html:63
#: common/templates/common/ldap_setting.html:63
#: common/templates/common/security_setting.html:71
#: common/templates/common/terminal_setting.html:108
#: perms/templates/perms/asset_permission_create_update.html:70
#: terminal/templates/terminal/session_list.html:120
#: terminal/templates/terminal/session_list.html:126
#: terminal/templates/terminal/terminal_update.html:48
#: users/templates/users/_user.html:47
#: users/templates/users/forgot_password.html:44
#: users/templates/users/user_bulk_update.html:24
#: users/templates/users/user_list.html:44
#: users/templates/users/user_password_update.html:59
#: users/templates/users/user_list.html:45
#: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile_update.html:64
#: users/templates/users/user_pubkey_update.html:77
msgid "Submit"
......@@ -699,16 +724,15 @@ msgstr "资产列表"
#: assets/templates/assets/admin_user_assets.html:54
#: assets/templates/assets/admin_user_list.html:26
#: assets/templates/assets/asset_list.html:90
#: assets/templates/assets/system_user_asset.html:52
#: assets/templates/assets/system_user_list.html:30
#: assets/templates/assets/system_user_list.html:31
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
#: assets/templates/assets/admin_user_assets.html:66
#: assets/templates/assets/system_user_asset.html:64
#: assets/templates/assets/system_user_detail.html:112
#: assets/templates/assets/system_user_detail.html:116
#: perms/templates/perms/asset_permission_detail.html:114
msgid "Quick update"
msgstr "快速更新"
......@@ -721,21 +745,21 @@ msgstr "测试可连接性"
#: assets/templates/assets/admin_user_assets.html:75
#: assets/templates/assets/asset_detail.html:171
#: assets/templates/assets/system_user_asset.html:81
#: assets/templates/assets/system_user_detail.html:147
#: assets/templates/assets/system_user_detail.html:151
msgid "Test"
msgstr "测试"
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:85
#: assets/templates/assets/asset_detail.html:24
#: assets/templates/assets/asset_list.html:170
#: assets/templates/assets/asset_list.html:175
#: assets/templates/assets/domain_detail.html:24
#: assets/templates/assets/domain_detail.html:103
#: assets/templates/assets/domain_gateway_list.html:85
#: assets/templates/assets/domain_list.html:42
#: assets/templates/assets/label_list.html:38
#: assets/templates/assets/system_user_detail.html:26
#: assets/templates/assets/system_user_list.html:88
#: assets/templates/assets/system_user_list.html:89
#: perms/templates/perms/asset_permission_detail.html:30
#: perms/templates/perms/asset_permission_list.html:191
#: terminal/templates/terminal/terminal_detail.html:16
......@@ -743,23 +767,23 @@ msgstr "测试"
#: users/templates/users/user_detail.html:25
#: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:76
#: users/templates/users/user_profile.html:144
#: users/templates/users/user_profile.html:173
#: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:151
#: users/templates/users/user_profile.html:180
msgid "Update"
msgstr "更新"
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:86
#: assets/templates/assets/asset_detail.html:28
#: assets/templates/assets/asset_list.html:171
#: assets/templates/assets/asset_list.html:176
#: assets/templates/assets/domain_detail.html:28
#: assets/templates/assets/domain_detail.html:104
#: assets/templates/assets/domain_gateway_list.html:86
#: assets/templates/assets/domain_list.html:43
#: assets/templates/assets/label_list.html:39
#: assets/templates/assets/system_user_detail.html:30
#: assets/templates/assets/system_user_list.html:89
#: assets/templates/assets/system_user_list.html:90
#: ops/templates/ops/task_list.html:72
#: perms/templates/perms/asset_permission_detail.html:34
#: perms/templates/perms/asset_permission_list.html:192
......@@ -767,8 +791,8 @@ msgstr "更新"
#: users/templates/users/user_detail.html:30
#: users/templates/users/user_group_detail.html:32
#: users/templates/users/user_group_list.html:45
#: users/templates/users/user_list.html:80
#: users/templates/users/user_list.html:84
#: users/templates/users/user_list.html:81
#: users/templates/users/user_list.html:85
msgid "Delete"
msgstr "删除"
......@@ -783,17 +807,17 @@ msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:200
#: assets/templates/assets/asset_list.html:600
#: assets/templates/assets/system_user_detail.html:183
#: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22
#: assets/templates/assets/asset_list.html:638
#: assets/templates/assets/system_user_detail.html:196
#: assets/templates/assets/system_user_list.html:139 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:362
#: users/templates/users/user_detail.html:387
#: users/templates/users/user_detail.html:410
#: users/templates/users/user_detail.html:366
#: users/templates/users/user_detail.html:391
#: users/templates/users/user_detail.html:414
#: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:196
#: users/templates/users/user_profile.html:215
#: users/templates/users/user_list.html:200
#: users/templates/users/user_profile.html:222
msgid "Confirm"
msgstr "确认"
......@@ -803,12 +827,12 @@ msgid "Create admin user"
msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:27
#: assets/templates/assets/system_user_list.html:31
#: assets/templates/assets/system_user_list.html:32
msgid "Unreachable"
msgstr "不可达"
#: assets/templates/assets/admin_user_list.html:28
#: assets/templates/assets/system_user_list.html:32
#: assets/templates/assets/system_user_list.html:33
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
......@@ -819,14 +843,14 @@ msgstr "比例"
#: assets/templates/assets/domain_gateway_list.html:62
#: assets/templates/assets/domain_list.html:18
#: assets/templates/assets/label_list.html:17
#: assets/templates/assets/system_user_list.html:34
#: assets/templates/assets/system_user_list.html:35
#: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64
#: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:42
#: perms/templates/perms/asset_permission_list.html:60
#: terminal/templates/terminal/session_list.html:80
#: terminal/templates/terminal/session_list.html:81
#: terminal/templates/terminal/terminal_list.html:36
#: users/templates/users/user_group_list.html:15
#: users/templates/users/user_list.html:28
#: users/templates/users/user_list.html:29
msgid "Action"
msgstr "动作"
......@@ -843,31 +867,31 @@ msgid "Disk"
msgstr "硬盘"
#: assets/templates/assets/asset_detail.html:121
#: users/templates/users/user_detail.html:111
#: users/templates/users/user_profile.html:97
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:104
msgid "Date joined"
msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:137
#: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:130
#: users/templates/users/user_profile.html:135
#: users/templates/users/user_detail.html:134
#: users/templates/users/user_profile.html:142
msgid "Quick modify"
msgstr "快速修改"
#: assets/templates/assets/asset_detail.html:143
#: assets/templates/assets/asset_list.html:89
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:25
#: perms/models.py:70
#: assets/templates/assets/user_asset_list.html:47 perms/models.py:35
#: perms/models.py:79
#: perms/templates/perms/asset_permission_create_update.html:47
#: perms/templates/perms/asset_permission_detail.html:120
#: perms/templates/perms/asset_permission_list.html:59
#: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:136
#: users/templates/users/user_detail.html:140
#: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_list.html:27
#: users/templates/users/user_list.html:28
#: users/templates/users/user_profile.html:63
msgid "Active"
msgstr "激活中"
......@@ -881,8 +905,8 @@ msgid "Refresh"
msgstr "刷新"
#: assets/templates/assets/asset_detail.html:300
#: users/templates/users/user_detail.html:282
#: users/templates/users/user_detail.html:309
#: users/templates/users/user_detail.html:286
#: users/templates/users/user_detail.html:313
msgid "Update successfully!"
msgstr "更新成功"
......@@ -905,12 +929,12 @@ msgid "Hardware"
msgstr "硬件"
#: assets/templates/assets/asset_list.html:100
#: users/templates/users/user_list.html:37
#: users/templates/users/user_list.html:38
msgid "Delete selected"
msgstr "批量删除"
#: assets/templates/assets/asset_list.html:101
#: users/templates/users/user_list.html:38
#: users/templates/users/user_list.html:39
msgid "Update selected"
msgstr "批量更新"
......@@ -919,12 +943,12 @@ msgid "Remove from this node"
msgstr "从节点移除"
#: assets/templates/assets/asset_list.html:103
#: users/templates/users/user_list.html:39
#: users/templates/users/user_list.html:40
msgid "Deactive selected"
msgstr "禁用所选"
#: assets/templates/assets/asset_list.html:104
#: users/templates/users/user_list.html:40
#: users/templates/users/user_list.html:41
msgid "Active selected"
msgstr "激活所选"
......@@ -956,45 +980,53 @@ msgstr "更新节点资产硬件信息"
msgid "Test node connective"
msgstr "测试节点资产可连接性"
#: assets/templates/assets/asset_list.html:204
#: assets/templates/assets/asset_list.html:131
msgid "Display only current node assets"
msgstr "仅显示当前节点资产"
#: assets/templates/assets/asset_list.html:132
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:218
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/asset_list.html:216
#: assets/templates/assets/asset_list.html:230
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/asset_list.html:218
#: assets/templates/assets/asset_list.html:232
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/asset_list.html:595
#: assets/templates/assets/system_user_list.html:133
#: users/templates/users/user_detail.html:357
#: users/templates/users/user_detail.html:382
#: assets/templates/assets/asset_list.html:633
#: assets/templates/assets/system_user_list.html:134
#: users/templates/users/user_detail.html:361
#: users/templates/users/user_detail.html:386
#: users/templates/users/user_group_list.html:81
#: users/templates/users/user_list.html:191
#: users/templates/users/user_list.html:195
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:596
#: assets/templates/assets/asset_list.html:634
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:604
#: assets/templates/assets/asset_list.html:642
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:605
#: assets/templates/assets/asset_list.html:610
#: assets/templates/assets/asset_list.html:643
#: assets/templates/assets/asset_list.html:648
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:609
#: assets/templates/assets/asset_list.html:647
msgid "Asset Deleting failed."
msgstr "删除失败"
#: assets/templates/assets/asset_update.html:59
#: assets/templates/assets/asset_update.html:60
msgid "Configuration"
msgstr "配置"
......@@ -1025,8 +1057,9 @@ msgid "Create gateway"
msgstr "创建网关"
#: assets/templates/assets/domain_gateway_list.html:87
#: common/templates/common/email_setting.html:58
#: common/templates/common/ldap_setting.html:58
#: assets/templates/assets/domain_gateway_list.html:89
#: common/templates/common/email_setting.html:61
#: common/templates/common/ldap_setting.html:61
msgid "Test connection"
msgstr "测试连接"
......@@ -1043,17 +1076,17 @@ msgid "Assets of "
msgstr "资产"
#: assets/templates/assets/system_user_asset.html:70
#: assets/templates/assets/system_user_detail.html:135
#: assets/templates/assets/system_user_detail.html:139
msgid "Push system user now"
msgstr "立刻推送系统"
#: assets/templates/assets/system_user_asset.html:73
#: assets/templates/assets/system_user_detail.html:138
#: assets/templates/assets/system_user_detail.html:142
msgid "Push"
msgstr "推送"
#: assets/templates/assets/system_user_asset.html:78
#: assets/templates/assets/system_user_detail.html:144
#: assets/templates/assets/system_user_detail.html:148
msgid "Test assets connective"
msgstr "测试资产可连接性"
......@@ -1065,37 +1098,50 @@ msgstr "任务已下发,查看ops任务列表"
msgid "Task has been send, seen left assets status"
msgstr "任务已下发,查看左侧资产状态"
#: assets/templates/assets/system_user_detail.html:81
#: assets/templates/assets/system_user_detail.html:85
msgid "Home"
msgstr "家目录"
#: assets/templates/assets/system_user_detail.html:87
#: assets/templates/assets/system_user_detail.html:91
msgid "Uid"
msgstr "Uid"
#: assets/templates/assets/system_user_detail.html:174
#: assets/templates/assets/system_user_detail.html:157
#: assets/templates/assets/system_user_detail.html:343
msgid "Clear auth"
msgstr "清除认证信息"
#: assets/templates/assets/system_user_detail.html:160
msgid "Clear"
msgstr "清除"
#: assets/templates/assets/system_user_detail.html:187
msgid "Add to node"
msgstr "添加到节点"
#: assets/templates/assets/system_user_detail.html:343
msgid "success"
msgstr "成功"
#: assets/templates/assets/system_user_list.html:18
#: assets/views/system_user.py:45
msgid "Create system user"
msgstr "创建系统用户"
#: assets/templates/assets/system_user_list.html:134
#: assets/templates/assets/system_user_list.html:135
msgid "This will delete the selected System Users !!!"
msgstr "删除选择系统用户"
#: assets/templates/assets/system_user_list.html:142
#: assets/templates/assets/system_user_list.html:143
msgid "System Users Deleted."
msgstr "已被删除"
#: assets/templates/assets/system_user_list.html:143
#: assets/templates/assets/system_user_list.html:148
#: assets/templates/assets/system_user_list.html:144
#: assets/templates/assets/system_user_list.html:149
msgid "System Users Delete"
msgstr "删除系统用户"
#: assets/templates/assets/system_user_list.html:147
#: assets/templates/assets/system_user_list.html:148
msgid "System Users Deleting failed."
msgstr "系统用户删除失败"
......@@ -1123,7 +1169,7 @@ msgstr "批量更新资产"
msgid "Update asset"
msgstr "更新资产"
#: assets/views/asset.py:311
#: assets/views/asset.py:314
msgid "already exists"
msgstr "已经存在"
......@@ -1143,7 +1189,7 @@ msgstr "网域详情"
msgid "Domain gateway list"
msgstr "域网关列表"
#: assets/views/domain.py:151
#: assets/views/domain.py:146
msgid "Update gateway"
msgstr "创建网关"
......@@ -1176,7 +1222,7 @@ msgid "System user asset"
msgstr "系统用户集群资产"
#: audits/models.py:10 audits/templates/audits/ftp_log_list.html:74
#: terminal/models.py:126 terminal/templates/terminal/session_list.html:74
#: terminal/models.py:130 terminal/templates/terminal/session_list.html:74
#: terminal/templates/terminal/terminal_detail.html:47
msgid "Remote addr"
msgstr "远端地址"
......@@ -1191,16 +1237,16 @@ msgid "Filename"
msgstr "文件名"
#: audits/models.py:15 audits/templates/audits/ftp_log_list.html:77
#: ops/templates/ops/task_list.html:39
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:66
msgid "Success"
msgstr "成功"
#: audits/templates/audits/ftp_log_list.html:78
#: ops/templates/ops/adhoc_history.html:52
#: ops/templates/ops/adhoc_history_detail.html:61
#: ops/templates/ops/task_history.html:58 perms/models.py:26
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:132
#: terminal/templates/terminal/session_list.html:77
#: ops/templates/ops/task_history.html:58 perms/models.py:36
#: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:137
#: terminal/templates/terminal/session_list.html:78
msgid "Date start"
msgstr "开始日期"
......@@ -1216,11 +1262,11 @@ msgstr "FTP日志"
msgid "Test mail sent to {}, please check"
msgstr "邮件已经发送{}, 请检查"
#: common/api.py:52
#: common/api.py:42
msgid "Test ldap success"
msgstr "连接LDAP成功"
#: common/api.py:90
#: common/api.py:80
msgid "Match {} s users"
msgstr "匹配 {} 个用户"
......@@ -1355,7 +1401,7 @@ msgstr "密码认证"
msgid "Public key auth"
msgstr "密钥认证"
#: common/forms.py:159 common/templates/common/terminal_setting.html:63
#: common/forms.py:159 common/templates/common/terminal_setting.html:68
#: terminal/forms.py:30 terminal/models.py:20
msgid "Command storage"
msgstr "命令存储"
......@@ -1366,7 +1412,7 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端命令存储,default是默认用的存储方式"
#: common/forms.py:165 common/templates/common/terminal_setting.html:81
#: common/forms.py:165 common/templates/common/terminal_setting.html:86
#: terminal/forms.py:35 terminal/models.py:21
msgid "Replay storage"
msgstr "录像存储"
......@@ -1377,6 +1423,75 @@ msgid ""
"other storage and some terminal using"
msgstr "设置终端录像存储,default是默认用的存储方式"
#: common/forms.py:176
msgid "MFA Secondary certification"
msgstr "MFA 二次认证"
#: common/forms.py:178
msgid ""
"After opening, the user login must use MFA secondary authentication (valid "
"for all users, including administrators)"
msgstr "开启后,用户登录必须使用MFA二次认证(对所有用户有效,包括管理员)"
#: common/forms.py:185
msgid "Limit the number of login failures"
msgstr "限制登录失败次数"
#: common/forms.py:190
msgid "No logon interval"
msgstr "禁止登录时间间隔"
#: common/forms.py:192
msgid ""
"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."
msgstr ""
"提示:(单位 / 分钟)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
#: common/forms.py:198
msgid "Password minimum length"
msgstr "密码最小长度 "
#: common/forms.py:205
msgid "Must contain capital letters"
msgstr "必须包含大写字母"
#: common/forms.py:207
msgid ""
"After opening, the user password changes and resets must contain uppercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:213
msgid "Must contain lowercase letters"
msgstr "必须包含小写字母"
#: common/forms.py:214
msgid ""
"After opening, the user password changes and resets must contain lowercase "
"letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:220
msgid "Must contain numeric characters"
msgstr "必须包含数字字符"
#: common/forms.py:221
msgid ""
"After opening, the user password changes and resets must contain numeric "
"characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:227
msgid "Must contain special characters"
msgstr "必须包含特殊字符"
#: common/forms.py:228
msgid ""
"After opening, the user password changes and resets must contain special "
"characters"
msgstr "开启后,用户密码修改、重置必须包含特殊字符"
#: common/mixins.py:29
msgid "is discard"
msgstr ""
......@@ -1385,21 +1500,24 @@ msgstr ""
msgid "discard time"
msgstr ""
#: common/models.py:29 users/templates/users/user_detail.html:96
#: common/models.py:29 users/models/authentication.py:51
#: users/templates/users/user_detail.html:96
msgid "Enabled"
msgstr "启用"
#: common/templates/common/basic_setting.html:15
#: common/templates/common/email_setting.html:15
#: common/templates/common/ldap_setting.html:15
#: common/templates/common/security_setting.html:15
#: common/templates/common/terminal_setting.html:16
#: common/templates/common/terminal_setting.html:42 common/views.py:22
#: common/templates/common/terminal_setting.html:46 common/views.py:22
msgid "Basic setting"
msgstr "基本设置"
#: common/templates/common/basic_setting.html:18
#: common/templates/common/email_setting.html:18
#: common/templates/common/ldap_setting.html:18
#: common/templates/common/security_setting.html:18
#: common/templates/common/terminal_setting.html:20 common/views.py:48
msgid "Email setting"
msgstr "邮件设置"
......@@ -1407,6 +1525,7 @@ msgstr "邮件设置"
#: common/templates/common/basic_setting.html:21
#: common/templates/common/email_setting.html:21
#: common/templates/common/ldap_setting.html:21
#: common/templates/common/security_setting.html:21
#: common/templates/common/terminal_setting.html:24 common/views.py:74
msgid "LDAP setting"
msgstr "LDAP设置"
......@@ -1414,22 +1533,44 @@ msgstr "LDAP设置"
#: common/templates/common/basic_setting.html:24
#: common/templates/common/email_setting.html:24
#: common/templates/common/ldap_setting.html:24
#: common/templates/common/security_setting.html:24
#: common/templates/common/terminal_setting.html:28 common/views.py:104
msgid "Terminal setting"
msgstr "终端设置"
#: common/templates/common/terminal_setting.html:68
#: common/templates/common/terminal_setting.html:86
#: common/templates/common/basic_setting.html:27
#: common/templates/common/email_setting.html:27
#: common/templates/common/ldap_setting.html:27
#: common/templates/common/security_setting.html:27
#: common/templates/common/terminal_setting.html:31 common/views.py:132
msgid "Security setting"
msgstr "安全设置"
#: common/templates/common/security_setting.html:42
msgid "User login settings"
msgstr "用户登录设置"
#: common/templates/common/security_setting.html:46
msgid "Password check rule"
msgstr "密码校验规则"
#: common/templates/common/terminal_setting.html:73
#: common/templates/common/terminal_setting.html:91
#: users/templates/users/login_log_list.html:50
msgid "Type"
msgstr "类型"
#: common/validators.py:7
msgid "Special char not allowed"
msgstr "不能包含特殊字符"
#: common/views.py:21 common/views.py:47 common/views.py:73 common/views.py:103
#: templates/_nav.html:81
#: common/views.py:131 templates/_nav.html:81
msgid "Settings"
msgstr "系统设置"
#: common/views.py:32 common/views.py:58 common/views.py:86 common/views.py:116
#: common/views.py:142
msgid "Update setting successfully, please restart program"
msgstr "更新设置成功, 请手动重启程序"
......@@ -1678,7 +1819,7 @@ msgid "Versions"
msgstr "版本"
#: ops/templates/ops/task_list.html:40
#: users/templates/users/login_log_list.html:54
#: users/templates/users/login_log_list.html:57
msgid "Date"
msgstr "日期"
......@@ -1703,17 +1844,17 @@ msgstr "任务列表"
msgid "Task run history"
msgstr "执行历史"
#: perms/forms.py:18 users/forms.py:230 users/forms.py:235 users/forms.py:247
#: users/forms.py:277
#: perms/forms.py:18 users/forms.py:239 users/forms.py:244 users/forms.py:256
#: users/forms.py:286
msgid "Select users"
msgstr "选择用户"
#: perms/forms.py:34 perms/models.py:21 perms/models.py:68
#: perms/forms.py:34 perms/models.py:31 perms/models.py:77
#: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:136 templates/_nav.html:14
#: users/models/group.py:25 users/models/user.py:48
#: users/models/group.py:23 users/models/user.py:55
#: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:188
#: users/templates/users/user_detail.html:192
#: users/templates/users/user_list.html:26
msgid "User group"
msgstr "用户组"
......@@ -1726,14 +1867,14 @@ msgstr ""
msgid "Asset or group at least one required"
msgstr ""
#: perms/models.py:27 perms/models.py:71
#: perms/models.py:37 perms/models.py:80
#: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:80 users/templates/users/user_detail.html:103
#: users/templates/users/user_profile.html:105
#: users/models/user.py:87 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112
msgid "Date expired"
msgstr "失效日期"
#: perms/models.py:81 templates/_nav.html:34
#: perms/models.py:90 templates/_nav.html:34
msgid "Asset permission"
msgstr "资产授权"
......@@ -1766,10 +1907,14 @@ msgid "Add node to this permission"
msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:205
#: users/templates/users/user_detail.html:209
msgid "Join"
msgstr "加入"
#: perms/templates/perms/asset_permission_create_update.html:53
msgid "Validity period"
msgstr "有效期"
#: perms/templates/perms/asset_permission_detail.html:66
msgid "User count"
msgstr "用户数量"
......@@ -1852,14 +1997,14 @@ msgstr "商业支持"
msgid "Docs"
msgstr "文档"
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:113
#: templates/_header_bar.html:37 templates/_nav_user.html:9 users/forms.py:122
#: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:37
#: users/templates/users/user_password_update.html:39
#: users/templates/users/user_profile.html:17
#: users/templates/users/user_profile_update.html:37
#: users/templates/users/user_profile_update.html:57
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:322
#: users/templates/users/user_pubkey_update.html:37 users/views/user.py:343
msgid "Profile"
msgstr "个人信息"
......@@ -1876,7 +2021,7 @@ msgid "Logout"
msgstr "注销登录"
#: templates/_header_bar.html:49 users/templates/users/login.html:44
#: users/templates/users/login.html:64
#: users/templates/users/login.html:68
msgid "Login"
msgstr "登录"
......@@ -1916,13 +2061,13 @@ msgstr "关闭"
#: templates/_nav.html:10 users/views/group.py:28 users/views/group.py:44
#: users/views/group.py:62 users/views/group.py:79 users/views/group.py:95
#: users/views/login.py:241 users/views/login.py:289 users/views/user.py:64
#: users/views/user.py:79 users/views/user.py:99 users/views/user.py:155
#: users/views/user.py:310 users/views/user.py:357 users/views/user.py:379
#: users/views/login.py:330 users/views/login.py:388 users/views/user.py:65
#: users/views/user.py:80 users/views/user.py:102 users/views/user.py:175
#: users/views/user.py:330 users/views/user.py:380 users/views/user.py:415
msgid "Users"
msgstr "用户管理"
#: templates/_nav.html:13 users/views/user.py:65
#: templates/_nav.html:13 users/views/user.py:66
msgid "User list"
msgstr "用户列表"
......@@ -1950,11 +2095,10 @@ msgstr "命令记录"
msgid "Web terminal"
msgstr "Web终端"
#: templates/_nav.html:51 terminal/templates/terminal/session_list.html:75
#: terminal/views/command.py:47 terminal/views/session.py:75
#: terminal/views/session.py:93 terminal/views/session.py:115
#: terminal/views/terminal.py:31 terminal/views/terminal.py:46
#: terminal/views/terminal.py:58
#: templates/_nav.html:51 terminal/views/command.py:47
#: terminal/views/session.py:75 terminal/views/session.py:93
#: terminal/views/session.py:115 terminal/views/terminal.py:31
#: terminal/views/terminal.py:46 terminal/views/terminal.py:58
msgid "Terminal"
msgstr "终端管理"
......@@ -2033,26 +2177,26 @@ msgstr "线程数"
msgid "Boot Time"
msgstr "运行时间"
#: terminal/models.py:128 terminal/templates/terminal/session_list.html:102
#: terminal/models.py:132 terminal/templates/terminal/session_list.html:104
msgid "Replay"
msgstr "回放"
#: terminal/models.py:129 terminal/templates/terminal/command_list.html:55
#: terminal/models.py:133 terminal/templates/terminal/command_list.html:55
#: terminal/templates/terminal/command_list.html:71
#: terminal/templates/terminal/session_detail.html:48
#: terminal/templates/terminal/session_list.html:76
#: terminal/templates/terminal/session_list.html:77
msgid "Command"
msgstr "命令"
#: terminal/models.py:131
#: terminal/models.py:136
msgid "Date last active"
msgstr "最后活跃日期"
#: terminal/models.py:133
#: terminal/models.py:138
msgid "Date end"
msgstr "结束日期"
#: terminal/models.py:150
#: terminal/models.py:155
msgid "Args"
msgstr "参数"
......@@ -2091,23 +2235,28 @@ msgstr "监控"
msgid "Terminate session"
msgstr "终止会话"
#: terminal/templates/terminal/session_list.html:79
#: terminal/templates/terminal/session_list.html:76
msgid "Login from"
msgstr "登录来源"
#: terminal/templates/terminal/session_list.html:80
msgid "Duration"
msgstr "时长"
#: terminal/templates/terminal/session_list.html:104
#: terminal/templates/terminal/session_list.html:106
msgid "Monitor"
msgstr "监控"
#: terminal/templates/terminal/session_list.html:105
#: terminal/templates/terminal/session_list.html:108
#: terminal/templates/terminal/session_list.html:110
msgid "Terminate"
msgstr "终断"
#: terminal/templates/terminal/session_list.html:116
#: terminal/templates/terminal/session_list.html:122
msgid "Terminate selected"
msgstr "终断所选"
#: terminal/templates/terminal/session_list.html:136
#: terminal/templates/terminal/session_list.html:142
msgid "Terminate task send, waiting ..."
msgstr "终断任务已发送,请等待"
......@@ -2177,6 +2326,10 @@ msgid ""
"You should use your ssh client tools connect terminal: {} <br /> <br />{}"
msgstr "你可以使用ssh客户端工具连接终端"
#: users/api.py:208 users/templates/users/login.html:50
msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试"
#: users/authentication.py:56
msgid "Invalid signature header. No credentials provided."
msgstr ""
......@@ -2228,11 +2381,11 @@ msgstr ""
msgid "Invalid token or cache refreshed."
msgstr ""
#: users/forms.py:30
#: users/forms.py:39
msgid "MFA code"
msgstr "MFA 验证码"
#: users/forms.py:41 users/models/user.py:52
#: users/forms.py:50 users/models/user.py:59
#: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:87
#: users/templates/users/user_list.html:25
......@@ -2240,31 +2393,31 @@ msgstr "MFA 验证码"
msgid "Role"
msgstr "角色"
#: users/forms.py:44 users/forms.py:193
#: users/forms.py:53 users/forms.py:202
msgid "ssh public key"
msgstr "ssh公钥"
#: users/forms.py:45 users/forms.py:194
#: users/forms.py:54 users/forms.py:203
msgid "ssh-rsa AAAA..."
msgstr ""
#: users/forms.py:46
#: users/forms.py:55
msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里"
#: users/forms.py:64 users/templates/users/user_detail.html:196
#: users/forms.py:73 users/templates/users/user_detail.html:200
msgid "Join user groups"
msgstr "添加到用户组"
#: users/forms.py:75 users/forms.py:208
#: users/forms.py:84 users/forms.py:217
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms.py:79 users/forms.py:212 users/serializers.py:45
#: users/forms.py:88 users/forms.py:221 users/serializers.py:48
msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法"
#: users/forms.py:119
#: users/forms.py:128
msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick "
......@@ -2273,16 +2426,17 @@ msgstr ""
"提示:启用之后您将会在下次登录时进入MFA绑定流程;您也可以在(个人信息->快速修"
"改->更改MFA设置)中直接绑定!"
#: users/forms.py:129
#: users/forms.py:138
msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证,使账号更加安全."
#: users/forms.py:134 users/models/user.py:64
#: users/forms.py:143 users/models/authentication.py:75 users/models/user.py:71
#: users/templates/users/first_login.html:45
#: users/templates/users/login_log_list.html:54
msgid "MFA"
msgstr "MFA"
#: users/forms.py:139
#: users/forms.py:148
msgid ""
"In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex "
......@@ -2291,42 +2445,43 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码,启用MFA认证)"
#: users/forms.py:146 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:110
#: users/forms.py:155 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130
msgid "Finish"
msgstr "完成"
#: users/forms.py:152
#: users/forms.py:161
msgid "Old password"
msgstr "原来密码"
#: users/forms.py:157
#: users/forms.py:166
msgid "New password"
msgstr "新密码"
#: users/forms.py:162
#: users/forms.py:171
msgid "Confirm password"
msgstr "确认密码"
#: users/forms.py:172
#: users/forms.py:181
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms.py:180
#: users/forms.py:189
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms.py:191
#: users/forms.py:200
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms.py:195
#: users/forms.py:204
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms.py:223 users/models/user.py:72
#: users/forms.py:232 users/models/user.py:79
#: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:43
#: users/templates/users/user_password_update.html:45
#: users/templates/users/user_profile.html:68
#: users/templates/users/user_profile_update.html:43
#: users/templates/users/user_pubkey_update.html:43
......@@ -2337,63 +2492,99 @@ msgstr "ssh公钥"
msgid "Private Token"
msgstr "ssh密钥"
#: users/models/authentication.py:46
#: users/models/authentication.py:50 users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/models/authentication.py:52 users/models/authentication.py:60
msgid "-"
msgstr ""
#: users/models/authentication.py:61
msgid "Username/password check failed"
msgstr "用户名/密码 校验失败"
#: users/models/authentication.py:62
msgid "MFA authentication failed"
msgstr "MFA 认证失败"
#: users/models/authentication.py:67
msgid "Failed"
msgstr "失败"
#: users/models/authentication.py:71
msgid "Login type"
msgstr "登录方式"
#: users/models/authentication.py:47
#: users/models/authentication.py:72
msgid "Login ip"
msgstr "登录IP"
#: users/models/authentication.py:48
#: users/models/authentication.py:73
msgid "Login city"
msgstr "登录城市"
#: users/models/authentication.py:49
#: users/models/authentication.py:74
msgid "User agent"
msgstr "Agent"
#: users/models/authentication.py:50
#: users/models/authentication.py:76
#: users/templates/users/login_log_list.html:55
msgid "Reason"
msgstr "原因"
#: users/models/authentication.py:77
#: users/templates/users/login_log_list.html:56
msgid "Status"
msgstr "状态"
#: users/models/authentication.py:78
msgid "Date login"
msgstr "登录日期"
#: users/models/user.py:29 users/models/user.py:326
#: users/models/user.py:30 users/models/user.py:341
msgid "Administrator"
msgstr "管理员"
#: users/models/user.py:31
#: users/models/user.py:32
msgid "Application"
msgstr "应用程序"
#: users/models/user.py:34 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:156
#: users/templates/users/user_profile.html:159
#: users/models/user.py:35 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166
msgid "Disable"
msgstr "禁用"
#: users/models/user.py:35 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:163
#: users/models/user.py:36 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:170
msgid "Enable"
msgstr "启用"
#: users/models/user.py:36 users/templates/users/user_profile.html:88
#: users/models/user.py:37 users/templates/users/user_profile.html:88
msgid "Force enable"
msgstr "强制启用"
#: users/models/user.py:44 users/templates/users/user_detail.html:71
#: users/models/user.py:51 users/templates/users/user_detail.html:71
#: users/templates/users/user_profile.html:59
msgid "Email"
msgstr "邮件"
#: users/models/user.py:55
#: users/models/user.py:62
msgid "Avatar"
msgstr "头像"
#: users/models/user.py:58 users/templates/users/user_detail.html:82
#: users/models/user.py:65 users/templates/users/user_detail.html:82
msgid "Wechat"
msgstr "微信"
#: users/models/user.py:329
#: users/models/user.py:94 users/templates/users/user_detail.html:103
#: users/templates/users/user_list.html:27
#: users/templates/users/user_profile.html:100
msgid "Source"
msgstr "用户来源"
#: users/models/user.py:344
msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员"
......@@ -2443,11 +2634,15 @@ msgstr "首次登陆"
msgid "I agree with the terms and conditions."
msgstr "我同意条款和条件"
#: users/templates/users/first_login.html:100
#: users/templates/users/first_login.html:73
msgid "Please choose the terms and conditions."
msgstr "请选择同意条款和条件"
#: users/templates/users/first_login.html:101
msgid "Previous"
msgstr "上一步"
#: users/templates/users/first_login.html:108
#: users/templates/users/first_login.html:105
#: users/templates/users/login_otp.html:66
#: users/templates/users/user_otp_authentication.html:22
#: users/templates/users/user_otp_enable_bind.html:19
......@@ -2469,7 +2664,7 @@ msgid " for more information"
msgstr "获取更多信息"
#: users/templates/users/forgot_password.html:26
#: users/templates/users/login.html:73
#: users/templates/users/login.html:77
msgid "Forgot password"
msgstr "忘记密码"
......@@ -2477,7 +2672,7 @@ msgstr "忘记密码"
msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/login.html:50
#: users/templates/users/login.html:53
msgid "Captcha invalid"
msgstr "验证码错误"
......@@ -2505,22 +2700,34 @@ msgstr "6位数字"
msgid "Can't provide security? Please contact the administrator!"
msgstr "如果不能提供MFA验证码,请联系管理员!"
#: users/templates/users/reset_password.html:45
#: users/templates/users/user_detail.html:348 users/utils.py:73
#: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:352 users/utils.py:80
msgid "Reset password"
msgstr "重置密码"
#: users/templates/users/reset_password.html:55
#: users/templates/users/reset_password.html:59
#: users/templates/users/user_password_update.html:60
#: users/templates/users/user_update.html:12
msgid "Your password must satisfy"
msgstr "您的密码必须满足:"
#: users/templates/users/reset_password.html:60
#: users/templates/users/user_password_update.html:61
#: users/templates/users/user_update.html:13
msgid "Password strength"
msgstr "密码强度:"
#: users/templates/users/reset_password.html:66
msgid "Password again"
msgstr "再次输入密码"
#: users/templates/users/reset_password.html:57
#: users/templates/users/reset_password.html:68
#: users/templates/users/user_profile.html:20
msgid "Setting"
msgstr "设置"
#: users/templates/users/user_create.html:4
#: users/templates/users/user_list.html:16 users/views/user.py:79
#: users/templates/users/user_list.html:16 users/views/user.py:80
msgid "Create user"
msgstr "创建用户"
......@@ -2529,7 +2736,7 @@ msgid "Reset link will be generated and sent to the user. "
msgstr "生成重置密码连接,通过邮件发送给用户"
#: users/templates/users/user_detail.html:19
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:156
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:176
msgid "User detail"
msgstr "用户详情"
......@@ -2544,67 +2751,63 @@ msgstr "授权的资产"
msgid "Force enabled"
msgstr "强制启用"
#: users/templates/users/user_detail.html:98
msgid "Disabled"
msgstr "禁用"
#: users/templates/users/user_detail.html:115
#: users/templates/users/user_profile.html:101
#: users/templates/users/user_detail.html:119
#: users/templates/users/user_profile.html:108
msgid "Last login"
msgstr "最后登录"
#: users/templates/users/user_detail.html:151
#: users/templates/users/user_detail.html:155
msgid "Force enabled MFA"
msgstr "强制启用MFA"
#: users/templates/users/user_detail.html:166
#: users/templates/users/user_detail.html:170
msgid "Send reset password mail"
msgstr "发送重置密码邮件"
#: users/templates/users/user_detail.html:169
#: users/templates/users/user_detail.html:177
#: users/templates/users/user_detail.html:173
#: users/templates/users/user_detail.html:181
msgid "Send"
msgstr "发送"
#: users/templates/users/user_detail.html:174
#: users/templates/users/user_detail.html:178
msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:291
#: users/templates/users/user_detail.html:295
msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:347
#: users/templates/users/user_detail.html:351
msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:358
#: users/templates/users/user_detail.html:362
msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:372
#: users/templates/users/user_detail.html:376
msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:373
#: users/templates/users/user_detail.html:377
msgid "Reset SSH public key"
msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:383
#: users/templates/users/user_detail.html:387
msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:400
#: users/templates/users/user_profile.html:204
#: users/templates/users/user_detail.html:404
#: users/templates/users/user_profile.html:211
msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:401
#: users/templates/users/user_detail.html:405
#: users/templates/users/user_profile.html:205
#: users/templates/users/user_profile.html:210
#: users/templates/users/user_detail.html:409
#: users/templates/users/user_profile.html:212
#: users/templates/users/user_profile.html:217
msgid "User SSH public key update"
msgstr "ssh密钥"
......@@ -2643,45 +2846,49 @@ msgstr "用户组删除"
msgid "UserGroup Deleting failed."
msgstr "用户组删除失败"
#: users/templates/users/user_list.html:192
#: users/templates/users/user_list.html:196
msgid "This will delete the selected users !!!"
msgstr "删除选中用户 !!!"
#: users/templates/users/user_list.html:200
#: users/templates/users/user_list.html:204
msgid "User Deleted."
msgstr "已被删除"
#: users/templates/users/user_list.html:201
#: users/templates/users/user_list.html:206
#: users/templates/users/user_list.html:205
#: users/templates/users/user_list.html:210
msgid "User Delete"
msgstr "删除"
#: users/templates/users/user_list.html:205
#: users/templates/users/user_list.html:209
msgid "User Deleting failed."
msgstr "用户删除失败"
#: users/templates/users/user_profile.html:109 users/views/user.py:185
#: users/views/user.py:239
#: users/templates/users/user_profile.html:95
msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:205
#: users/views/user.py:259
msgid "User groups"
msgstr "用户组"
#: users/templates/users/user_profile.html:141
#: users/templates/users/user_profile.html:148
msgid "Update password"
msgstr "更改密码"
#: users/templates/users/user_profile.html:149
#: users/templates/users/user_profile.html:156
msgid "Update MFA settings"
msgstr "更改MFA设置"
#: users/templates/users/user_profile.html:170
#: users/templates/users/user_profile.html:177
msgid "Update SSH public key"
msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:178
#: users/templates/users/user_profile.html:185
msgid "Reset public key and download"
msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:208
#: users/templates/users/user_profile.html:215
msgid "Failed to update SSH public key."
msgstr "更新密钥失败"
......@@ -2701,15 +2908,21 @@ msgstr "更新密钥"
msgid "Or reset by server"
msgstr "或者重置并下载密钥"
#: users/templates/users/user_update.html:4 users/views/user.py:99
#: users/templates/users/user_pubkey_update.html:94
msgid ""
"The new public key has been set successfully, Please download the "
"corresponding private key."
msgstr "新的公钥已设置成功,请下载对应的私钥"
#: users/templates/users/user_update.html:4 users/views/user.py:103
msgid "Update user"
msgstr "更新用户"
#: users/utils.py:37
#: users/utils.py:41
msgid "Create account successfully"
msgstr "创建账户成功"
#: users/utils.py:39
#: users/utils.py:43
#, python-format
msgid ""
"\n"
......@@ -2717,6 +2930,8 @@ msgid ""
" </br>\n"
" Your account has been created successfully\n"
" </br>\n"
" Username: %(username)s\n"
" </br>\n"
" <a href=\"%(rest_password_url)s?token=%(rest_password_token)s\">click "
"here to set your password</a>\n"
" </br>\n"
......@@ -2736,6 +2951,8 @@ msgstr ""
" 你好 %(name)s:\n"
" </br>\n"
" 恭喜您,您的账号已经创建成功 </br>\n"
" 用户名: %(username)s\n"
" </br>\n"
" <a href=\"%(rest_password_url)s?token=%(rest_password_token)s\">请点击这"
"里设置密码</a> </br>\n"
" 这个链接有效期1小时, 超过时间您可以 <a href=\"%(forget_password_url)s?"
......@@ -2750,7 +2967,7 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:75
#: users/utils.py:82
#, python-format
msgid ""
"\n"
......@@ -2794,11 +3011,11 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:106
#: users/utils.py:113
msgid "SSH Key Reset"
msgstr "重置ssh密钥"
#: users/utils.py:108
#: users/utils.py:115
#, python-format
msgid ""
"\n"
......@@ -2823,18 +3040,22 @@ msgstr ""
" </br>\n"
" "
#: users/utils.py:141
#: users/utils.py:148
msgid "User not exist"
msgstr "用户不存在"
#: users/utils.py:143
#: users/utils.py:150
msgid "Disabled or expired"
msgstr "禁用或失效"
#: users/utils.py:156
#: users/utils.py:163
msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法"
#: users/utils.py:289 users/utils.py:299
msgid "Bit"
msgstr " 位"
#: users/views/group.py:29
msgid "User group list"
msgstr "用户组列表"
......@@ -2847,105 +3068,106 @@ msgstr "更新用户组"
msgid "User group granted asset"
msgstr "用户组授权资产"
#: users/views/login.py:56
#: users/views/login.py:75
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:107 users/views/user.py:464 users/views/user.py:489
#: users/views/login.py:178 users/views/user.py:500 users/views/user.py:525
msgid "MFA code invalid"
msgstr "MFA码认证失败"
#: users/views/login.py:133
#: users/views/login.py:207
msgid "Logout success"
msgstr "退出登录成功"
#: users/views/login.py:134
#: users/views/login.py:208
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:150
#: users/views/login.py:224
msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:163
#: users/views/login.py:237
msgid "Send reset password message"
msgstr "发送重置密码邮件"
#: users/views/login.py:164
#: users/views/login.py:238
msgid "Send reset password mail success, login your mail box and follow it "
msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:177
#: users/views/login.py:251
msgid "Reset password success"
msgstr "重置密码成功"
#: users/views/login.py:178
#: users/views/login.py:252
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:195 users/views/login.py:208
#: users/views/login.py:273 users/views/login.py:286
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/login.py:204
#: users/views/login.py:282
msgid "Password not same"
msgstr "密码不一致"
#: users/views/login.py:241
#: users/views/login.py:292 users/views/user.py:118 users/views/user.py:398
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/login.py:330
msgid "First login"
msgstr "首次登陆"
#: users/views/login.py:290
#: users/views/login.py:389
msgid "Login log list"
msgstr "登录日志"
#: users/views/user.py:109
#: users/views/user.py:129
msgid "Bulk update user success"
msgstr "批量更新用户成功"
#: users/views/user.py:214
#: users/views/user.py:234
msgid "Invalid file."
msgstr "文件不合法"
#: users/views/user.py:311
#: users/views/user.py:331
msgid "User granted assets"
msgstr "用户授权资产"
#: users/views/user.py:340
#: users/views/user.py:362
msgid "Profile setting"
msgstr "个人信息设置"
#: users/views/user.py:358
#: users/views/user.py:381
msgid "Password update"
msgstr "密码更新"
#: users/views/user.py:380
#: users/views/user.py:416
msgid "Public key update"
msgstr "密钥更新"
#: users/views/user.py:421
#: users/views/user.py:457
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/user.py:515
#: users/views/user.py:551
msgid "MFA enable success"
msgstr "MFA 绑定成功"
#: users/views/user.py:516
#: users/views/user.py:552
msgid "MFA enable success, return login page"
msgstr "MFA 绑定成功,返回到登录页面"
#: users/views/user.py:518
#: users/views/user.py:554
msgid "MFA disable success"
msgstr "MFA 解绑成功"
#: users/views/user.py:519
#: users/views/user.py:555
msgid "MFA disable success, return login page"
msgstr "MFA 解绑成功,返回登录页面"
#~ msgid "Step"
#~ msgstr "Step"
#~ msgid "Add asset"
#~ msgstr "添加资产到节点"
#~ msgid "MFA setting"
#~ msgstr "MFA 设置"
......@@ -229,7 +229,11 @@ LOGGING = {
'django_auth_ldap': {
'handlers': ['console', 'ansible_logs'],
'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_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_BACKEND = 'django_auth_ldap.backend.LDAPBackend'
......@@ -336,10 +343,11 @@ if AUTH_LDAP:
AUTHENTICATION_BACKENDS.insert(0, AUTH_LDAP_BACKEND)
# 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 '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
'db':CONFIG.REDIS_DB_CELERY_BROKER or 3,
}
CELERY_TASK_SERIALIZER = 'pickle'
CELERY_RESULT_SERIALIZER = 'pickle'
......@@ -360,10 +368,11 @@ CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CACHES = {
'default': {
'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 '',
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'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
BOOTSTRAP3 = {
'horizontal_label_class': 'col-md-2',
......
......@@ -72,7 +72,7 @@ class CeleryTaskLogApi(generics.RetrieveAPIView):
def get(self, request, *args, **kwargs):
mark = request.query_params.get("mark") or str(uuid.uuid4())
task = super().get_object()
task = self.get_object()
log_path = task.full_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()
proxy_command_list = [
"ssh", "-p", str(gateway.port),
"-o", "StrictHostKeyChecking=no",
"{}@{}".format(gateway.username, gateway.ip),
"-W", "%h:%p", "-q",
]
if gateway.password:
proxy_command_list.insert(
0, "sshpass -p {}".format(gateway.password)
0, "sshpass -p '{}'".format(gateway.password)
)
if gateway.private_key:
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>
</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>
</ul>
</div>
......
......@@ -2,38 +2,25 @@
<head>
<title>term.js</title>
<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>
html {
background: #000;
body {
background-color: black;
}
h1 {
margin-bottom: 20px;
font: 20px/1.5 sans-serif;
}
.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;
.xterm-rows {
{#padding: 15px;#}
font-family: "Bitstream Vera Sans Mono", Monaco, "Consolas", Courier, monospace;
font-size: 13px;
}
</style>
</head>
<div class="container">
<div id="term">
<div id="term" style="height: 100%;width: 100%">
</div>
</div>
<script src="{% static 'js/term.js' %}"></script>
<script>
var rowHeight = 1;
var colWidth = 1;
var rowHeight = 18;
var colWidth = 10;
var mark = '';
var url = "{% url 'api-ops:celery-task-log' pk=object.id %}";
var term;
......@@ -42,13 +29,16 @@
var interval = 200;
function calWinSize() {
var t = $('.terminal');
rowHeight = 1.00 * t.height() / 24;
colWidth = 1.00 * t.width() / 80;
var t = $('#marker');
{#rowHeight = 1.00 * t.height();#}
{#colWidth = 1.00 * t.width() / 6;#}
}
function resize() {
var rows = Math.floor(window.innerHeight / rowHeight) - 2;
var cols = Math.floor(window.innerWidth / colWidth) - 10;
{#console.log(rowHeight, window.innerHeight);#}
{#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);
}
function requestAndWrite() {
......@@ -74,21 +64,18 @@
}
}
$(document).ready(function () {
term = new Terminal({
cols: 80,
rows: 24,
useStyle: true,
screenKeys: false,
convertEol: false,
cursorBlink: false
});
term.open();
term = new Terminal();
term.open(document.getElementById('term'));
term.resize(80, 24);
resize();
term.on('data', function (data) {
term.write(data.replace('\r', '\r\n'))
{#term.write(data.replace('\r', '\r\n'))#}
term.write(data);
});
calWinSize();
resize();
$('.terminal').detach().appendTo('#term');
window.onresize = function () {
resize()
};
{#$('.terminal').detach().appendTo('#term');#}
setInterval(function () {
requestAndWrite()
}, 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>
</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>
</ul>
</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>
</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>
</ul>
</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>
</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>
</ul>
</div>
......
......@@ -115,7 +115,7 @@ $(document).ready(function() {
var success = function(data) {
var task_id = data.task;
var url = '{% url "ops:celery-task-log" pk=DEFAULT_PK %}'.replace("{{ DEFAULT_PK }}", task_id);
window.open(url, '', 'width=800,height=600')
window.open(url, '', 'width=800,height=600,left=400,top=400')
};
APIUpdateAttr({
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 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 .utils import AssetPermissionUtil
from .models import AssetPermission
......@@ -41,11 +41,11 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
asset = get_object_or_404(Asset, pk=asset_id)
permissions = set(queryset.filter(assets=asset))
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:
node = get_object_or_404(Node, pk=node_id)
permissions = set(queryset.filter(nodes=node))
inherit_nodes = node.ancestor
inherit_nodes = node.get_ancestor()
for n in inherit_nodes:
_permissions = queryset.filter(nodes=n)
......@@ -70,11 +70,12 @@ class UserGrantedAssetsApi(ListAPIView):
else:
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():
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:
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
queryset.append(k)
return queryset
......@@ -95,7 +96,8 @@ class UserGrantedNodesApi(ListAPIView):
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
nodes = AssetPermissionUtil.get_user_nodes_with_assets(user)
util = AssetPermissionUtil(user)
nodes = util.get_nodes_with_assets()
return nodes.keys()
def get_permissions(self):
......@@ -116,14 +118,15 @@ class UserGrantedNodesWithAssetsApi(ListAPIView):
else:
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():
assets = _assets.keys()
for k, v in _assets.items():
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:
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
node.assets_granted = assets
queryset.append(node)
......@@ -147,8 +150,9 @@ class UserGrantedNodeAssetsApi(ListAPIView):
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
util = AssetPermissionUtil(user)
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, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
......@@ -172,7 +176,8 @@ class UserGroupGrantedAssetsApi(ListAPIView):
return queryset
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():
k.system_users_granted = v
queryset.append(k)
......@@ -189,7 +194,8 @@ class UserGroupGrantedNodesApi(ListAPIView):
if 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 queryset
......@@ -206,7 +212,8 @@ class UserGroupGrantedNodesWithAssetsApi(ListAPIView):
return queryset
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():
assets = _assets.keys()
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)
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, [])
for asset, system_users in assets.items():
asset.system_users_granted = system_users
......@@ -246,7 +254,8 @@ class ValidateUserAssetPermissionView(APIView):
asset = get_object_or_404(Asset, id=asset_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, []):
return Response({'msg': True}, status=200)
else:
......
......@@ -7,13 +7,23 @@ from django.utils import timezone
from common.utils import date_expired_default, set_or_append_attr_bulk
class ValidManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True) \
.filter(date_start__lt=timezone.now())\
class AssetPermissionQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
def valid(self):
return self.active().filter(date_start__lt=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):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
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"))
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user"))
is_active = models.BooleanField(default=True, verbose_name=_('Active'))
date_start = models.DateTimeField(default=timezone.now, verbose_name=_("Date start"))
date_expired = models.DateTimeField(default=date_expired_default, verbose_name=_('Date expired'))
date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start"))
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'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Date created'))
comment = models.TextField(verbose_name=_('Comment'), blank=True)
objects = models.Manager()
valid = ValidManager()
objects = AssetPermissionManager()
def __str__(self):
return self.name
......
......@@ -9,7 +9,7 @@ from common.fields import StringManyToManyField
class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = AssetPermission
exclude = ('id', 'created_by', 'date_created')
exclude = ('created_by', 'date_created')
class AssetPermissionListSerializer(serializers.ModelSerializer):
......
......@@ -50,7 +50,7 @@
</div>
</div>
<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="input-daterange input-group" id="datepicker">
<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) {
setCookie('node_selected', treeNode.id);
var url = table.ajax.url();
if (treeNode.is_asset) {
url = setUrlParam(url, 'node', "");
url = setUrlParam(url, 'asset', treeNode.id)
} else {
if (treeNode.is_node) {
url = setUrlParam(url, 'asset', "");
url = setUrlParam(url, 'node', treeNode.id)
} else {
url = setUrlParam(url, 'node', "");
url = setUrlParam(url, 'asset', treeNode.id)
}
setCookie('node_selected', treeNode.id);
table.ajax.url(url);
......@@ -113,14 +113,14 @@ function filter(treeId, parentNode, childNodes) {
$.each(childNodes, function (index, value) {
value["pId"] = value["parent"];
value["name"] = value["value"];
value["isParent"] = value["assets_amount"] !== 0;
value["iconSkin"] = value["is_asset"] ? "file" : null;
value["isParent"] = value["is_node"];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
return childNodes;
}
function beforeAsync(treeId, treeNode) {
return true;
return treeNode.is_node
}
function makeLabel(data) {
......@@ -226,7 +226,7 @@ function initTree() {
},
async: {
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"],
dataFilter: filter,
type: 'get'
......@@ -238,18 +238,19 @@ function initTree() {
};
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) {
value["pId"] = value["parent"];
value["isParent"] = value["assets_amount"] !== 0;
value["name"] = value["value"];
value["open"] = value["key"] === "0";
value["isParent"] = value["is_node"];
value["iconSkin"] = value["is_node"] ? null : 'file';
});
zNodes = data;
{#$.fn.zTree.init($("#assetTree"), setting);#}
$.fn.zTree.init($("#assetTree"), setting, zNodes);
zTree = $.fn.zTree.getZTreeObj("assetTree");
selectQueryNode();
{#selectQueryNode();#}
});
}
......@@ -286,10 +287,10 @@ $(document).ready(function(){
var _nodes = [];
var _assets = [];
$.each(nodes, function (id, node) {
if (node.is_asset) {
_assets.push(node.id)
} else {
if (node.is_node) {
_nodes.push(node.id)
} else {
_assets.push(node.id)
}
});
url += "?assets=" + _assets.join(",") + "&nodes=" + _nodes.join(",");
......@@ -303,6 +304,7 @@ $(document).ready(function(){
if (row.child.isShown()) {
tr.removeClass('details');
$(this).children('i:first-child').removeClass('fa-angle-down').addClass('fa-angle-right');
row.child.hide();
// Remove from the 'open' array
......@@ -310,7 +312,7 @@ $(document).ready(function(){
}
else {
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();
// Add to the 'open' array
if ( idx === -1 ) {
......
# coding: utf-8
from __future__ import absolute_import, unicode_literals
import collections
from collections import defaultdict
from django.utils import timezone
import copy
from django.db.models import Q
from common.utils import set_or_append_attr_bulk, get_logger
from common.utils import get_logger
from .models import AssetPermission
from .hands import Node
logger = get_logger(__file__)
class AssetPermissionUtil:
@staticmethod
def get_user_permissions(user):
return AssetPermission.valid.all().filter(users=user)
@staticmethod
def get_user_group_permissions(user_group):
return AssetPermission.valid.all().filter(user_groups=user_group)
@staticmethod
def get_asset_permissions(asset):
return AssetPermission.valid.all().filter(assets=asset)
@staticmethod
def get_node_permissions(node):
return AssetPermission.valid.all().filter(nodes=node)
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 get_system_user_permissions(system_user):
return AssetPermission.objects.all().filter(system_users=system_user)
def init_node_asset_map(self):
for node in self.__all_nodes:
assets = node.get_assets().values_list('id', flat=True)
for asset in assets:
self.__node_asset_map[str(asset)].add(node)
@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
def add_asset(self, asset, system_users):
nodes = self.__node_asset_map.get(str(asset.id), [])
self.add_nodes(nodes)
for node in nodes:
self.nodes[node][asset].update(system_users)
@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
def add_node(self, node):
if node in self.nodes:
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
def add_nodes(self, nodes):
for node in nodes:
self.add_node(node)
@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)
def get_user_permissions(user, include_group=True):
if include_group:
groups = user.groups.all()
arg = Q(users=user) | Q(user_groups__in=groups)
else:
nodes[node][asset] = _system_users
return nodes
arg = Q(users=user)
return AssetPermission.objects.all().valid().filter(arg)
@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
def get_user_group_permissions(user_group):
return AssetPermission.objects.all().valid().filter(
user_groups=user_group
)
@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)
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:
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
arg = Q(assets=asset)
return AssetPermission.objects.all().valid().filter(arg)
@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
def get_node_permissions(node):
return AssetPermission.objects.all().valid().filter(nodes=node)
# Abandon
class NodePermissionUtil:
"""
"""
def get_system_user_permissions(system_user):
return AssetPermission.objects.valid().all().filter(
system_users=system_user
)
@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())
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,
}
@classmethod
def get_user_group_nodes(cls, user_group):
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):
"""
获取用户组授权的node和系统用户
:param user_group:
:return: {"node": set(systemuser1, systemuser2), ..}
返回用户/组授权规则直接关联的节点
:return: {node1: set(system_user1,)}
"""
permissions = cls.get_user_group_permissions(user_group)
nodes_directed = collections.defaultdict(set)
nodes = defaultdict(set)
permissions = self.permissions.prefetch_related('nodes', 'system_users')
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)
for node in perm.nodes.all():
nodes[node].update(perm.system_users.all())
return nodes
@classmethod
def get_user_group_nodes_with_assets(cls, user_group):
def get_assets_direct(self):
"""
获取用户组授权的节点和系统用户,节点下带有资产
:param user_group:
:return: {"node": {"assets": "", "system_user": ""}, {}}
返回用户授权规则直接关联的资产
:return: {asset1: set(system_user1,)}
"""
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)
assets = defaultdict(set)
permissions = self.permissions.prefetch_related('assets', 'system_users')
for perm in permissions:
for asset in perm.node.get_all_assets():
assets[asset].add(perm.system_user)
for asset in perm.assets.all().valid().prefetch_related('nodes'):
assets[asset].update(perm.system_users.all())
return assets
@classmethod
def get_user_nodes(cls, user):
nodes = collections.defaultdict(set)
groups = user.groups.all()
for group in groups:
group_nodes = cls.get_user_group_nodes(group)
for node, system_users in group_nodes.items():
nodes[node].update(system_users)
return nodes
@classmethod
def get_user_nodes_with_assets(cls, user):
nodes = cls.get_user_nodes(user)
nodes_with_assets = dict()
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():
nodes_with_assets[node] = {
'assets': node.get_active_assets(),
'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
_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
@classmethod
def get_system_user_assets(cls, system_user):
assets = set()
permissions = cls.get_system_user_permissions(system_user)
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
for perm in permissions:
assets.update(perm.node.get_all_assets())
return assets
......@@ -42,7 +42,7 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView):
if nodes_id:
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
if assets_id:
assets_id = assets_id.split(",")
......
......@@ -198,7 +198,8 @@ function objectDelete(obj, name, url, redirectTo) {
}
};
var fail = function() {
swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
// swal("错误", "删除"+"[ "+name+" ]"+"遇到错误", "error");
swal("错误", "[ "+name+" ]"+"正在被资产使用中,请先解除资产绑定", "error");
};
APIUpdateAttr({
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));
}
},
{className: 'text-center', targets: '_all'}
{className: 'text-center', render: $.fn.dataTable.render.text(), targets: '_all'}
];
columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs;
var select = {
......@@ -609,3 +610,91 @@ function setUrlParam(url, name, value) {
}
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);
}
/**
* xterm.js: xterm, in the browser
* Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/*
* Default style for xterm.js
*/
.terminal {
background-color: #000;
color: #fff;
font-family: courier-new, courier, monospace;
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.terminal.focus,
.terminal:focus {
outline: none;
}
.terminal .xterm-helpers {
position: absolute;
top: 0;
}
.terminal .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.terminal a {
color: inherit;
text-decoration: none;
}
.terminal a:hover {
cursor: pointer;
text-decoration: underline;
}
.terminal a.xterm-invalid-link:hover {
cursor: text;
text-decoration: none;
}
.terminal .terminal-cursor {
position: relative;
}
.terminal:not(.focus) .terminal-cursor {
outline: 1px solid #fff;
outline-offset: -1px;
}
.terminal.xterm-cursor-style-block.focus:not(.xterm-cursor-blink-on) .terminal-cursor {
background-color: #fff;
color: #000;
}
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before,
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
content: '';
position: absolute;
background-color: #fff;
}
.terminal.focus.xterm-cursor-style-bar:not(.xterm-cursor-blink-on) .terminal-cursor::before {
top: 0;
left: 0;
bottom: 0;
width: 1px;
}
.terminal.focus.xterm-cursor-style-underline:not(.xterm-cursor-blink-on) .terminal-cursor::before {
bottom: 0;
left: 0;
right: 0;
height: 1px;
}
.terminal .composition-view {
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.terminal .composition-view.active {
display: block;
}
.terminal .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
}
.terminal .xterm-wide-char,
.terminal .xterm-normal-char {
display: inline-block;
}
.terminal .xterm-rows {
position: absolute;
left: 0;
top: 0;
}
.terminal .xterm-rows > div {
/* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */
white-space: nowrap;
}
.terminal .xterm-scroll-area {
visibility: hidden;
}
.terminal .xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
left: -9999em;
}
.terminal.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.terminal .xterm-selection {
position: absolute;
top: 0;
left: 0;
z-index: 1;
opacity: 0.3;
pointer-events: none;
}
.terminal .xterm-selection div {
position: absolute;
background-color: #fff;
}
/*
* Determine default colors for xterm.js
*/
.terminal .xterm-bold {
font-weight: bold;
}
.terminal .xterm-underline {
text-decoration: underline;
}
.terminal .xterm-blink {
text-decoration: blink;
}
.terminal .xterm-blink.xterm-underline {
text-decoration: blink underline;
}
.terminal .xterm-hidden {
visibility: hidden;
}
.terminal .xterm-color-0 {
color: #2e3436;
}
.terminal .xterm-bg-color-0 {
background-color: #2e3436;
}
.terminal .xterm-color-1 {
color: #cc0000;
}
.terminal .xterm-bg-color-1 {
background-color: #cc0000;
}
.terminal .xterm-color-2 {
color: #4e9a06;
}
.terminal .xterm-bg-color-2 {
background-color: #4e9a06;
}
.terminal .xterm-color-3 {
color: #c4a000;
}
.terminal .xterm-bg-color-3 {
background-color: #c4a000;
}
.terminal .xterm-color-4 {
color: #3465a4;
}
.terminal .xterm-bg-color-4 {
background-color: #3465a4;
}
.terminal .xterm-color-5 {
color: #75507b;
}
.terminal .xterm-bg-color-5 {
background-color: #75507b;
}
.terminal .xterm-color-6 {
color: #06989a;
}
.terminal .xterm-bg-color-6 {
background-color: #06989a;
}
.terminal .xterm-color-7 {
color: #d3d7cf;
}
.terminal .xterm-bg-color-7 {
background-color: #d3d7cf;
}
.terminal .xterm-color-8 {
color: #555753;
}
.terminal .xterm-bg-color-8 {
background-color: #555753;
}
.terminal .xterm-color-9 {
color: #ef2929;
}
.terminal .xterm-bg-color-9 {
background-color: #ef2929;
}
.terminal .xterm-color-10 {
color: #8ae234;
}
.terminal .xterm-bg-color-10 {
background-color: #8ae234;
}
.terminal .xterm-color-11 {
color: #fce94f;
}
.terminal .xterm-bg-color-11 {
background-color: #fce94f;
}
.terminal .xterm-color-12 {
color: #729fcf;
}
.terminal .xterm-bg-color-12 {
background-color: #729fcf;
}
.terminal .xterm-color-13 {
color: #ad7fa8;
}
.terminal .xterm-bg-color-13 {
background-color: #ad7fa8;
}
.terminal .xterm-color-14 {
color: #34e2e2;
}
.terminal .xterm-bg-color-14 {
background-color: #34e2e2;
}
.terminal .xterm-color-15 {
color: #eeeeec;
}
.terminal .xterm-bg-color-15 {
background-color: #eeeeec;
}
.terminal .xterm-color-16 {
color: #000000;
}
.terminal .xterm-bg-color-16 {
background-color: #000000;
}
.terminal .xterm-color-17 {
color: #00005f;
}
.terminal .xterm-bg-color-17 {
background-color: #00005f;
}
.terminal .xterm-color-18 {
color: #000087;
}
.terminal .xterm-bg-color-18 {
background-color: #000087;
}
.terminal .xterm-color-19 {
color: #0000af;
}
.terminal .xterm-bg-color-19 {
background-color: #0000af;
}
.terminal .xterm-color-20 {
color: #0000d7;
}
.terminal .xterm-bg-color-20 {
background-color: #0000d7;
}
.terminal .xterm-color-21 {
color: #0000ff;
}
.terminal .xterm-bg-color-21 {
background-color: #0000ff;
}
.terminal .xterm-color-22 {
color: #005f00;
}
.terminal .xterm-bg-color-22 {
background-color: #005f00;
}
.terminal .xterm-color-23 {
color: #005f5f;
}
.terminal .xterm-bg-color-23 {
background-color: #005f5f;
}
.terminal .xterm-color-24 {
color: #005f87;
}
.terminal .xterm-bg-color-24 {
background-color: #005f87;
}
.terminal .xterm-color-25 {
color: #005faf;
}
.terminal .xterm-bg-color-25 {
background-color: #005faf;
}
.terminal .xterm-color-26 {
color: #005fd7;
}
.terminal .xterm-bg-color-26 {
background-color: #005fd7;
}
.terminal .xterm-color-27 {
color: #005fff;
}
.terminal .xterm-bg-color-27 {
background-color: #005fff;
}
.terminal .xterm-color-28 {
color: #008700;
}
.terminal .xterm-bg-color-28 {
background-color: #008700;
}
.terminal .xterm-color-29 {
color: #00875f;
}
.terminal .xterm-bg-color-29 {
background-color: #00875f;
}
.terminal .xterm-color-30 {
color: #008787;
}
.terminal .xterm-bg-color-30 {
background-color: #008787;
}
.terminal .xterm-color-31 {
color: #0087af;
}
.terminal .xterm-bg-color-31 {
background-color: #0087af;
}
.terminal .xterm-color-32 {
color: #0087d7;
}
.terminal .xterm-bg-color-32 {
background-color: #0087d7;
}
.terminal .xterm-color-33 {
color: #0087ff;
}
.terminal .xterm-bg-color-33 {
background-color: #0087ff;
}
.terminal .xterm-color-34 {
color: #00af00;
}
.terminal .xterm-bg-color-34 {
background-color: #00af00;
}
.terminal .xterm-color-35 {
color: #00af5f;
}
.terminal .xterm-bg-color-35 {
background-color: #00af5f;
}
.terminal .xterm-color-36 {
color: #00af87;
}
.terminal .xterm-bg-color-36 {
background-color: #00af87;
}
.terminal .xterm-color-37 {
color: #00afaf;
}
.terminal .xterm-bg-color-37 {
background-color: #00afaf;
}
.terminal .xterm-color-38 {
color: #00afd7;
}
.terminal .xterm-bg-color-38 {
background-color: #00afd7;
}
.terminal .xterm-color-39 {
color: #00afff;
}
.terminal .xterm-bg-color-39 {
background-color: #00afff;
}
.terminal .xterm-color-40 {
color: #00d700;
}
.terminal .xterm-bg-color-40 {
background-color: #00d700;
}
.terminal .xterm-color-41 {
color: #00d75f;
}
.terminal .xterm-bg-color-41 {
background-color: #00d75f;
}
.terminal .xterm-color-42 {
color: #00d787;
}
.terminal .xterm-bg-color-42 {
background-color: #00d787;
}
.terminal .xterm-color-43 {
color: #00d7af;
}
.terminal .xterm-bg-color-43 {
background-color: #00d7af;
}
.terminal .xterm-color-44 {
color: #00d7d7;
}
.terminal .xterm-bg-color-44 {
background-color: #00d7d7;
}
.terminal .xterm-color-45 {
color: #00d7ff;
}
.terminal .xterm-bg-color-45 {
background-color: #00d7ff;
}
.terminal .xterm-color-46 {
color: #00ff00;
}
.terminal .xterm-bg-color-46 {
background-color: #00ff00;
}
.terminal .xterm-color-47 {
color: #00ff5f;
}
.terminal .xterm-bg-color-47 {
background-color: #00ff5f;
}
.terminal .xterm-color-48 {
color: #00ff87;
}
.terminal .xterm-bg-color-48 {
background-color: #00ff87;
}
.terminal .xterm-color-49 {
color: #00ffaf;
}
.terminal .xterm-bg-color-49 {
background-color: #00ffaf;
}
.terminal .xterm-color-50 {
color: #00ffd7;
}
.terminal .xterm-bg-color-50 {
background-color: #00ffd7;
}
.terminal .xterm-color-51 {
color: #00ffff;
}
.terminal .xterm-bg-color-51 {
background-color: #00ffff;
}
.terminal .xterm-color-52 {
color: #5f0000;
}
.terminal .xterm-bg-color-52 {
background-color: #5f0000;
}
.terminal .xterm-color-53 {
color: #5f005f;
}
.terminal .xterm-bg-color-53 {
background-color: #5f005f;
}
.terminal .xterm-color-54 {
color: #5f0087;
}
.terminal .xterm-bg-color-54 {
background-color: #5f0087;
}
.terminal .xterm-color-55 {
color: #5f00af;
}
.terminal .xterm-bg-color-55 {
background-color: #5f00af;
}
.terminal .xterm-color-56 {
color: #5f00d7;
}
.terminal .xterm-bg-color-56 {
background-color: #5f00d7;
}
.terminal .xterm-color-57 {
color: #5f00ff;
}
.terminal .xterm-bg-color-57 {
background-color: #5f00ff;
}
.terminal .xterm-color-58 {
color: #5f5f00;
}
.terminal .xterm-bg-color-58 {
background-color: #5f5f00;
}
.terminal .xterm-color-59 {
color: #5f5f5f;
}
.terminal .xterm-bg-color-59 {
background-color: #5f5f5f;
}
.terminal .xterm-color-60 {
color: #5f5f87;
}
.terminal .xterm-bg-color-60 {
background-color: #5f5f87;
}
.terminal .xterm-color-61 {
color: #5f5faf;
}
.terminal .xterm-bg-color-61 {
background-color: #5f5faf;
}
.terminal .xterm-color-62 {
color: #5f5fd7;
}
.terminal .xterm-bg-color-62 {
background-color: #5f5fd7;
}
.terminal .xterm-color-63 {
color: #5f5fff;
}
.terminal .xterm-bg-color-63 {
background-color: #5f5fff;
}
.terminal .xterm-color-64 {
color: #5f8700;
}
.terminal .xterm-bg-color-64 {
background-color: #5f8700;
}
.terminal .xterm-color-65 {
color: #5f875f;
}
.terminal .xterm-bg-color-65 {
background-color: #5f875f;
}
.terminal .xterm-color-66 {
color: #5f8787;
}
.terminal .xterm-bg-color-66 {
background-color: #5f8787;
}
.terminal .xterm-color-67 {
color: #5f87af;
}
.terminal .xterm-bg-color-67 {
background-color: #5f87af;
}
.terminal .xterm-color-68 {
color: #5f87d7;
}
.terminal .xterm-bg-color-68 {
background-color: #5f87d7;
}
.terminal .xterm-color-69 {
color: #5f87ff;
}
.terminal .xterm-bg-color-69 {
background-color: #5f87ff;
}
.terminal .xterm-color-70 {
color: #5faf00;
}
.terminal .xterm-bg-color-70 {
background-color: #5faf00;
}
.terminal .xterm-color-71 {
color: #5faf5f;
}
.terminal .xterm-bg-color-71 {
background-color: #5faf5f;
}
.terminal .xterm-color-72 {
color: #5faf87;
}
.terminal .xterm-bg-color-72 {
background-color: #5faf87;
}
.terminal .xterm-color-73 {
color: #5fafaf;
}
.terminal .xterm-bg-color-73 {
background-color: #5fafaf;
}
.terminal .xterm-color-74 {
color: #5fafd7;
}
.terminal .xterm-bg-color-74 {
background-color: #5fafd7;
}
.terminal .xterm-color-75 {
color: #5fafff;
}
.terminal .xterm-bg-color-75 {
background-color: #5fafff;
}
.terminal .xterm-color-76 {
color: #5fd700;
}
.terminal .xterm-bg-color-76 {
background-color: #5fd700;
}
.terminal .xterm-color-77 {
color: #5fd75f;
}
.terminal .xterm-bg-color-77 {
background-color: #5fd75f;
}
.terminal .xterm-color-78 {
color: #5fd787;
}
.terminal .xterm-bg-color-78 {
background-color: #5fd787;
}
.terminal .xterm-color-79 {
color: #5fd7af;
}
.terminal .xterm-bg-color-79 {
background-color: #5fd7af;
}
.terminal .xterm-color-80 {
color: #5fd7d7;
}
.terminal .xterm-bg-color-80 {
background-color: #5fd7d7;
}
.terminal .xterm-color-81 {
color: #5fd7ff;
}
.terminal .xterm-bg-color-81 {
background-color: #5fd7ff;
}
.terminal .xterm-color-82 {
color: #5fff00;
}
.terminal .xterm-bg-color-82 {
background-color: #5fff00;
}
.terminal .xterm-color-83 {
color: #5fff5f;
}
.terminal .xterm-bg-color-83 {
background-color: #5fff5f;
}
.terminal .xterm-color-84 {
color: #5fff87;
}
.terminal .xterm-bg-color-84 {
background-color: #5fff87;
}
.terminal .xterm-color-85 {
color: #5fffaf;
}
.terminal .xterm-bg-color-85 {
background-color: #5fffaf;
}
.terminal .xterm-color-86 {
color: #5fffd7;
}
.terminal .xterm-bg-color-86 {
background-color: #5fffd7;
}
.terminal .xterm-color-87 {
color: #5fffff;
}
.terminal .xterm-bg-color-87 {
background-color: #5fffff;
}
.terminal .xterm-color-88 {
color: #870000;
}
.terminal .xterm-bg-color-88 {
background-color: #870000;
}
.terminal .xterm-color-89 {
color: #87005f;
}
.terminal .xterm-bg-color-89 {
background-color: #87005f;
}
.terminal .xterm-color-90 {
color: #870087;
}
.terminal .xterm-bg-color-90 {
background-color: #870087;
}
.terminal .xterm-color-91 {
color: #8700af;
}
.terminal .xterm-bg-color-91 {
background-color: #8700af;
}
.terminal .xterm-color-92 {
color: #8700d7;
}
.terminal .xterm-bg-color-92 {
background-color: #8700d7;
}
.terminal .xterm-color-93 {
color: #8700ff;
}
.terminal .xterm-bg-color-93 {
background-color: #8700ff;
}
.terminal .xterm-color-94 {
color: #875f00;
}
.terminal .xterm-bg-color-94 {
background-color: #875f00;
}
.terminal .xterm-color-95 {
color: #875f5f;
}
.terminal .xterm-bg-color-95 {
background-color: #875f5f;
}
.terminal .xterm-color-96 {
color: #875f87;
}
.terminal .xterm-bg-color-96 {
background-color: #875f87;
}
.terminal .xterm-color-97 {
color: #875faf;
}
.terminal .xterm-bg-color-97 {
background-color: #875faf;
}
.terminal .xterm-color-98 {
color: #875fd7;
}
.terminal .xterm-bg-color-98 {
background-color: #875fd7;
}
.terminal .xterm-color-99 {
color: #875fff;
}
.terminal .xterm-bg-color-99 {
background-color: #875fff;
}
.terminal .xterm-color-100 {
color: #878700;
}
.terminal .xterm-bg-color-100 {
background-color: #878700;
}
.terminal .xterm-color-101 {
color: #87875f;
}
.terminal .xterm-bg-color-101 {
background-color: #87875f;
}
.terminal .xterm-color-102 {
color: #878787;
}
.terminal .xterm-bg-color-102 {
background-color: #878787;
}
.terminal .xterm-color-103 {
color: #8787af;
}
.terminal .xterm-bg-color-103 {
background-color: #8787af;
}
.terminal .xterm-color-104 {
color: #8787d7;
}
.terminal .xterm-bg-color-104 {
background-color: #8787d7;
}
.terminal .xterm-color-105 {
color: #8787ff;
}
.terminal .xterm-bg-color-105 {
background-color: #8787ff;
}
.terminal .xterm-color-106 {
color: #87af00;
}
.terminal .xterm-bg-color-106 {
background-color: #87af00;
}
.terminal .xterm-color-107 {
color: #87af5f;
}
.terminal .xterm-bg-color-107 {
background-color: #87af5f;
}
.terminal .xterm-color-108 {
color: #87af87;
}
.terminal .xterm-bg-color-108 {
background-color: #87af87;
}
.terminal .xterm-color-109 {
color: #87afaf;
}
.terminal .xterm-bg-color-109 {
background-color: #87afaf;
}
.terminal .xterm-color-110 {
color: #87afd7;
}
.terminal .xterm-bg-color-110 {
background-color: #87afd7;
}
.terminal .xterm-color-111 {
color: #87afff;
}
.terminal .xterm-bg-color-111 {
background-color: #87afff;
}
.terminal .xterm-color-112 {
color: #87d700;
}
.terminal .xterm-bg-color-112 {
background-color: #87d700;
}
.terminal .xterm-color-113 {
color: #87d75f;
}
.terminal .xterm-bg-color-113 {
background-color: #87d75f;
}
.terminal .xterm-color-114 {
color: #87d787;
}
.terminal .xterm-bg-color-114 {
background-color: #87d787;
}
.terminal .xterm-color-115 {
color: #87d7af;
}
.terminal .xterm-bg-color-115 {
background-color: #87d7af;
}
.terminal .xterm-color-116 {
color: #87d7d7;
}
.terminal .xterm-bg-color-116 {
background-color: #87d7d7;
}
.terminal .xterm-color-117 {
color: #87d7ff;
}
.terminal .xterm-bg-color-117 {
background-color: #87d7ff;
}
.terminal .xterm-color-118 {
color: #87ff00;
}
.terminal .xterm-bg-color-118 {
background-color: #87ff00;
}
.terminal .xterm-color-119 {
color: #87ff5f;
}
.terminal .xterm-bg-color-119 {
background-color: #87ff5f;
}
.terminal .xterm-color-120 {
color: #87ff87;
}
.terminal .xterm-bg-color-120 {
background-color: #87ff87;
}
.terminal .xterm-color-121 {
color: #87ffaf;
}
.terminal .xterm-bg-color-121 {
background-color: #87ffaf;
}
.terminal .xterm-color-122 {
color: #87ffd7;
}
.terminal .xterm-bg-color-122 {
background-color: #87ffd7;
}
.terminal .xterm-color-123 {
color: #87ffff;
}
.terminal .xterm-bg-color-123 {
background-color: #87ffff;
}
.terminal .xterm-color-124 {
color: #af0000;
}
.terminal .xterm-bg-color-124 {
background-color: #af0000;
}
.terminal .xterm-color-125 {
color: #af005f;
}
.terminal .xterm-bg-color-125 {
background-color: #af005f;
}
.terminal .xterm-color-126 {
color: #af0087;
}
.terminal .xterm-bg-color-126 {
background-color: #af0087;
}
.terminal .xterm-color-127 {
color: #af00af;
}
.terminal .xterm-bg-color-127 {
background-color: #af00af;
}
.terminal .xterm-color-128 {
color: #af00d7;
}
.terminal .xterm-bg-color-128 {
background-color: #af00d7;
}
.terminal .xterm-color-129 {
color: #af00ff;
}
.terminal .xterm-bg-color-129 {
background-color: #af00ff;
}
.terminal .xterm-color-130 {
color: #af5f00;
}
.terminal .xterm-bg-color-130 {
background-color: #af5f00;
}
.terminal .xterm-color-131 {
color: #af5f5f;
}
.terminal .xterm-bg-color-131 {
background-color: #af5f5f;
}
.terminal .xterm-color-132 {
color: #af5f87;
}
.terminal .xterm-bg-color-132 {
background-color: #af5f87;
}
.terminal .xterm-color-133 {
color: #af5faf;
}
.terminal .xterm-bg-color-133 {
background-color: #af5faf;
}
.terminal .xterm-color-134 {
color: #af5fd7;
}
.terminal .xterm-bg-color-134 {
background-color: #af5fd7;
}
.terminal .xterm-color-135 {
color: #af5fff;
}
.terminal .xterm-bg-color-135 {
background-color: #af5fff;
}
.terminal .xterm-color-136 {
color: #af8700;
}
.terminal .xterm-bg-color-136 {
background-color: #af8700;
}
.terminal .xterm-color-137 {
color: #af875f;
}
.terminal .xterm-bg-color-137 {
background-color: #af875f;
}
.terminal .xterm-color-138 {
color: #af8787;
}
.terminal .xterm-bg-color-138 {
background-color: #af8787;
}
.terminal .xterm-color-139 {
color: #af87af;
}
.terminal .xterm-bg-color-139 {
background-color: #af87af;
}
.terminal .xterm-color-140 {
color: #af87d7;
}
.terminal .xterm-bg-color-140 {
background-color: #af87d7;
}
.terminal .xterm-color-141 {
color: #af87ff;
}
.terminal .xterm-bg-color-141 {
background-color: #af87ff;
}
.terminal .xterm-color-142 {
color: #afaf00;
}
.terminal .xterm-bg-color-142 {
background-color: #afaf00;
}
.terminal .xterm-color-143 {
color: #afaf5f;
}
.terminal .xterm-bg-color-143 {
background-color: #afaf5f;
}
.terminal .xterm-color-144 {
color: #afaf87;
}
.terminal .xterm-bg-color-144 {
background-color: #afaf87;
}
.terminal .xterm-color-145 {
color: #afafaf;
}
.terminal .xterm-bg-color-145 {
background-color: #afafaf;
}
.terminal .xterm-color-146 {
color: #afafd7;
}
.terminal .xterm-bg-color-146 {
background-color: #afafd7;
}
.terminal .xterm-color-147 {
color: #afafff;
}
.terminal .xterm-bg-color-147 {
background-color: #afafff;
}
.terminal .xterm-color-148 {
color: #afd700;
}
.terminal .xterm-bg-color-148 {
background-color: #afd700;
}
.terminal .xterm-color-149 {
color: #afd75f;
}
.terminal .xterm-bg-color-149 {
background-color: #afd75f;
}
.terminal .xterm-color-150 {
color: #afd787;
}
.terminal .xterm-bg-color-150 {
background-color: #afd787;
}
.terminal .xterm-color-151 {
color: #afd7af;
}
.terminal .xterm-bg-color-151 {
background-color: #afd7af;
}
.terminal .xterm-color-152 {
color: #afd7d7;
}
.terminal .xterm-bg-color-152 {
background-color: #afd7d7;
}
.terminal .xterm-color-153 {
color: #afd7ff;
}
.terminal .xterm-bg-color-153 {
background-color: #afd7ff;
}
.terminal .xterm-color-154 {
color: #afff00;
}
.terminal .xterm-bg-color-154 {
background-color: #afff00;
}
.terminal .xterm-color-155 {
color: #afff5f;
}
.terminal .xterm-bg-color-155 {
background-color: #afff5f;
}
.terminal .xterm-color-156 {
color: #afff87;
}
.terminal .xterm-bg-color-156 {
background-color: #afff87;
}
.terminal .xterm-color-157 {
color: #afffaf;
}
.terminal .xterm-bg-color-157 {
background-color: #afffaf;
}
.terminal .xterm-color-158 {
color: #afffd7;
}
.terminal .xterm-bg-color-158 {
background-color: #afffd7;
}
.terminal .xterm-color-159 {
color: #afffff;
}
.terminal .xterm-bg-color-159 {
background-color: #afffff;
}
.terminal .xterm-color-160 {
color: #d70000;
}
.terminal .xterm-bg-color-160 {
background-color: #d70000;
}
.terminal .xterm-color-161 {
color: #d7005f;
}
.terminal .xterm-bg-color-161 {
background-color: #d7005f;
}
.terminal .xterm-color-162 {
color: #d70087;
}
.terminal .xterm-bg-color-162 {
background-color: #d70087;
}
.terminal .xterm-color-163 {
color: #d700af;
}
.terminal .xterm-bg-color-163 {
background-color: #d700af;
}
.terminal .xterm-color-164 {
color: #d700d7;
}
.terminal .xterm-bg-color-164 {
background-color: #d700d7;
}
.terminal .xterm-color-165 {
color: #d700ff;
}
.terminal .xterm-bg-color-165 {
background-color: #d700ff;
}
.terminal .xterm-color-166 {
color: #d75f00;
}
.terminal .xterm-bg-color-166 {
background-color: #d75f00;
}
.terminal .xterm-color-167 {
color: #d75f5f;
}
.terminal .xterm-bg-color-167 {
background-color: #d75f5f;
}
.terminal .xterm-color-168 {
color: #d75f87;
}
.terminal .xterm-bg-color-168 {
background-color: #d75f87;
}
.terminal .xterm-color-169 {
color: #d75faf;
}
.terminal .xterm-bg-color-169 {
background-color: #d75faf;
}
.terminal .xterm-color-170 {
color: #d75fd7;
}
.terminal .xterm-bg-color-170 {
background-color: #d75fd7;
}
.terminal .xterm-color-171 {
color: #d75fff;
}
.terminal .xterm-bg-color-171 {
background-color: #d75fff;
}
.terminal .xterm-color-172 {
color: #d78700;
}
.terminal .xterm-bg-color-172 {
background-color: #d78700;
}
.terminal .xterm-color-173 {
color: #d7875f;
}
.terminal .xterm-bg-color-173 {
background-color: #d7875f;
}
.terminal .xterm-color-174 {
color: #d78787;
}
.terminal .xterm-bg-color-174 {
background-color: #d78787;
}
.terminal .xterm-color-175 {
color: #d787af;
}
.terminal .xterm-bg-color-175 {
background-color: #d787af;
}
.terminal .xterm-color-176 {
color: #d787d7;
}
.terminal .xterm-bg-color-176 {
background-color: #d787d7;
}
.terminal .xterm-color-177 {
color: #d787ff;
}
.terminal .xterm-bg-color-177 {
background-color: #d787ff;
}
.terminal .xterm-color-178 {
color: #d7af00;
}
.terminal .xterm-bg-color-178 {
background-color: #d7af00;
}
.terminal .xterm-color-179 {
color: #d7af5f;
}
.terminal .xterm-bg-color-179 {
background-color: #d7af5f;
}
.terminal .xterm-color-180 {
color: #d7af87;
}
.terminal .xterm-bg-color-180 {
background-color: #d7af87;
}
.terminal .xterm-color-181 {
color: #d7afaf;
}
.terminal .xterm-bg-color-181 {
background-color: #d7afaf;
}
.terminal .xterm-color-182 {
color: #d7afd7;
}
.terminal .xterm-bg-color-182 {
background-color: #d7afd7;
}
.terminal .xterm-color-183 {
color: #d7afff;
}
.terminal .xterm-bg-color-183 {
background-color: #d7afff;
}
.terminal .xterm-color-184 {
color: #d7d700;
}
.terminal .xterm-bg-color-184 {
background-color: #d7d700;
}
.terminal .xterm-color-185 {
color: #d7d75f;
}
.terminal .xterm-bg-color-185 {
background-color: #d7d75f;
}
.terminal .xterm-color-186 {
color: #d7d787;
}
.terminal .xterm-bg-color-186 {
background-color: #d7d787;
}
.terminal .xterm-color-187 {
color: #d7d7af;
}
.terminal .xterm-bg-color-187 {
background-color: #d7d7af;
}
.terminal .xterm-color-188 {
color: #d7d7d7;
}
.terminal .xterm-bg-color-188 {
background-color: #d7d7d7;
}
.terminal .xterm-color-189 {
color: #d7d7ff;
}
.terminal .xterm-bg-color-189 {
background-color: #d7d7ff;
}
.terminal .xterm-color-190 {
color: #d7ff00;
}
.terminal .xterm-bg-color-190 {
background-color: #d7ff00;
}
.terminal .xterm-color-191 {
color: #d7ff5f;
}
.terminal .xterm-bg-color-191 {
background-color: #d7ff5f;
}
.terminal .xterm-color-192 {
color: #d7ff87;
}
.terminal .xterm-bg-color-192 {
background-color: #d7ff87;
}
.terminal .xterm-color-193 {
color: #d7ffaf;
}
.terminal .xterm-bg-color-193 {
background-color: #d7ffaf;
}
.terminal .xterm-color-194 {
color: #d7ffd7;
}
.terminal .xterm-bg-color-194 {
background-color: #d7ffd7;
}
.terminal .xterm-color-195 {
color: #d7ffff;
}
.terminal .xterm-bg-color-195 {
background-color: #d7ffff;
}
.terminal .xterm-color-196 {
color: #ff0000;
}
.terminal .xterm-bg-color-196 {
background-color: #ff0000;
}
.terminal .xterm-color-197 {
color: #ff005f;
}
.terminal .xterm-bg-color-197 {
background-color: #ff005f;
}
.terminal .xterm-color-198 {
color: #ff0087;
}
.terminal .xterm-bg-color-198 {
background-color: #ff0087;
}
.terminal .xterm-color-199 {
color: #ff00af;
}
.terminal .xterm-bg-color-199 {
background-color: #ff00af;
}
.terminal .xterm-color-200 {
color: #ff00d7;
}
.terminal .xterm-bg-color-200 {
background-color: #ff00d7;
}
.terminal .xterm-color-201 {
color: #ff00ff;
}
.terminal .xterm-bg-color-201 {
background-color: #ff00ff;
}
.terminal .xterm-color-202 {
color: #ff5f00;
}
.terminal .xterm-bg-color-202 {
background-color: #ff5f00;
}
.terminal .xterm-color-203 {
color: #ff5f5f;
}
.terminal .xterm-bg-color-203 {
background-color: #ff5f5f;
}
.terminal .xterm-color-204 {
color: #ff5f87;
}
.terminal .xterm-bg-color-204 {
background-color: #ff5f87;
}
.terminal .xterm-color-205 {
color: #ff5faf;
}
.terminal .xterm-bg-color-205 {
background-color: #ff5faf;
}
.terminal .xterm-color-206 {
color: #ff5fd7;
}
.terminal .xterm-bg-color-206 {
background-color: #ff5fd7;
}
.terminal .xterm-color-207 {
color: #ff5fff;
}
.terminal .xterm-bg-color-207 {
background-color: #ff5fff;
}
.terminal .xterm-color-208 {
color: #ff8700;
}
.terminal .xterm-bg-color-208 {
background-color: #ff8700;
}
.terminal .xterm-color-209 {
color: #ff875f;
}
.terminal .xterm-bg-color-209 {
background-color: #ff875f;
}
.terminal .xterm-color-210 {
color: #ff8787;
}
.terminal .xterm-bg-color-210 {
background-color: #ff8787;
}
.terminal .xterm-color-211 {
color: #ff87af;
}
.terminal .xterm-bg-color-211 {
background-color: #ff87af;
}
.terminal .xterm-color-212 {
color: #ff87d7;
}
.terminal .xterm-bg-color-212 {
background-color: #ff87d7;
}
.terminal .xterm-color-213 {
color: #ff87ff;
}
.terminal .xterm-bg-color-213 {
background-color: #ff87ff;
}
.terminal .xterm-color-214 {
color: #ffaf00;
}
.terminal .xterm-bg-color-214 {
background-color: #ffaf00;
}
.terminal .xterm-color-215 {
color: #ffaf5f;
}
.terminal .xterm-bg-color-215 {
background-color: #ffaf5f;
}
.terminal .xterm-color-216 {
color: #ffaf87;
}
.terminal .xterm-bg-color-216 {
background-color: #ffaf87;
}
.terminal .xterm-color-217 {
color: #ffafaf;
}
.terminal .xterm-bg-color-217 {
background-color: #ffafaf;
}
.terminal .xterm-color-218 {
color: #ffafd7;
}
.terminal .xterm-bg-color-218 {
background-color: #ffafd7;
}
.terminal .xterm-color-219 {
color: #ffafff;
}
.terminal .xterm-bg-color-219 {
background-color: #ffafff;
}
.terminal .xterm-color-220 {
color: #ffd700;
}
.terminal .xterm-bg-color-220 {
background-color: #ffd700;
}
.terminal .xterm-color-221 {
color: #ffd75f;
}
.terminal .xterm-bg-color-221 {
background-color: #ffd75f;
}
.terminal .xterm-color-222 {
color: #ffd787;
}
.terminal .xterm-bg-color-222 {
background-color: #ffd787;
}
.terminal .xterm-color-223 {
color: #ffd7af;
}
.terminal .xterm-bg-color-223 {
background-color: #ffd7af;
}
.terminal .xterm-color-224 {
color: #ffd7d7;
}
.terminal .xterm-bg-color-224 {
background-color: #ffd7d7;
}
.terminal .xterm-color-225 {
color: #ffd7ff;
}
.terminal .xterm-bg-color-225 {
background-color: #ffd7ff;
}
.terminal .xterm-color-226 {
color: #ffff00;
}
.terminal .xterm-bg-color-226 {
background-color: #ffff00;
}
.terminal .xterm-color-227 {
color: #ffff5f;
}
.terminal .xterm-bg-color-227 {
background-color: #ffff5f;
}
.terminal .xterm-color-228 {
color: #ffff87;
}
.terminal .xterm-bg-color-228 {
background-color: #ffff87;
}
.terminal .xterm-color-229 {
color: #ffffaf;
}
.terminal .xterm-bg-color-229 {
background-color: #ffffaf;
}
.terminal .xterm-color-230 {
color: #ffffd7;
}
.terminal .xterm-bg-color-230 {
background-color: #ffffd7;
}
.terminal .xterm-color-231 {
color: #ffffff;
}
.terminal .xterm-bg-color-231 {
background-color: #ffffff;
}
.terminal .xterm-color-232 {
color: #080808;
}
.terminal .xterm-bg-color-232 {
background-color: #080808;
}
.terminal .xterm-color-233 {
color: #121212;
}
.terminal .xterm-bg-color-233 {
background-color: #121212;
}
.terminal .xterm-color-234 {
color: #1c1c1c;
}
.terminal .xterm-bg-color-234 {
background-color: #1c1c1c;
}
.terminal .xterm-color-235 {
color: #262626;
}
.terminal .xterm-bg-color-235 {
background-color: #262626;
}
.terminal .xterm-color-236 {
color: #303030;
}
.terminal .xterm-bg-color-236 {
background-color: #303030;
}
.terminal .xterm-color-237 {
color: #3a3a3a;
}
.terminal .xterm-bg-color-237 {
background-color: #3a3a3a;
}
.terminal .xterm-color-238 {
color: #444444;
}
.terminal .xterm-bg-color-238 {
background-color: #444444;
}
.terminal .xterm-color-239 {
color: #4e4e4e;
}
.terminal .xterm-bg-color-239 {
background-color: #4e4e4e;
}
.terminal .xterm-color-240 {
color: #585858;
}
.terminal .xterm-bg-color-240 {
background-color: #585858;
}
.terminal .xterm-color-241 {
color: #626262;
}
.terminal .xterm-bg-color-241 {
background-color: #626262;
}
.terminal .xterm-color-242 {
color: #6c6c6c;
}
.terminal .xterm-bg-color-242 {
background-color: #6c6c6c;
}
.terminal .xterm-color-243 {
color: #767676;
}
.terminal .xterm-bg-color-243 {
background-color: #767676;
}
.terminal .xterm-color-244 {
color: #808080;
}
.terminal .xterm-bg-color-244 {
background-color: #808080;
}
.terminal .xterm-color-245 {
color: #8a8a8a;
}
.terminal .xterm-bg-color-245 {
background-color: #8a8a8a;
}
.terminal .xterm-color-246 {
color: #949494;
}
.terminal .xterm-bg-color-246 {
background-color: #949494;
}
.terminal .xterm-color-247 {
color: #9e9e9e;
}
.terminal .xterm-bg-color-247 {
background-color: #9e9e9e;
}
.terminal .xterm-color-248 {
color: #a8a8a8;
}
.terminal .xterm-bg-color-248 {
background-color: #a8a8a8;
}
.terminal .xterm-color-249 {
color: #b2b2b2;
}
.terminal .xterm-bg-color-249 {
background-color: #b2b2b2;
}
.terminal .xterm-color-250 {
color: #bcbcbc;
}
.terminal .xterm-bg-color-250 {
background-color: #bcbcbc;
}
.terminal .xterm-color-251 {
color: #c6c6c6;
}
.terminal .xterm-bg-color-251 {
background-color: #c6c6c6;
}
.terminal .xterm-color-252 {
color: #d0d0d0;
}
.terminal .xterm-bg-color-252 {
background-color: #d0d0d0;
}
.terminal .xterm-color-253 {
color: #dadada;
}
.terminal .xterm-bg-color-253 {
background-color: #dadada;
}
.terminal .xterm-color-254 {
color: #e4e4e4;
}
.terminal .xterm-bg-color-254 {
background-color: #e4e4e4;
}
.terminal .xterm-color-255 {
color: #eeeeee;
}
.terminal .xterm-bg-color-255 {
background-color: #eeeeee;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/*!
* 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));
\ No newline at end of file
......@@ -5,6 +5,7 @@
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<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 %}
{% endblock %}
......
<div class="footer fixed">
<div class="pull-right">
Version <strong>1.2.1-{% include '_build.html' %}</strong> GPLv2.
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
Version <strong>1.3.2-{% include '_build.html' %}</strong> GPLv2.
<!--<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">-->
</div>
<div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018
......
......@@ -9,6 +9,7 @@ from django.core.cache import cache
from django.shortcuts import get_object_or_404, redirect
from django.utils import timezone
from django.core.files.storage import default_storage
from django.http.response import HttpResponseRedirectBase
from django.http import HttpResponseNotFound
from django.conf import settings
......@@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
from .backends import get_command_store, get_multi_command_store, \
from .backends import get_command_storage, get_multi_command_storage, \
SessionCommandSerializer
logger = logging.getLogger(__file__)
......@@ -108,6 +109,8 @@ class StatusViewSet(viewsets.ModelViewSet):
task_serializer_class = TaskSerializer
def create(self, request, *args, **kwargs):
from_gua = self.request.query_params.get("from_guacamole", None)
if not from_gua:
self.handle_sessions()
super().create(request, *args, **kwargs)
tasks = self.request.user.terminal.task_set.filter(is_finished=False)
......@@ -224,8 +227,8 @@ class CommandViewSet(viewsets.ViewSet):
}
"""
command_store = get_command_store()
multi_command_storage = get_multi_command_store()
command_store = get_command_storage()
multi_command_storage = get_multi_command_storage()
serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,)
......@@ -255,10 +258,35 @@ class SessionReplayViewSet(viewsets.ViewSet):
serializer_class = ReplaySerializer
permission_classes = (IsSuperUserOrAppUser,)
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')
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):
session_id = kwargs.get('pk')
......@@ -267,91 +295,64 @@ class SessionReplayViewSet(viewsets.ViewSet):
if serializer.is_valid():
file = serializer.validated_data['file']
file_path = self.gen_session_path()
try:
default_storage.save(file_path, file)
return Response({'url': default_storage.url(file_path)},
status=201)
except IOError:
return Response("Save error", status=500)
name, err = self.save_to_storage(file)
if not name:
msg = "Failed save replay `{}`: {}".format(session_id, err)
logger.error(msg)
return Response({'msg': str(err)}, status=400)
url = default_storage.url(name)
return Response({'url': url}, status=201)
else:
logger.error(
'Update load data invalid: {}'.format(serializer.errors))
msg = 'Upload data invalid: {}'.format(serializer.errors)
logger.error(msg)
return Response({'msg': serializer.errors}, status=401)
def retrieve(self, request, *args, **kwargs):
session_id = kwargs.get('pk')
self.session = get_object_or_404(Session, id=session_id)
path = self.gen_session_path()
if default_storage.exists(path):
url = default_storage.url(path)
# 新版本和老版本的文件后缀不同
session_path = self.get_session_path() # 存在外部存储上的路径
local_path = self.get_local_path()
local_path_v1 = self.get_local_path(version=1)
# 去default storage中查找
for _local_path in (local_path, local_path_v1, session_path):
if default_storage.exists(_local_path):
url = default_storage.url(_local_path)
return redirect(url)
else:
configs = settings.TERMINAL_REPLAY_STORAGE.items()
# 去定义的外部storage查找
configs = settings.TERMINAL_REPLAY_STORAGE
configs = {k: v for k, v in configs.items() if v['TYPE'] != 'server'}
if not configs:
return HttpResponseNotFound()
for name, config in configs:
client = jms_storage.init(config)
date = self.session.date_start.strftime('%Y-%m-%d')
file_path = os.path.join(date, str(self.session.id) + '.replay.gz')
target_path = default_storage.base_location + '/' + path
if client and client.has_file(file_path) and \
client.download_file(file_path, target_path):
return redirect(default_storage.url(path))
target_path = os.path.join(default_storage.base_location, local_path) # 保存到storage的路径
target_dir = os.path.dirname(target_path)
if not os.path.isdir(target_dir):
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
permission_classes = (IsSuperUserOrAppUser,)
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):
session_id = kwargs.get('pk')
self.session = get_object_or_404(Session, id=session_id)
replay = self.gen_session_path()
if replay.get("path", "") == "":
return HttpResponseNotFound()
if default_storage.exists(replay["path"]):
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)
response = super().retrieve(request, *args, **kwargs)
data = {
'type': 'guacamole' if self.session.protocol == 'rdp' else 'json',
'src': '',
}
if isinstance(response, HttpResponseRedirectBase):
data['src'] = response.url
return Response(data)
return HttpResponseNotFound()
......
......@@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = {
}
def get_command_store():
params = settings.COMMAND_STORAGE
engine_class = import_module(params['ENGINE'])
storage = engine_class.CommandStore(params)
def get_command_storage():
config = settings.COMMAND_STORAGE
engine_class = import_module(config['ENGINE'])
storage = engine_class.CommandStore(config)
return storage
def get_terminal_command_store():
def get_terminal_command_storages():
storage_list = {}
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
tp = params['TYPE']
if tp == 'server':
storage = get_command_store()
storage = get_command_storage()
else:
if not TYPE_ENGINE_MAPPING.get(tp):
continue
......@@ -29,9 +29,9 @@ def get_terminal_command_store():
return storage_list
def get_multi_command_store():
def get_multi_command_storage():
from .command.multi import CommandStore
storage_list = get_terminal_command_store().values()
storage_list = get_terminal_command_storages().values()
storage = CommandStore(storage_list)
return storage
......
# -*- coding: utf-8 -*-
#
from jms_es_sdk import ESStore
from jms_storage.es import ESStorage
from .base import CommandBase
from .models import AbstractSessionCommand
class CommandStore(CommandBase, ESStore):
class CommandStore(ESStorage, CommandBase):
def __init__(self, params):
hosts = params.get('HOSTS', ['http://localhost'])
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)
super().__init__(params)
def filter(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
data = ESStore.filter(
self, date_from=date_from, date_to=date_to,
data = super().filter(date_from=date_from, date_to=date_to,
user=user, asset=asset, system_user=system_user,
input=input, session=session
)
input=input, session=session)
return AbstractSessionCommand.from_multi_dict(
[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.utils import get_object_or_none
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):
......@@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer):
class SessionSerializer(serializers.ModelSerializer):
command_amount = serializers.SerializerMethodField()
command_store = get_multi_command_store()
command_store = get_multi_command_storage()
class Meta:
model = Session
......
......@@ -88,7 +88,7 @@
<td>{% trans 'Replay session' %}:</td>
<td>
<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>
</td>
</tr>
......
......@@ -73,6 +73,7 @@
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Remote addr' %}</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 'Date start' %}</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.remote_addr|default:"" }}</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.date_start }}</td>
......@@ -99,11 +101,15 @@
<td class="text-center">{{ session.date_start|time_util_with_seconds:session.date_end }}</td>
<td>
{% 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 %}
<!--<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>-->
{% 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 %}
</td>
</tr>
{% endfor %}
......
# ~*~ coding: utf-8 ~*~
from django import template
from ..backends import get_multi_command_store
from ..backends import get_multi_command_storage
register = template.Library()
command_store = get_multi_command_store()
command_store = get_multi_command_storage()
@register.filter
......
......@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from ..models import Command
from .. import utils
from ..backends import get_multi_command_store
from ..backends import get_multi_command_storage
__all__ = ['CommandListView']
common_storage = get_multi_command_store()
common_storage = get_multi_command_storage()
class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView):
......
......@@ -10,7 +10,7 @@ from django.conf import settings
from users.utils import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal
from ..backends import get_multi_command_store
from ..backends import get_multi_command_storage
from .. import utils
......@@ -19,7 +19,7 @@ __all__ = [
'SessionDetailView',
]
command_store = get_multi_command_store()
command_store = get_multi_command_storage()
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
......
......@@ -3,6 +3,7 @@ import uuid
from django.core.cache import cache
from django.urls import reverse
from django.utils.translation import ugettext as _
from rest_framework import generics
from rest_framework.permissions import AllowAny, IsAuthenticated
......@@ -14,10 +15,11 @@ from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup
from .models import User, UserGroup, LoginLog
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly, \
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.utils import get_logger
......@@ -128,16 +130,12 @@ class UserToken(APIView):
return Response({'error': msg}, status=406)
class UserProfile(APIView):
permission_classes = (IsValidUser,)
class UserProfile(generics.RetrieveAPIView):
permission_classes = (IsAuthenticated,)
serializer_class = UserSerializer
def get(self, request):
# return Response(request.user.to_json())
return Response(self.serializer_class(request.user).data)
def post(self, request):
return Response(self.serializer_class(request.user).data)
def get_object(self):
return self.request.user
class UserOtpAuthApi(APIView):
......@@ -153,10 +151,23 @@ class UserOtpAuthApi(APIView):
return Response({'msg': '请先进行用户名和密码验证'}, status=401)
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)
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)
self.write_login_log(request, user)
return Response(
{
'token': token,
......@@ -165,7 +176,7 @@ class UserOtpAuthApi(APIView):
)
@staticmethod
def write_login_log(request, user):
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
......@@ -173,25 +184,52 @@ class UserOtpAuthApi(APIView):
if not login_ip:
login_ip = get_login_ip(request)
write_login_log_async.delay(
user.username, ip=login_ip,
type=login_type, user_agent=user_agent,
)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserAuthApi(APIView):
permission_classes = (AllowAny,)
serializer_class = UserSerializer
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
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:
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)
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)
self.write_login_log(request, user)
return Response(
{
'token': token,
......@@ -208,7 +246,8 @@ class UserAuthApi(APIView):
'otp_url': reverse('api-users:user-otp-auth'),
'seed': seed,
'user': self.serializer_class(user).data
}, status=300)
}, status=300
)
@staticmethod
def check_user_valid(request):
......@@ -222,7 +261,7 @@ class UserAuthApi(APIView):
return user, msg
@staticmethod
def write_login_log(request, user):
def write_login_log(request, data):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
......@@ -230,10 +269,14 @@ class UserAuthApi(APIView):
if not login_ip:
login_ip = get_login_ip(request)
write_login_log_async.delay(
user.username, ip=login_ip,
type=login_type, user_agent=user_agent,
)
tmp_data = {
'ip': login_ip,
'type': login_type,
'user_agent': user_agent,
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserConnectionTokenApi(APIView):
......
......@@ -16,13 +16,14 @@ class UserLoginForm(AuthenticationForm):
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)
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
max_length=128, strip=False
)
class UserLoginCaptchaForm(UserLoginForm):
captcha = CaptchaField()
......@@ -72,7 +73,7 @@ class UserCreateUpdateForm(forms.ModelForm):
'data-placeholder': _('Join user groups')
}
),
'otp_level': forms.RadioSelect()
'otp_level': forms.RadioSelect(),
}
def clean_public_key(self):
......
......@@ -41,12 +41,40 @@ class LoginLog(models.Model):
('W', 'Web'),
('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)
username = models.CharField(max_length=20, verbose_name=_('Username'))
type = models.CharField(choices=LOGIN_TYPE_CHOICE, max_length=2, verbose_name=_('Login type'))
ip = models.GenericIPAddressField(verbose_name=_('Login ip'))
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'))
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'))
class Meta:
......
......@@ -4,14 +4,12 @@ import uuid
from django.db import models, IntegrityError
from django.utils.translation import ugettext_lazy as _
from common.mixins import NoDeleteModelMixin
__all__ = ['UserGroup']
class UserGroup(NoDeleteModelMixin):
class UserGroup(models.Model):
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'))
date_created = models.DateTimeField(auto_now_add=True, null=True,
verbose_name=_('Date created'))
......
......@@ -14,6 +14,7 @@ from django.utils import timezone
from django.shortcuts import reverse
from common.utils import get_signer, date_expired_default
from common.models import Setting
__all__ = ['User']
......@@ -35,6 +36,12 @@ class User(AbstractUser):
(1, _('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)
username = models.CharField(
max_length=128, unique=True, verbose_name=_('Username')
......@@ -77,11 +84,15 @@ class User(AbstractUser):
is_first_login = models.BooleanField(default=True)
date_expired = models.DateTimeField(
default=date_expired_default, blank=True, null=True,
verbose_name=_('Date expired')
db_index=True, verbose_name=_('Date expired')
)
created_by = models.CharField(
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):
return '{0.name}({0.username})'.format(self)
......@@ -248,14 +259,17 @@ class User(AbstractUser):
@property
def otp_enabled(self):
return self.otp_level > 0
return self.otp_force_enabled or self.otp_level > 0
@property
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
def enable_otp(self):
if not self.otp_force_enabled:
if not self.otp_level == 2:
self.otp_level = 1
def force_enable_otp(self):
......@@ -275,6 +289,7 @@ class User(AbstractUser):
'is_superuser': self.is_superuser,
'role': self.get_role_display(),
'groups': [group.name for group in self.groups.all()],
'source': self.get_source_display(),
'wechat': self.wechat,
'phone': self.phone,
'otp_level': self.otp_level,
......
......@@ -26,7 +26,10 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer):
def get_field_names(self, 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
@staticmethod
......
......@@ -2,6 +2,7 @@
#
from django.dispatch import receiver
from django_auth_ldap.backend import populate_user
# from django.db.models.signals import post_save
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))
if user.email:
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>
</form>
{% endblock %}
{% block custom_foot_js %}
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
......
......@@ -70,6 +70,7 @@
<br>
<input type="checkbox" id="acceptTerms">
<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 %}
{% bootstrap_form wizard.form %}
......@@ -99,11 +100,7 @@
{% if wizard.steps.prev %}
<li><a class="fl_goto" name="wizard_goto_step" data-goto="{{ wizard.steps.prev }}">{% trans "Previous" %}</a></li>
{% 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 %}
<li><a id="fl_submit" >{% trans "Next" %}</a></li>
{% else %}
......@@ -124,20 +121,26 @@
{% block custom_foot_js %}
<script>
$('#id_2-otp_level div').eq(2).css('display', 'none');
$(document).on('click', ".fl_goto", function(){
var $form = $('#fl_form');
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
$form.submit();
return false;
}).on('click', '#fl_submit', function(){
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
var noChecked = !$('#acceptTerms').prop('checked');
if ( isFinish && noChecked){
$('#noTerms').css('visibility', 'visible');
}
else{
$('#fl_form').submit();
return false;
}
}).on('click', '#btn-reset-pubkey', function () {
var the_url = '{% url "users:user-pubkey-generate" %}';
window.open(the_url, "_blank")
})
window.open(the_url, "_blank");
$('#fl_form').submit();
})
</script>
{% endblock %}
......@@ -45,13 +45,17 @@
</div>
<form class="m-t" role="form" method="post" action="">
{% 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 %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
{% else %}
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %}
{% endif %}
<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 %}">
</div>
......
......@@ -51,6 +51,9 @@
<th class="text-center">{% trans 'UA' %}</th>
<th class="text-center">{% trans 'IP' %}</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>
{% endblock %}
......@@ -65,6 +68,9 @@
</td>
<td class="text-center">{{ login_log.ip }}</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>
</tr>
{% endfor %}
......
......@@ -11,6 +11,7 @@
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}"></script>
<script type="text/javascript" src="{% static 'js/pwstrength-bootstrap.js' %}"></script>
</head>
......@@ -49,10 +50,20 @@
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<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 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>
<button type="submit" class="btn btn-primary block full-width m-b">{% trans "Setting" %}</button>
......@@ -79,4 +90,33 @@
</body>
</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 %}
</b></td>
</tr>
<tr>
<td>{% trans 'Source' %}:</td>
<td><b>{{ user_object.get_source_display }}</b></td>
</tr>
<tr>
<td>{% trans 'Date expired' %}:</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});
}).on('click', '.btn-delete-user', function () {
var $this = $(this);
var name = "{{ user.name }}";
var uid = "{{ user.id }}";
var name = "{{ user_object.name }}";
var uid = "{{ user_object.id }}";
var the_url = '{% url "api-users:user-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', uid);
var redirect_url = "{% url 'users:user-list' %}";
objectDelete($this, name, the_url, redirect_url);
......
......@@ -68,7 +68,7 @@ var asset_table;
function initTable() {
if (inited){
return
return asset_table
} else {
inited = true;
}
......
......@@ -64,10 +64,11 @@
var zTree;
var inited = false;
var url;
var asset_table;
function initTable() {
if (inited){
return
return asset_table
} else {
inited = true;
}
......
......@@ -24,6 +24,7 @@
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Role' %}</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 'Action' %}</th>
</tr>
......@@ -58,21 +59,21 @@ function initTable() {
ele: $('#user_list_table'),
columnDefs: [
{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));
}},
{targets: 4, createdCell: function (td, 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>');
}},
{targets: 5, createdCell: function (td, cellData) {
{targets: 6, createdCell: function (td, cellData) {
if (!cellData) {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else {
$(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 del_btn = "";
......@@ -90,7 +91,7 @@ function initTable() {
ajax_url: '{% url "api-users:user-list" %}',
columns: [
{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()
};
......
......@@ -7,6 +7,8 @@
<link href="{% static "css/plugins/cropper/cropper.min.css" %}" rel="stylesheet">
<link href="{% static "css/plugins/sweetalert/sweetalert.css" %}" rel="stylesheet">
<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>
.crop {
......@@ -50,6 +52,16 @@
{% csrf_token %}
{% bootstrap_field form.old_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" %}
<div class="hr-line-dashed"></div>
......@@ -71,5 +83,34 @@
{% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></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>
{% endblock %}
......@@ -91,8 +91,15 @@
{% else %}
{% trans 'Disable' %}
{% endif %}
{% if mfa_setting %}
( {% trans 'Administrator Settings force MFA login' %} )
{% endif %}
</td>
</tr>
<tr>
<td class="text-navy">{% trans 'Source' %}</td>
<td>{{ user.get_source_display }}</td>
</tr>
<tr>
<td class="text-navy">{% trans 'Date joined' %}</td>
<td>{{ user.date_joined|date:"Y-m-d H:i:s" }}</td>
......
......@@ -67,7 +67,7 @@
<div class="form-group">
<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 ">
<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 class="hr-line-dashed"></div>
......@@ -89,5 +89,10 @@
{% block custom_foot_js %}
<script src="{% static 'js/plugins/cropper/cropper.min.js' %}"></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>
{% endblock %}
......@@ -4,5 +4,50 @@
{% block user_template_title %}{% trans "Update user" %}{% endblock %}
{% block password %}
{% bootstrap_field form.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.public_key layout="horizontal" %}
{% endblock %}
{% block custom_foot_js %}
{{ block.super }}
<script>
$(document).ready(function(){
var el = $('#id_password_rules'),
idPassword = $('#id_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>
{% endblock %}
......@@ -2,6 +2,7 @@
#
from __future__ import unicode_literals
import os
import re
import pyotp
import base64
import logging
......@@ -12,14 +13,17 @@ import ipaddress
from django.http import Http404
from django.conf import settings
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth import authenticate, login as auth_login
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from django.core.cache import cache
from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none
from common.models import Setting
from common.forms import SecuritySettingForm
from .models import User, LoginLog
logger = logging.getLogger('jumpserver')
......@@ -41,6 +45,8 @@ def send_user_created_mail(user):
</br>
Your account has been created successfully
</br>
Username: %(username)s
</br>
<a href="%(rest_password_url)s?token=%(rest_password_token)s">click here to set your password</a>
</br>
This link is valid for 1 hour. After it expires, <a href="%(forget_password_url)s?email=%(email)s">request new one</a>
......@@ -54,6 +60,7 @@ def send_user_created_mail(user):
</br>
""") % {
'name': user.name,
'username': user.username,
'rest_password_url': reverse('users:reset-password', external=True),
'rest_password_token': user.generate_reset_token(),
'forget_password_url': reverse('users:forgot-password', external=True),
......@@ -193,16 +200,15 @@ def get_login_ip(request):
return login_ip
def write_login_log(username, type='', ip='', user_agent=''):
def write_login_log(*args, **kwargs):
ip = kwargs.get('ip', '')
if not (ip and validate_ip(ip)):
ip = ip[:15]
city = "Unknown"
else:
city = get_ip_city(ip)
LoginLog.objects.create(
username=username, type=type,
ip=ip, city=city, user_agent=user_agent
)
kwargs.update({'ip': ip, 'city': city})
LoginLog.objects.create(**kwargs)
def get_ip_city(ip, timeout=10):
......@@ -268,3 +274,86 @@ def generate_otp_uri(request, issuer="Jumpserver"):
def check_otp_code(otp_secret_key, otp_code):
totp = pyotp.TOTP(otp_secret_key)
return totp.verify(otp_code)
def get_password_check_rules():
check_rules = []
min_length = settings.DEFAULT_PASSWORD_MIN_LENGTH
min_name = 'SECURITY_PASSWORD_MIN_LENGTH'
base_filed = SecuritySettingForm.base_fields
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
# 用户还没有设置过密码校验规则
label = base_filed.get(min_name).label
label += ' ' + str(min_length) + _('Bit')
id = 'rule_' + min_name
rules = {'id': id, 'label': label}
check_rules.append(rules)
for setting in password_setting:
if setting.cleaned_value:
id = 'rule_' + setting.name
label = base_filed.get(setting.name).label
if setting.name == min_name:
label += str(setting.cleaned_value) + _('Bit')
min_length = setting.cleaned_value
rules = {'id': id, 'label': label}
check_rules.append(rules)
return check_rules, min_length
def check_password_rules(password):
min_field_name = 'SECURITY_PASSWORD_MIN_LENGTH'
upper_field_name = 'SECURITY_PASSWORD_UPPER_CASE'
lower_field_name = 'SECURITY_PASSWORD_LOWER_CASE'
number_field_name = 'SECURITY_PASSWORD_NUMBER'
special_field_name = 'SECURITY_PASSWORD_SPECIAL_CHAR'
min_length_setting = Setting.objects.filter(name=min_field_name).first()
min_length = min_length_setting.value if min_length_setting else settings.DEFAULT_PASSWORD_MIN_LENGTH
password_setting = Setting.objects.filter(name__startswith='SECURITY_PASSWORD')
if not password_setting:
pattern = r"^.{" + str(min_length) + ",}$"
else:
pattern = r"^"
for setting in password_setting:
if setting.cleaned_value and setting.name == upper_field_name:
pattern += '(?=.*[A-Z])'
elif setting.cleaned_value and setting.name == lower_field_name:
pattern += '(?=.*[a-z])'
elif setting.cleaned_value and setting.name == number_field_name:
pattern += '(?=.*\d)'
elif setting.cleaned_value and setting.name == special_field_name:
pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?])'
pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'",\.<>\/\?]'
match_obj = re.match(pattern, password)
return bool(match_obj)
def set_user_login_failed_count_to_cache(key_limit):
count = cache.get(key_limit)
count = count + 1 if count else 1
setting_limit_time = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_TIME'
).first()
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
else settings.DEFAULT_LOGIN_LIMIT_TIME
cache.set(key_limit, count, int(limit_time)*60)
def is_block_login(key_limit):
count = cache.get(key_limit)
setting_limit_count = Setting.objects.filter(
name='SECURITY_LOGIN_LIMIT_COUNT'
).first()
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
else settings.DEFAULT_LOGIN_LIMIT_COUNT
if count and count >= limit_count:
return True
......@@ -23,9 +23,12 @@ from django.conf import settings
from common.utils import get_object_or_none
from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin
from common.models import Setting
from ..models import User, LoginLog
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, redirect_user_first_login_or_index, \
get_user_or_tmp_user, set_tmp_user_to_cache
from ..utils import send_reset_password_mail, check_otp_code, get_login_ip, \
redirect_user_first_login_or_index, get_user_or_tmp_user, \
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
is_block_login, set_user_login_failed_count_to_cache
from ..tasks import write_login_log_async
from .. import forms
......@@ -46,14 +49,27 @@ class UserLoginView(FormView):
form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm
redirect_field_name = 'next'
key_prefix = "_LOGIN_INVALID_{}"
key_prefix_captcha = "_LOGIN_INVALID_{}"
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
def get(self, request, *args, **kwargs):
if request.user.is_staff:
return redirect(self.get_success_url())
return redirect(redirect_user_first_login_or_index(
request, self.redirect_field_name)
)
request.session.set_test_cookie()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# limit login authentication
ip = get_login_ip(request)
username = self.request.POST.get('username')
key_limit = self.key_prefix_limit.format(ip, username)
if is_block_login(key_limit):
return self.render_to_response(self.get_context_data(block_login=True))
return super().post(request, *args, **kwargs)
def form_valid(self, form):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
......@@ -62,8 +78,23 @@ class UserLoginView(FormView):
return redirect(self.get_success_url())
def form_invalid(self, form):
# write login failed log
username = form.cleaned_data.get('username')
data = {
'username': username,
'mfa': LoginLog.MFA_UNKNOWN,
'reason': LoginLog.REASON_PASSWORD,
'status': False
}
self.write_login_log(data)
# limit user login failed count
ip = get_login_ip(self.request)
cache.set(self.key_prefix.format(ip), 1, 3600)
key_limit = self.key_prefix_limit.format(ip, username)
set_user_login_failed_count_to_cache(key_limit)
# show captcha
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
old_form = form
form = self.form_class_captcha(data=form.data)
form._errors = old_form.errors
......@@ -71,7 +102,7 @@ class UserLoginView(FormView):
def get_form_class(self):
ip = get_login_ip(self.request)
if cache.get(self.key_prefix.format(ip)):
if cache.get(self.key_prefix_captcha.format(ip)):
return self.form_class_captcha
else:
return self.form_class
......@@ -80,15 +111,21 @@ class UserLoginView(FormView):
user = get_user_or_tmp_user(self.request)
if user.otp_enabled and user.otp_secret_key:
# 1,2 & T
# 1,2,mfa_setting & T
return reverse('users:login-otp')
elif user.otp_enabled and not user.otp_secret_key:
# 1,2 & F
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
elif not user.otp_enabled:
# 0 & T,F
auth_login(self.request, user)
self.write_login_log()
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def get_context_data(self, **kwargs):
......@@ -98,13 +135,16 @@ class UserLoginView(FormView):
kwargs.update(context)
return super().get_context_data(**kwargs)
def write_login_log(self):
def write_login_log(self, data):
login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay(
self.request.user.username, type='W',
ip=login_ip, user_agent=user_agent
)
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
class UserLoginOtpView(FormView):
......@@ -119,22 +159,38 @@ class UserLoginOtpView(FormView):
if check_otp_code(otp_secret_key, otp_code):
auth_login(self.request, user)
self.write_login_log()
data = {
'username': self.request.user.username,
'mfa': int(self.request.user.otp_enabled),
'reason': LoginLog.REASON_NOTHING,
'status': True
}
self.write_login_log(data)
return redirect(self.get_success_url())
else:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_MFA,
'status': False
}
self.write_login_log(data)
form.add_error('otp_code', _('MFA code invalid'))
return super().form_invalid(form)
def get_success_url(self):
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
def write_login_log(self):
def write_login_log(self, data):
login_ip = get_login_ip(self.request)
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay(
self.request.user.username, type='W',
ip=login_ip, user_agent=user_agent
)
tmp_data = {
'ip': login_ip,
'type': 'W',
'user_agent': user_agent
}
data.update(tmp_data)
write_login_log_async.delay(**data)
@method_decorator(never_cache, name='dispatch')
......@@ -209,6 +265,10 @@ class UserResetPasswordView(TemplateView):
token = request.GET.get('token')
user = User.validate_reset_token(token)
check_rules, min_length = get_password_check_rules()
password_rules = {'password_check_rules': check_rules, 'min_length': min_length}
kwargs.update(password_rules)
if not user:
kwargs.update({'errors': _('Token invalid or expired')})
return super().get(request, *args, **kwargs)
......@@ -225,6 +285,13 @@ class UserResetPasswordView(TemplateView):
if not user:
return self.get(request, errors=_('Token invalid or expired'))
is_ok = check_password_rules(password)
if not is_ok:
return self.get(
request,
errors=_('* Your password does not meet the requirements')
)
user.reset_password(password)
return HttpResponseRedirect(reverse('users:reset-password-success'))
......@@ -278,6 +345,16 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
def get_form(self, step=None, data=None, files=None):
form = super().get_form(step, data, files)
form.instance = self.request.user
if isinstance(form, forms.UserMFAForm):
choices = form.fields["otp_level"].choices
if self.request.user.otp_force_enabled:
choices = [(k, v) for k, v in choices if k == 2]
else:
choices = [(k, v) for k, v in choices if k in [0, 1]]
form.fields["otp_level"].choices = choices
form.fields["otp_level"].initial = self.request.user.otp_level
return form
......
......@@ -33,9 +33,10 @@ from django.contrib.auth import logout as auth_logout
from common.const import create_success_msg, update_success_msg
from common.mixins import JSONResponseMixin
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
from common.models import Setting
from .. import forms
from ..models import User, UserGroup
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user, get_password_check_rules, check_password_rules
from ..signals import post_user_create
from ..tasks import write_login_log_async
......@@ -96,10 +97,29 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView):
success_message = update_success_msg
def get_context_data(self, **kwargs):
context = {'app': _('Users'), 'action': _('Update user')}
check_rules, min_length = get_password_check_rules()
context = {
'app': _('Users'),
'action': _('Update user'),
'password_check_rules': check_rules,
'min_length': min_length
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def form_valid(self, form):
password = form.cleaned_data.get('password')
if not password:
return super().form_valid(form)
is_ok = check_password_rules(password)
if not is_ok:
form.add_error(
"password", _("* Your password does not meet the requirements")
)
return self.form_invalid(form)
return super().form_valid(form)
class UserBulkUpdateView(AdminUserRequiredMixin, TemplateView):
model = User
......@@ -318,8 +338,10 @@ class UserProfileView(LoginRequiredMixin, TemplateView):
template_name = 'users/user_profile.html'
def get_context_data(self, **kwargs):
mfa_setting = Setting.objects.filter(name='SECURITY_MFA_AUTH').first()
context = {
'action': _('Profile'),
'mfa_setting': mfa_setting.cleaned_value if mfa_setting else False,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -353,9 +375,12 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
return self.request.user
def get_context_data(self, **kwargs):
check_rules, min_length = get_password_check_rules()
context = {
'app': _('Users'),
'action': _('Password update'),
'password_check_rules': check_rules,
'min_length': min_length,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
......@@ -364,6 +389,17 @@ class UserPasswordUpdateView(LoginRequiredMixin, UpdateView):
auth_logout(self.request)
return super().get_success_url()
def form_valid(self, form):
password = form.cleaned_data.get('new_password')
is_ok = check_password_rules(password)
if not is_ok:
form.add_error(
"new_password",
_("* Your password does not meet the requirements")
)
return self.form_invalid(form)
return super().form_valid(form)
class UserPublicKeyUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'users/user_pubkey_update.html'
......
......@@ -21,10 +21,10 @@ class Config:
ALLOWED_HOSTS = ['*']
# Development env open this, when error occur display the full process track, Production disable it
DEBUG = True
DEBUG = os.environ.get("DEBUG") or True
# DEBUG, INFO, WARNING, ERROR, CRITICAL can set. See https://docs.djangoproject.com/en/1.10/topics/logging/
LOG_LEVEL = 'DEBUG'
LOG_LEVEL = os.environ.get("LOG_LEVEL") or 'DEBUG'
LOG_DIR = os.path.join(BASE_DIR, 'logs')
# Database setting, Support sqlite3, mysql, postgres ....
......@@ -35,12 +35,12 @@ class Config:
DB_NAME = os.path.join(BASE_DIR, 'data', 'db.sqlite3')
# MySQL or postgres setting like:
# DB_ENGINE = 'mysql'
# DB_HOST = '127.0.0.1'
# DB_PORT = 3306
# DB_USER = 'root'
# DB_PASSWORD = ''
# DB_NAME = 'jumpserver'
# DB_ENGINE = os.environ.get("DB_ENGINE") or 'mysql'
# DB_HOST = os.environ.get("DB_HOST") or '127.0.0.1'
# DB_PORT = os.environ.get("DB_PORT") or 3306
# DB_USER = os.environ.get("DB_USER") or 'jumpserver'
# DB_PASSWORD = os.environ.get("DB_PASSWORD") or 'weakPassword'
# DB_NAME = os.environ.get("DB_NAME") or 'jumpserver'
# When Django start it will bind this host and port
# ./manage.py runserver 127.0.0.1:8080
......@@ -48,14 +48,11 @@ class Config:
HTTP_LISTEN_PORT = 8080
# Use Redis as broker for celery and web socket
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_PASSWORD = ''
BROKER_URL = 'redis://%(password)s%(host)s:%(port)s/3' % {
'password': REDIS_PASSWORD,
'host': REDIS_HOST,
'port': REDIS_PORT,
}
REDIS_HOST = os.environ.get("REDIS_HOST") or '127.0.0.1'
REDIS_PORT = os.environ.get("REDIS_PORT") or 6379
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD") or ''
REDIS_DB_CELERY = os.environ.get('REDIS_DB') or 3
REDIS_DB_CACHE = os.environ.get('REDIS_DB') or 4
def __init__(self):
pass
......
......@@ -123,6 +123,7 @@ def start_gunicorn():
'gunicorn', 'jumpserver.wsgi',
'-b', bind,
'-w', str(WORKERS),
'-k', 'eventlet',
'--access-logformat', log_format,
'-p', pid_file,
]
......
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev
......@@ -3,10 +3,10 @@ ansible==2.4.2.0
asn1crypto==0.24.0
bcrypt==3.1.4
billiard==3.5.0.3
boto3==1.6.4
botocore==1.9.4
boto3==1.6.5
botocore==1.9.5
celery==4.1.0
certifi==2017.11.5
certifi==2018.1.18
cffi==1.11.2
chardet==3.0.4
configparser==3.5.0
......@@ -31,7 +31,7 @@ ecdsa==0.13
elasticsearch==6.1.1
enum-compat==0.0.2
ephem==3.7.6.0
eventlet==0.21.0
eventlet==0.22.1
ForgeryPy==0.1
greenlet==0.4.12
gunicorn==19.7.1
......@@ -40,7 +40,6 @@ itsdangerous==0.24
itypes==1.1.0
Jinja2==2.10
jmespath==0.9.3
jms-es-sdk
kombu==4.0.2
ldap3==2.4
MarkupSafe==1.0
......@@ -58,11 +57,11 @@ pyotp==2.2.6
PyNaCl==1.2.1
python-dateutil==2.6.1
python-gssapi==0.6.4
pytz==2017.3
pytz==2018.3
PyYAML==3.12
redis==2.10.6
requests==2.18.4
jms-storage==0.0.13
jms-storage==0.0.18
s3transfer==0.1.13
simplejson==3.13.2
six==1.11.0
......
#!/usr/bin/python
#
import os
import sys
from collections import Counter
import django
from django.db.models import Count
if os.path.exists('../apps'):
sys.path.insert(0, '../apps')
elif os.path.exists('./apps'):
sys.path.insert(0, './apps')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings")
django.setup()
from users.models import UserGroup
from django.core.exceptions import FieldError
def clean_group(interactive=True):
try:
UserGroup.objects.all().filter(is_discard=True).delete()
except FieldError:
pass
groups = UserGroup.objects.all()
groups_name_list = groups.values_list('name', flat=True)
groups_with_info = groups.annotate(Count('users'))\
.annotate(Count('asset_permissions'))
counter = Counter(groups_name_list)
for name, count in counter.items():
if count == 0:
continue
groups_duplicate = groups_with_info.filter(name=name)
need_clean_count = groups_duplicate.count()
for group in groups_duplicate:
need_clean = True
if group.users__count > 0:
need_clean = False
elif group.asset_permissions__count > 0:
need_clean = False
elif need_clean_count == 1:
need_clean = False
if need_clean:
confirm = True
if interactive:
confirm = False
while True:
confirm = input(
"Delete user group <{}>, create at {}? ([y]/n)".format(
name, group.date_created)
)
if confirm.lower() in ["y", ""]:
confirm = True
break
elif confirm.lower() == "n":
confirm = False
break
else:
print("No valid input")
continue
if confirm:
group.delete()
print("Delete success: {}".format(name))
need_clean_count -= 1
else:
continue
if __name__ == '__main__':
clean_group()
......@@ -4,3 +4,5 @@
python3 ../apps/manage.py makemigrations
python3 ../apps/manage.py migrate
python3 ../apps/manage.py makemigrations --merge
#!/bin/bash
if grep -q 'source ~/.autoenv/activate.sh' ~/.bashrc; then
if grep -q 'source /opt/autoenv/activate.sh' ~/.bashrc; then
echo -e "\033[31m 正在自动载入 python 环境 \033[0m"
else
echo -e "\033[31m 不支持自动升级,请参考 http://docs.jumpserver.org/zh/docs/upgrade.html 手动升级 \033[0m"
......@@ -40,5 +40,6 @@ git pull && pip install -r requirements/requirements.txt && cd utils && sh make_
cd .. && ./jms start all -d
echo -e "\033[31m 请检查jumpserver是否启动成功 \033[0m"
echo -e "\033[31m 备份文件存放于$jumpserver_backup目录 \033[0m"
stty erase ^?
exit 0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment