[Update] 修改tickets

pull/3428/head
ibuler 2019-11-08 20:17:25 +08:00
parent bd323d608e
commit 9e4874834f
12 changed files with 169 additions and 120 deletions

View File

@ -10,7 +10,7 @@ from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin
from ..models import LoginConfirmSetting
from ..serializers import LoginConfirmSettingSerializer
from .. import errors
from .. import errors, mixins
__all__ = ['LoginConfirmSettingUpdateApi', 'LoginConfirmTicketStatusApi']
logger = get_logger(__name__)
@ -31,7 +31,7 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
return s
class LoginConfirmTicketStatusApi(APIView):
class LoginConfirmTicketStatusApi(mixins.AuthMixin, APIView):
permission_classes = ()
def get_ticket(self):
@ -45,24 +45,9 @@ class LoginConfirmTicketStatusApi(APIView):
return ticket
def get(self, request, *args, **kwargs):
ticket_id = self.request.session.get("auth_ticket_id")
ticket = self.get_ticket()
try:
if not ticket:
raise errors.LoginConfirmOtherError(ticket_id, _("not found"))
if ticket.status == 'open':
raise errors.LoginConfirmWaitError(ticket_id)
elif ticket.action == ticket.ACTION_APPROVE:
self.request.session["auth_confirm"] = "1"
return Response({"msg": "ok"})
elif ticket.action == ticket.ACTION_REJECT:
raise errors.LoginConfirmOtherError(
ticket_id, ticket.get_action_display()
)
else:
raise errors.LoginConfirmOtherError(
ticket_id, ticket.get_status_display()
)
self.check_user_login_confirm()
return Response({"msg": "ok"})
except errors.NeedMoreInfoError as e:
return Response(e.as_data(), status=200)

View File

@ -28,7 +28,7 @@ class TokenCreateApi(AuthMixin, CreateAPIView):
self.create_session_if_need()
# 如果认证没有过,检查账号密码
try:
user = self.check_user_auth()
user = self.check_user_auth_if_need()
self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user)
self.send_auth_signal(success=True, user=user)

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
import time
from django.conf import settings
from common.utils import get_object_or_none, get_request_ip, get_logger
from users.models import User
@ -49,8 +50,8 @@ class AuthMixin:
raise errors.BlockLoginError(username=username, ip=ip)
def check_user_auth(self):
request = self.request
self.check_is_block()
request = self.request
if hasattr(request, 'data'):
username = request.data.get('username', '')
password = request.data.get('password', '')
@ -73,11 +74,20 @@ class AuthMixin:
request.session['user_id'] = str(user.id)
return user
def check_user_auth_if_need(self):
request = self.request
if request.session.get('auth_password') and \
request.session.get('user_id'):
user = self.get_user_from_session()
if user:
return user
return self.check_user_auth()
def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'):
return True
return
if not user.otp_enabled or not user.otp_secret_key:
return True
return
raise errors.MFARequiredError()
def check_user_mfa(self, code):
@ -90,28 +100,53 @@ class AuthMixin:
return
raise errors.MFAFailedError(username=user.username, request=self.request)
def check_user_login_confirm_if_need(self, user):
def get_ticket(self):
from tickets.models import LoginConfirmTicket
confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting:
return
ticket = None
if self.request.session.get('auth_ticket_id'):
ticket_id = self.request.session['auth_ticket_id']
ticket_id = self.request.session.get("auth_ticket_id")
logger.debug('Login confirm ticket id: {}'.format(ticket_id))
if not ticket_id:
ticket = None
else:
ticket = get_object_or_none(LoginConfirmTicket, pk=ticket_id)
return ticket
def get_ticket_or_create(self, confirm_setting):
ticket = self.get_ticket()
if not ticket:
ticket = confirm_setting.create_confirm_ticket(self.request)
self.request.session['auth_ticket_id'] = str(ticket.id)
return ticket
if ticket.status == "accepted":
return
elif ticket.status == "rejected":
raise errors.LoginConfirmOtherError(ticket.id)
else:
def check_user_login_confirm(self):
ticket = self.get_ticket()
if not ticket:
raise errors.LoginConfirmOtherError('', "Not found")
if ticket.status == ticket.STATUS_OPEN:
raise errors.LoginConfirmWaitError(ticket.id)
elif ticket.action == ticket.ACTION_APPROVE:
self.request.session["auth_confirm"] = "1"
return
elif ticket.action == ticket.ACTION_REJECT:
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_action_display()
)
else:
raise errors.LoginConfirmOtherError(
ticket.id, ticket.get_status_display()
)
def check_user_login_confirm_if_need(self, user):
if not settings.CONFIG.LOGIN_CONFIRM_ENABLE:
return
confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting:
return
self.get_ticket_or_create(confirm_setting)
self.check_user_login_confirm()
def clear_auth_mark(self):
self.request.session['auth_password'] = ''
self.request.session['auth_user_id'] = ''
self.request.session['auth_mfa'] = ''
self.request.session['auth_confirm'] = ''
self.request.session['auth_ticket_id'] = ''

View File

@ -43,7 +43,7 @@
</a>
</div>
<div class="col-lg-3">
<a class="btn btn-primary btn-sm block btn-copy" data-link="{{ order_detail_url }}">
<a class="btn btn-primary btn-sm block btn-copy" data-link="{{ ticket_detail_url }}">
<i class="fa fa-clipboard"></i> {% trans 'Copy link' %}
</a>
</div>
@ -132,7 +132,11 @@ $(document).ready(function () {
checkInterval = setInterval(doRequestAuth, 5000);
doRequestAuth();
initClipboard();
window.onbeforeunload = function (e) {
return "{% trans "Confirm" %}";
};
}).on('click', '.btn-refresh', function () {
window.onbeforeunload = function() {};
window.location.reload();
})

View File

@ -19,9 +19,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from common.utils import get_request_ip, get_object_or_none
from users.models import User
from users.utils import (
get_user_or_tmp_user, increase_login_failed_count,
redirect_user_first_login_or_index
)
from ..signals import post_auth_success, post_auth_failed
@ -117,42 +115,28 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
return url
def get_redirect_url(self, *args, **kwargs):
if not self.request.session.get('auth_password'):
try:
user = self.check_user_auth_if_need()
self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user)
except errors.CredentialError:
return self.format_redirect_url(self.login_url)
user = self.get_user_from_session()
# 启用并设置了otp
if user.otp_enabled and user.otp_secret_key and \
not self.request.session.get('auth_mfa'):
except errors.MFARequiredError:
return self.format_redirect_url(self.login_otp_url)
confirm_setting = user.get_login_confirm_setting()
if confirm_setting and not self.request.session.get('auth_confirm'):
ticket = confirm_setting.create_confirm_ticket(self.request)
self.request.session['auth_ticket_id'] = str(ticket.id)
url = self.format_redirect_url(self.login_confirm_url)
return url
self.login_success(user)
self.clear_auth_mark()
# 启用但是没有设置otp
if user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
url = redirect_user_first_login_or_index(
self.request, self.redirect_field_name
)
return url
def login_success(self, user):
auth_login(self.request, user)
self.send_auth_signal(success=True, user=user)
def send_auth_signal(self, success=True, user=None, username='', reason=''):
if success:
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
except errors.LoginConfirmBaseError:
return self.format_redirect_url(self.login_confirm_url)
else:
post_auth_failed.send(
sender=self.__class__, username=username,
request=self.request, reason=reason
auth_login(self.request, user)
self.send_auth_signal(success=True, user=user)
self.clear_auth_mark()
# 启用但是没有设置otp
if user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
return reverse('users:user-otp-enable-authentication')
url = redirect_user_first_login_or_index(
self.request, self.redirect_field_name
)
return url
class UserLoginWaitConfirmView(TemplateView):

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-08 15:42+0800\n"
"POT-Creation-Date: 2019-11-08 17:27+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"
@ -354,6 +354,7 @@ msgstr "重置"
#: terminal/templates/terminal/command_list.html:47
#: terminal/templates/terminal/session_list.html:52
#: terminal/templates/terminal/terminal_update.html:46
#: tickets/templates/tickets/login_confirm_ticket_list.html:32
#: users/templates/users/_user.html:52
#: users/templates/users/forgot_password.html:42
#: users/templates/users/user_bulk_update.html:24
@ -528,7 +529,7 @@ msgstr "创建远程应用"
#: terminal/templates/terminal/session_list.html:36
#: terminal/templates/terminal/terminal_list.html:36
#: tickets/templates/tickets/login_confirm_ticket_list.html:18
#: tickets/templates/tickets/login_confirm_ticket_list.html:92
#: tickets/templates/tickets/login_confirm_ticket_list.html:105
#: users/templates/users/_granted_assets.html:34
#: users/templates/users/user_group_list.html:38
#: users/templates/users/user_list.html:41
@ -1480,7 +1481,7 @@ msgid "Asset user auth"
msgstr "资产用户信息"
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
#: authentication/templates/authentication/login_wait_confirm.html:114
#: authentication/templates/authentication/login_wait_confirm.html:115
msgid "Copy success"
msgstr "复制成功"
@ -1666,6 +1667,7 @@ msgstr "选择节点"
#: assets/templates/assets/system_user_detail.html:182
#: assets/templates/assets/system_user_list.html:135
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
#: authentication/templates/authentication/login_wait_confirm.html:136
#: settings/templates/settings/terminal_setting.html:168
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
#: users/templates/users/user_detail.html:271
@ -2294,7 +2296,7 @@ msgstr "原因"
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
#: tickets/templates/tickets/login_confirm_ticket_list.html:16
#: tickets/templates/tickets/login_confirm_ticket_list.html:88
#: tickets/templates/tickets/login_confirm_ticket_list.html:101
#: tickets/templates/tickets/ticket_detail.html:34
#: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
@ -2390,10 +2392,6 @@ msgstr "登录日志"
msgid "Command execution log"
msgstr "命令执行"
#: authentication/api/login_confirm.py:52
msgid "not found"
msgstr "没有发现"
#: authentication/backends/api.py:53
msgid "Invalid signature header. No credentials provided."
msgstr ""
@ -2703,11 +2701,11 @@ msgstr "返回"
msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/views/login.py:73
#: authentication/views/login.py:71
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:172
#: authentication/views/login.py:170
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@ -2715,15 +2713,15 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面"
#: authentication/views/login.py:177
#: authentication/views/login.py:175
msgid "No ticket found"
msgstr "没有发现工单"
#: authentication/views/login.py:200
#: authentication/views/login.py:198
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:201
#: authentication/views/login.py:199
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
@ -4571,8 +4569,8 @@ msgstr "接受"
#: terminal/templates/terminal/terminal_list.html:80
#: tickets/models/login_confirm.py:16
#: tickets/templates/tickets/login_confirm_ticket_detail.html:10
#: tickets/templates/tickets/login_confirm_ticket_list.html:57
#: tickets/templates/tickets/login_confirm_ticket_list.html:94
#: tickets/templates/tickets/login_confirm_ticket_list.html:70
#: tickets/templates/tickets/login_confirm_ticket_list.html:107
msgid "Reject"
msgstr "拒绝"
@ -4610,12 +4608,12 @@ msgid ""
msgstr "你可以使用ssh客户端工具连接终端"
#: tickets/models/base.py:16 tickets/models/base.py:52
#: tickets/templates/tickets/login_confirm_ticket_list.html:89
#: tickets/templates/tickets/login_confirm_ticket_list.html:102
msgid "Open"
msgstr ""
msgstr "开启"
#: tickets/models/base.py:17
#: tickets/templates/tickets/login_confirm_ticket_list.html:90
#: tickets/templates/tickets/login_confirm_ticket_list.html:103
msgid "Closed"
msgstr "关闭"
@ -4629,7 +4627,7 @@ msgstr "用户显示名称"
#: tickets/models/base.py:28
#: tickets/templates/tickets/login_confirm_ticket_list.html:14
#: tickets/templates/tickets/login_confirm_ticket_list.html:87
#: tickets/templates/tickets/login_confirm_ticket_list.html:100
msgid "Title"
msgstr "标题"
@ -4659,8 +4657,8 @@ msgstr "{} {} 这个工单"
#: tickets/models/login_confirm.py:15
#: tickets/templates/tickets/login_confirm_ticket_detail.html:9
#: tickets/templates/tickets/login_confirm_ticket_list.html:56
#: tickets/templates/tickets/login_confirm_ticket_list.html:93
#: tickets/templates/tickets/login_confirm_ticket_list.html:69
#: tickets/templates/tickets/login_confirm_ticket_list.html:106
msgid "Approve"
msgstr "同意"
@ -4668,6 +4666,14 @@ msgstr "同意"
msgid "this order"
msgstr "这个工单"
#: tickets/templates/tickets/login_confirm_ticket_list.html:27
msgid "Approve selected"
msgstr "同意所选"
#: tickets/templates/tickets/login_confirm_ticket_list.html:28
msgid "Reject selected"
msgstr "拒绝所选"
#: tickets/templates/tickets/ticket_detail.html:66
#: tickets/templates/tickets/ticket_detail.html:81
msgid "ago"
@ -6431,6 +6437,12 @@ msgstr "密码匣子"
msgid "vault create"
msgstr "创建"
#~ msgid "selected"
#~ msgstr "所选"
#~ msgid "not found"
#~ msgstr "没有发现"
#~ msgid "Log in frequently and try again later"
#~ msgstr "登录频繁, 稍后重试"
@ -6446,9 +6458,6 @@ msgstr "创建"
#~ msgid "Accepted"
#~ msgstr "已接受"
#~ msgid "Rejected"
#~ msgstr "已拒绝"
#~ msgid "New order"
#~ msgstr "新工单"

View File

@ -1319,5 +1319,5 @@ function initDateRangePicker(selector, options) {
}
function reloadPage() {
window.location.reload();
setTimeout( function () {window.location.reload();}, 300);
}

View File

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
#
from rest_framework import viewsets, generics
from rest_framework.serializers import ValidationError
from django.shortcuts import get_object_or_404
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsValidUser
from common.mixins import CommonApiMixin
@ -10,21 +8,9 @@ from .. import serializers, mixins
from ..models import LoginConfirmTicket
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, viewsets.ModelViewSet):
class LoginConfirmTicketViewSet(CommonApiMixin, mixins.TicketMixin, BulkModelViewSet):
serializer_class = serializers.LoginConfirmTicketSerializer
permission_classes = (IsValidUser,)
queryset = LoginConfirmTicket.objects.all()
filter_fields = ['status', 'title', 'action', 'ip']
search_fields = ['user_display', 'title', 'ip', 'city']
# def check_update_permission(self, serializer):
# data = serializer.validated_data
# action = data.get("action")
# user = self.request.user
# instance = serializer.instance
# if action and user not in instance.assignees.all():
# error = {"action": "Only assignees can update"}
# raise ValidationError(error)
#
# def perform_update(self, serializer):
# self.check_update_permission(serializer)

View File

@ -2,6 +2,8 @@
#
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from .base import TicketSerializer
from ..models import LoginConfirmTicket
@ -9,8 +11,9 @@ from ..models import LoginConfirmTicket
__all__ = ['LoginConfirmTicketSerializer', 'LoginConfirmTicketActionSerializer']
class LoginConfirmTicketSerializer(serializers.ModelSerializer):
class LoginConfirmTicketSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = LoginConfirmTicket
fields = TicketSerializer.Meta.fields + [
'ip', 'city', 'action'
@ -24,11 +27,14 @@ class LoginConfirmTicketSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
action = validated_data.get("action")
user = self.context["request"].user
if action and user not in instance.assignees.all():
error = {"action": "Only assignees can update"}
raise serializers.ValidationError(error)
if instance.status == instance.STATUS_CLOSED:
validated_data.pop('action')
instance = super().update(instance, validated_data)
if action:
if not instance.status == instance.STATUS_CLOSED:
instance.perform_action(action, user)
return instance

View File

@ -21,6 +21,19 @@
<tbody>
</tbody>
</table>
<div id="actions" class="hide">
<div class="input-group">
<select class="form-control m-b" style="width: auto" id="slct_bulk_update">
<option value="approve">{% trans 'Approve selected' %}</option>
<option value="reject">{% trans 'Reject selected' %}</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">
{% trans 'Submit' %}
</button>
</div>
</div>
</div>
{% include '_filter_dropdown.html' %}
{% endblock %}
{% block content_bottom_left %}{% endblock %}
@ -38,9 +51,9 @@ function initTable() {
$(td).html(detailBtn.replace("{{ DEFAULT_PK }}", rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
if (cellData === "approval") {
if (cellData === "approve") {
$(td).html('<i class="fa fa-check text-navy"></i>')
} else if (cellData === "rejected") {
} else if (cellData === "reject") {
$(td).html('<i class="fa fa-times text-danger"></i>')
} else if (cellData === "open") {
$(td).html('<i class="fa fa-spinner text-info"></i>')
@ -70,9 +83,9 @@ function initTable() {
columns: [
{data: "id"}, {data: "title"},
{data: "user_display"},
{data: "status", ticketable: false},
{data: "action", width: "40px"},
{data: "date_created", width: "120px"},
{data: "id", ticketable: false}
{data: "id", orderable: false}
],
op_html: $('#actions').html()
};
@ -85,6 +98,7 @@ $(document).ready(function(){
var menu = [
{title: "IP", value: "ip"},
{title: "{% trans 'Title' %}", value: "title"},
{title: "{% trans 'User' %}", value: "user_display"},
{title: "{% trans 'Status' %}", value: "status", submenu: [
{title: "{% trans 'Open' %}", value: "open"},
{title: "{% trans 'Closed' %}", value: "closed"},
@ -107,6 +121,33 @@ $(document).ready(function(){
success: reloadPage
};
requestApi(data);
}).on('click', '#btn_bulk_update', function () {
var action = $('#slct_bulk_update').val();
var idList = ticketTable.selected;
if (idList.length === 0) {
return false;
}
var theUrl = "{% url 'api-tickets:login-confirm-ticket-list' %}";
function doAction(action) {
var data = [];
$.each(idList, function(index, object_id) {
var obj = {
"pk": object_id, "action": action
};
data.push(obj);
});
requestApi({
url: theUrl,
method: 'PATCH',
body: JSON.stringify(data),
success: function (){
$(".ipt_check_all").prop("checked", false)
ticketTable.ajax.reload();
}
});
}
doAction(action)
})
</script>
{% endblock %}

View File

@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
#
from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_bulk.routes import BulkRouter
from .. import api
app_name = 'tickets'
router = DefaultRouter()
router = BulkRouter()
router.register('tickets', api.TicketViewSet, 'ticket')
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')