mirror of https://github.com/jumpserver/jumpserver
[Update] 基本完成登录逻辑
parent
9d201bbf98
commit
6ce9815d51
|
@ -110,5 +110,14 @@ class UserLoginLog(models.Model):
|
||||||
login_logs = login_logs.filter(username__in=username_list)
|
login_logs = login_logs.filter(username__in=username_list)
|
||||||
return login_logs
|
return login_logs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reason_display(self):
|
||||||
|
from authentication.errors import reason_choices, old_reason_choices
|
||||||
|
reason = reason_choices.get(self.reason)
|
||||||
|
if reason:
|
||||||
|
return reason
|
||||||
|
reason = old_reason_choices.get(self.reason, self.reason)
|
||||||
|
return reason
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-datetime', 'username']
|
ordering = ['-datetime', 'username']
|
||||||
|
|
|
@ -4,15 +4,18 @@
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from jumpserver.utils import current_request
|
from jumpserver.utils import current_request
|
||||||
from common.utils import get_request_ip, get_logger, get_syslogger
|
from common.utils import get_request_ip, get_logger, get_syslogger
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
from authentication.signals import post_auth_failed, post_auth_success
|
||||||
from terminal.models import Session, Command
|
from terminal.models import Session, Command
|
||||||
from terminal.backends.command.serializers import SessionCommandSerializer
|
from terminal.backends.command.serializers import SessionCommandSerializer
|
||||||
from . import models
|
from . import models, serializers
|
||||||
from . import serializers
|
from .tasks import write_login_log_async
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
sys_logger = get_syslogger("audits")
|
sys_logger = get_syslogger("audits")
|
||||||
|
@ -99,3 +102,39 @@ def on_audits_log_create(sender, instance=None, **kwargs):
|
||||||
data = json_render.render(s.data).decode(errors='ignore')
|
data = json_render.render(s.data).decode(errors='ignore')
|
||||||
msg = "{} - {}".format(category, data)
|
msg = "{} - {}".format(category, data)
|
||||||
sys_logger.info(msg)
|
sys_logger.info(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_data(username, request):
|
||||||
|
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
|
||||||
|
if isinstance(request, Request):
|
||||||
|
login_ip = request.data.get('remote_addr', None)
|
||||||
|
login_type = request.data.get('login_type', '')
|
||||||
|
else:
|
||||||
|
login_ip = get_request_ip(request)
|
||||||
|
login_type = 'W'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': username,
|
||||||
|
'ip': login_ip,
|
||||||
|
'type': login_type,
|
||||||
|
'user_agent': user_agent,
|
||||||
|
'datetime': timezone.now()
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_auth_success)
|
||||||
|
def on_user_auth_success(sender, user, request, **kwargs):
|
||||||
|
logger.debug('User login success: {}'.format(user.username))
|
||||||
|
data = generate_data(user.username, request)
|
||||||
|
data.update({'mfa': int(user.otp_enabled), 'status': True})
|
||||||
|
write_login_log_async.delay(**data)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_auth_failed)
|
||||||
|
def on_user_auth_failed(sender, username, request, reason, **kwargs):
|
||||||
|
logger.debug('User login failed: {}'.format(username))
|
||||||
|
data = generate_data(username, request)
|
||||||
|
data.update({'reason': reason, 'status': False})
|
||||||
|
write_login_log_async.delay(**data)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from celery import shared_task
|
||||||
|
|
||||||
from ops.celery.decorator import register_as_period_task
|
from ops.celery.decorator import register_as_period_task
|
||||||
from .models import UserLoginLog
|
from .models import UserLoginLog
|
||||||
|
from .utils import write_login_log
|
||||||
|
|
||||||
|
|
||||||
@register_as_period_task(interval=3600*24)
|
@register_as_period_task(interval=3600*24)
|
||||||
|
@ -19,3 +20,8 @@ def clean_login_log_period():
|
||||||
days = 90
|
days = 90
|
||||||
expired_day = now - datetime.timedelta(days=days)
|
expired_day = now - datetime.timedelta(days=days)
|
||||||
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
|
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def write_login_log_async(*args, **kwargs):
|
||||||
|
write_login_log(*args, **kwargs)
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
<td class="text-center">{{ login_log.ip }}</td>
|
<td class="text-center">{{ login_log.ip }}</td>
|
||||||
<td class="text-center">{{ login_log.city }}</td>
|
<td class="text-center">{{ login_log.city }}</td>
|
||||||
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
<td class="text-center">{{ login_log.get_mfa_display }}</td>
|
||||||
<td class="text-center">{% trans login_log.reason %}</td>
|
<td class="text-center">{{ login_log.reason_display }}</td>
|
||||||
<td class="text-center">{{ login_log.get_status_display }}</td>
|
<td class="text-center">{{ login_log.get_status_display }}</td>
|
||||||
<td class="text-center">{{ login_log.datetime }}</td>
|
<td class="text-center">{{ login_log.datetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import csv
|
import csv
|
||||||
import codecs
|
import codecs
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from common.utils import validate_ip, get_ip_city
|
||||||
|
|
||||||
|
|
||||||
def get_excel_response(filename):
|
def get_excel_response(filename):
|
||||||
|
@ -19,4 +22,17 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None):
|
||||||
for log in login_logs:
|
for log in login_logs:
|
||||||
data = [getattr(log, field.name) for field in fields]
|
data = [getattr(log, field.name) for field in fields]
|
||||||
writer.writerow(data)
|
writer.writerow(data)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def write_login_log(*args, **kwargs):
|
||||||
|
from audits.models import UserLoginLog
|
||||||
|
default_city = _("Unknown")
|
||||||
|
ip = kwargs.get('ip') or ''
|
||||||
|
if not (ip and validate_ip(ip)):
|
||||||
|
ip = ip[:15]
|
||||||
|
city = default_city
|
||||||
|
else:
|
||||||
|
city = get_ip_city(ip) or default_city
|
||||||
|
kwargs.update({'ip': ip, 'city': city})
|
||||||
|
UserLoginLog.objects.create(**kwargs)
|
||||||
|
|
|
@ -1,114 +1,25 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.urls import reverse
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.generics import CreateAPIView
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from common.utils import get_logger, get_request_ip, get_object_or_none
|
from common.utils import get_logger
|
||||||
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
|
from common.permissions import IsOrgAdminOrAppUser
|
||||||
from orgs.mixins.api import RootOrgViewMixin
|
from orgs.mixins.api import RootOrgViewMixin
|
||||||
from users.serializers import UserSerializer
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from assets.models import Asset, SystemUser
|
from assets.models import Asset, SystemUser
|
||||||
from users.utils import (
|
|
||||||
check_otp_code, increase_login_failed_count,
|
|
||||||
is_block_login, clean_failed_count
|
|
||||||
)
|
|
||||||
from .. import errors
|
|
||||||
from ..utils import check_user_valid
|
|
||||||
from ..serializers import OtpVerifySerializer
|
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
|
'UserConnectionTokenApi',
|
||||||
'UserOtpVerifyApi', 'UserOrderAcceptAuthApi',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class UserAuthApi(RootOrgViewMixin, APIView):
|
|
||||||
permission_classes = (AllowAny,)
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
|
|
||||||
def get_serializer_context(self):
|
|
||||||
return {
|
|
||||||
'request': self.request,
|
|
||||||
'view': self
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
|
||||||
kwargs['context'] = self.get_serializer_context()
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
# limit login
|
|
||||||
username = request.data.get('username')
|
|
||||||
ip = request.data.get('remote_addr', None)
|
|
||||||
ip = ip or get_request_ip(request)
|
|
||||||
|
|
||||||
if is_block_login(username, ip):
|
|
||||||
msg = _("Log in frequently and try again later")
|
|
||||||
logger.warn(msg + ': ' + username + ':' + ip)
|
|
||||||
return Response({'msg': msg}, status=401)
|
|
||||||
|
|
||||||
user, msg = self.check_user_valid(request)
|
|
||||||
if not user:
|
|
||||||
username = request.data.get('username', '')
|
|
||||||
self.send_auth_signal(success=False, username=username, reason=msg)
|
|
||||||
increase_login_failed_count(username, ip)
|
|
||||||
return Response({'msg': msg}, status=401)
|
|
||||||
|
|
||||||
if not user.otp_enabled:
|
|
||||||
self.send_auth_signal(success=True, user=user)
|
|
||||||
# 登陆成功,清除原来的缓存计数
|
|
||||||
clean_failed_count(username, ip)
|
|
||||||
token, expired_at = user.create_bearer_token(request)
|
|
||||||
return Response(
|
|
||||||
{'token': token, 'user': self.get_serializer(user).data}
|
|
||||||
)
|
|
||||||
|
|
||||||
seed = uuid.uuid4().hex
|
|
||||||
cache.set(seed, user, 300)
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
'code': 101,
|
|
||||||
'msg': _('Please carry seed value and '
|
|
||||||
'conduct MFA secondary certification'),
|
|
||||||
'otp_url': reverse('api-auth:user-otp-auth'),
|
|
||||||
'seed': seed,
|
|
||||||
'user': self.get_serializer(user).data
|
|
||||||
}, status=300
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_user_valid(request):
|
|
||||||
username = request.data.get('username', '')
|
|
||||||
password = request.data.get('password', '')
|
|
||||||
public_key = request.data.get('public_key', '')
|
|
||||||
user, msg = check_user_valid(
|
|
||||||
username=username, password=password,
|
|
||||||
public_key=public_key
|
|
||||||
)
|
|
||||||
return user, msg
|
|
||||||
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
post_auth_failed.send(
|
|
||||||
sender=self.__class__, username=username,
|
|
||||||
request=self.request, reason=reason
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
||||||
permission_classes = (IsOrgAdminOrAppUser,)
|
permission_classes = (IsOrgAdminOrAppUser,)
|
||||||
|
|
||||||
|
@ -150,82 +61,5 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
||||||
return super().get_permissions()
|
return super().get_permissions()
|
||||||
|
|
||||||
|
|
||||||
class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
|
||||||
permission_classes = (AllowAny,)
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
|
|
||||||
def get_serializer_context(self):
|
|
||||||
return {
|
|
||||||
'request': self.request,
|
|
||||||
'view': self
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
|
||||||
kwargs['context'] = self.get_serializer_context()
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
otp_code = request.data.get('otp_code', '')
|
|
||||||
seed = request.data.get('seed', '')
|
|
||||||
user = cache.get(seed, None)
|
|
||||||
if not user:
|
|
||||||
return Response(
|
|
||||||
{'msg': _('Please verify the user name and password first')},
|
|
||||||
status=401
|
|
||||||
)
|
|
||||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
|
||||||
self.send_auth_signal(success=False, username=user.username, reason=errors.mfa_failed)
|
|
||||||
return Response({'msg': _('MFA certification failed')}, status=401)
|
|
||||||
self.send_auth_signal(success=True, user=user)
|
|
||||||
token, expired_at = user.create_bearer_token(request)
|
|
||||||
data = {'token': token, 'user': self.get_serializer(user).data}
|
|
||||||
return Response(data)
|
|
||||||
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
post_auth_failed.send(
|
|
||||||
sender=self.__class__, username=username,
|
|
||||||
request=self.request, reason=reason
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserOtpVerifyApi(CreateAPIView):
|
|
||||||
permission_classes = (IsValidUser,)
|
|
||||||
serializer_class = OtpVerifySerializer
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
serializer.is_valid(raise_exception=True)
|
|
||||||
code = serializer.validated_data["code"]
|
|
||||||
|
|
||||||
if request.user.check_otp(code):
|
|
||||||
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
|
||||||
return Response({"ok": "1"})
|
|
||||||
else:
|
|
||||||
return Response({"error": "Code not valid"}, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class UserOrderAcceptAuthApi(APIView):
|
|
||||||
permission_classes = ()
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
from orders.models import LoginConfirmOrder
|
|
||||||
order_id = self.request.session.get("auth_order_id")
|
|
||||||
logger.debug('Login confirm order id: {}'.format(order_id))
|
|
||||||
if not order_id:
|
|
||||||
order = None
|
|
||||||
else:
|
|
||||||
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
|
||||||
if not order:
|
|
||||||
error = _("No order found or order expired")
|
|
||||||
return Response({"error": error, "status": "not found"}, status=404)
|
|
||||||
if order.status == order.STATUS_ACCEPTED:
|
|
||||||
self.request.session["auth_confirm"] = "1"
|
|
||||||
return Response({"msg": "ok"})
|
|
||||||
elif order.status == order.STATUS_REJECTED:
|
|
||||||
error = _("Order was rejected by {}").format(order.assignee_display)
|
|
||||||
else:
|
|
||||||
error = "Order status: {}".format(order.status)
|
|
||||||
return Response({"error": error, "status": order.status}, status=400)
|
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from rest_framework.generics import UpdateAPIView
|
from rest_framework.generics import UpdateAPIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from common.utils import get_logger, get_object_or_none
|
||||||
from common.permissions import IsOrgAdmin
|
from common.permissions import IsOrgAdmin
|
||||||
from ..models import LoginConfirmSetting
|
from ..models import LoginConfirmSetting
|
||||||
from ..serializers import LoginConfirmSettingSerializer
|
from ..serializers import LoginConfirmSettingSerializer
|
||||||
|
from .. import errors
|
||||||
|
|
||||||
__all__ = ['LoginConfirmSettingUpdateApi']
|
__all__ = ['LoginConfirmSettingUpdateApi', 'UserOrderAcceptAuthApi']
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
||||||
|
@ -23,3 +28,29 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
|
||||||
defaults, user=user,
|
defaults, user=user,
|
||||||
)
|
)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class UserOrderAcceptAuthApi(APIView):
|
||||||
|
permission_classes = ()
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
from orders.models import LoginConfirmOrder
|
||||||
|
order_id = self.request.session.get("auth_order_id")
|
||||||
|
logger.debug('Login confirm order id: {}'.format(order_id))
|
||||||
|
if not order_id:
|
||||||
|
order = None
|
||||||
|
else:
|
||||||
|
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
||||||
|
try:
|
||||||
|
if not order:
|
||||||
|
raise errors.LoginConfirmOrderNotFound(order_id)
|
||||||
|
if order.status == order.STATUS_ACCEPTED:
|
||||||
|
self.request.session["auth_confirm"] = "1"
|
||||||
|
return Response({"msg": "ok"})
|
||||||
|
elif order.status == order.STATUS_REJECTED:
|
||||||
|
raise errors.LoginConfirmRejectedError(order_id)
|
||||||
|
else:
|
||||||
|
return errors.LoginConfirmWaitError(order_id)
|
||||||
|
except errors.AuthFailedError as e:
|
||||||
|
data = e.as_data()
|
||||||
|
return Response(data, status=400)
|
||||||
|
|
|
@ -1,11 +1,56 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import time
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
|
from rest_framework.serializers import ValidationError
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from common.permissions import IsValidUser
|
||||||
|
from ..serializers import OtpVerifySerializer
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
from .. import errors
|
||||||
|
from ..mixins import AuthMixin
|
||||||
|
|
||||||
|
|
||||||
class MFAChallengeApi(CreateAPIView):
|
__all__ = ['MFAChallengeApi', 'UserOtpVerifyApi']
|
||||||
|
|
||||||
|
|
||||||
|
class MFAChallengeApi(AuthMixin, CreateAPIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
serializer_class = serializers.MFAChallengeSerializer
|
serializer_class = serializers.MFAChallengeSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
try:
|
||||||
|
user = self.get_user_from_session()
|
||||||
|
code = serializer.validated_data.get('code')
|
||||||
|
valid = user.check_otp(code)
|
||||||
|
if not valid:
|
||||||
|
self.request.session['auth_mfa'] = ''
|
||||||
|
raise errors.MFAFailedError(
|
||||||
|
username=user.username, request=self.request
|
||||||
|
)
|
||||||
|
|
||||||
|
except errors.AuthFailedError as e:
|
||||||
|
data = {"error": e.error, "msg": e.reason}
|
||||||
|
raise ValidationError(data)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
super().create(request, *args, **kwargs)
|
||||||
|
return Response({'msg': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
|
class UserOtpVerifyApi(CreateAPIView):
|
||||||
|
permission_classes = (IsValidUser,)
|
||||||
|
serializer_class = OtpVerifySerializer
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
code = serializer.validated_data["code"]
|
||||||
|
|
||||||
|
if request.user.check_otp(code):
|
||||||
|
request.session["MFA_VERIFY_TIME"] = int(time.time())
|
||||||
|
return Response({"ok": "1"})
|
||||||
|
else:
|
||||||
|
return Response({"error": "Code not valid"}, status=400)
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
|
|
||||||
from common.utils import get_request_ip, get_logger, get_object_or_none
|
from common.utils import get_logger
|
||||||
from users.utils import (
|
|
||||||
check_otp_code, increase_login_failed_count,
|
|
||||||
is_block_login, clean_failed_count
|
|
||||||
)
|
|
||||||
from users.models import User
|
|
||||||
from ..utils import check_user_valid
|
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
|
||||||
from .. import serializers, errors
|
from .. import serializers, errors
|
||||||
|
from ..mixins import AuthMixin
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -22,126 +16,24 @@ logger = get_logger(__name__)
|
||||||
__all__ = ['TokenCreateApi']
|
__all__ = ['TokenCreateApi']
|
||||||
|
|
||||||
|
|
||||||
class TokenCreateApi(CreateAPIView):
|
class TokenCreateApi(AuthMixin, CreateAPIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
serializer_class = serializers.BearerTokenSerializer
|
serializer_class = serializers.BearerTokenSerializer
|
||||||
|
|
||||||
def check_session(self):
|
def create_session_if_need(self):
|
||||||
pass
|
if self.request.session.is_empty():
|
||||||
|
self.request.session.create()
|
||||||
def get_request_ip(self):
|
|
||||||
ip = self.request.data.get('remote_addr', None)
|
|
||||||
ip = ip or get_request_ip(self.request)
|
|
||||||
return ip
|
|
||||||
|
|
||||||
def check_is_block(self):
|
|
||||||
username = self.request.data.get("username")
|
|
||||||
ip = self.get_request_ip()
|
|
||||||
if is_block_login(username, ip):
|
|
||||||
msg = errors.ip_blocked
|
|
||||||
logger.warn(msg + ': ' + username + ':' + ip)
|
|
||||||
raise errors.AuthFailedError(msg, 'blocked')
|
|
||||||
|
|
||||||
def get_user_from_session(self):
|
|
||||||
user_id = self.request.session["user_id"]
|
|
||||||
user = get_object_or_none(User, pk=user_id)
|
|
||||||
if not user:
|
|
||||||
error = "Not user in session: {}".format(user_id)
|
|
||||||
raise errors.AuthFailedError(error, 'session_error')
|
|
||||||
return user
|
|
||||||
|
|
||||||
def check_user_auth(self):
|
|
||||||
request = self.request
|
|
||||||
if request.session.get("auth_password") and \
|
|
||||||
request.session.get('user_id'):
|
|
||||||
user = self.get_user_from_session()
|
|
||||||
return user
|
|
||||||
self.check_is_block()
|
|
||||||
username = request.data.get('username', '')
|
|
||||||
password = request.data.get('password', '')
|
|
||||||
public_key = request.data.get('public_key', '')
|
|
||||||
user, msg = check_user_valid(
|
|
||||||
username=username, password=password,
|
|
||||||
public_key=public_key
|
|
||||||
)
|
|
||||||
ip = self.get_request_ip()
|
|
||||||
if not user:
|
|
||||||
raise errors.AuthFailedError(msg, error='auth_failed', username=username)
|
|
||||||
clean_failed_count(username, ip)
|
|
||||||
request.session['auth_password'] = 1
|
|
||||||
request.session['user_id'] = str(user.id)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def check_user_mfa_if_need(self, user):
|
|
||||||
if self.request.session.get('auth_mfa'):
|
|
||||||
return True
|
|
||||||
if not user.otp_enabled or not user.otp_secret_key:
|
|
||||||
return True
|
|
||||||
otp_code = self.request.data.get("otp_code")
|
|
||||||
if not otp_code:
|
|
||||||
raise errors.MFARequiredError()
|
|
||||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
|
||||||
raise errors.AuthFailedError(
|
|
||||||
errors.mfa_failed, error='mfa_failed',
|
|
||||||
username=user.username,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check_user_login_confirm_if_need(self, user):
|
|
||||||
from orders.models import LoginConfirmOrder
|
|
||||||
confirm_setting = user.get_login_confirm_setting()
|
|
||||||
if self.request.session.get('auth_confirm') or not confirm_setting:
|
|
||||||
return
|
|
||||||
order = None
|
|
||||||
if self.request.session.get('auth_order_id'):
|
|
||||||
order_id = self.request.session['auth_order_id']
|
|
||||||
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
|
||||||
if not order:
|
|
||||||
order = confirm_setting.create_confirm_order(self.request)
|
|
||||||
self.request.session['auth_order_id'] = str(order.id)
|
|
||||||
|
|
||||||
if order.status == "accepted":
|
|
||||||
return
|
|
||||||
elif order.status == "rejected":
|
|
||||||
raise errors.LoginConfirmRejectedError()
|
|
||||||
else:
|
|
||||||
raise errors.LoginConfirmWaitError()
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
self.check_session()
|
self.create_session_if_need()
|
||||||
# 如果认证没有过,检查账号密码
|
# 如果认证没有过,检查账号密码
|
||||||
try:
|
try:
|
||||||
user = self.check_user_auth()
|
user = self.check_user_auth()
|
||||||
self.check_user_mfa_if_need(user)
|
self.check_user_mfa_if_need(user)
|
||||||
self.check_user_login_confirm_if_need(user)
|
self.check_user_login_confirm_if_need(user)
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
|
self.clear_auth_mark()
|
||||||
resp = super().create(request, *args, **kwargs)
|
resp = super().create(request, *args, **kwargs)
|
||||||
return resp
|
return resp
|
||||||
except errors.AuthFailedError as e:
|
except errors.AuthFailedError as e:
|
||||||
if e.username:
|
return Response(e.as_data(), status=401)
|
||||||
increase_login_failed_count(e.username, self.get_request_ip())
|
|
||||||
self.send_auth_signal(
|
|
||||||
success=False, username=e.username, reason=e.reason
|
|
||||||
)
|
|
||||||
return Response({'msg': e.reason, 'error': e.error}, status=401)
|
|
||||||
except errors.MFARequiredError:
|
|
||||||
msg = _("MFA required")
|
|
||||||
data = {'msg': msg, "choices": ["otp"], "error": 'mfa_required'}
|
|
||||||
return Response(data, status=300)
|
|
||||||
except errors.LoginConfirmRejectedError as e:
|
|
||||||
pass
|
|
||||||
except errors.LoginConfirmWaitError as e:
|
|
||||||
pass
|
|
||||||
except errors.LoginConfirmRequiredError as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
post_auth_failed.send(
|
|
||||||
sender=self.__class__, username=username,
|
|
||||||
request=self.request, reason=reason
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,41 +1,180 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
password_failed = _('Username/password check failed')
|
from .signals import post_auth_failed
|
||||||
mfa_failed = _('MFA authentication failed')
|
from users.utils import (
|
||||||
user_not_exist = _("Username does not exist")
|
increase_login_failed_count, get_login_failed_count
|
||||||
password_expired = _("Password expired")
|
)
|
||||||
user_invalid = _('Disabled or expired')
|
|
||||||
ip_blocked = _("Log in frequently and try again later")
|
|
||||||
|
|
||||||
mfa_required = _("MFA required")
|
reason_password_failed = 'password_failed'
|
||||||
login_confirm_required = _("Login confirm required")
|
reason_mfa_failed = 'mfa_failed'
|
||||||
login_confirm_wait = _("Wait login confirm")
|
reason_user_not_exist = 'user_not_exist'
|
||||||
|
reason_password_expired = 'password_expired'
|
||||||
|
reason_user_invalid = 'user_invalid'
|
||||||
|
reason_user_inactive = 'user_inactive'
|
||||||
|
|
||||||
|
reason_choices = {
|
||||||
|
reason_password_failed: _('Username/password check failed'),
|
||||||
|
reason_mfa_failed: _('MFA authentication failed'),
|
||||||
|
reason_user_not_exist: _("Username does not exist"),
|
||||||
|
reason_password_expired: _("Password expired"),
|
||||||
|
reason_user_invalid: _('Disabled or expired'),
|
||||||
|
reason_user_inactive: _("This account is inactive.")
|
||||||
|
}
|
||||||
|
old_reason_choices = {
|
||||||
|
'0': '-',
|
||||||
|
'1': reason_choices[reason_password_failed],
|
||||||
|
'2': reason_choices[reason_mfa_failed],
|
||||||
|
'3': reason_choices[reason_user_not_exist],
|
||||||
|
'4': reason_choices[reason_password_expired],
|
||||||
|
}
|
||||||
|
|
||||||
|
session_empty_msg = _("No session found, check your cookie")
|
||||||
|
invalid_login_msg = _(
|
||||||
|
"The username or password you entered is incorrect, "
|
||||||
|
"please enter it again. "
|
||||||
|
"You can also try {times_try} times "
|
||||||
|
"(The account will be temporarily locked for {block_time} minutes)"
|
||||||
|
)
|
||||||
|
block_login_msg = _(
|
||||||
|
"The account has been locked "
|
||||||
|
"(please contact admin to unlock it or try again after {} minutes)"
|
||||||
|
)
|
||||||
|
mfa_failed_msg = _("MFA code invalid, or ntp sync server time")
|
||||||
|
|
||||||
|
mfa_required_msg = _("MFA required")
|
||||||
|
login_confirm_required_msg = _("Login confirm required")
|
||||||
|
login_confirm_wait_msg = _("Wait login confirm order for accept")
|
||||||
|
login_confirm_rejected_msg = _("Login confirm order was rejected")
|
||||||
|
login_confirm_order_not_found_msg = _("Order not found")
|
||||||
|
|
||||||
|
|
||||||
|
class AuthFailedNeedLogMixin:
|
||||||
|
username = ''
|
||||||
|
request = None
|
||||||
|
error = ''
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
post_auth_failed.send(
|
||||||
|
sender=self.__class__, username=self.username,
|
||||||
|
request=self.request, reason=self.error
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthFailedNeedBlockMixin:
|
||||||
|
username = ''
|
||||||
|
ip = ''
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
increase_login_failed_count(self.username, self.ip)
|
||||||
|
|
||||||
|
|
||||||
class AuthFailedError(Exception):
|
class AuthFailedError(Exception):
|
||||||
def __init__(self, reason, error=None, username=None):
|
username = ''
|
||||||
self.reason = reason
|
msg = ''
|
||||||
self.error = error
|
error = ''
|
||||||
self.username = username
|
request = None
|
||||||
|
ip = ''
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def as_data(self):
|
||||||
|
return {
|
||||||
|
'error': self.error,
|
||||||
|
'msg': self.msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MFARequiredError(Exception):
|
class CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFailedError):
|
||||||
reason = mfa_required
|
def __init__(self, error, username, ip, request):
|
||||||
error = 'mfa_required'
|
super().__init__(error=error, username=username, ip=ip, request=request)
|
||||||
|
times_up = settings.SECURITY_LOGIN_LIMIT_COUNT
|
||||||
|
times_failed = get_login_failed_count(username, ip)
|
||||||
|
times_try = int(times_up) - int(times_failed)
|
||||||
|
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
||||||
|
|
||||||
|
default_msg = invalid_login_msg.format(
|
||||||
|
times_try=times_try, block_time=block_time
|
||||||
|
)
|
||||||
|
if error == reason_password_failed:
|
||||||
|
self.msg = default_msg
|
||||||
|
else:
|
||||||
|
self.msg = reason_choices.get(error, default_msg)
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmRequiredError(Exception):
|
class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||||
reason = login_confirm_required
|
reason = reason_mfa_failed
|
||||||
error = 'login_confirm_required'
|
error = 'mfa_failed'
|
||||||
|
msg = mfa_failed_msg
|
||||||
|
|
||||||
|
def __init__(self, username, request):
|
||||||
|
super().__init__(username=username, request=request)
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmWaitError(Exception):
|
class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
|
||||||
reason = login_confirm_wait
|
error = 'block_login'
|
||||||
error = 'login_confirm_wait'
|
msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
||||||
|
|
||||||
|
def __init__(self, username, ip):
|
||||||
|
super().__init__(username=username, ip=ip)
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmRejectedError(Exception):
|
class SessionEmptyError(AuthFailedError):
|
||||||
reason = login_confirm_wait
|
msg = session_empty_msg
|
||||||
error = 'login_confirm_rejected'
|
error = 'session_empty_msg'
|
||||||
|
|
||||||
|
|
||||||
|
class MFARequiredError(AuthFailedError):
|
||||||
|
msg = mfa_required_msg
|
||||||
|
error = 'mfa_required_msg'
|
||||||
|
|
||||||
|
def as_data(self):
|
||||||
|
return {
|
||||||
|
'error': self.error,
|
||||||
|
'msg': self.msg,
|
||||||
|
'choices': ['otp'],
|
||||||
|
'url': reverse('api-auth:mfa-challenge')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LoginConfirmRequiredError(AuthFailedError):
|
||||||
|
msg = login_confirm_required_msg
|
||||||
|
error = 'login_confirm_required_msg'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginConfirmError(AuthFailedError):
|
||||||
|
msg = login_confirm_wait_msg
|
||||||
|
error = 'login_confirm_wait_msg'
|
||||||
|
|
||||||
|
def __init__(self, order_id, **kwargs):
|
||||||
|
self.order_id = order_id
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def as_data(self):
|
||||||
|
return {
|
||||||
|
"error": self.error,
|
||||||
|
"msg": self.msg,
|
||||||
|
"order_id": self.order_id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LoginConfirmWaitError(LoginConfirmError):
|
||||||
|
msg = login_confirm_wait_msg
|
||||||
|
error = 'login_confirm_wait_msg'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginConfirmRejectedError(LoginConfirmError):
|
||||||
|
msg = login_confirm_rejected_msg
|
||||||
|
error = 'login_confirm_rejected_msg'
|
||||||
|
|
||||||
|
|
||||||
|
class LoginConfirmOrderNotFound(LoginConfirmError):
|
||||||
|
msg = login_confirm_order_not_found_msg
|
||||||
|
error = 'login_confirm_order_not_found_msg'
|
||||||
|
|
|
@ -9,53 +9,19 @@ from django.conf import settings
|
||||||
from users.utils import get_login_failed_count
|
from users.utils import get_login_failed_count
|
||||||
|
|
||||||
|
|
||||||
class UserLoginForm(AuthenticationForm):
|
class UserLoginForm(forms.Form):
|
||||||
username = forms.CharField(label=_('Username'), max_length=100)
|
username = forms.CharField(label=_('Username'), max_length=100)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
label=_('Password'), widget=forms.PasswordInput,
|
label=_('Password'), widget=forms.PasswordInput,
|
||||||
max_length=128, strip=False
|
max_length=128, strip=False
|
||||||
)
|
)
|
||||||
|
|
||||||
error_messages = {
|
|
||||||
'invalid_login': _(
|
|
||||||
"The username or password you entered is incorrect, "
|
|
||||||
"please enter it again."
|
|
||||||
),
|
|
||||||
'inactive': _("This account is inactive."),
|
|
||||||
'limit_login': _(
|
|
||||||
"You can also try {times_try} times "
|
|
||||||
"(The account will be temporarily locked for {block_time} minutes)"
|
|
||||||
),
|
|
||||||
'block_login': _(
|
|
||||||
"The account has been locked "
|
|
||||||
"(please contact admin to unlock it or try again after {} minutes)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
def confirm_login_allowed(self, user):
|
def confirm_login_allowed(self, user):
|
||||||
if not user.is_staff:
|
if not user.is_staff:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
self.error_messages['inactive'],
|
self.error_messages['inactive'],
|
||||||
code='inactive',)
|
code='inactive',
|
||||||
|
|
||||||
def get_limit_login_error_message(self, username, ip):
|
|
||||||
times_up = settings.SECURITY_LOGIN_LIMIT_COUNT
|
|
||||||
times_failed = get_login_failed_count(username, ip)
|
|
||||||
times_try = int(times_up) - int(times_failed)
|
|
||||||
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
|
||||||
if times_try <= 0:
|
|
||||||
error_message = self.error_messages['block_login']
|
|
||||||
error_message = error_message.format(block_time)
|
|
||||||
else:
|
|
||||||
error_message = self.error_messages['limit_login']
|
|
||||||
error_message = error_message.format(
|
|
||||||
times_try=times_try, block_time=block_time,
|
|
||||||
)
|
)
|
||||||
return error_message
|
|
||||||
|
|
||||||
def add_limit_login_error(self, username, ip):
|
|
||||||
error = self.get_limit_login_error_message(username, ip)
|
|
||||||
self.add_error('password', error)
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoginCaptchaForm(UserLoginForm):
|
class UserLoginCaptchaForm(UserLoginForm):
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import time
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none, get_request_ip, get_logger
|
||||||
|
from users.models import User
|
||||||
|
from users.utils import (
|
||||||
|
is_block_login, clean_failed_count, increase_login_failed_count
|
||||||
|
)
|
||||||
|
from . import errors
|
||||||
|
from .utils import check_user_valid
|
||||||
|
from .signals import post_auth_success, post_auth_failed
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AuthMixin:
|
||||||
|
request = None
|
||||||
|
|
||||||
|
def get_user_from_session(self):
|
||||||
|
if self.request.session.is_empty():
|
||||||
|
raise errors.SessionEmptyError()
|
||||||
|
if self.request.user and not self.request.user.is_anonymous:
|
||||||
|
return self.request.user
|
||||||
|
user_id = self.request.session.get('user_id')
|
||||||
|
if not user_id:
|
||||||
|
user = None
|
||||||
|
else:
|
||||||
|
user = get_object_or_none(User, pk=user_id)
|
||||||
|
if not user:
|
||||||
|
raise errors.SessionEmptyError()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_request_ip(self):
|
||||||
|
ip = ''
|
||||||
|
if hasattr(self.request, 'data'):
|
||||||
|
ip = self.request.data.get('remote_addr', '')
|
||||||
|
ip = ip or get_request_ip(self.request)
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def check_is_block(self):
|
||||||
|
if hasattr(self.request, 'data'):
|
||||||
|
username = self.request.data.get("username")
|
||||||
|
else:
|
||||||
|
username = self.request.POST.get("username")
|
||||||
|
ip = self.get_request_ip()
|
||||||
|
if is_block_login(username, ip):
|
||||||
|
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
|
||||||
|
raise errors.BlockLoginError(username=username, ip=ip)
|
||||||
|
|
||||||
|
def check_user_auth(self):
|
||||||
|
request = self.request
|
||||||
|
self.check_is_block()
|
||||||
|
if hasattr(request, 'data'):
|
||||||
|
username = request.data.get('username', '')
|
||||||
|
password = request.data.get('password', '')
|
||||||
|
public_key = request.data.get('public_key', '')
|
||||||
|
else:
|
||||||
|
username = request.POST.get('username', '')
|
||||||
|
password = request.POST.get('password', '')
|
||||||
|
public_key = request.POST.get('public_key', '')
|
||||||
|
user, error = check_user_valid(
|
||||||
|
username=username, password=password,
|
||||||
|
public_key=public_key
|
||||||
|
)
|
||||||
|
ip = self.get_request_ip()
|
||||||
|
if not user:
|
||||||
|
raise errors.CredentialError(
|
||||||
|
username=username, error=error, ip=ip, request=request
|
||||||
|
)
|
||||||
|
clean_failed_count(username, ip)
|
||||||
|
request.session['auth_password'] = 1
|
||||||
|
request.session['user_id'] = str(user.id)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def check_user_mfa_if_need(self, user):
|
||||||
|
if self.request.session.get('auth_mfa'):
|
||||||
|
return True
|
||||||
|
if not user.otp_enabled or not user.otp_secret_key:
|
||||||
|
return True
|
||||||
|
raise errors.MFARequiredError()
|
||||||
|
|
||||||
|
def check_user_mfa(self, code):
|
||||||
|
user = self.get_user_from_session()
|
||||||
|
ok = user.check_otp(code)
|
||||||
|
if ok:
|
||||||
|
self.request.session['auth_mfa'] = 1
|
||||||
|
self.request.session['auth_mfa_time'] = time.time()
|
||||||
|
self.request.session['auth_mfa_type'] = 'otp'
|
||||||
|
return
|
||||||
|
raise errors.MFAFailedError(username=user.username, request=self.request)
|
||||||
|
|
||||||
|
def check_user_login_confirm_if_need(self, user):
|
||||||
|
from orders.models import LoginConfirmOrder
|
||||||
|
confirm_setting = user.get_login_confirm_setting()
|
||||||
|
if self.request.session.get('auth_confirm') or not confirm_setting:
|
||||||
|
return
|
||||||
|
order = None
|
||||||
|
if self.request.session.get('auth_order_id'):
|
||||||
|
order_id = self.request.session['auth_order_id']
|
||||||
|
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
|
||||||
|
if not order:
|
||||||
|
order = confirm_setting.create_confirm_order(self.request)
|
||||||
|
self.request.session['auth_order_id'] = str(order.id)
|
||||||
|
|
||||||
|
if order.status == "accepted":
|
||||||
|
return
|
||||||
|
elif order.status == "rejected":
|
||||||
|
raise errors.LoginConfirmRejectedError(order.id)
|
||||||
|
else:
|
||||||
|
raise errors.LoginConfirmWaitError(order.id)
|
||||||
|
|
||||||
|
def clear_auth_mark(self):
|
||||||
|
self.request.session['auth_password'] = ''
|
||||||
|
self.request.session['auth_mfa'] = ''
|
||||||
|
self.request.session['auth_confirm'] = ''
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
post_auth_failed.send(
|
||||||
|
sender=self.__class__, username=username,
|
||||||
|
request=self.request, reason=reason
|
||||||
|
)
|
|
@ -3,6 +3,7 @@
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .models import AccessKey, LoginConfirmSetting
|
from .models import AccessKey, LoginConfirmSetting
|
||||||
|
|
||||||
|
@ -24,7 +25,12 @@ class OtpVerifySerializer(serializers.Serializer):
|
||||||
code = serializers.CharField(max_length=6, min_length=6)
|
code = serializers.CharField(max_length=6, min_length=6)
|
||||||
|
|
||||||
|
|
||||||
class BearerTokenMixin(serializers.Serializer):
|
class BearerTokenSerializer(serializers.Serializer):
|
||||||
|
username = serializers.CharField(allow_null=True, required=False)
|
||||||
|
password = serializers.CharField(write_only=True, allow_null=True,
|
||||||
|
required=False)
|
||||||
|
public_key = serializers.CharField(write_only=True, allow_null=True,
|
||||||
|
required=False)
|
||||||
token = serializers.CharField(read_only=True)
|
token = serializers.CharField(read_only=True)
|
||||||
keyword = serializers.SerializerMethodField()
|
keyword = serializers.SerializerMethodField()
|
||||||
date_expired = serializers.DateTimeField(read_only=True)
|
date_expired = serializers.DateTimeField(read_only=True)
|
||||||
|
@ -33,58 +39,35 @@ class BearerTokenMixin(serializers.Serializer):
|
||||||
def get_keyword(obj):
|
def get_keyword(obj):
|
||||||
return 'Bearer'
|
return 'Bearer'
|
||||||
|
|
||||||
def create_response(self, username):
|
def create(self, validated_data):
|
||||||
request = self.context.get("request")
|
request = self.context.get('request')
|
||||||
try:
|
if request.user and not request.user.is_anonymous:
|
||||||
user = User.objects.get(username=username)
|
user = request.user
|
||||||
except User.DoesNotExist:
|
else:
|
||||||
raise serializers.ValidationError("username %s not exist" % username)
|
user_id = request.session.get('user_id')
|
||||||
|
user = get_object_or_none(User, pk=user_id)
|
||||||
|
if not user:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"user id {} not exist".format(user_id)
|
||||||
|
)
|
||||||
token, date_expired = user.create_bearer_token(request)
|
token, date_expired = user.create_bearer_token(request)
|
||||||
instance = {
|
instance = {
|
||||||
"username": username,
|
"username": user.username,
|
||||||
"token": token,
|
"token": token,
|
||||||
"date_expired": date_expired,
|
"date_expired": date_expired,
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
class MFAChallengeSerializer(serializers.Serializer):
|
||||||
class BearerTokenSerializer(BearerTokenMixin, serializers.Serializer):
|
auth_type = serializers.CharField(write_only=True, required=False, allow_blank=True)
|
||||||
username = serializers.CharField()
|
|
||||||
password = serializers.CharField(write_only=True, allow_null=True,
|
|
||||||
required=False)
|
|
||||||
public_key = serializers.CharField(write_only=True, allow_null=True,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
username = validated_data.get("username")
|
|
||||||
return self.create_response(username)
|
|
||||||
|
|
||||||
|
|
||||||
class MFAChallengeSerializer(BearerTokenMixin, serializers.Serializer):
|
|
||||||
req = serializers.CharField(write_only=True)
|
|
||||||
auth_type = serializers.CharField(write_only=True)
|
|
||||||
code = serializers.CharField(write_only=True)
|
code = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
def validate_req(self, attr):
|
|
||||||
username = cache.get(attr)
|
|
||||||
if not username:
|
|
||||||
raise serializers.ValidationError("Not valid, may be expired")
|
|
||||||
self.context["username"] = username
|
|
||||||
|
|
||||||
def validate_code(self, code):
|
|
||||||
username = self.context["username"]
|
|
||||||
user = User.objects.get(username=username)
|
|
||||||
ok = user.check_otp(code)
|
|
||||||
if not ok:
|
|
||||||
msg = "Otp code not valid, may be expired"
|
|
||||||
raise serializers.ValidationError(msg)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
username = self.context["username"]
|
pass
|
||||||
return self.create_response(username)
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LoginConfirmSettingSerializer(serializers.ModelSerializer):
|
class LoginConfirmSettingSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
from rest_framework.request import Request
|
|
||||||
from django.http.request import QueryDict
|
from django.http.request import QueryDict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.auth.signals import user_logged_out
|
from django.contrib.auth.signals import user_logged_out
|
||||||
from django.utils import timezone
|
|
||||||
from django_auth_ldap.backend import populate_user
|
from django_auth_ldap.backend import populate_user
|
||||||
|
|
||||||
from common.utils import get_request_ip
|
|
||||||
from .backends.openid import new_client
|
from .backends.openid import new_client
|
||||||
from .backends.openid.signals import (
|
from .backends.openid.signals import (
|
||||||
post_create_openid_user, post_openid_login_success
|
post_create_openid_user, post_openid_login_success
|
||||||
)
|
)
|
||||||
from .tasks import write_login_log_async
|
from .signals import post_auth_success
|
||||||
from .signals import post_auth_success, post_auth_failed
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_out)
|
@receiver(user_logged_out)
|
||||||
|
@ -52,35 +48,4 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
def generate_data(username, request):
|
|
||||||
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
|
|
||||||
if isinstance(request, Request):
|
|
||||||
login_ip = request.data.get('remote_addr', None)
|
|
||||||
login_type = request.data.get('login_type', '')
|
|
||||||
else:
|
|
||||||
login_ip = get_request_ip(request)
|
|
||||||
login_type = 'W'
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'username': username,
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': login_type,
|
|
||||||
'user_agent': user_agent,
|
|
||||||
'datetime': timezone.now()
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_auth_success)
|
|
||||||
def on_user_auth_success(sender, user, request, **kwargs):
|
|
||||||
data = generate_data(user.username, request)
|
|
||||||
data.update({'mfa': int(user.otp_enabled), 'status': True})
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_auth_failed)
|
|
||||||
def on_user_auth_failed(sender, username, request, reason, **kwargs):
|
|
||||||
data = generate_data(username, request)
|
|
||||||
data.update({'reason': reason, 'status': False})
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
|
@ -6,17 +6,8 @@ from ops.celery.decorator import register_as_period_task
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .utils import write_login_log
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def write_login_log_async(*args, **kwargs):
|
|
||||||
write_login_log(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@register_as_period_task(interval=3600*24)
|
@register_as_period_task(interval=3600*24)
|
||||||
@shared_task
|
@shared_task
|
||||||
def clean_django_sessions():
|
def clean_django_sessions():
|
||||||
Session.objects.filter(expire_date__lt=timezone.now()).delete()
|
Session.objects.filter(expire_date__lt=timezone.now()).delete()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
<p>
|
<p>
|
||||||
{% trans "Changes the world, starting with a little bit." %}
|
{% trans "Changes the world, starting with a little bit." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="ibox-content">
|
<div class="ibox-content">
|
||||||
|
@ -47,25 +46,29 @@
|
||||||
</div>
|
</div>
|
||||||
<form class="m-t" role="form" method="post" action="">
|
<form class="m-t" role="form" method="post" action="">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
{% if block_login %}
|
<div style="line-height: 17px;">
|
||||||
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
|
|
||||||
{% elif password_expired %}
|
|
||||||
<p class="red-fonts">{% trans 'The user password has expired' %}</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>
|
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
||||||
{% endif %}
|
</div>
|
||||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
{% elif form.errors.captcha %}
|
||||||
|
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
|
||||||
|
{% if form.errors.username %}
|
||||||
|
<div class="help-block field-error">
|
||||||
|
<p class="red-fonts">{{ form.errors.username.as_text }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||||
|
{% if form.errors.password %}
|
||||||
|
<div class="help-block field-error">
|
||||||
|
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ form.captcha }}
|
{{ form.captcha }}
|
||||||
|
|
|
@ -86,7 +86,7 @@ function doRequestAuth() {
|
||||||
window.location = successUrl;
|
window.location = successUrl;
|
||||||
},
|
},
|
||||||
error: function (text, data) {
|
error: function (text, data) {
|
||||||
if (data.status !== "pending") {
|
if (data.error !== "login_confirm_wait") {
|
||||||
if (!errorMsgShow) {
|
if (!errorMsgShow) {
|
||||||
infoMsgRef.hide();
|
infoMsgRef.hide();
|
||||||
errorMsgRef.show();
|
errorMsgRef.show();
|
||||||
|
@ -97,7 +97,7 @@ function doRequestAuth() {
|
||||||
clearInterval(checkInterval);
|
clearInterval(checkInterval);
|
||||||
$(".copy-btn").attr('disabled', 'disabled')
|
$(".copy-btn").attr('disabled', 'disabled')
|
||||||
}
|
}
|
||||||
errorMsgRef.html(data.error)
|
errorMsgRef.html(data.msg)
|
||||||
},
|
},
|
||||||
flash_message: false
|
flash_message: false
|
||||||
})
|
})
|
||||||
|
|
|
@ -48,6 +48,13 @@
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.red-fonts {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-error {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
@ -69,30 +76,32 @@
|
||||||
<div style="margin-bottom: 10px">
|
<div style="margin-bottom: 10px">
|
||||||
<div>
|
<div>
|
||||||
<div class="col-md-1"></div>
|
<div class="col-md-1"></div>
|
||||||
<div class="contact-form col-md-10" style="margin-top: 10px;height: 35px">
|
<div class="contact-form col-md-10" style="margin-top: 20px;height: 35px">
|
||||||
<form id="contact-form" action="" method="post" role="form" novalidate="novalidate">
|
<form id="contact-form" action="" method="post" role="form" novalidate="novalidate">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
<div style="height: 70px;color: red;line-height: 17px;">
|
<div style="height: 70px;color: red;line-height: 17px;">
|
||||||
{% if block_login %}
|
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
|
||||||
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
|
|
||||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
|
||||||
{% elif password_expired %}
|
|
||||||
<p class="red-fonts">{% trans 'The user password has expired' %}</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 %}
|
|
||||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% elif form.errors.captcha %}
|
||||||
|
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}" style="height: 35px">
|
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}" style="height: 35px">
|
||||||
|
{% if form.errors.username %}
|
||||||
|
<div class="help-block field-error">
|
||||||
|
<p class="red-fonts">{{ form.errors.username.as_text }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||||
|
{% if form.errors.password %}
|
||||||
|
<div class="help-block field-error">
|
||||||
|
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
|
<div class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
|
||||||
{{ form.captcha }}
|
{{ form.captcha }}
|
||||||
|
@ -116,4 +125,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -12,12 +12,11 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# path('token/', api.UserToken.as_view(), name='user-token'),
|
# path('token/', api.UserToken.as_view(), name='user-token'),
|
||||||
path('auth/', api.UserAuthApi.as_view(), name='user-auth'),
|
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
|
||||||
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
|
||||||
path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
|
path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
|
||||||
path('connection-token/',
|
path('connection-token/',
|
||||||
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
api.UserConnectionTokenApi.as_view(), name='connection-token'),
|
||||||
path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
|
||||||
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
|
||||||
path('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth'),
|
path('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth'),
|
||||||
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
|
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')
|
||||||
|
|
|
@ -1,34 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.utils.translation import ugettext as _, ugettext_lazy as __
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from common.utils import (
|
from common.utils import (
|
||||||
get_ip_city, get_object_or_none, validate_ip, get_request_ip
|
get_ip_city, get_object_or_none, validate_ip
|
||||||
)
|
)
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from . import errors
|
from . import errors
|
||||||
|
|
||||||
|
|
||||||
def write_login_log(*args, **kwargs):
|
|
||||||
from audits.models import UserLoginLog
|
|
||||||
default_city = _("Unknown")
|
|
||||||
ip = kwargs.get('ip') or ''
|
|
||||||
if not (ip and validate_ip(ip)):
|
|
||||||
ip = ip[:15]
|
|
||||||
city = default_city
|
|
||||||
else:
|
|
||||||
city = get_ip_city(ip) or default_city
|
|
||||||
kwargs.update({'ip': ip, 'city': city})
|
|
||||||
UserLoginLog.objects.create(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def check_user_valid(**kwargs):
|
def check_user_valid(**kwargs):
|
||||||
password = kwargs.pop('password', None)
|
password = kwargs.pop('password', None)
|
||||||
public_key = kwargs.pop('public_key', None)
|
public_key = kwargs.pop('public_key', None)
|
||||||
email = kwargs.pop('email', None)
|
email = kwargs.pop('email', None)
|
||||||
username = kwargs.pop('username', None)
|
username = kwargs.pop('username', None)
|
||||||
|
request = kwargs.get('request')
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
user = get_object_or_none(User, username=username)
|
user = get_object_or_none(User, username=username)
|
||||||
|
@ -38,21 +25,25 @@ def check_user_valid(**kwargs):
|
||||||
user = None
|
user = None
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return None, errors.user_not_exist
|
return None, errors.reason_user_not_exist
|
||||||
elif not user.is_valid:
|
elif user.is_expired:
|
||||||
return None, errors.user_invalid
|
return None, errors.reason_password_expired
|
||||||
|
elif not user.is_active:
|
||||||
|
return None, errors.reason_user_inactive
|
||||||
elif user.password_has_expired:
|
elif user.password_has_expired:
|
||||||
return None, errors.password_expired
|
return None, errors.reason_password_expired
|
||||||
|
|
||||||
if password and authenticate(username=username, password=password):
|
if password:
|
||||||
return user, ''
|
user = authenticate(request, username=username, password=password)
|
||||||
|
if user:
|
||||||
|
return user, ''
|
||||||
|
|
||||||
if public_key and user.public_key:
|
if public_key and user.public_key:
|
||||||
public_key_saved = user.public_key.split()
|
public_key_saved = user.public_key.split()
|
||||||
if len(public_key_saved) == 1:
|
if len(public_key_saved) == 1:
|
||||||
if public_key == public_key_saved[0]:
|
public_key_saved = public_key_saved[0]
|
||||||
return user, ''
|
else:
|
||||||
elif len(public_key_saved) > 1:
|
public_key_saved = public_key_saved[1]
|
||||||
if public_key == public_key_saved[1]:
|
if public_key == public_key_saved:
|
||||||
return user, ''
|
return user, ''
|
||||||
return None, errors.password_failed
|
return None, errors.reason_password_failed
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
|
||||||
from .login import *
|
from .login import *
|
||||||
|
from .mfa import *
|
||||||
|
|
|
@ -16,22 +16,20 @@ from django.views.decorators.debug import sensitive_post_parameters
|
||||||
from django.views.generic.base import TemplateView, RedirectView
|
from django.views.generic.base import TemplateView, RedirectView
|
||||||
from django.views.generic.edit import FormView
|
from django.views.generic.edit import FormView
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from common.utils import get_request_ip, get_object_or_none
|
from common.utils import get_request_ip, get_object_or_none
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from users.utils import (
|
from users.utils import (
|
||||||
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
|
get_user_or_tmp_user, increase_login_failed_count,
|
||||||
set_tmp_user_to_cache, increase_login_failed_count,
|
|
||||||
redirect_user_first_login_or_index
|
redirect_user_first_login_or_index
|
||||||
)
|
)
|
||||||
from ..models import LoginConfirmSetting
|
|
||||||
from ..signals import post_auth_success, post_auth_failed
|
from ..signals import post_auth_success, post_auth_failed
|
||||||
from .. import forms
|
from .. import forms, mixins, errors
|
||||||
from .. import errors
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
'UserLoginView', 'UserLogoutView',
|
||||||
'UserLoginGuardView', 'UserLoginWaitConfirmView',
|
'UserLoginGuardView', 'UserLoginWaitConfirmView',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -39,10 +37,11 @@ __all__ = [
|
||||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
@method_decorator(never_cache, name='dispatch')
|
@method_decorator(never_cache, name='dispatch')
|
||||||
class UserLoginView(FormView):
|
class UserLoginView(mixins.AuthMixin, FormView):
|
||||||
form_class = forms.UserLoginForm
|
form_class = forms.UserLoginForm
|
||||||
form_class_captcha = forms.UserLoginCaptchaForm
|
form_class_captcha = forms.UserLoginCaptchaForm
|
||||||
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
||||||
|
redirect_field_name = 'next'
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
template_name = 'authentication/login.html'
|
template_name = 'authentication/login.html'
|
||||||
|
@ -69,54 +68,25 @@ class UserLoginView(FormView):
|
||||||
request.session.set_test_cookie()
|
request.session.set_test_cookie()
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
# limit login authentication
|
|
||||||
ip = get_request_ip(request)
|
|
||||||
username = self.request.POST.get('username')
|
|
||||||
if is_block_login(username, ip):
|
|
||||||
return self.render_to_response(self.get_context_data(block_login=True))
|
|
||||||
return super().post(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if not self.request.session.test_cookie_worked():
|
if not self.request.session.test_cookie_worked():
|
||||||
return HttpResponse(_("Please enable cookies and try again."))
|
return HttpResponse(_("Please enable cookies and try again."))
|
||||||
user = form.get_user()
|
try:
|
||||||
# user password expired
|
self.check_user_auth()
|
||||||
if user.password_has_expired:
|
except errors.AuthFailedError as e:
|
||||||
reason = errors.password_expired
|
form.add_error(None, e.msg)
|
||||||
self.send_auth_signal(success=False, username=user.username, reason=reason)
|
ip = self.get_request_ip()
|
||||||
return self.render_to_response(self.get_context_data(password_expired=True))
|
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||||
|
context = self.get_context_data(form=form)
|
||||||
set_tmp_user_to_cache(self.request, user)
|
return self.render_to_response(context)
|
||||||
username = form.cleaned_data.get('username')
|
|
||||||
ip = get_request_ip(self.request)
|
|
||||||
# 登陆成功,清除缓存计数
|
|
||||||
clean_failed_count(username, ip)
|
|
||||||
self.request.session['auth_password'] = '1'
|
|
||||||
return self.redirect_to_guard_view()
|
return self.redirect_to_guard_view()
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def redirect_to_guard_view(self):
|
||||||
# write login failed log
|
guard_url = reverse('authentication:login-guard')
|
||||||
username = form.cleaned_data.get('username')
|
args = self.request.META.get('QUERY_STRING', '')
|
||||||
exist = User.objects.filter(username=username).first()
|
if args and self.query_string:
|
||||||
reason = errors.password_failed if exist else errors.user_not_exist
|
guard_url = "%s?%s" % (guard_url, args)
|
||||||
# limit user login failed count
|
return redirect(guard_url)
|
||||||
ip = get_request_ip(self.request)
|
|
||||||
increase_login_failed_count(username, ip)
|
|
||||||
form.add_limit_login_error(username, ip)
|
|
||||||
# show captcha
|
|
||||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
|
||||||
self.send_auth_signal(success=False, username=username, reason=reason)
|
|
||||||
|
|
||||||
old_form = form
|
|
||||||
form = self.form_class_captcha(data=form.data)
|
|
||||||
form._errors = old_form.errors
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def redirect_to_guard_view():
|
|
||||||
continue_url = reverse('authentication:login-guard')
|
|
||||||
return redirect(continue_url)
|
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
ip = get_request_ip(self.request)
|
ip = get_request_ip(self.request)
|
||||||
|
@ -134,58 +104,34 @@ class UserLoginView(FormView):
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserLoginOtpView(FormView):
|
class UserLoginGuardView(mixins.AuthMixin, RedirectView):
|
||||||
template_name = 'authentication/login_otp.html'
|
|
||||||
form_class = forms.UserCheckOtpCodeForm
|
|
||||||
redirect_field_name = 'next'
|
redirect_field_name = 'next'
|
||||||
|
login_url = reverse_lazy('authentication:login')
|
||||||
|
login_otp_url = reverse_lazy('authentication:login-otp')
|
||||||
|
login_confirm_url = reverse_lazy('authentication:login-wait-confirm')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def format_redirect_url(self, url):
|
||||||
user = get_user_or_tmp_user(self.request)
|
args = self.request.META.get('QUERY_STRING', '')
|
||||||
otp_code = form.cleaned_data.get('otp_code')
|
if args and self.query_string:
|
||||||
otp_secret_key = user.otp_secret_key
|
url = "%s?%s" % (url, args)
|
||||||
|
return url
|
||||||
if check_otp_code(otp_secret_key, otp_code):
|
|
||||||
self.request.session['auth_otp'] = '1'
|
|
||||||
return UserLoginView.redirect_to_guard_view()
|
|
||||||
else:
|
|
||||||
self.send_auth_signal(
|
|
||||||
success=False, username=user.username,
|
|
||||||
reason=errors.mfa_failed
|
|
||||||
)
|
|
||||||
form.add_error(
|
|
||||||
'otp_code', _('MFA code invalid, or ntp sync server time')
|
|
||||||
)
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
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)
|
|
||||||
else:
|
|
||||||
post_auth_failed.send(
|
|
||||||
sender=self.__class__, username=username,
|
|
||||||
request=self.request, reason=reason
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoginGuardView(RedirectView):
|
|
||||||
redirect_field_name = 'next'
|
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
if not self.request.session.get('auth_password'):
|
if not self.request.session.get('auth_password'):
|
||||||
return reverse('authentication:login')
|
return self.format_redirect_url(self.login_url)
|
||||||
|
user = self.get_user_from_session()
|
||||||
user = get_user_or_tmp_user(self.request)
|
|
||||||
# 启用并设置了otp
|
# 启用并设置了otp
|
||||||
if user.otp_enabled and user.otp_secret_key and \
|
if user.otp_enabled and user.otp_secret_key and \
|
||||||
not self.request.session.get('auth_otp'):
|
not self.request.session.get('auth_mfa'):
|
||||||
return reverse('authentication:login-otp')
|
return self.format_redirect_url(self.login_otp_url)
|
||||||
confirm_setting = user.get_login_confirm_setting()
|
confirm_setting = user.get_login_confirm_setting()
|
||||||
if confirm_setting and not self.request.session.get('auth_confirm'):
|
if confirm_setting and not self.request.session.get('auth_confirm'):
|
||||||
order = confirm_setting.create_confirm_order(self.request)
|
order = confirm_setting.create_confirm_order(self.request)
|
||||||
self.request.session['auth_order_id'] = str(order.id)
|
self.request.session['auth_order_id'] = str(order.id)
|
||||||
url = reverse('authentication:login-wait-confirm')
|
url = self.format_redirect_url(self.login_confirm_url)
|
||||||
return url
|
return url
|
||||||
self.login_success(user)
|
self.login_success(user)
|
||||||
|
self.clear_auth_mark()
|
||||||
# 启用但是没有设置otp
|
# 启用但是没有设置otp
|
||||||
if user.otp_enabled and not user.otp_secret_key:
|
if user.otp_enabled and not user.otp_secret_key:
|
||||||
# 1,2,mfa_setting & F
|
# 1,2,mfa_setting & F
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
from .. import forms, errors, mixins
|
||||||
|
from .utils import redirect_to_guard_view
|
||||||
|
|
||||||
|
__all__ = ['UserLoginOtpView']
|
||||||
|
|
||||||
|
|
||||||
|
class UserLoginOtpView(mixins.AuthMixin, FormView):
|
||||||
|
template_name = 'authentication/login_otp.html'
|
||||||
|
form_class = forms.UserCheckOtpCodeForm
|
||||||
|
redirect_field_name = 'next'
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
otp_code = form.cleaned_data.get('otp_code')
|
||||||
|
try:
|
||||||
|
self.check_user_mfa(otp_code)
|
||||||
|
return redirect_to_guard_view()
|
||||||
|
except errors.MFAFailedError as e:
|
||||||
|
form.add_error('otp_code', e.reason)
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.shortcuts import reverse, redirect
|
||||||
|
|
||||||
|
|
||||||
|
def redirect_to_guard_view():
|
||||||
|
continue_url = reverse('authentication:login-guard')
|
||||||
|
return redirect(continue_url)
|
|
@ -153,6 +153,14 @@ def get_request_ip(request):
|
||||||
return login_ip
|
return login_ip
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_ip_or_data(request):
|
||||||
|
ip = ''
|
||||||
|
if hasattr(request, 'data'):
|
||||||
|
ip = request.data.get('remote_addr', '')
|
||||||
|
ip = ip or get_request_ip(request)
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
def validate_ip(ip):
|
def validate_ip(ip):
|
||||||
try:
|
try:
|
||||||
ipaddress.ip_address(ip)
|
ipaddress.ip_address(ip)
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Jumpserver 0.3.3\n"
|
"Project-Id-Version: Jumpserver 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-10-31 16:57+0800\n"
|
"POT-Creation-Date: 2019-11-05 15:00+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
|
||||||
|
@ -96,7 +96,7 @@ msgstr "运行参数"
|
||||||
#: terminal/templates/terminal/session_list.html:28
|
#: terminal/templates/terminal/session_list.html:28
|
||||||
#: terminal/templates/terminal/session_list.html:72
|
#: terminal/templates/terminal/session_list.html:72
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:73
|
#: xpack/plugins/change_auth_plan/forms.py:73
|
||||||
#: xpack/plugins/change_auth_plan/models.py:412
|
#: xpack/plugins/change_auth_plan/models.py:419
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13
|
||||||
|
@ -145,14 +145,14 @@ msgstr "资产"
|
||||||
#: terminal/models.py:260 terminal/templates/terminal/terminal_detail.html:43
|
#: terminal/models.py:260 terminal/templates/terminal/terminal_detail.html:43
|
||||||
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
|
#: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14
|
||||||
#: users/models/user.py:382 users/templates/users/_select_user_modal.html:13
|
#: users/models/user.py:382 users/templates/users/_select_user_modal.html:13
|
||||||
#: users/templates/users/user_detail.html:63
|
#: users/templates/users/user_detail.html:64
|
||||||
#: users/templates/users/user_group_detail.html:55
|
#: users/templates/users/user_group_detail.html:55
|
||||||
#: users/templates/users/user_group_list.html:35
|
#: users/templates/users/user_group_list.html:35
|
||||||
#: users/templates/users/user_list.html:35
|
#: users/templates/users/user_list.html:35
|
||||||
#: users/templates/users/user_profile.html:51
|
#: users/templates/users/user_profile.html:51
|
||||||
#: users/templates/users/user_pubkey_update.html:57
|
#: users/templates/users/user_pubkey_update.html:57
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:56
|
#: xpack/plugins/change_auth_plan/forms.py:56
|
||||||
#: xpack/plugins/change_auth_plan/models.py:63
|
#: xpack/plugins/change_auth_plan/models.py:64
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
|
||||||
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
|
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:144
|
||||||
|
@ -198,8 +198,8 @@ msgstr "参数"
|
||||||
#: perms/templates/perms/asset_permission_detail.html:98
|
#: perms/templates/perms/asset_permission_detail.html:98
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:90
|
#: perms/templates/perms/remote_app_permission_detail.html:90
|
||||||
#: users/models/user.py:423 users/serializers/group.py:32
|
#: users/models/user.py:423 users/serializers/group.py:32
|
||||||
#: users/templates/users/user_detail.html:111
|
#: users/templates/users/user_detail.html:112
|
||||||
#: xpack/plugins/change_auth_plan/models.py:108
|
#: xpack/plugins/change_auth_plan/models.py:109
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113
|
||||||
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
|
#: xpack/plugins/cloud/models.py:80 xpack/plugins/cloud/models.py:179
|
||||||
#: xpack/plugins/gathered_user/models.py:46
|
#: xpack/plugins/gathered_user/models.py:46
|
||||||
|
@ -261,11 +261,11 @@ msgstr "创建日期"
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:94
|
#: perms/templates/perms/remote_app_permission_detail.html:94
|
||||||
#: settings/models.py:34 terminal/models.py:33
|
#: settings/models.py:34 terminal/models.py:33
|
||||||
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
|
#: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15
|
||||||
#: users/models/user.py:415 users/templates/users/user_detail.html:129
|
#: users/models/user.py:415 users/templates/users/user_detail.html:130
|
||||||
#: users/templates/users/user_group_detail.html:67
|
#: users/templates/users/user_group_detail.html:67
|
||||||
#: users/templates/users/user_group_list.html:37
|
#: users/templates/users/user_group_list.html:37
|
||||||
#: users/templates/users/user_profile.html:138
|
#: users/templates/users/user_profile.html:138
|
||||||
#: xpack/plugins/change_auth_plan/models.py:104
|
#: xpack/plugins/change_auth_plan/models.py:105
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:117
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:19
|
||||||
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
|
#: xpack/plugins/cloud/models.py:77 xpack/plugins/cloud/models.py:173
|
||||||
|
@ -313,7 +313,7 @@ msgstr "远程应用"
|
||||||
#: terminal/templates/terminal/terminal_update.html:45
|
#: terminal/templates/terminal/terminal_update.html:45
|
||||||
#: users/templates/users/_user.html:50
|
#: users/templates/users/_user.html:50
|
||||||
#: users/templates/users/user_bulk_update.html:23
|
#: users/templates/users/user_bulk_update.html:23
|
||||||
#: users/templates/users/user_detail.html:178
|
#: users/templates/users/user_detail.html:179
|
||||||
#: users/templates/users/user_group_create_update.html:31
|
#: users/templates/users/user_group_create_update.html:31
|
||||||
#: users/templates/users/user_password_update.html:75
|
#: users/templates/users/user_password_update.html:75
|
||||||
#: users/templates/users/user_profile.html:209
|
#: users/templates/users/user_profile.html:209
|
||||||
|
@ -420,7 +420,7 @@ msgstr "详情"
|
||||||
#: perms/templates/perms/remote_app_permission_list.html:64
|
#: perms/templates/perms/remote_app_permission_list.html:64
|
||||||
#: terminal/templates/terminal/terminal_detail.html:16
|
#: terminal/templates/terminal/terminal_detail.html:16
|
||||||
#: terminal/templates/terminal/terminal_list.html:73
|
#: terminal/templates/terminal/terminal_list.html:73
|
||||||
#: users/templates/users/user_detail.html:25
|
#: users/templates/users/user_detail.html:26
|
||||||
#: users/templates/users/user_group_detail.html:28
|
#: users/templates/users/user_group_detail.html:28
|
||||||
#: users/templates/users/user_group_list.html:20
|
#: users/templates/users/user_group_list.html:20
|
||||||
#: users/templates/users/user_group_list.html:71
|
#: users/templates/users/user_group_list.html:71
|
||||||
|
@ -467,7 +467,7 @@ msgstr "更新"
|
||||||
#: settings/templates/settings/terminal_setting.html:93
|
#: settings/templates/settings/terminal_setting.html:93
|
||||||
#: settings/templates/settings/terminal_setting.html:115
|
#: settings/templates/settings/terminal_setting.html:115
|
||||||
#: terminal/templates/terminal/terminal_list.html:75
|
#: terminal/templates/terminal/terminal_list.html:75
|
||||||
#: users/templates/users/user_detail.html:30
|
#: users/templates/users/user_detail.html:31
|
||||||
#: users/templates/users/user_group_detail.html:32
|
#: users/templates/users/user_group_detail.html:32
|
||||||
#: users/templates/users/user_group_list.html:73
|
#: users/templates/users/user_group_list.html:73
|
||||||
#: users/templates/users/user_list.html:111
|
#: users/templates/users/user_list.html:111
|
||||||
|
@ -606,7 +606,7 @@ msgstr "端口"
|
||||||
#: assets/templates/assets/asset_detail.html:196
|
#: assets/templates/assets/asset_detail.html:196
|
||||||
#: assets/templates/assets/system_user_assets.html:83
|
#: assets/templates/assets/system_user_assets.html:83
|
||||||
#: perms/models/asset_permission.py:81
|
#: perms/models/asset_permission.py:81
|
||||||
#: xpack/plugins/change_auth_plan/models.py:74
|
#: xpack/plugins/change_auth_plan/models.py:75
|
||||||
#: xpack/plugins/gathered_user/models.py:31
|
#: xpack/plugins/gathered_user/models.py:31
|
||||||
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17
|
#: xpack/plugins/gathered_user/templates/gathered_user/task_list.html:17
|
||||||
msgid "Nodes"
|
msgid "Nodes"
|
||||||
|
@ -700,21 +700,21 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
|
||||||
#: assets/templates/assets/admin_user_list.html:45
|
#: assets/templates/assets/admin_user_list.html:45
|
||||||
#: assets/templates/assets/domain_gateway_list.html:71
|
#: assets/templates/assets/domain_gateway_list.html:71
|
||||||
#: assets/templates/assets/system_user_detail.html:62
|
#: assets/templates/assets/system_user_detail.html:62
|
||||||
#: assets/templates/assets/system_user_list.html:48 audits/models.py:81
|
#: assets/templates/assets/system_user_list.html:48 audits/models.py:82
|
||||||
#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13
|
#: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13
|
||||||
#: authentication/templates/authentication/login.html:65
|
#: authentication/templates/authentication/login.html:60
|
||||||
#: authentication/templates/authentication/xpack_login.html:92
|
#: authentication/templates/authentication/xpack_login.html:87
|
||||||
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70
|
#: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70
|
||||||
#: perms/templates/perms/asset_permission_user.html:55
|
#: perms/templates/perms/asset_permission_user.html:55
|
||||||
#: perms/templates/perms/remote_app_permission_user.html:54
|
#: perms/templates/perms/remote_app_permission_user.html:54
|
||||||
#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13
|
#: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13
|
||||||
#: users/models/user.py:380 users/templates/users/_select_user_modal.html:14
|
#: users/models/user.py:380 users/templates/users/_select_user_modal.html:14
|
||||||
#: users/templates/users/user_detail.html:67
|
#: users/templates/users/user_detail.html:68
|
||||||
#: users/templates/users/user_list.html:36
|
#: users/templates/users/user_list.html:36
|
||||||
#: users/templates/users/user_profile.html:47
|
#: users/templates/users/user_profile.html:47
|
||||||
#: xpack/plugins/change_auth_plan/forms.py:58
|
#: xpack/plugins/change_auth_plan/forms.py:58
|
||||||
#: xpack/plugins/change_auth_plan/models.py:65
|
#: xpack/plugins/change_auth_plan/models.py:66
|
||||||
#: xpack/plugins/change_auth_plan/models.py:408
|
#: xpack/plugins/change_auth_plan/models.py:415
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12
|
||||||
|
@ -732,8 +732,8 @@ msgstr "密码或密钥密码"
|
||||||
#: assets/templates/assets/_asset_user_auth_update_modal.html:21
|
#: assets/templates/assets/_asset_user_auth_update_modal.html:21
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:27
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:27
|
||||||
#: authentication/forms.py:15
|
#: authentication/forms.py:15
|
||||||
#: authentication/templates/authentication/login.html:68
|
#: authentication/templates/authentication/login.html:63
|
||||||
#: authentication/templates/authentication/xpack_login.html:95
|
#: authentication/templates/authentication/xpack_login.html:90
|
||||||
#: settings/forms.py:114 users/forms.py:15 users/forms.py:27
|
#: settings/forms.py:114 users/forms.py:15 users/forms.py:27
|
||||||
#: users/templates/users/reset_password.html:53
|
#: users/templates/users/reset_password.html:53
|
||||||
#: users/templates/users/user_password_authentication.html:18
|
#: users/templates/users/user_password_authentication.html:18
|
||||||
|
@ -741,8 +741,8 @@ msgstr "密码或密钥密码"
|
||||||
#: users/templates/users/user_profile_update.html:41
|
#: users/templates/users/user_profile_update.html:41
|
||||||
#: users/templates/users/user_pubkey_update.html:41
|
#: users/templates/users/user_pubkey_update.html:41
|
||||||
#: users/templates/users/user_update.html:20
|
#: users/templates/users/user_update.html:20
|
||||||
#: xpack/plugins/change_auth_plan/models.py:95
|
#: xpack/plugins/change_auth_plan/models.py:96
|
||||||
#: xpack/plugins/change_auth_plan/models.py:263
|
#: xpack/plugins/change_auth_plan/models.py:264
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "密码"
|
msgstr "密码"
|
||||||
|
|
||||||
|
@ -938,13 +938,13 @@ msgstr "版本"
|
||||||
msgid "AuthBook"
|
msgid "AuthBook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:99
|
#: assets/models/base.py:31 xpack/plugins/change_auth_plan/models.py:100
|
||||||
#: xpack/plugins/change_auth_plan/models.py:270
|
#: xpack/plugins/change_auth_plan/models.py:271
|
||||||
msgid "SSH private key"
|
msgid "SSH private key"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:102
|
#: assets/models/base.py:32 xpack/plugins/change_auth_plan/models.py:103
|
||||||
#: xpack/plugins/change_auth_plan/models.py:266
|
#: xpack/plugins/change_auth_plan/models.py:267
|
||||||
msgid "SSH public key"
|
msgid "SSH public key"
|
||||||
msgstr "ssh公钥"
|
msgstr "ssh公钥"
|
||||||
|
|
||||||
|
@ -965,7 +965,7 @@ msgid "Contact"
|
||||||
msgstr "联系人"
|
msgstr "联系人"
|
||||||
|
|
||||||
#: assets/models/cluster.py:22 users/models/user.py:401
|
#: assets/models/cluster.py:22 users/models/user.py:401
|
||||||
#: users/templates/users/user_detail.html:76
|
#: users/templates/users/user_detail.html:77
|
||||||
msgid "Phone"
|
msgid "Phone"
|
||||||
msgstr "手机"
|
msgstr "手机"
|
||||||
|
|
||||||
|
@ -1121,7 +1121,7 @@ msgstr "默认资产组"
|
||||||
#: terminal/templates/terminal/command_list.html:65
|
#: terminal/templates/terminal/command_list.html:65
|
||||||
#: terminal/templates/terminal/session_list.html:27
|
#: terminal/templates/terminal/session_list.html:27
|
||||||
#: terminal/templates/terminal/session_list.html:71 users/forms.py:319
|
#: terminal/templates/terminal/session_list.html:71 users/forms.py:319
|
||||||
#: users/models/user.py:136 users/models/user.py:152 users/models/user.py:509
|
#: users/models/user.py:132 users/models/user.py:148 users/models/user.py:509
|
||||||
#: users/serializers/group.py:21
|
#: users/serializers/group.py:21
|
||||||
#: users/templates/users/user_group_detail.html:78
|
#: users/templates/users/user_group_detail.html:78
|
||||||
#: users/templates/users/user_group_list.html:36 users/views/user.py:250
|
#: users/templates/users/user_group_list.html:36 users/views/user.py:250
|
||||||
|
@ -1187,7 +1187,7 @@ msgstr "手动登录"
|
||||||
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
|
#: assets/views/label.py:27 assets/views/label.py:45 assets/views/label.py:73
|
||||||
#: assets/views/system_user.py:29 assets/views/system_user.py:46
|
#: assets/views/system_user.py:29 assets/views/system_user.py:46
|
||||||
#: assets/views/system_user.py:63 assets/views/system_user.py:79
|
#: assets/views/system_user.py:63 assets/views/system_user.py:79
|
||||||
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:70
|
#: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:71
|
||||||
msgid "Assets"
|
msgid "Assets"
|
||||||
msgstr "资产管理"
|
msgstr "资产管理"
|
||||||
|
|
||||||
|
@ -1236,17 +1236,17 @@ msgstr "系统用户"
|
||||||
msgid "%(value)s is not an even number"
|
msgid "%(value)s is not an even number"
|
||||||
msgstr "%(value)s is not an even number"
|
msgstr "%(value)s is not an even number"
|
||||||
|
|
||||||
#: assets/models/utils.py:43 assets/tasks/const.py:84
|
#: assets/models/utils.py:43 assets/tasks/const.py:87
|
||||||
msgid "Unreachable"
|
msgid "Unreachable"
|
||||||
msgstr "不可达"
|
msgstr "不可达"
|
||||||
|
|
||||||
#: assets/models/utils.py:44 assets/tasks/const.py:85
|
#: assets/models/utils.py:44 assets/tasks/const.py:88
|
||||||
#: assets/templates/assets/asset_list.html:99
|
#: assets/templates/assets/asset_list.html:99
|
||||||
msgid "Reachable"
|
msgid "Reachable"
|
||||||
msgstr "可连接"
|
msgstr "可连接"
|
||||||
|
|
||||||
#: assets/models/utils.py:45 assets/tasks/const.py:86
|
#: assets/models/utils.py:45 assets/tasks/const.py:89 audits/utils.py:29
|
||||||
#: authentication/utils.py:16 xpack/plugins/license/models.py:78
|
#: xpack/plugins/license/models.py:78
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "未知"
|
msgstr "未知"
|
||||||
|
|
||||||
|
@ -1332,7 +1332,7 @@ msgstr "测试资产可连接性: {}"
|
||||||
|
|
||||||
#: assets/tasks/asset_user_connectivity.py:27
|
#: assets/tasks/asset_user_connectivity.py:27
|
||||||
#: assets/tasks/push_system_user.py:130
|
#: assets/tasks/push_system_user.py:130
|
||||||
#: xpack/plugins/change_auth_plan/models.py:521
|
#: xpack/plugins/change_auth_plan/models.py:528
|
||||||
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
msgid "The asset {} system platform {} does not support run Ansible tasks"
|
||||||
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
msgstr "资产 {} 系统平台 {} 不支持运行 Ansible 任务"
|
||||||
|
|
||||||
|
@ -1470,8 +1470,8 @@ msgstr "请输入密码"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_update_modal.html:68
|
#: assets/templates/assets/_asset_user_auth_update_modal.html:68
|
||||||
#: assets/templates/assets/asset_detail.html:302
|
#: assets/templates/assets/asset_detail.html:302
|
||||||
#: users/templates/users/user_detail.html:364
|
#: users/templates/users/user_detail.html:366
|
||||||
#: users/templates/users/user_detail.html:391
|
#: users/templates/users/user_detail.html:393
|
||||||
#: xpack/plugins/interface/views.py:35
|
#: xpack/plugins/interface/views.py:35
|
||||||
msgid "Update successfully!"
|
msgid "Update successfully!"
|
||||||
msgstr "更新成功"
|
msgstr "更新成功"
|
||||||
|
@ -1481,7 +1481,7 @@ msgid "Asset user auth"
|
||||||
msgstr "资产用户信息"
|
msgstr "资产用户信息"
|
||||||
|
|
||||||
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
#: assets/templates/assets/_asset_user_auth_view_modal.html:54
|
||||||
#: authentication/templates/authentication/login_wait_confirm.html:117
|
#: authentication/templates/authentication/login_wait_confirm.html:114
|
||||||
msgid "Copy success"
|
msgid "Copy success"
|
||||||
msgstr "复制成功"
|
msgstr "复制成功"
|
||||||
|
|
||||||
|
@ -1669,10 +1669,10 @@ msgstr "选择节点"
|
||||||
#: settings/templates/settings/terminal_setting.html:168
|
#: settings/templates/settings/terminal_setting.html:168
|
||||||
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
#: templates/_modal.html:23 terminal/templates/terminal/session_detail.html:112
|
||||||
#: users/templates/users/user_detail.html:271
|
#: users/templates/users/user_detail.html:271
|
||||||
#: users/templates/users/user_detail.html:445
|
#: users/templates/users/user_detail.html:447
|
||||||
#: users/templates/users/user_detail.html:471
|
#: users/templates/users/user_detail.html:473
|
||||||
#: users/templates/users/user_detail.html:494
|
#: users/templates/users/user_detail.html:496
|
||||||
#: users/templates/users/user_detail.html:539
|
#: users/templates/users/user_detail.html:541
|
||||||
#: users/templates/users/user_group_create_update.html:32
|
#: users/templates/users/user_group_create_update.html:32
|
||||||
#: users/templates/users/user_group_list.html:120
|
#: users/templates/users/user_group_list.html:120
|
||||||
#: users/templates/users/user_list.html:256
|
#: users/templates/users/user_list.html:256
|
||||||
|
@ -1750,7 +1750,7 @@ msgstr "资产用户"
|
||||||
#: assets/templates/assets/asset_asset_user_list.html:47
|
#: assets/templates/assets/asset_asset_user_list.html:47
|
||||||
#: assets/templates/assets/asset_detail.html:142
|
#: assets/templates/assets/asset_detail.html:142
|
||||||
#: terminal/templates/terminal/session_detail.html:85
|
#: terminal/templates/terminal/session_detail.html:85
|
||||||
#: users/templates/users/user_detail.html:140
|
#: users/templates/users/user_detail.html:141
|
||||||
#: users/templates/users/user_profile.html:150
|
#: users/templates/users/user_profile.html:150
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:132
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:132
|
||||||
|
@ -1777,7 +1777,7 @@ msgid "Disk"
|
||||||
msgstr "硬盘"
|
msgstr "硬盘"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_detail.html:126
|
#: assets/templates/assets/asset_detail.html:126
|
||||||
#: users/templates/users/user_detail.html:115
|
#: users/templates/users/user_detail.html:116
|
||||||
#: users/templates/users/user_profile.html:106
|
#: users/templates/users/user_profile.html:106
|
||||||
msgid "Date joined"
|
msgid "Date joined"
|
||||||
msgstr "创建日期"
|
msgstr "创建日期"
|
||||||
|
@ -1791,7 +1791,7 @@ msgstr "创建日期"
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:112
|
#: perms/templates/perms/remote_app_permission_detail.html:112
|
||||||
#: terminal/templates/terminal/terminal_list.html:34
|
#: terminal/templates/terminal/terminal_list.html:34
|
||||||
#: users/templates/users/_select_user_modal.html:18
|
#: users/templates/users/_select_user_modal.html:18
|
||||||
#: users/templates/users/user_detail.html:146
|
#: users/templates/users/user_detail.html:147
|
||||||
#: users/templates/users/user_profile.html:63
|
#: users/templates/users/user_profile.html:63
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr "激活中"
|
msgstr "激活中"
|
||||||
|
@ -1872,9 +1872,9 @@ msgstr "显示所有子节点资产"
|
||||||
|
|
||||||
#: assets/templates/assets/asset_list.html:417
|
#: assets/templates/assets/asset_list.html:417
|
||||||
#: assets/templates/assets/system_user_list.html:129
|
#: assets/templates/assets/system_user_list.html:129
|
||||||
#: users/templates/users/user_detail.html:439
|
#: users/templates/users/user_detail.html:441
|
||||||
#: users/templates/users/user_detail.html:465
|
#: users/templates/users/user_detail.html:467
|
||||||
#: users/templates/users/user_detail.html:533
|
#: users/templates/users/user_detail.html:535
|
||||||
#: users/templates/users/user_group_list.html:114
|
#: users/templates/users/user_group_list.html:114
|
||||||
#: users/templates/users/user_list.html:250
|
#: users/templates/users/user_list.html:250
|
||||||
#: xpack/plugins/interface/templates/interface/interface.html:97
|
#: xpack/plugins/interface/templates/interface/interface.html:97
|
||||||
|
@ -1888,9 +1888,9 @@ msgstr "删除选择资产"
|
||||||
#: assets/templates/assets/asset_list.html:421
|
#: assets/templates/assets/asset_list.html:421
|
||||||
#: assets/templates/assets/system_user_list.html:133
|
#: assets/templates/assets/system_user_list.html:133
|
||||||
#: settings/templates/settings/terminal_setting.html:166
|
#: settings/templates/settings/terminal_setting.html:166
|
||||||
#: users/templates/users/user_detail.html:443
|
#: users/templates/users/user_detail.html:445
|
||||||
#: users/templates/users/user_detail.html:469
|
#: users/templates/users/user_detail.html:471
|
||||||
#: users/templates/users/user_detail.html:537
|
#: users/templates/users/user_detail.html:539
|
||||||
#: users/templates/users/user_group_list.html:118
|
#: users/templates/users/user_group_list.html:118
|
||||||
#: users/templates/users/user_list.html:254
|
#: users/templates/users/user_list.html:254
|
||||||
#: xpack/plugins/interface/templates/interface/interface.html:101
|
#: xpack/plugins/interface/templates/interface/interface.html:101
|
||||||
|
@ -2214,11 +2214,11 @@ msgstr "操作"
|
||||||
msgid "Filename"
|
msgid "Filename"
|
||||||
msgstr "文件名"
|
msgstr "文件名"
|
||||||
|
|
||||||
#: audits/models.py:24 audits/models.py:77
|
#: audits/models.py:24 audits/models.py:78
|
||||||
#: audits/templates/audits/ftp_log_list.html:79
|
#: audits/templates/audits/ftp_log_list.html:79
|
||||||
#: ops/templates/ops/command_execution_list.html:68
|
#: ops/templates/ops/command_execution_list.html:68
|
||||||
#: ops/templates/ops/task_list.html:15
|
#: ops/templates/ops/task_list.html:15
|
||||||
#: users/templates/users/user_detail.html:515
|
#: users/templates/users/user_detail.html:517
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:14
|
||||||
#: xpack/plugins/cloud/api.py:61
|
#: xpack/plugins/cloud/api.py:61
|
||||||
msgid "Success"
|
msgid "Success"
|
||||||
|
@ -2243,12 +2243,12 @@ msgstr "资源"
|
||||||
msgid "Change by"
|
msgid "Change by"
|
||||||
msgstr "修改者"
|
msgstr "修改者"
|
||||||
|
|
||||||
#: audits/models.py:71 users/templates/users/user_detail.html:98
|
#: audits/models.py:71 users/templates/users/user_detail.html:99
|
||||||
msgid "Disabled"
|
msgid "Disabled"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: audits/models.py:72 settings/models.py:33
|
#: audits/models.py:72 settings/models.py:33
|
||||||
#: users/templates/users/user_detail.html:96
|
#: users/templates/users/user_detail.html:97
|
||||||
msgid "Enabled"
|
msgid "Enabled"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
|
||||||
|
@ -2256,43 +2256,43 @@ msgstr "启用"
|
||||||
msgid "-"
|
msgid "-"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: audits/models.py:78 xpack/plugins/cloud/models.py:264
|
#: audits/models.py:79 xpack/plugins/cloud/models.py:264
|
||||||
#: xpack/plugins/cloud/models.py:287
|
#: xpack/plugins/cloud/models.py:287
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr "失败"
|
msgstr "失败"
|
||||||
|
|
||||||
#: audits/models.py:82
|
#: audits/models.py:83
|
||||||
msgid "Login type"
|
msgid "Login type"
|
||||||
msgstr "登录方式"
|
msgstr "登录方式"
|
||||||
|
|
||||||
#: audits/models.py:83
|
#: audits/models.py:84
|
||||||
msgid "Login ip"
|
msgid "Login ip"
|
||||||
msgstr "登录IP"
|
msgstr "登录IP"
|
||||||
|
|
||||||
#: audits/models.py:84
|
#: audits/models.py:85
|
||||||
msgid "Login city"
|
msgid "Login city"
|
||||||
msgstr "登录城市"
|
msgstr "登录城市"
|
||||||
|
|
||||||
#: audits/models.py:85
|
#: audits/models.py:86
|
||||||
msgid "User agent"
|
msgid "User agent"
|
||||||
msgstr "Agent"
|
msgstr "Agent"
|
||||||
|
|
||||||
#: audits/models.py:86 audits/templates/audits/login_log_list.html:62
|
#: audits/models.py:87 audits/templates/audits/login_log_list.html:62
|
||||||
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
#: authentication/templates/authentication/_mfa_confirm_modal.html:14
|
||||||
#: users/forms.py:174 users/models/user.py:404
|
#: users/forms.py:174 users/models/user.py:404
|
||||||
#: users/templates/users/first_login.html:45
|
#: users/templates/users/first_login.html:45
|
||||||
msgid "MFA"
|
msgid "MFA"
|
||||||
msgstr "MFA"
|
msgstr "MFA"
|
||||||
|
|
||||||
#: audits/models.py:87 audits/templates/audits/login_log_list.html:63
|
#: audits/models.py:88 audits/templates/audits/login_log_list.html:63
|
||||||
#: xpack/plugins/change_auth_plan/models.py:416
|
#: xpack/plugins/change_auth_plan/models.py:423
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15
|
||||||
#: xpack/plugins/cloud/models.py:278
|
#: xpack/plugins/cloud/models.py:278
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
|
||||||
msgid "Reason"
|
msgid "Reason"
|
||||||
msgstr "原因"
|
msgstr "原因"
|
||||||
|
|
||||||
#: audits/models.py:88 audits/templates/audits/login_log_list.html:64
|
#: audits/models.py:89 audits/templates/audits/login_log_list.html:64
|
||||||
#: orders/templates/orders/login_confirm_order_detail.html:35
|
#: orders/templates/orders/login_confirm_order_detail.html:35
|
||||||
#: orders/templates/orders/login_confirm_order_list.html:17
|
#: orders/templates/orders/login_confirm_order_list.html:17
|
||||||
#: orders/templates/orders/login_confirm_order_list.html:91
|
#: orders/templates/orders/login_confirm_order_list.html:91
|
||||||
|
@ -2302,7 +2302,7 @@ msgstr "原因"
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "状态"
|
msgstr "状态"
|
||||||
|
|
||||||
#: audits/models.py:89
|
#: audits/models.py:90
|
||||||
msgid "Date login"
|
msgid "Date login"
|
||||||
msgstr "登录日期"
|
msgstr "登录日期"
|
||||||
|
|
||||||
|
@ -2314,8 +2314,8 @@ msgstr "登录日期"
|
||||||
#: perms/templates/perms/asset_permission_detail.html:86
|
#: perms/templates/perms/asset_permission_detail.html:86
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:78
|
#: perms/templates/perms/remote_app_permission_detail.html:78
|
||||||
#: terminal/models.py:167 terminal/templates/terminal/session_list.html:34
|
#: terminal/models.py:167 terminal/templates/terminal/session_list.html:34
|
||||||
#: xpack/plugins/change_auth_plan/models.py:249
|
#: xpack/plugins/change_auth_plan/models.py:250
|
||||||
#: xpack/plugins/change_auth_plan/models.py:419
|
#: xpack/plugins/change_auth_plan/models.py:426
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17
|
||||||
#: xpack/plugins/gathered_user/models.py:143
|
#: xpack/plugins/gathered_user/models.py:143
|
||||||
|
@ -2391,9 +2391,7 @@ msgstr "登录日志"
|
||||||
msgid "Command execution log"
|
msgid "Command execution log"
|
||||||
msgstr "命令执行"
|
msgstr "命令执行"
|
||||||
|
|
||||||
#: authentication/api/auth.py:58 authentication/api/token.py:45
|
#: authentication/api/auth.py:58
|
||||||
#: authentication/templates/authentication/login.html:52
|
|
||||||
#: authentication/templates/authentication/xpack_login.html:77
|
|
||||||
msgid "Log in frequently and try again later"
|
msgid "Log in frequently and try again later"
|
||||||
msgstr "登录频繁, 稍后重试"
|
msgstr "登录频繁, 稍后重试"
|
||||||
|
|
||||||
|
@ -2409,18 +2407,6 @@ msgstr "请先进行用户名和密码验证"
|
||||||
msgid "MFA certification failed"
|
msgid "MFA certification failed"
|
||||||
msgstr "MFA认证失败"
|
msgstr "MFA认证失败"
|
||||||
|
|
||||||
#: authentication/api/auth.py:222
|
|
||||||
msgid "No order found or order expired"
|
|
||||||
msgstr "没有找到工单,或者已过期"
|
|
||||||
|
|
||||||
#: authentication/api/auth.py:228
|
|
||||||
msgid "Order was rejected by {}"
|
|
||||||
msgstr "工单被拒绝 {}"
|
|
||||||
|
|
||||||
#: authentication/api/token.py:81
|
|
||||||
msgid "MFA required"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentication/backends/api.py:53
|
#: authentication/backends/api.py:53
|
||||||
msgid "Invalid signature header. No credentials provided."
|
msgid "Invalid signature header. No credentials provided."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -2472,49 +2458,75 @@ msgstr ""
|
||||||
msgid "Invalid token or cache refreshed."
|
msgid "Invalid token or cache refreshed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/const.py:6
|
#: authentication/errors.py:20
|
||||||
msgid "Username/password check failed"
|
msgid "Username/password check failed"
|
||||||
msgstr "用户名/密码 校验失败"
|
msgstr "用户名/密码 校验失败"
|
||||||
|
|
||||||
#: authentication/const.py:7
|
#: authentication/errors.py:21
|
||||||
msgid "MFA authentication failed"
|
msgid "MFA authentication failed"
|
||||||
msgstr "MFA 认证失败"
|
msgstr "MFA 认证失败"
|
||||||
|
|
||||||
#: authentication/const.py:8
|
#: authentication/errors.py:22
|
||||||
msgid "Username does not exist"
|
msgid "Username does not exist"
|
||||||
msgstr "用户名不存在"
|
msgstr "用户名不存在"
|
||||||
|
|
||||||
#: authentication/const.py:9
|
#: authentication/errors.py:23
|
||||||
msgid "Password expired"
|
msgid "Password expired"
|
||||||
msgstr "密码过期"
|
msgstr "密码已过期"
|
||||||
|
|
||||||
#: authentication/const.py:10
|
#: authentication/errors.py:24
|
||||||
msgid "Disabled or expired"
|
msgid "Disabled or expired"
|
||||||
msgstr "禁用或失效"
|
msgstr "禁用或失效"
|
||||||
|
|
||||||
#: authentication/forms.py:21
|
#: authentication/errors.py:25
|
||||||
msgid ""
|
|
||||||
"The username or password you entered is incorrect, please enter it again."
|
|
||||||
msgstr "您输入的用户名或密码不正确,请重新输入。"
|
|
||||||
|
|
||||||
#: authentication/forms.py:24
|
|
||||||
msgid "This account is inactive."
|
msgid "This account is inactive."
|
||||||
msgstr "此账户无效"
|
msgstr "此账户已禁用"
|
||||||
|
|
||||||
#: authentication/forms.py:26
|
#: authentication/errors.py:28
|
||||||
|
msgid "No session found, check your cookie"
|
||||||
|
msgstr "会话已变更,刷新页面"
|
||||||
|
|
||||||
|
#: authentication/errors.py:30
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
|
"The username or password you entered is incorrect, please enter it again. "
|
||||||
"You can also try {times_try} times (The account will be temporarily locked "
|
"You can also try {times_try} times (The account will be temporarily locked "
|
||||||
"for {block_time} minutes)"
|
"for {block_time} minutes)"
|
||||||
msgstr "您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)"
|
msgstr ""
|
||||||
|
"您输入的用户名或密码不正确,请重新输入。 您还可以尝试 {times_try} 次(账号将"
|
||||||
|
"被临时 锁定 {block_time} 分钟)"
|
||||||
|
|
||||||
#: authentication/forms.py:30
|
#: authentication/errors.py:36
|
||||||
msgid ""
|
msgid ""
|
||||||
"The account has been locked (please contact admin to unlock it or try again "
|
"The account has been locked (please contact admin to unlock it or try again "
|
||||||
"after {} minutes)"
|
"after {} minutes)"
|
||||||
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
|
msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)"
|
||||||
|
|
||||||
#: authentication/forms.py:66 users/forms.py:21
|
#: authentication/errors.py:39 users/views/user.py:393 users/views/user.py:418
|
||||||
|
msgid "MFA code invalid, or ntp sync server time"
|
||||||
|
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
||||||
|
|
||||||
|
#: authentication/errors.py:41
|
||||||
|
msgid "MFA required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: authentication/errors.py:42
|
||||||
|
msgid "Login confirm required"
|
||||||
|
msgstr "需要登录复核"
|
||||||
|
|
||||||
|
#: authentication/errors.py:43
|
||||||
|
msgid "Wait login confirm order for accept"
|
||||||
|
msgstr "等待登录复核处理"
|
||||||
|
|
||||||
|
#: authentication/errors.py:44
|
||||||
|
msgid "Login confirm order was rejected"
|
||||||
|
msgstr "登录已被拒绝"
|
||||||
|
|
||||||
|
#: authentication/errors.py:45
|
||||||
|
msgid "Order not found"
|
||||||
|
msgstr "没有发现工单"
|
||||||
|
|
||||||
|
#: authentication/forms.py:32 users/forms.py:21
|
||||||
msgid "MFA code"
|
msgid "MFA code"
|
||||||
msgstr "MFA 验证码"
|
msgstr "MFA 验证码"
|
||||||
|
|
||||||
|
@ -2522,18 +2534,10 @@ msgstr "MFA 验证码"
|
||||||
msgid "Private Token"
|
msgid "Private Token"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
#: authentication/models.py:43
|
|
||||||
msgid "login_confirm_setting"
|
|
||||||
msgstr "登录复核设置"
|
|
||||||
|
|
||||||
#: authentication/models.py:44 users/templates/users/user_detail.html:265
|
#: authentication/models.py:44 users/templates/users/user_detail.html:265
|
||||||
msgid "Reviewers"
|
msgid "Reviewers"
|
||||||
msgstr "审批人"
|
msgstr "审批人"
|
||||||
|
|
||||||
#: authentication/models.py:44
|
|
||||||
msgid "review_login_confirm_settings"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: authentication/models.py:53
|
#: authentication/models.py:53
|
||||||
msgid "User login confirm: {}"
|
msgid "User login confirm: {}"
|
||||||
msgstr "用户登录复核: {}"
|
msgstr "用户登录复核: {}"
|
||||||
|
@ -2572,14 +2576,14 @@ msgid "Show"
|
||||||
msgstr "显示"
|
msgstr "显示"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||||
#: users/models/user.py:339 users/templates/users/user_profile.html:94
|
#: users/models/user.py:335 users/templates/users/user_profile.html:94
|
||||||
#: users/templates/users/user_profile.html:163
|
#: users/templates/users/user_profile.html:163
|
||||||
#: users/templates/users/user_profile.html:166
|
#: users/templates/users/user_profile.html:166
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr "禁用"
|
msgstr "禁用"
|
||||||
|
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:67
|
#: authentication/templates/authentication/_access_key_modal.html:67
|
||||||
#: users/models/user.py:340 users/templates/users/user_profile.html:92
|
#: users/models/user.py:336 users/templates/users/user_profile.html:92
|
||||||
#: users/templates/users/user_profile.html:170
|
#: users/templates/users/user_profile.html:170
|
||||||
msgid "Enable"
|
msgid "Enable"
|
||||||
msgstr "启用"
|
msgstr "启用"
|
||||||
|
@ -2640,39 +2644,34 @@ msgid "Changes the world, starting with a little bit."
|
||||||
msgstr "改变世界,从一点点开始。"
|
msgstr "改变世界,从一点点开始。"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:46
|
#: authentication/templates/authentication/login.html:46
|
||||||
#: authentication/templates/authentication/login.html:73
|
#: authentication/templates/authentication/login.html:68
|
||||||
#: authentication/templates/authentication/xpack_login.html:101
|
#: authentication/templates/authentication/xpack_login.html:96
|
||||||
#: templates/_header_bar.html:83
|
#: templates/_header_bar.html:83
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "登录"
|
msgstr "登录"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:54
|
#: authentication/templates/authentication/login.html:52
|
||||||
#: authentication/templates/authentication/xpack_login.html:80
|
#: authentication/templates/authentication/xpack_login.html:78
|
||||||
msgid "The user password has expired"
|
|
||||||
msgstr "用户密码已过期"
|
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:57
|
|
||||||
#: authentication/templates/authentication/xpack_login.html:83
|
|
||||||
msgid "Captcha invalid"
|
msgid "Captcha invalid"
|
||||||
msgstr "验证码错误"
|
msgstr "验证码错误"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:84
|
#: authentication/templates/authentication/login.html:79
|
||||||
#: authentication/templates/authentication/xpack_login.html:105
|
#: authentication/templates/authentication/xpack_login.html:100
|
||||||
#: users/templates/users/forgot_password.html:10
|
#: users/templates/users/forgot_password.html:10
|
||||||
#: users/templates/users/forgot_password.html:25
|
#: users/templates/users/forgot_password.html:25
|
||||||
msgid "Forgot password"
|
msgid "Forgot password"
|
||||||
msgstr "忘记密码"
|
msgstr "忘记密码"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:91
|
#: authentication/templates/authentication/login.html:86
|
||||||
msgid "More login options"
|
msgid "More login options"
|
||||||
msgstr "更多登录方式"
|
msgstr "更多登录方式"
|
||||||
|
|
||||||
#: authentication/templates/authentication/login.html:95
|
#: authentication/templates/authentication/login.html:90
|
||||||
msgid "Keycloak"
|
msgid "Keycloak"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentication/templates/authentication/login_otp.html:46
|
#: authentication/templates/authentication/login_otp.html:46
|
||||||
#: users/templates/users/user_detail.html:91
|
#: users/templates/users/user_detail.html:92
|
||||||
#: users/templates/users/user_profile.html:87
|
#: users/templates/users/user_profile.html:87
|
||||||
msgid "MFA certification"
|
msgid "MFA certification"
|
||||||
msgstr "MFA认证"
|
msgstr "MFA认证"
|
||||||
|
@ -2721,16 +2720,11 @@ msgstr "返回"
|
||||||
msgid "Welcome back, please enter username and password to login"
|
msgid "Welcome back, please enter username and password to login"
|
||||||
msgstr "欢迎回来,请输入用户名和密码登录"
|
msgstr "欢迎回来,请输入用户名和密码登录"
|
||||||
|
|
||||||
#: authentication/views/login.py:82
|
#: authentication/views/login.py:80
|
||||||
msgid "Please enable cookies and try again."
|
msgid "Please enable cookies and try again."
|
||||||
msgstr "设置你的浏览器支持cookie"
|
msgstr "设置你的浏览器支持cookie"
|
||||||
|
|
||||||
#: authentication/views/login.py:156 users/views/user.py:393
|
#: authentication/views/login.py:192
|
||||||
#: users/views/user.py:418
|
|
||||||
msgid "MFA code invalid, or ntp sync server time"
|
|
||||||
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
|
||||||
|
|
||||||
#: authentication/views/login.py:226
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
|
||||||
" Don't close this page"
|
" Don't close this page"
|
||||||
|
@ -2738,15 +2732,15 @@ msgstr ""
|
||||||
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
|
||||||
" 不要关闭本页面"
|
" 不要关闭本页面"
|
||||||
|
|
||||||
#: authentication/views/login.py:231
|
#: authentication/views/login.py:197
|
||||||
msgid "No order found"
|
msgid "No order found"
|
||||||
msgstr "没有发现工单"
|
msgstr "没有发现工单"
|
||||||
|
|
||||||
#: authentication/views/login.py:254
|
#: authentication/views/login.py:220
|
||||||
msgid "Logout success"
|
msgid "Logout success"
|
||||||
msgstr "退出登录成功"
|
msgstr "退出登录成功"
|
||||||
|
|
||||||
#: authentication/views/login.py:255
|
#: authentication/views/login.py:221
|
||||||
msgid "Logout success, return login page"
|
msgid "Logout success, return login page"
|
||||||
msgstr "退出登录成功,返回到登录页面"
|
msgstr "退出登录成功,返回到登录页面"
|
||||||
|
|
||||||
|
@ -2930,8 +2924,8 @@ msgstr "完成时间"
|
||||||
|
|
||||||
#: ops/models/adhoc.py:357 ops/templates/ops/adhoc_history.html:57
|
#: ops/models/adhoc.py:357 ops/templates/ops/adhoc_history.html:57
|
||||||
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17
|
#: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:17
|
||||||
#: xpack/plugins/change_auth_plan/models.py:252
|
#: xpack/plugins/change_auth_plan/models.py:253
|
||||||
#: xpack/plugins/change_auth_plan/models.py:422
|
#: xpack/plugins/change_auth_plan/models.py:429
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16
|
||||||
#: xpack/plugins/gathered_user/models.py:146
|
#: xpack/plugins/gathered_user/models.py:146
|
||||||
|
@ -3305,11 +3299,11 @@ msgstr ""
|
||||||
" </div>\n"
|
" </div>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: orders/utils.py:52
|
#: orders/utils.py:48
|
||||||
msgid "Order has been reply"
|
msgid "Order has been reply"
|
||||||
msgstr "工单已被回复"
|
msgstr "工单已被回复"
|
||||||
|
|
||||||
#: orders/utils.py:53
|
#: orders/utils.py:49
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -3418,7 +3412,7 @@ msgstr "资产授权"
|
||||||
#: perms/models/base.py:53
|
#: perms/models/base.py:53
|
||||||
#: perms/templates/perms/asset_permission_detail.html:90
|
#: perms/templates/perms/asset_permission_detail.html:90
|
||||||
#: perms/templates/perms/remote_app_permission_detail.html:82
|
#: perms/templates/perms/remote_app_permission_detail.html:82
|
||||||
#: users/models/user.py:420 users/templates/users/user_detail.html:107
|
#: users/models/user.py:420 users/templates/users/user_detail.html:108
|
||||||
#: users/templates/users/user_profile.html:120
|
#: users/templates/users/user_profile.html:120
|
||||||
msgid "Date expired"
|
msgid "Date expired"
|
||||||
msgstr "失效日期"
|
msgstr "失效日期"
|
||||||
|
@ -3982,7 +3976,7 @@ msgid "Please submit the LDAP configuration before import"
|
||||||
msgstr "请先提交LDAP配置再进行导入"
|
msgstr "请先提交LDAP配置再进行导入"
|
||||||
|
|
||||||
#: settings/templates/settings/_ldap_list_users_modal.html:32
|
#: settings/templates/settings/_ldap_list_users_modal.html:32
|
||||||
#: users/models/user.py:384 users/templates/users/user_detail.html:71
|
#: users/models/user.py:384 users/templates/users/user_detail.html:72
|
||||||
#: users/templates/users/user_profile.html:59
|
#: users/templates/users/user_profile.html:59
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "邮件"
|
msgstr "邮件"
|
||||||
|
@ -4775,7 +4769,7 @@ msgstr "不能再该页面重置MFA, 请去个人信息页面重置"
|
||||||
|
|
||||||
#: users/forms.py:32 users/models/user.py:392
|
#: users/forms.py:32 users/models/user.py:392
|
||||||
#: users/templates/users/_select_user_modal.html:15
|
#: users/templates/users/_select_user_modal.html:15
|
||||||
#: users/templates/users/user_detail.html:87
|
#: users/templates/users/user_detail.html:88
|
||||||
#: users/templates/users/user_list.html:37
|
#: users/templates/users/user_list.html:37
|
||||||
#: users/templates/users/user_profile.html:55
|
#: users/templates/users/user_profile.html:55
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
|
@ -4818,7 +4812,7 @@ msgstr "生成重置密码链接,通过邮件发送给用户"
|
||||||
msgid "Set password"
|
msgid "Set password"
|
||||||
msgstr "设置密码"
|
msgstr "设置密码"
|
||||||
|
|
||||||
#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:88
|
#: users/forms.py:132 xpack/plugins/change_auth_plan/models.py:89
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:51
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:69
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:57
|
||||||
|
@ -4892,28 +4886,28 @@ msgstr "选择用户"
|
||||||
msgid "User auth from {}, go there change password"
|
msgid "User auth from {}, go there change password"
|
||||||
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
|
||||||
|
|
||||||
#: users/models/user.py:135 users/models/user.py:517
|
#: users/models/user.py:131 users/models/user.py:517
|
||||||
msgid "Administrator"
|
msgid "Administrator"
|
||||||
msgstr "管理员"
|
msgstr "管理员"
|
||||||
|
|
||||||
#: users/models/user.py:137
|
#: users/models/user.py:133
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr "应用程序"
|
msgstr "应用程序"
|
||||||
|
|
||||||
#: users/models/user.py:138 xpack/plugins/orgs/forms.py:30
|
#: users/models/user.py:134 xpack/plugins/orgs/forms.py:30
|
||||||
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
|
#: xpack/plugins/orgs/templates/orgs/org_list.html:14
|
||||||
msgid "Auditor"
|
msgid "Auditor"
|
||||||
msgstr "审计员"
|
msgstr "审计员"
|
||||||
|
|
||||||
#: users/models/user.py:148
|
#: users/models/user.py:144
|
||||||
msgid "Org admin"
|
msgid "Org admin"
|
||||||
msgstr "组织管理员"
|
msgstr "组织管理员"
|
||||||
|
|
||||||
#: users/models/user.py:150
|
#: users/models/user.py:146
|
||||||
msgid "Org auditor"
|
msgid "Org auditor"
|
||||||
msgstr "组织审计员"
|
msgstr "组织审计员"
|
||||||
|
|
||||||
#: users/models/user.py:341 users/templates/users/user_profile.html:90
|
#: users/models/user.py:337 users/templates/users/user_profile.html:90
|
||||||
msgid "Force enable"
|
msgid "Force enable"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
|
@ -4921,11 +4915,11 @@ msgstr "强制启用"
|
||||||
msgid "Avatar"
|
msgid "Avatar"
|
||||||
msgstr "头像"
|
msgstr "头像"
|
||||||
|
|
||||||
#: users/models/user.py:398 users/templates/users/user_detail.html:82
|
#: users/models/user.py:398 users/templates/users/user_detail.html:83
|
||||||
msgid "Wechat"
|
msgid "Wechat"
|
||||||
msgstr "微信"
|
msgstr "微信"
|
||||||
|
|
||||||
#: users/models/user.py:427 users/templates/users/user_detail.html:103
|
#: users/models/user.py:427 users/templates/users/user_detail.html:104
|
||||||
#: users/templates/users/user_list.html:39
|
#: users/templates/users/user_list.html:39
|
||||||
#: users/templates/users/user_profile.html:102
|
#: users/templates/users/user_profile.html:102
|
||||||
msgid "Source"
|
msgid "Source"
|
||||||
|
@ -5107,7 +5101,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
|
||||||
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
|
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
|
||||||
|
|
||||||
#: users/templates/users/reset_password.html:46
|
#: users/templates/users/reset_password.html:46
|
||||||
#: users/templates/users/user_detail.html:430 users/utils.py:83
|
#: users/templates/users/user_detail.html:432 users/utils.py:83
|
||||||
msgid "Reset password"
|
msgid "Reset password"
|
||||||
msgstr "重置密码"
|
msgstr "重置密码"
|
||||||
|
|
||||||
|
@ -5176,102 +5170,102 @@ msgstr "很强"
|
||||||
msgid "Create user"
|
msgid "Create user"
|
||||||
msgstr "创建用户"
|
msgstr "创建用户"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:19
|
#: users/templates/users/user_detail.html:20
|
||||||
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:190
|
#: users/templates/users/user_granted_asset.html:18 users/views/user.py:190
|
||||||
msgid "User detail"
|
msgid "User detail"
|
||||||
msgstr "用户详情"
|
msgstr "用户详情"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:22
|
#: users/templates/users/user_detail.html:23
|
||||||
#: users/templates/users/user_granted_asset.html:21
|
#: users/templates/users/user_granted_asset.html:21
|
||||||
#: users/templates/users/user_group_detail.html:25
|
#: users/templates/users/user_group_detail.html:25
|
||||||
#: users/templates/users/user_group_granted_asset.html:21
|
#: users/templates/users/user_group_granted_asset.html:21
|
||||||
msgid "Asset granted"
|
msgid "Asset granted"
|
||||||
msgstr "授权的资产"
|
msgstr "授权的资产"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:94
|
#: users/templates/users/user_detail.html:95
|
||||||
msgid "Force enabled"
|
msgid "Force enabled"
|
||||||
msgstr "强制启用"
|
msgstr "强制启用"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:119
|
#: users/templates/users/user_detail.html:120
|
||||||
#: users/templates/users/user_profile.html:110
|
#: users/templates/users/user_profile.html:110
|
||||||
msgid "Last login"
|
msgid "Last login"
|
||||||
msgstr "最后登录"
|
msgstr "最后登录"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:124
|
#: users/templates/users/user_detail.html:125
|
||||||
#: users/templates/users/user_profile.html:115
|
#: users/templates/users/user_profile.html:115
|
||||||
msgid "Last password updated"
|
msgid "Last password updated"
|
||||||
msgstr "最后更新密码"
|
msgstr "最后更新密码"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:160
|
#: users/templates/users/user_detail.html:161
|
||||||
msgid "Force enabled MFA"
|
msgid "Force enabled MFA"
|
||||||
msgstr "强制启用MFA"
|
msgstr "强制启用MFA"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:175
|
#: users/templates/users/user_detail.html:176
|
||||||
msgid "Reset MFA"
|
msgid "Reset MFA"
|
||||||
msgstr "重置MFA"
|
msgstr "重置MFA"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:184
|
#: users/templates/users/user_detail.html:185
|
||||||
msgid "Send reset password mail"
|
msgid "Send reset password mail"
|
||||||
msgstr "发送重置密码邮件"
|
msgstr "发送重置密码邮件"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:187
|
#: users/templates/users/user_detail.html:188
|
||||||
#: users/templates/users/user_detail.html:197
|
#: users/templates/users/user_detail.html:198
|
||||||
msgid "Send"
|
msgid "Send"
|
||||||
msgstr "发送"
|
msgstr "发送"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:194
|
#: users/templates/users/user_detail.html:195
|
||||||
msgid "Send reset ssh key mail"
|
msgid "Send reset ssh key mail"
|
||||||
msgstr "发送重置密钥邮件"
|
msgstr "发送重置密钥邮件"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:203
|
#: users/templates/users/user_detail.html:204
|
||||||
#: users/templates/users/user_detail.html:518
|
#: users/templates/users/user_detail.html:520
|
||||||
msgid "Unblock user"
|
msgid "Unblock user"
|
||||||
msgstr "解除登录限制"
|
msgstr "解除登录限制"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:206
|
#: users/templates/users/user_detail.html:207
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "解除"
|
msgstr "解除"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:373
|
#: users/templates/users/user_detail.html:375
|
||||||
msgid "Goto profile page enable MFA"
|
msgid "Goto profile page enable MFA"
|
||||||
msgstr "请去个人信息页面启用自己的MFA"
|
msgstr "请去个人信息页面启用自己的MFA"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:429
|
#: users/templates/users/user_detail.html:431
|
||||||
msgid "An e-mail has been sent to the user`s mailbox."
|
msgid "An e-mail has been sent to the user`s mailbox."
|
||||||
msgstr "已发送邮件到用户邮箱"
|
msgstr "已发送邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:440
|
#: users/templates/users/user_detail.html:442
|
||||||
msgid "This will reset the user password and send a reset mail"
|
msgid "This will reset the user password and send a reset mail"
|
||||||
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
|
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:455
|
#: users/templates/users/user_detail.html:457
|
||||||
msgid ""
|
msgid ""
|
||||||
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
|
"The reset-ssh-public-key E-mail has been sent successfully. Please inform "
|
||||||
"the user to update his new ssh public key."
|
"the user to update his new ssh public key."
|
||||||
msgstr "重设密钥邮件将会发送到用户邮箱"
|
msgstr "重设密钥邮件将会发送到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:456
|
#: users/templates/users/user_detail.html:458
|
||||||
msgid "Reset SSH public key"
|
msgid "Reset SSH public key"
|
||||||
msgstr "重置SSH密钥"
|
msgstr "重置SSH密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:466
|
#: users/templates/users/user_detail.html:468
|
||||||
msgid "This will reset the user public key and send a reset mail"
|
msgid "This will reset the user public key and send a reset mail"
|
||||||
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
|
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:484
|
#: users/templates/users/user_detail.html:486
|
||||||
msgid "Successfully updated the SSH public key."
|
msgid "Successfully updated the SSH public key."
|
||||||
msgstr "更新ssh密钥成功"
|
msgstr "更新ssh密钥成功"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:485
|
#: users/templates/users/user_detail.html:487
|
||||||
#: users/templates/users/user_detail.html:489
|
#: users/templates/users/user_detail.html:491
|
||||||
msgid "User SSH public key update"
|
msgid "User SSH public key update"
|
||||||
msgstr "ssh密钥"
|
msgstr "ssh密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:534
|
#: users/templates/users/user_detail.html:536
|
||||||
msgid "After unlocking the user, the user can log in normally."
|
msgid "After unlocking the user, the user can log in normally."
|
||||||
msgstr "解除用户登录限制后,此用户即可正常登录"
|
msgstr "解除用户登录限制后,此用户即可正常登录"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:548
|
#: users/templates/users/user_detail.html:550
|
||||||
msgid "Reset user MFA success"
|
msgid "Reset user MFA success"
|
||||||
msgstr "重置用户MFA成功"
|
msgstr "重置用户MFA成功"
|
||||||
|
|
||||||
|
@ -5754,8 +5748,8 @@ msgstr ""
|
||||||
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
|
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/meta.py:9
|
#: xpack/plugins/change_auth_plan/meta.py:9
|
||||||
#: xpack/plugins/change_auth_plan/models.py:116
|
#: xpack/plugins/change_auth_plan/models.py:117
|
||||||
#: xpack/plugins/change_auth_plan/models.py:256
|
#: xpack/plugins/change_auth_plan/models.py:257
|
||||||
#: xpack/plugins/change_auth_plan/views.py:33
|
#: xpack/plugins/change_auth_plan/views.py:33
|
||||||
#: xpack/plugins/change_auth_plan/views.py:50
|
#: xpack/plugins/change_auth_plan/views.py:50
|
||||||
#: xpack/plugins/change_auth_plan/views.py:74
|
#: xpack/plugins/change_auth_plan/views.py:74
|
||||||
|
@ -5766,20 +5760,20 @@ msgstr ""
|
||||||
msgid "Change auth plan"
|
msgid "Change auth plan"
|
||||||
msgstr "改密计划"
|
msgstr "改密计划"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:57
|
#: xpack/plugins/change_auth_plan/models.py:58
|
||||||
msgid "Custom password"
|
msgid "Custom password"
|
||||||
msgstr "自定义密码"
|
msgstr "自定义密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:58
|
#: xpack/plugins/change_auth_plan/models.py:59
|
||||||
msgid "All assets use the same random password"
|
msgid "All assets use the same random password"
|
||||||
msgstr "所有资产使用相同的随机密码"
|
msgstr "所有资产使用相同的随机密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:59
|
#: xpack/plugins/change_auth_plan/models.py:60
|
||||||
msgid "All assets use different random password"
|
msgid "All assets use different random password"
|
||||||
msgstr "所有资产使用不同的随机密码"
|
msgstr "所有资产使用不同的随机密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:78
|
#: xpack/plugins/change_auth_plan/models.py:79
|
||||||
#: xpack/plugins/change_auth_plan/models.py:147
|
#: xpack/plugins/change_auth_plan/models.py:148
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100
|
||||||
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
|
#: xpack/plugins/cloud/models.py:165 xpack/plugins/cloud/models.py:219
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:91
|
||||||
|
@ -5788,8 +5782,8 @@ msgstr "所有资产使用不同的随机密码"
|
||||||
msgid "Cycle perform"
|
msgid "Cycle perform"
|
||||||
msgstr "周期执行"
|
msgstr "周期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:83
|
#: xpack/plugins/change_auth_plan/models.py:84
|
||||||
#: xpack/plugins/change_auth_plan/models.py:145
|
#: xpack/plugins/change_auth_plan/models.py:146
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92
|
||||||
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
|
#: xpack/plugins/cloud/models.py:170 xpack/plugins/cloud/models.py:217
|
||||||
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
|
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_detail.html:83
|
||||||
|
@ -5798,37 +5792,37 @@ msgstr "周期执行"
|
||||||
msgid "Regularly perform"
|
msgid "Regularly perform"
|
||||||
msgstr "定期执行"
|
msgstr "定期执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:92
|
#: xpack/plugins/change_auth_plan/models.py:93
|
||||||
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
|
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:74
|
||||||
msgid "Password rules"
|
msgid "Password rules"
|
||||||
msgstr "密码规则"
|
msgstr "密码规则"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:212
|
#: xpack/plugins/change_auth_plan/models.py:213
|
||||||
msgid "* For security, do not change {} user's password"
|
msgid "* For security, do not change {} user's password"
|
||||||
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
msgstr "* 为了安全,禁止更改 {} 用户的密码"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:216
|
#: xpack/plugins/change_auth_plan/models.py:217
|
||||||
msgid "Assets is empty, please add the asset"
|
msgid "Assets is empty, please add the asset"
|
||||||
msgstr "资产为空,请添加资产"
|
msgstr "资产为空,请添加资产"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:260
|
#: xpack/plugins/change_auth_plan/models.py:261
|
||||||
msgid "Change auth plan snapshot"
|
msgid "Change auth plan snapshot"
|
||||||
msgstr "改密计划快照"
|
msgstr "改密计划快照"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:275
|
#: xpack/plugins/change_auth_plan/models.py:276
|
||||||
#: xpack/plugins/change_auth_plan/models.py:426
|
#: xpack/plugins/change_auth_plan/models.py:433
|
||||||
msgid "Change auth plan execution"
|
msgid "Change auth plan execution"
|
||||||
msgstr "改密计划执行"
|
msgstr "改密计划执行"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:435
|
#: xpack/plugins/change_auth_plan/models.py:442
|
||||||
msgid "Change auth plan execution subtask"
|
msgid "Change auth plan execution subtask"
|
||||||
msgstr "改密计划执行子任务"
|
msgstr "改密计划执行子任务"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:453
|
#: xpack/plugins/change_auth_plan/models.py:460
|
||||||
msgid "Authentication failed"
|
msgid "Authentication failed"
|
||||||
msgstr "认证失败"
|
msgstr "认证失败"
|
||||||
|
|
||||||
#: xpack/plugins/change_auth_plan/models.py:455
|
#: xpack/plugins/change_auth_plan/models.py:462
|
||||||
msgid "Connection timeout"
|
msgid "Connection timeout"
|
||||||
msgstr "连接超时"
|
msgstr "连接超时"
|
||||||
|
|
||||||
|
@ -6438,6 +6432,27 @@ msgstr "密码匣子"
|
||||||
msgid "vault create"
|
msgid "vault create"
|
||||||
msgstr "创建"
|
msgstr "创建"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "The username or password you entered is incorrect, please enter it again."
|
||||||
|
#~ msgstr "您输入的用户名或密码不正确,请重新输入。"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "You can also try {times_try} times (The account will be temporarily "
|
||||||
|
#~ "locked for {block_time} minutes)"
|
||||||
|
#~ msgstr "您还可以尝试 {times_try} 次(账号将被临时锁定 {block_time} 分钟)"
|
||||||
|
|
||||||
|
#~ msgid "No order found or order expired"
|
||||||
|
#~ msgstr "没有找到工单,或者已过期"
|
||||||
|
|
||||||
|
#~ msgid "Order was rejected by {}"
|
||||||
|
#~ msgstr "工单被拒绝 {}"
|
||||||
|
|
||||||
|
#~ msgid "login_confirm_setting"
|
||||||
|
#~ msgstr "登录复核设置"
|
||||||
|
|
||||||
|
#~ msgid "The user password has expired"
|
||||||
|
#~ msgstr "用户密码已过期"
|
||||||
|
|
||||||
#~ msgid "Recipient"
|
#~ msgid "Recipient"
|
||||||
#~ msgstr "收件人"
|
#~ msgstr "收件人"
|
||||||
|
|
||||||
|
|
|
@ -100,16 +100,6 @@
|
||||||
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
|
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var dateOptions = {
|
|
||||||
singleDatePicker: true,
|
|
||||||
showDropdowns: true,
|
|
||||||
timePicker: true,
|
|
||||||
timePicker24Hour: true,
|
|
||||||
autoApply: true,
|
|
||||||
locale: {
|
|
||||||
format: 'YYYY-MM-DD HH:mm'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var api_action = "{{ api_action }}";
|
var api_action = "{{ api_action }}";
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -119,8 +109,8 @@ $(document).ready(function () {
|
||||||
nodesSelect2Init(".nodes-select2");
|
nodesSelect2Init(".nodes-select2");
|
||||||
usersSelect2Init(".users-select2");
|
usersSelect2Init(".users-select2");
|
||||||
|
|
||||||
$('#date_start').daterangepicker(dateOptions);
|
initDateRangePicker('#date_start');
|
||||||
$('#date_expired').daterangepicker(dateOptions);
|
initDateRangePicker('#date_expired');
|
||||||
|
|
||||||
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
|
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
|
||||||
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
|
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
|
||||||
|
|
|
@ -115,8 +115,8 @@ $(document).ready(function () {
|
||||||
closeOnSelect: false
|
closeOnSelect: false
|
||||||
});
|
});
|
||||||
usersSelect2Init('.users-select2');
|
usersSelect2Init('.users-select2');
|
||||||
$('#date_start').daterangepicker(dateOptions);
|
initDateRangePicker('#date_start');
|
||||||
$('#date_expired').daterangepicker(dateOptions);
|
initDateRangePicker('#date_expired');
|
||||||
})
|
})
|
||||||
.on("submit", "form", function (evt) {
|
.on("submit", "form", function (evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
|
@ -1289,3 +1289,31 @@ function showCeleryTaskLog(taskId) {
|
||||||
var url = '/ops/celery/task/taskId/log/'.replace('taskId', taskId);
|
var url = '/ops/celery/task/taskId/log/'.replace('taskId', taskId);
|
||||||
window.open(url, '', 'width=900,height=600')
|
window.open(url, '', 'width=900,height=600')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initDateRangePicker(selector, options) {
|
||||||
|
if (!options) {
|
||||||
|
options = {}
|
||||||
|
}
|
||||||
|
var zhLocale = {
|
||||||
|
format: 'YYYY-MM-DD HH:mm',
|
||||||
|
separator: ' ~ ',
|
||||||
|
applyLabel: "应用",
|
||||||
|
cancelLabel: "取消",
|
||||||
|
resetLabel: "重置",
|
||||||
|
daysOfWeek: ["日", "一", "二", "三", "四", "五", "六"],//汉化处理
|
||||||
|
monthNames: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||||
|
};
|
||||||
|
var defaultOption = {
|
||||||
|
singleDatePicker: true,
|
||||||
|
showDropdowns: true,
|
||||||
|
timePicker: true,
|
||||||
|
timePicker24Hour: true,
|
||||||
|
autoApply: true,
|
||||||
|
};
|
||||||
|
var userLang = navigator.language || navigator.userLanguage;;
|
||||||
|
if (userLang.indexOf('zh') !== -1) {
|
||||||
|
defaultOption.locale = zhLocale;
|
||||||
|
}
|
||||||
|
options = Object.assign(defaultOption, options);
|
||||||
|
return $(selector).daterangepicker(options);
|
||||||
|
}
|
||||||
|
|
|
@ -12,4 +12,4 @@
|
||||||
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
|
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
|
||||||
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
<script src="{% static 'js/plugins/sweetalert/sweetalert.min.js' %}"></script>
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
|
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
|
||||||
|
|
|
@ -62,10 +62,6 @@ class AuthMixin:
|
||||||
def can_use_ssh_key_login(self):
|
def can_use_ssh_key_login(self):
|
||||||
return settings.TERMINAL_PUBLIC_KEY_AUTH
|
return settings.TERMINAL_PUBLIC_KEY_AUTH
|
||||||
|
|
||||||
def check_otp(self, code):
|
|
||||||
from ..utils import check_otp_code
|
|
||||||
return check_otp_code(self.otp_secret_key, code)
|
|
||||||
|
|
||||||
def is_public_key_valid(self):
|
def is_public_key_valid(self):
|
||||||
"""
|
"""
|
||||||
Check if the user's ssh public key is valid.
|
Check if the user's ssh public key is valid.
|
||||||
|
@ -362,6 +358,10 @@ class MFAMixin:
|
||||||
self.otp_level = 0
|
self.otp_level = 0
|
||||||
self.otp_secret_key = None
|
self.otp_secret_key = None
|
||||||
|
|
||||||
|
def check_otp(self, code):
|
||||||
|
from ..utils import check_otp_code
|
||||||
|
return check_otp_code(self.otp_secret_key, code)
|
||||||
|
|
||||||
|
|
||||||
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||||
SOURCE_LOCAL = 'local'
|
SOURCE_LOCAL = 'local'
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block custom_foot_js %}
|
{% block custom_foot_js %}
|
||||||
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.js' %}"></script>
|
||||||
|
<script src="{% static 'js/plugins/datepicker/bootstrap-datepicker.zh-CN.min.js' %}"></script>
|
||||||
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/moment.min.js" %}'></script>
|
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/moment.min.js" %}'></script>
|
||||||
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'></script>
|
<script type="text/javascript" src='{% static "js/plugins/daterangepicker/daterangepicker.min.js" %}'></script>
|
||||||
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
|
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
|
||||||
|
@ -72,19 +73,9 @@
|
||||||
$(groups_id).closest('.form-group').removeClass('hidden');
|
$(groups_id).closest('.form-group').removeClass('hidden');
|
||||||
}}
|
}}
|
||||||
|
|
||||||
var dateOptions = {
|
|
||||||
singleDatePicker: true,
|
|
||||||
showDropdowns: true,
|
|
||||||
timePicker: true,
|
|
||||||
timePicker24Hour: true,
|
|
||||||
autoApply: true,
|
|
||||||
locale: {
|
|
||||||
format: 'YYYY-MM-DD HH:mm'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('.select2').select2();
|
$('.select2').select2();
|
||||||
$('#id_date_expired').daterangepicker(dateOptions);
|
initDateRangePicker('#id_date_expired');
|
||||||
var mfa_radio = $('#id_otp_level');
|
var mfa_radio = $('#id_otp_level');
|
||||||
mfa_radio.addClass("form-inline");
|
mfa_radio.addClass("form-inline");
|
||||||
mfa_radio.children().css("margin-right","15px");
|
mfa_radio.children().css("margin-right","15px");
|
||||||
|
|
|
@ -212,7 +212,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if user_object.is_current_org_admin or user_object.is_superuser %}
|
{% if user.is_current_org_admin or user.is_superuser %}
|
||||||
<div class="panel panel-info">
|
<div class="panel panel-info">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<i class="fa fa-info-circle"></i> {% trans 'User group' %}
|
<i class="fa fa-info-circle"></i> {% trans 'User group' %}
|
||||||
|
|
|
@ -20,9 +20,6 @@ router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'us
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
|
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
|
||||||
name='connection-token'),
|
name='connection-token'),
|
||||||
path('auth/', auth_api.UserAuthApi.as_view(), name='user-auth'),
|
|
||||||
path('otp/auth/', auth_api.UserOtpAuthApi.as_view(), name='user-otp-auth'),
|
|
||||||
|
|
||||||
path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
|
path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
|
||||||
path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
|
path('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
|
||||||
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
|
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
|
||||||
|
|
|
@ -218,9 +218,11 @@ def set_tmp_user_to_cache(request, user, ttl=3600):
|
||||||
def redirect_user_first_login_or_index(request, redirect_field_name):
|
def redirect_user_first_login_or_index(request, redirect_field_name):
|
||||||
if request.user.is_first_login:
|
if request.user.is_first_login:
|
||||||
return reverse('users:user-first-login')
|
return reverse('users:user-first-login')
|
||||||
return request.POST.get(
|
url_in_post = request.POST.get(redirect_field_name)
|
||||||
redirect_field_name,
|
if url_in_post:
|
||||||
request.GET.get(redirect_field_name, reverse('index')))
|
return url_in_post
|
||||||
|
url_in_get = request.GET.get(redirect_field_name, reverse('index'))
|
||||||
|
return url_in_get
|
||||||
|
|
||||||
|
|
||||||
def generate_otp_uri(request, issuer="Jumpserver"):
|
def generate_otp_uri(request, issuer="Jumpserver"):
|
||||||
|
|
Loading…
Reference in New Issue