mirror of https://github.com/jumpserver/jumpserver
commit
1d462aea1b
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
|||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset, Node
|
||||
from ..models import Asset
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -5,7 +5,6 @@ from rest_framework_bulk.routes import BulkRouter
|
|||
|
||||
app_name = 'assets'
|
||||
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'assets', api.AssetViewSet, 'asset')
|
||||
router.register(r'admin-user', api.AdminUserViewSet, 'admin-user')
|
||||
|
|
|
@ -197,7 +197,6 @@ class SecuritySettingForm(BaseForm):
|
|||
)
|
||||
# upper case
|
||||
SECURITY_PASSWORD_UPPER_CASE = forms.BooleanField(
|
||||
|
||||
initial=False, required=False,
|
||||
label=_("Must contain capital letters"),
|
||||
help_text=_(
|
||||
|
|
|
@ -71,19 +71,20 @@ class BulkSerializerMixin(object):
|
|||
ret = super(BulkSerializerMixin, self).to_internal_value(data)
|
||||
|
||||
id_attr = getattr(self.Meta, 'update_lookup_field', 'id')
|
||||
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
|
||||
# add update_lookup_field field back to validated data
|
||||
# since super by default strips out read-only fields
|
||||
# hence id will no longer be present in validated_data
|
||||
if all((isinstance(self.root, BulkListSerializer),
|
||||
id_attr,
|
||||
request_method in ('PUT', 'PATCH'))):
|
||||
id_field = self.fields[id_attr]
|
||||
if data.get("id"):
|
||||
id_value = id_field.to_internal_value(data.get("id"))
|
||||
else:
|
||||
id_value = id_field.to_internal_value(data.get("pk"))
|
||||
ret[id_attr] = id_value
|
||||
if self.context.get('view'):
|
||||
request_method = getattr(getattr(self.context.get('view'), 'request'), 'method', '')
|
||||
# add update_lookup_field field back to validated data
|
||||
# since super by default strips out read-only fields
|
||||
# hence id will no longer be present in validated_data
|
||||
if all((isinstance(self.root, BulkListSerializer),
|
||||
id_attr,
|
||||
request_method in ('PUT', 'PATCH'))):
|
||||
id_field = self.fields[id_attr]
|
||||
if data.get("id"):
|
||||
id_value = id_field.to_internal_value(data.get("id"))
|
||||
else:
|
||||
id_value = id_field.to_internal_value(data.get("pk"))
|
||||
ret[id_attr] = id_value
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ INSTALLED_APPS = [
|
|||
'captcha',
|
||||
'django_celery_beat',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-09-04 16:00+0800\n"
|
||||
"POT-Creation-Date: 2018-09-07 12:00+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"
|
||||
|
@ -759,7 +759,7 @@ msgstr "重置"
|
|||
#: common/templates/common/terminal_setting.html:108
|
||||
#: perms/templates/perms/asset_permission_create_update.html:70
|
||||
#: terminal/templates/terminal/command_list.html:103
|
||||
#: terminal/templates/terminal/session_list.html:126
|
||||
#: terminal/templates/terminal/session_list.html:127
|
||||
#: terminal/templates/terminal/terminal_update.html:48
|
||||
#: users/templates/users/_user.html:47
|
||||
#: users/templates/users/forgot_password.html:45
|
||||
|
@ -1759,7 +1759,7 @@ 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:187
|
||||
msgid "Connection max idle time"
|
||||
|
@ -1768,47 +1768,47 @@ msgstr "SSH最大空闲时间"
|
|||
#: common/forms.py:189
|
||||
msgid ""
|
||||
"If idle time more than it, disconnect connection(only ssh now) Unit: minute"
|
||||
msgstr "如果超过该配置没有操作,连接会被断开"
|
||||
msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) "
|
||||
|
||||
#: common/forms.py:195
|
||||
msgid "Password minimum length"
|
||||
msgstr "密码最小长度 "
|
||||
|
||||
#: common/forms.py:202
|
||||
#: common/forms.py:201
|
||||
msgid "Must contain capital letters"
|
||||
msgstr "必须包含大写字母"
|
||||
|
||||
#: common/forms.py:204
|
||||
#: common/forms.py:203
|
||||
msgid ""
|
||||
"After opening, the user password changes and resets must contain uppercase "
|
||||
"letters"
|
||||
msgstr "开启后,用户密码修改、重置必须包含大写字母"
|
||||
|
||||
#: common/forms.py:210
|
||||
#: common/forms.py:209
|
||||
msgid "Must contain lowercase letters"
|
||||
msgstr "必须包含小写字母"
|
||||
|
||||
#: common/forms.py:211
|
||||
#: common/forms.py:210
|
||||
msgid ""
|
||||
"After opening, the user password changes and resets must contain lowercase "
|
||||
"letters"
|
||||
msgstr "开启后,用户密码修改、重置必须包含小写字母"
|
||||
|
||||
#: common/forms.py:217
|
||||
#: common/forms.py:216
|
||||
msgid "Must contain numeric characters"
|
||||
msgstr "必须包含数字字符"
|
||||
|
||||
#: common/forms.py:218
|
||||
#: common/forms.py:217
|
||||
msgid ""
|
||||
"After opening, the user password changes and resets must contain numeric "
|
||||
"characters"
|
||||
msgstr "开启后,用户密码修改、重置必须包含数字字符"
|
||||
|
||||
#: common/forms.py:224
|
||||
#: common/forms.py:223
|
||||
msgid "Must contain special characters"
|
||||
msgstr "必须包含特殊字符"
|
||||
|
||||
#: common/forms.py:225
|
||||
#: common/forms.py:224
|
||||
msgid ""
|
||||
"After opening, the user password changes and resets must contain special "
|
||||
"characters"
|
||||
|
@ -2717,10 +2717,20 @@ msgstr "终断"
|
|||
msgid "Terminate selected"
|
||||
msgstr "终断所选"
|
||||
|
||||
#: terminal/templates/terminal/session_list.html:142
|
||||
#: terminal/templates/terminal/session_list.html:123
|
||||
msgid "Confirm finished"
|
||||
msgstr "确认已完成"
|
||||
|
||||
#: terminal/templates/terminal/session_list.html:143
|
||||
msgid "Terminate task send, waiting ..."
|
||||
msgstr "终断任务已发送,请等待"
|
||||
|
||||
#: terminal/templates/terminal/session_list.html:156
|
||||
#, fuzzy
|
||||
#| msgid "MFA enable success"
|
||||
msgid "Finish session success"
|
||||
msgstr "MFA 绑定成功"
|
||||
|
||||
#: terminal/templates/terminal/terminal_detail.html:13
|
||||
#: terminal/views/terminal.py:59
|
||||
msgid "Terminal detail"
|
||||
|
@ -2805,7 +2815,7 @@ msgstr "MFA认证失败"
|
|||
|
||||
#: users/api/user.py:134
|
||||
msgid "Could not reset self otp, use profile reset instead"
|
||||
msgstr ""
|
||||
msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
|
||||
|
||||
#: users/authentication.py:56
|
||||
msgid "Invalid signature header. No credentials provided."
|
||||
|
|
|
@ -154,8 +154,10 @@ function activeNav() {
|
|||
function APIUpdateAttr(props) {
|
||||
// props = {url: .., body: , success: , error: , method: ,}
|
||||
props = props || {};
|
||||
var success_message = props.success_message || gettext('Update is successful!');
|
||||
var fail_message = props.fail_message || gettext('An unknown error occurred while updating..');
|
||||
var user_success_message = props.success_message;
|
||||
var default_success_message = gettext('Update is successful!');
|
||||
var user_fail_message = props.fail_message;
|
||||
var default_failed_message = gettext('An unknown error occurred while updating..');
|
||||
var flash_message = props.flash_message || true;
|
||||
if (props.flash_message === false){
|
||||
flash_message = false;
|
||||
|
@ -169,14 +171,36 @@ function APIUpdateAttr(props) {
|
|||
dataType: props.data_type || "json"
|
||||
}).done(function(data, textStatue, jqXHR) {
|
||||
if (flash_message) {
|
||||
toastr.success(success_message);
|
||||
var msg = "";
|
||||
if (user_fail_message) {
|
||||
msg = user_success_message;
|
||||
} else {
|
||||
msg = default_success_message;
|
||||
}
|
||||
toastr.success(msg);
|
||||
}
|
||||
if (typeof props.success === 'function') {
|
||||
return props.success(data);
|
||||
}
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (flash_message) {
|
||||
toastr.error(fail_message);
|
||||
var msg = "";
|
||||
console.log(jqXHR);
|
||||
console.log(textStatus);
|
||||
console.log(errorThrown);
|
||||
if (user_fail_message) {
|
||||
msg = user_fail_message;
|
||||
} else if (jqXHR.responseJSON) {
|
||||
if (jqXHR.responseJSON.error) {
|
||||
msg = jqXHR.responseJSON.error
|
||||
} else if (jqXHR.responseJSON.msg) {
|
||||
msg = jqXHR.responseJSON.msg
|
||||
}
|
||||
}
|
||||
if (msg === "") {
|
||||
msg = default_failed_message;
|
||||
}
|
||||
toastr.error(msg);
|
||||
}
|
||||
if (typeof props.error === 'function') {
|
||||
return props.error(jqXHR.responseText, jqXHR.status);
|
||||
|
|
|
@ -15,6 +15,7 @@ from django.conf import settings
|
|||
|
||||
import jms_storage
|
||||
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.views import APIView, Response
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
@ -173,9 +174,10 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
return super().get_permissions()
|
||||
|
||||
|
||||
class SessionViewSet(viewsets.ModelViewSet):
|
||||
class SessionViewSet(BulkModelViewSet):
|
||||
queryset = Session.objects.all()
|
||||
serializer_class = SessionSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -183,7 +185,7 @@ class SessionViewSet(viewsets.ModelViewSet):
|
|||
if terminal_id:
|
||||
terminal = get_object_or_404(Terminal, id=terminal_id)
|
||||
self.queryset = terminal.session_set.all()
|
||||
return self.queryset
|
||||
return self.queryset.all()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if hasattr(self.request.user, 'terminal'):
|
||||
|
|
|
@ -5,9 +5,7 @@ from django.utils import timezone
|
|||
from rest_framework import serializers
|
||||
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_storage
|
||||
|
||||
|
@ -45,7 +43,7 @@ class TerminalSerializer(serializers.ModelSerializer):
|
|||
return False
|
||||
|
||||
|
||||
class SessionSerializer(serializers.ModelSerializer):
|
||||
class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
command_amount = serializers.SerializerMethodField()
|
||||
command_store = get_multi_command_storage()
|
||||
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
<div class="input-group">
|
||||
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
|
||||
<option value="terminate">{% trans 'Terminate selected' %}</option>
|
||||
<option value="finished">{% trans 'Confirm finished' %}</option>
|
||||
</select>
|
||||
<div class="input-group-btn pull-left" style="padding-left: 5px;">
|
||||
<button id='btn_bulk_update' style="height: 32px;" class="btn btn-sm btn-primary">
|
||||
|
@ -149,6 +150,21 @@
|
|||
success_message: success_message
|
||||
});
|
||||
}
|
||||
|
||||
function finishedSession(data) {
|
||||
var the_url = "{% url 'api-terminal:session-list' %}";
|
||||
var success_message = '{% trans "Finish session success" %}';
|
||||
var success = function() {
|
||||
location.reload();
|
||||
};
|
||||
APIUpdateAttr({
|
||||
url: the_url,
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(data),
|
||||
success: success,
|
||||
success_message: success_message
|
||||
});
|
||||
}
|
||||
$(document).ready(function() {
|
||||
jumpserver.initStaticTable('table');
|
||||
$('.select2').select2({
|
||||
|
@ -183,10 +199,24 @@
|
|||
function doTerminate() {
|
||||
terminateSession(id_list)
|
||||
}
|
||||
|
||||
function doFinishSession() {
|
||||
var data = [];
|
||||
$.each(id_list, function (i, v) {
|
||||
data.push({
|
||||
"pk": v,
|
||||
"is_finished": true
|
||||
})
|
||||
});
|
||||
finishedSession(data)
|
||||
}
|
||||
switch(action) {
|
||||
case 'terminate':
|
||||
doTerminate();
|
||||
break;
|
||||
case "finished":
|
||||
doFinishSession();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
#
|
||||
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'terminal'
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router = BulkRouter()
|
||||
router.register(r'sessions', api.SessionViewSet, 'session')
|
||||
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?status', api.StatusViewSet, 'terminal-status')
|
||||
router.register(r'terminal/(?P<terminal>[a-zA-Z0-9\-]{36})?/?sessions', api.SessionViewSet, 'terminal-sessions')
|
||||
router.register(r'terminal', api.TerminalViewSet, 'terminal')
|
||||
router.register(r'tasks', api.TaskViewSet, 'tasks')
|
||||
router.register(r'command', api.CommandViewSet, 'command')
|
||||
router.register(r'sessions', api.SessionViewSet, 'session')
|
||||
router.register(r'status', api.StatusViewSet, 'session')
|
||||
router.register(r'status', api.StatusViewSet, 'status')
|
||||
|
||||
urlpatterns = [
|
||||
path('sessions/<uuid:pk>/replay/',
|
||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
|||
|
||||
from django.core.cache import cache
|
||||
from django.contrib.auth import logout
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
|
@ -132,7 +133,7 @@ class UserResetOTPApi(generics.RetrieveAPIView):
|
|||
user = self.get_object() if kwargs.get('pk') else request.user
|
||||
if user == request.user:
|
||||
msg = _("Could not reset self otp, use profile reset instead")
|
||||
return Response({"msg": msg}, status=401)
|
||||
return Response({"error": msg}, status=401)
|
||||
if user.otp_enabled and user.otp_secret_key:
|
||||
user.otp_secret_key = ''
|
||||
user.save()
|
||||
|
|
|
@ -9,9 +9,15 @@
|
|||
{% block content %}
|
||||
|
||||
<div class="verify">
|
||||
<p style="margin:20px auto;"><strong style="color: #000000">{% trans 'Use the mobile Google Authenticator application to scan the following qr code for a 6-bit verification code' %}</strong></p>
|
||||
<p style="margin:20px auto;">
|
||||
<strong style="color: #000000">
|
||||
{% trans 'Use the mobile Google Authenticator application to scan the following qr code for a 6-bit verification code' %}
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
<div id="qr_code"></div>
|
||||
<div id="qr_code">
|
||||
</div>
|
||||
<div style="display: block; margin: 0">Secret: {{ otp_secret_key }}</div>
|
||||
|
||||
<form class="" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -263,7 +263,7 @@ def generate_otp_uri(request, issuer="Jumpserver"):
|
|||
otp_secret_key = base64.b32encode(os.urandom(10)).decode('utf-8')
|
||||
cache.set(request.session.session_key+'otp_key', otp_secret_key, 600)
|
||||
totp = pyotp.TOTP(otp_secret_key)
|
||||
return totp.provisioning_uri(name=user.username, issuer_name=issuer)
|
||||
return totp.provisioning_uri(name=user.username, issuer_name=issuer), otp_secret_key
|
||||
|
||||
|
||||
def check_otp_code(otp_secret_key, otp_code):
|
||||
|
|
|
@ -505,8 +505,10 @@ class UserOtpEnableBindView(TemplateView, FormView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
user = get_user_or_tmp_user(self.request)
|
||||
otp_uri, otp_secret_key = generate_otp_uri(self.request)
|
||||
context = {
|
||||
'otp_uri': generate_otp_uri(self.request),
|
||||
'otp_uri': otp_uri,
|
||||
'otp_secret_key': otp_secret_key,
|
||||
'user': user
|
||||
}
|
||||
kwargs.update(context)
|
||||
|
|
|
@ -31,10 +31,10 @@ ecdsa==0.13
|
|||
elasticsearch==6.1.1
|
||||
enum-compat==0.0.2
|
||||
ephem==3.7.6.0
|
||||
eventlet==0.22.1
|
||||
eventlet==0.24.1
|
||||
ForgeryPy==0.1
|
||||
greenlet==0.4.12
|
||||
gunicorn==19.7.1
|
||||
greenlet==0.4.14
|
||||
gunicorn==19.9.0
|
||||
idna==2.6
|
||||
itsdangerous==0.24
|
||||
itypes==1.1.0
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/python
|
||||
import os
|
||||
import sys
|
||||
import django
|
||||
|
||||
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 assets.models import Node, Asset
|
||||
from common.utils import get_object_or_none
|
||||
from django.db import transaction
|
||||
|
||||
src_node = Node.objects.get(key="0:1")
|
||||
target_node = Node.objects.get(key="2:1")
|
||||
|
||||
def sync_node(src, target, cut=False):
|
||||
assets = src.get_assets()
|
||||
# 同步本节点资产
|
||||
for asset in assets:
|
||||
if cut:
|
||||
src.assets.remove(asset)
|
||||
asset.org_id = target.org_id
|
||||
asset.save()
|
||||
new_asset = asset
|
||||
else:
|
||||
new_asset = get_object_or_none(Asset, hostname=asset.hostname, org_id=target.org_id)
|
||||
if new_asset is None:
|
||||
asset.id = None
|
||||
asset.org_id = target.org_id
|
||||
asset.save()
|
||||
new_asset = asset
|
||||
target.assets.add(new_asset)
|
||||
# 同步子节点资产
|
||||
for child in src.get_children():
|
||||
node_new = target.create_child(child.value)
|
||||
node_new.org_id = target.org_id
|
||||
node_new.save()
|
||||
sync_node(child, node_new)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with transaction.atomic():
|
||||
sync_node(src_node, target_node)
|
||||
|
Loading…
Reference in New Issue