Commit 6ce9815d authored by ibuler's avatar ibuler

[Update] 基本完成登录逻辑

parent 9d201bbf
......@@ -110,5 +110,14 @@ class UserLoginLog(models.Model):
login_logs = login_logs.filter(username__in=username_list)
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:
ordering = ['-datetime', 'username']
......@@ -4,15 +4,18 @@
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.db import transaction
from django.utils import timezone
from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request
from jumpserver.utils import current_request
from common.utils import get_request_ip, get_logger, get_syslogger
from users.models import User
from authentication.signals import post_auth_failed, post_auth_success
from terminal.models import Session, Command
from terminal.backends.command.serializers import SessionCommandSerializer
from . import models
from . import serializers
from . import models, serializers
from .tasks import write_login_log_async
logger = get_logger(__name__)
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')
msg = "{} - {}".format(category, data)
sys_logger.info(msg)
def generate_data(username, request):
user_agent = request.META.get('HTTP_USER_AGENT', '')
if isinstance(request, Request):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
else:
login_ip = get_request_ip(request)
login_type = 'W'
data = {
'username': username,
'ip': login_ip,
'type': login_type,
'user_agent': user_agent,
'datetime': timezone.now()
}
return data
@receiver(post_auth_success)
def on_user_auth_success(sender, user, request, **kwargs):
logger.debug('User login success: {}'.format(user.username))
data = generate_data(user.username, request)
data.update({'mfa': int(user.otp_enabled), 'status': True})
write_login_log_async.delay(**data)
@receiver(post_auth_failed)
def on_user_auth_failed(sender, username, request, reason, **kwargs):
logger.debug('User login failed: {}'.format(username))
data = generate_data(username, request)
data.update({'reason': reason, 'status': False})
write_login_log_async.delay(**data)
......@@ -7,6 +7,7 @@ from celery import shared_task
from ops.celery.decorator import register_as_period_task
from .models import UserLoginLog
from .utils import write_login_log
@register_as_period_task(interval=3600*24)
......@@ -19,3 +20,8 @@ def clean_login_log_period():
days = 90
expired_day = now - datetime.timedelta(days=days)
UserLoginLog.objects.filter(datetime__lt=expired_day).delete()
@shared_task
def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs)
......@@ -78,7 +78,7 @@
<td class="text-center">{{ login_log.ip }}</td>
<td class="text-center">{{ login_log.city }}</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.datetime }}</td>
</tr>
......
import csv
import codecs
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):
......@@ -20,3 +23,16 @@ def write_content_to_excel(response, header=None, login_logs=None, fields=None):
data = [getattr(log, field.name) for field in fields]
writer.writerow(data)
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)
# -*- coding: utf-8 -*-
#
import uuid
import time
from django.core.cache import cache
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from common.utils import get_logger, get_request_ip, get_object_or_none
from common.permissions import IsOrgAdminOrAppUser, IsValidUser
from common.utils import get_logger
from common.permissions import IsOrgAdminOrAppUser
from orgs.mixins.api import RootOrgViewMixin
from users.serializers import UserSerializer
from users.models import User
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__)
__all__ = [
'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi',
'UserOtpVerifyApi', 'UserOrderAcceptAuthApi',
'UserConnectionTokenApi',
]
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):
permission_classes = (IsOrgAdminOrAppUser,)
......@@ -150,82 +61,5 @@ class UserConnectionTokenApi(RootOrgViewMixin, APIView):
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)
# -*- coding: utf-8 -*-
#
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 common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin
from ..models import LoginConfirmSetting
from ..serializers import LoginConfirmSettingSerializer
from .. import errors
__all__ = ['LoginConfirmSettingUpdateApi']
__all__ = ['LoginConfirmSettingUpdateApi', 'UserOrderAcceptAuthApi']
logger = get_logger(__name__)
class LoginConfirmSettingUpdateApi(UpdateAPIView):
......@@ -23,3 +28,29 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
defaults, user=user,
)
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)
# -*- coding: utf-8 -*-
#
import time
from rest_framework.permissions import AllowAny
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 errors
from ..mixins import AuthMixin
class MFAChallengeApi(CreateAPIView):
__all__ = ['MFAChallengeApi', 'UserOtpVerifyApi']
class MFAChallengeApi(AuthMixin, CreateAPIView):
permission_classes = (AllowAny,)
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)
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from common.utils import get_request_ip, get_logger, get_object_or_none
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 common.utils import get_logger
from .. import serializers, errors
from ..mixins import AuthMixin
logger = get_logger(__name__)
......@@ -22,126 +16,24 @@ logger = get_logger(__name__)
__all__ = ['TokenCreateApi']
class TokenCreateApi(CreateAPIView):
class TokenCreateApi(AuthMixin, CreateAPIView):
permission_classes = (AllowAny,)
serializer_class = serializers.BearerTokenSerializer
def check_session(self):
pass
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_session_if_need(self):
if self.request.session.is_empty():
self.request.session.create()
def create(self, request, *args, **kwargs):
self.check_session()
self.create_session_if_need()
# 如果认证没有过,检查账号密码
try:
user = self.check_user_auth()
self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user)
self.send_auth_signal(success=True, user=user)
self.clear_auth_mark()
resp = super().create(request, *args, **kwargs)
return resp
except errors.AuthFailedError as e:
if e.username:
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
)
return Response(e.as_data(), status=401)
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from django.conf import settings
password_failed = _('Username/password check failed')
mfa_failed = _('MFA authentication failed')
user_not_exist = _("Username does not exist")
password_expired = _("Password expired")
user_invalid = _('Disabled or expired')
ip_blocked = _("Log in frequently and try again later")
from .signals import post_auth_failed
from users.utils import (
increase_login_failed_count, get_login_failed_count
)
mfa_required = _("MFA required")
login_confirm_required = _("Login confirm required")
login_confirm_wait = _("Wait login confirm")
reason_password_failed = 'password_failed'
reason_mfa_failed = 'mfa_failed'
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):
def __init__(self, reason, error=None, username=None):
self.reason = reason
self.error = error
self.username = username
username = ''
msg = ''
error = ''
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 CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFailedError):
def __init__(self, error, username, ip, request):
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 MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
reason = reason_mfa_failed
error = 'mfa_failed'
msg = mfa_failed_msg
def __init__(self, username, request):
super().__init__(username=username, request=request)
class BlockLoginError(AuthFailedNeedBlockMixin, AuthFailedError):
error = 'block_login'
msg = block_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
def __init__(self, username, ip):
super().__init__(username=username, ip=ip)
class SessionEmptyError(AuthFailedError):
msg = session_empty_msg
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)
class MFARequiredError(Exception):
reason = mfa_required
error = 'mfa_required'
def as_data(self):
return {
"error": self.error,
"msg": self.msg,
"order_id": self.order_id
}
class LoginConfirmRequiredError(Exception):
reason = login_confirm_required
error = 'login_confirm_required'
class LoginConfirmWaitError(LoginConfirmError):
msg = login_confirm_wait_msg
error = 'login_confirm_wait_msg'
class LoginConfirmWaitError(Exception):
reason = login_confirm_wait
error = 'login_confirm_wait'
class LoginConfirmRejectedError(LoginConfirmError):
msg = login_confirm_rejected_msg
error = 'login_confirm_rejected_msg'
class LoginConfirmRejectedError(Exception):
reason = login_confirm_wait
error = 'login_confirm_rejected'
class LoginConfirmOrderNotFound(LoginConfirmError):
msg = login_confirm_order_not_found_msg
error = 'login_confirm_order_not_found_msg'
......@@ -9,53 +9,19 @@ from django.conf import settings
from users.utils import get_login_failed_count
class UserLoginForm(AuthenticationForm):
class UserLoginForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=100)
password = forms.CharField(
label=_('Password'), widget=forms.PasswordInput,
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):
if not user.is_staff:
raise forms.ValidationError(
self.error_messages['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,
code='inactive',
)
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):
......
# -*- coding: utf-8 -*-
#
import time
from common.utils import get_object_or_none, get_request_ip, get_logger
from users.models import User
from users.utils import (
is_block_login, clean_failed_count, increase_login_failed_count
)
from . import errors
from .utils import check_user_valid
from .signals import post_auth_success, post_auth_failed
logger = get_logger(__name__)
class AuthMixin:
request = None
def get_user_from_session(self):
if self.request.session.is_empty():
raise errors.SessionEmptyError()
if self.request.user and not self.request.user.is_anonymous:
return self.request.user
user_id = self.request.session.get('user_id')
if not user_id:
user = None
else:
user = get_object_or_none(User, pk=user_id)
if not user:
raise errors.SessionEmptyError()
return user
def get_request_ip(self):
ip = ''
if hasattr(self.request, 'data'):
ip = self.request.data.get('remote_addr', '')
ip = ip or get_request_ip(self.request)
return ip
def check_is_block(self):
if hasattr(self.request, 'data'):
username = self.request.data.get("username")
else:
username = self.request.POST.get("username")
ip = self.get_request_ip()
if is_block_login(username, ip):
logger.warn('Ip was blocked' + ': ' + username + ':' + ip)
raise errors.BlockLoginError(username=username, ip=ip)
def check_user_auth(self):
request = self.request
self.check_is_block()
if hasattr(request, 'data'):
username = request.data.get('username', '')
password = request.data.get('password', '')
public_key = request.data.get('public_key', '')
else:
username = request.POST.get('username', '')
password = request.POST.get('password', '')
public_key = request.POST.get('public_key', '')
user, error = check_user_valid(
username=username, password=password,
public_key=public_key
)
ip = self.get_request_ip()
if not user:
raise errors.CredentialError(
username=username, error=error, ip=ip, request=request
)
clean_failed_count(username, ip)
request.session['auth_password'] = 1
request.session['user_id'] = str(user.id)
return user
def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'):
return True
if not user.otp_enabled or not user.otp_secret_key:
return True
raise errors.MFARequiredError()
def check_user_mfa(self, code):
user = self.get_user_from_session()
ok = user.check_otp(code)
if ok:
self.request.session['auth_mfa'] = 1
self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_type'] = 'otp'
return
raise errors.MFAFailedError(username=user.username, request=self.request)
def check_user_login_confirm_if_need(self, user):
from orders.models import LoginConfirmOrder
confirm_setting = user.get_login_confirm_setting()
if self.request.session.get('auth_confirm') or not confirm_setting:
return
order = None
if self.request.session.get('auth_order_id'):
order_id = self.request.session['auth_order_id']
order = get_object_or_none(LoginConfirmOrder, pk=order_id)
if not order:
order = confirm_setting.create_confirm_order(self.request)
self.request.session['auth_order_id'] = str(order.id)
if order.status == "accepted":
return
elif order.status == "rejected":
raise errors.LoginConfirmRejectedError(order.id)
else:
raise errors.LoginConfirmWaitError(order.id)
def clear_auth_mark(self):
self.request.session['auth_password'] = ''
self.request.session['auth_mfa'] = ''
self.request.session['auth_confirm'] = ''
def send_auth_signal(self, success=True, user=None, username='', reason=''):
if success:
post_auth_success.send(
sender=self.__class__, user=user, request=self.request
)
else:
post_auth_failed.send(
sender=self.__class__, username=username,
request=self.request, reason=reason
)
......@@ -3,6 +3,7 @@
from django.core.cache import cache
from rest_framework import serializers
from common.utils import get_object_or_none
from users.models import User
from .models import AccessKey, LoginConfirmSetting
......@@ -24,7 +25,12 @@ class OtpVerifySerializer(serializers.Serializer):
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)
keyword = serializers.SerializerMethodField()
date_expired = serializers.DateTimeField(read_only=True)
......@@ -33,58 +39,35 @@ class BearerTokenMixin(serializers.Serializer):
def get_keyword(obj):
return 'Bearer'
def create_response(self, username):
request = self.context.get("request")
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise serializers.ValidationError("username %s not exist" % username)
def create(self, validated_data):
request = self.context.get('request')
if request.user and not request.user.is_anonymous:
user = request.user
else:
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)
instance = {
"username": username,
"username": user.username,
"token": token,
"date_expired": date_expired,
}
return instance
def update(self, instance, validated_data):
pass
class BearerTokenSerializer(BearerTokenMixin, serializers.Serializer):
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)
class MFAChallengeSerializer(serializers.Serializer):
auth_type = serializers.CharField(write_only=True, required=False, allow_blank=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):
username = self.context["username"]
return self.create_response(username)
pass
def update(self, instance, validated_data):
pass
class LoginConfirmSettingSerializer(serializers.ModelSerializer):
......
from rest_framework.request import Request
from django.http.request import QueryDict
from django.conf import settings
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_out
from django.utils import timezone
from django_auth_ldap.backend import populate_user
from common.utils import get_request_ip
from .backends.openid import new_client
from .backends.openid.signals import (
post_create_openid_user, post_openid_login_success
)
from .tasks import write_login_log_async
from .signals import post_auth_success, post_auth_failed
from .signals import post_auth_success
@receiver(user_logged_out)
......@@ -52,35 +48,4 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
user.save()
def generate_data(username, request):
user_agent = request.META.get('HTTP_USER_AGENT', '')
if isinstance(request, Request):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
else:
login_ip = get_request_ip(request)
login_type = 'W'
data = {
'username': username,
'ip': login_ip,
'type': login_type,
'user_agent': user_agent,
'datetime': timezone.now()
}
return data
@receiver(post_auth_success)
def on_user_auth_success(sender, user, request, **kwargs):
data = generate_data(user.username, request)
data.update({'mfa': int(user.otp_enabled), 'status': True})
write_login_log_async.delay(**data)
@receiver(post_auth_failed)
def on_user_auth_failed(sender, username, request, reason, **kwargs):
data = generate_data(username, request)
data.update({'reason': reason, 'status': False})
write_login_log_async.delay(**data)
......@@ -6,17 +6,8 @@ from ops.celery.decorator import register_as_period_task
from django.contrib.sessions.models import Session
from django.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)
@shared_task
def clean_django_sessions():
Session.objects.filter(expire_date__lt=timezone.now()).delete()
......@@ -37,7 +37,6 @@
<p>
{% trans "Changes the world, starting with a little bit." %}
</p>
</div>
<div class="col-md-6">
<div class="ibox-content">
......@@ -47,25 +46,29 @@
</div>
<form class="m-t" role="form" method="post" action="">
{% csrf_token %}
{% if block_login %}
<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 %}
{% if form.non_field_errors %}
<div style="line-height: 17px;">
<p class="red-fonts">{{ form.non_field_errors.as_text }}</p>
{% endif %}
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
</div>
{% elif form.errors.captcha %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
{% endif %}
<div class="form-group">
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}">
{% if form.errors.username %}
<div class="help-block field-error">
<p class="red-fonts">{{ form.errors.username.as_text }}</p>
</div>
{% endif %}
</div>
<div class="form-group">
<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>
{{ form.captcha }}
......
......@@ -86,7 +86,7 @@ function doRequestAuth() {
window.location = successUrl;
},
error: function (text, data) {
if (data.status !== "pending") {
if (data.error !== "login_confirm_wait") {
if (!errorMsgShow) {
infoMsgRef.hide();
errorMsgRef.show();
......@@ -97,7 +97,7 @@ function doRequestAuth() {
clearInterval(checkInterval);
$(".copy-btn").attr('disabled', 'disabled')
}
errorMsgRef.html(data.error)
errorMsgRef.html(data.msg)
},
flash_message: false
})
......
......@@ -48,6 +48,13 @@
float: right;
}
.red-fonts {
color: red;
}
.field-error {
text-align: left;
}
</style>
</head>
......@@ -69,30 +76,32 @@
<div style="margin-bottom: 10px">
<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">
{% csrf_token %}
{% if form.non_field_errors %}
<div style="height: 70px;color: red;line-height: 17px;">
{% if block_login %}
<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>
{% elif form.errors.captcha %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p>
{% endif %}
<div class="form-group">
<input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}" 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 class="form-group">
<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 class="form-group" style="height: 50px;margin-bottom: 0;font-size: 13px">
{{ form.captcha }}
......
......@@ -12,12 +12,11 @@ router.register('access-keys', api.AccessKeyViewSet, 'access-key')
urlpatterns = [
# 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('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
path('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('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')
......
# -*- 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.utils import timezone
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 . 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):
password = kwargs.pop('password', None)
public_key = kwargs.pop('public_key', None)
email = kwargs.pop('email', None)
username = kwargs.pop('username', None)
request = kwargs.get('request')
if username:
user = get_object_or_none(User, username=username)
......@@ -38,21 +25,25 @@ def check_user_valid(**kwargs):
user = None
if user is None:
return None, errors.user_not_exist
elif not user.is_valid:
return None, errors.user_invalid
return None, errors.reason_user_not_exist
elif user.is_expired:
return None, errors.reason_password_expired
elif not user.is_active:
return None, errors.reason_user_inactive
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:
user = authenticate(request, username=username, password=password)
if user:
return user, ''
if public_key and user.public_key:
public_key_saved = user.public_key.split()
if len(public_key_saved) == 1:
if public_key == public_key_saved[0]:
return user, ''
elif len(public_key_saved) > 1:
if public_key == public_key_saved[1]:
public_key_saved = public_key_saved[0]
else:
public_key_saved = public_key_saved[1]
if public_key == public_key_saved:
return user, ''
return None, errors.password_failed
return None, errors.reason_password_failed
# -*- coding: utf-8 -*-
#
from .login import *
from .mfa import *
......@@ -16,22 +16,20 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.edit import FormView
from django.conf import settings
from django.urls import reverse_lazy
from common.utils import get_request_ip, get_object_or_none
from users.models import User
from users.utils import (
check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user,
set_tmp_user_to_cache, increase_login_failed_count,
get_user_or_tmp_user, increase_login_failed_count,
redirect_user_first_login_or_index
)
from ..models import LoginConfirmSetting
from ..signals import post_auth_success, post_auth_failed
from .. import forms
from .. import errors
from .. import forms, mixins, errors
__all__ = [
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
'UserLoginView', 'UserLogoutView',
'UserLoginGuardView', 'UserLoginWaitConfirmView',
]
......@@ -39,10 +37,11 @@ __all__ = [
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(never_cache, name='dispatch')
class UserLoginView(FormView):
class UserLoginView(mixins.AuthMixin, FormView):
form_class = forms.UserLoginForm
form_class_captcha = forms.UserLoginCaptchaForm
key_prefix_captcha = "_LOGIN_INVALID_{}"
redirect_field_name = 'next'
def get_template_names(self):
template_name = 'authentication/login.html'
......@@ -69,54 +68,25 @@ class UserLoginView(FormView):
request.session.set_test_cookie()
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):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
user = form.get_user()
# user password expired
if user.password_has_expired:
reason = errors.password_expired
self.send_auth_signal(success=False, username=user.username, reason=reason)
return self.render_to_response(self.get_context_data(password_expired=True))
set_tmp_user_to_cache(self.request, user)
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()
def form_invalid(self, form):
# write login failed log
username = form.cleaned_data.get('username')
exist = User.objects.filter(username=username).first()
reason = errors.password_failed if exist else errors.user_not_exist
# limit user login failed count
ip = get_request_ip(self.request)
increase_login_failed_count(username, ip)
form.add_limit_login_error(username, ip)
# show captcha
try:
self.check_user_auth()
except errors.AuthFailedError as e:
form.add_error(None, e.msg)
ip = self.get_request_ip()
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)
context = self.get_context_data(form=form)
return self.render_to_response(context)
return self.redirect_to_guard_view()
@staticmethod
def redirect_to_guard_view():
continue_url = reverse('authentication:login-guard')
return redirect(continue_url)
def redirect_to_guard_view(self):
guard_url = reverse('authentication:login-guard')
args = self.request.META.get('QUERY_STRING', '')
if args and self.query_string:
guard_url = "%s?%s" % (guard_url, args)
return redirect(guard_url)
def get_form_class(self):
ip = get_request_ip(self.request)
......@@ -134,58 +104,34 @@ class UserLoginView(FormView):
return super().get_context_data(**kwargs)
class UserLoginOtpView(FormView):
template_name = 'authentication/login_otp.html'
form_class = forms.UserCheckOtpCodeForm
redirect_field_name = 'next'
def form_valid(self, form):
user = get_user_or_tmp_user(self.request)
otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = user.otp_secret_key
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):
class UserLoginGuardView(mixins.AuthMixin, RedirectView):
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 format_redirect_url(self, url):
args = self.request.META.get('QUERY_STRING', '')
if args and self.query_string:
url = "%s?%s" % (url, args)
return url
def get_redirect_url(self, *args, **kwargs):
if not self.request.session.get('auth_password'):
return reverse('authentication:login')
user = get_user_or_tmp_user(self.request)
return self.format_redirect_url(self.login_url)
user = self.get_user_from_session()
# 启用并设置了otp
if user.otp_enabled and user.otp_secret_key and \
not self.request.session.get('auth_otp'):
return reverse('authentication:login-otp')
not self.request.session.get('auth_mfa'):
return self.format_redirect_url(self.login_otp_url)
confirm_setting = user.get_login_confirm_setting()
if confirm_setting and not self.request.session.get('auth_confirm'):
order = confirm_setting.create_confirm_order(self.request)
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
self.login_success(user)
self.clear_auth_mark()
# 启用但是没有设置otp
if user.otp_enabled and not user.otp_secret_key:
# 1,2,mfa_setting & F
......
# -*- 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)
# -*- coding: utf-8 -*-
#
from django.shortcuts import reverse, redirect
def redirect_to_guard_view():
continue_url = reverse('authentication:login-guard')
return redirect(continue_url)
......@@ -153,6 +153,14 @@ def get_request_ip(request):
return login_ip
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):
try:
ipaddress.ip_address(ip)
......
This diff is collapsed.
......@@ -100,16 +100,6 @@
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
<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 }}";
$(document).ready(function () {
......@@ -119,8 +109,8 @@ $(document).ready(function () {
nodesSelect2Init(".nodes-select2");
usersSelect2Init(".users-select2");
$('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions);
initDateRangePicker('#date_start');
initDateRangePicker('#date_expired');
$("#id_assets").parent().find(".select2-selection").on('click', function (e) {
if ($(e.target).attr('class') !== 'select2-selection__choice__remove'){
......
......@@ -115,8 +115,8 @@ $(document).ready(function () {
closeOnSelect: false
});
usersSelect2Init('.users-select2');
$('#date_start').daterangepicker(dateOptions);
$('#date_expired').daterangepicker(dateOptions);
initDateRangePicker('#date_start');
initDateRangePicker('#date_expired');
})
.on("submit", "form", function (evt) {
evt.preventDefault();
......
......@@ -1289,3 +1289,31 @@ function showCeleryTaskLog(taskId) {
var url = '/ops/celery/task/taskId/log/'.replace('taskId', taskId);
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);
}
......@@ -62,10 +62,6 @@ class AuthMixin:
def can_use_ssh_key_login(self):
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):
"""
Check if the user's ssh public key is valid.
......@@ -362,6 +358,10 @@ class MFAMixin:
self.otp_level = 0
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):
SOURCE_LOCAL = 'local'
......
......@@ -56,6 +56,7 @@
{% endblock %}
{% block custom_foot_js %}
<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/daterangepicker.min.js" %}'></script>
<link rel="stylesheet" type="text/css" href={% static "css/plugins/daterangepicker/daterangepicker.css" %} />
......@@ -72,19 +73,9 @@
$(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 () {
$('.select2').select2();
$('#id_date_expired').daterangepicker(dateOptions);
initDateRangePicker('#id_date_expired');
var mfa_radio = $('#id_otp_level');
mfa_radio.addClass("form-inline");
mfa_radio.children().css("margin-right","15px");
......
......@@ -212,7 +212,7 @@
</table>
</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-heading">
<i class="fa fa-info-circle"></i> {% trans 'User group' %}
......
......@@ -20,9 +20,6 @@ router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'us
urlpatterns = [
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
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('otp/reset/', api.UserResetOTPApi.as_view(), name='my-otp-reset'),
path('users/<uuid:pk>/otp/reset/', api.UserResetOTPApi.as_view(), name='user-reset-otp'),
......
......@@ -218,9 +218,11 @@ def set_tmp_user_to_cache(request, user, ttl=3600):
def redirect_user_first_login_or_index(request, redirect_field_name):
if request.user.is_first_login:
return reverse('users:user-first-login')
return request.POST.get(
redirect_field_name,
request.GET.get(redirect_field_name, reverse('index')))
url_in_post = request.POST.get(redirect_field_name)
if url_in_post:
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"):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment