[Update] 基本完成登录逻辑

pull/3428/head
ibuler 2019-11-05 18:46:29 +08:00
parent 9d201bbf98
commit 6ce9815d51
36 changed files with 874 additions and 819 deletions

View File

@ -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']

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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'

View File

@ -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):

View File

@ -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
)

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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 }}

View File

@ -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
}) })

View File

@ -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>

View File

@ -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')

View File

@ -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

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from .login import * from .login import *
from .mfa import *

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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 "收件人"

View File

@ -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'){

View File

@ -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();

View File

@ -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);
}

View File

@ -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>

View File

@ -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'

View File

@ -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");

View File

@ -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' %}

View File

@ -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'),

View File

@ -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"):