mirror of https://github.com/jumpserver/jumpserver
[Update] Stash
parent
1a247d60e7
commit
21714cc411
|
@ -2,3 +2,5 @@ from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
post_create_openid_user = Signal(providing_args=('user',))
|
post_create_openid_user = Signal(providing_args=('user',))
|
||||||
|
post_auth_success = Signal(providing_args=('user', 'request'))
|
||||||
|
post_auth_failed = Signal(providing_args=('username', 'request', 'reason'))
|
||||||
|
|
|
@ -2,9 +2,15 @@ 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 .openid import client
|
from .openid import client
|
||||||
from .signals import post_create_openid_user
|
from .tasks import write_login_log_async
|
||||||
|
from .signals import (
|
||||||
|
post_create_openid_user, post_auth_success, post_auth_failed
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_out)
|
@receiver(user_logged_out)
|
||||||
|
@ -38,3 +44,36 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
|
||||||
if user and user.name != 'admin':
|
if user and user.name != 'admin':
|
||||||
user.source = user.SOURCE_LDAP
|
user.source = user.SOURCE_LDAP
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_data(username, request):
|
||||||
|
if not request.user.is_anonymous and request.user.is_app:
|
||||||
|
login_ip = request.data.get('remote_addr', None)
|
||||||
|
login_type = request.data.get('login_type', '')
|
||||||
|
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
||||||
|
else:
|
||||||
|
login_ip = get_request_ip(request)
|
||||||
|
user_agent = request.META.get('HTTP_USER_AGENT', '')
|
||||||
|
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)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
|
||||||
|
from .utils import write_login_log
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def write_login_log_async(*args, **kwargs):
|
||||||
|
write_login_log(*args, **kwargs)
|
|
@ -2,15 +2,13 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from authentication.openid import views
|
from .. import views
|
||||||
|
|
||||||
app_name = 'authentication'
|
app_name = 'authentication'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# openid
|
# openid
|
||||||
path('openid/login/', views.LoginView.as_view(), name='openid-login'),
|
path('openid/login/', views.OpenIDLoginView.as_view(), name='openid-login'),
|
||||||
path('openid/login/complete/', views.LoginCompleteView.as_view(),
|
path('openid/login/complete/', views.OpenIDLoginCompleteView.as_view(),
|
||||||
name='openid-login-complete'),
|
name='openid-login-complete'),
|
||||||
|
|
||||||
# other
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from common.utils import get_ip_city, validate_ip
|
||||||
|
|
||||||
|
|
||||||
|
def write_login_log(*args, **kwargs):
|
||||||
|
from users.models import LoginLog
|
||||||
|
default_city = _("Unknown")
|
||||||
|
ip = kwargs.get('ip', '')
|
||||||
|
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})
|
||||||
|
LoginLog.objects.create(**kwargs)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from .openid import *
|
|
@ -0,0 +1,212 @@
|
||||||
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib.auth import login as auth_login, logout as auth_logout
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
from django.shortcuts import reverse, redirect
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.views.decorators.cache import never_cache
|
||||||
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
from django.conf import settings
|
||||||
|
from formtools.wizard.views import SessionWizardView
|
||||||
|
|
||||||
|
from common.utils import get_object_or_none, get_request_ip
|
||||||
|
from authentication.signals import post_auth_success, post_auth_failed
|
||||||
|
from users.models import User, LoginLog
|
||||||
|
from users.utils import send_reset_password_mail, check_otp_code, \
|
||||||
|
redirect_user_first_login_or_index, get_user_or_tmp_user, \
|
||||||
|
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
|
||||||
|
is_block_login, increase_login_failed_count, clean_failed_count
|
||||||
|
from users import forms
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
||||||
|
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
||||||
|
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
||||||
|
'UserFirstLoginView', 'LoginLogListView'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
||||||
|
@method_decorator(csrf_protect, name='dispatch')
|
||||||
|
@method_decorator(never_cache, name='dispatch')
|
||||||
|
class UserLoginView(FormView):
|
||||||
|
form_class = forms.UserLoginForm
|
||||||
|
form_class_captcha = forms.UserLoginCaptchaForm
|
||||||
|
redirect_field_name = 'next'
|
||||||
|
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
||||||
|
|
||||||
|
def get_template_names(self):
|
||||||
|
template_name = 'users/login.html'
|
||||||
|
if not settings.XPACK_ENABLED:
|
||||||
|
return template_name
|
||||||
|
|
||||||
|
from xpack.plugins.license.models import License
|
||||||
|
if not License.has_valid_license():
|
||||||
|
return template_name
|
||||||
|
|
||||||
|
template_name = 'users/new_login.html'
|
||||||
|
return template_name
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_staff:
|
||||||
|
return redirect(redirect_user_first_login_or_index(
|
||||||
|
request, self.redirect_field_name)
|
||||||
|
)
|
||||||
|
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 = LoginLog.REASON_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)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
# write login failed log
|
||||||
|
username = form.cleaned_data.get('username')
|
||||||
|
exist = User.objects.filter(username=username).first()
|
||||||
|
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
||||||
|
# limit user login failed count
|
||||||
|
ip = get_request_ip(self.request)
|
||||||
|
increase_login_failed_count(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)
|
||||||
|
|
||||||
|
def get_form_class(self):
|
||||||
|
ip = get_request_ip(self.request)
|
||||||
|
if cache.get(self.key_prefix_captcha.format(ip)):
|
||||||
|
return self.form_class_captcha
|
||||||
|
else:
|
||||||
|
return self.form_class
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
user = get_user_or_tmp_user(self.request)
|
||||||
|
|
||||||
|
if user.otp_enabled and user.otp_secret_key:
|
||||||
|
# 1,2,mfa_setting & T
|
||||||
|
return reverse('users:login-otp')
|
||||||
|
elif user.otp_enabled and not user.otp_secret_key:
|
||||||
|
# 1,2,mfa_setting & F
|
||||||
|
return reverse('users:user-otp-enable-authentication')
|
||||||
|
elif not user.otp_enabled:
|
||||||
|
# 0 & T,F
|
||||||
|
auth_login(self.request, user)
|
||||||
|
self.send_auth_signal(success=True, user=user)
|
||||||
|
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||||
|
'AUTH_OPENID': settings.AUTH_OPENID,
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
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 UserLoginOtpView(FormView):
|
||||||
|
template_name = 'users/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):
|
||||||
|
auth_login(self.request, user)
|
||||||
|
self.send_auth_signal(success=True, user=user)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
else:
|
||||||
|
self.send_auth_signal(
|
||||||
|
success=False, username=user.username,
|
||||||
|
reason=LoginLog.REASON_MFA
|
||||||
|
)
|
||||||
|
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(never_cache, name='dispatch')
|
||||||
|
class UserLogoutView(TemplateView):
|
||||||
|
template_name = 'flash_message_standalone.html'
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
auth_logout(request)
|
||||||
|
next_uri = request.COOKIES.get("next")
|
||||||
|
if next_uri:
|
||||||
|
return redirect(next_uri)
|
||||||
|
response = super().get(request, *args, **kwargs)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = {
|
||||||
|
'title': _('Logout success'),
|
||||||
|
'messages': _('Logout success, return login page'),
|
||||||
|
'interval': 1,
|
||||||
|
'redirect_url': reverse('users:login'),
|
||||||
|
'auto_redirect': True,
|
||||||
|
}
|
||||||
|
kwargs.update(context)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,43 +14,36 @@ from django.http.response import (
|
||||||
HttpResponseRedirect
|
HttpResponseRedirect
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import client
|
from ..openid import client
|
||||||
from .models import Nonce
|
from ..openid.models import Nonce
|
||||||
from users.models import LoginLog
|
from ..signals import post_auth_success
|
||||||
from users.tasks import write_login_log_async
|
|
||||||
from common.utils import get_request_ip
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_base_site_url():
|
__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
|
||||||
return settings.BASE_SITE_URL
|
|
||||||
|
|
||||||
|
|
||||||
class LoginView(RedirectView):
|
class OpenIDLoginView(RedirectView):
|
||||||
|
|
||||||
def get_redirect_url(self, *args, **kwargs):
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
redirect_uri = settings.BASE_SITE_URL + \
|
||||||
|
reverse("authentication:openid-login-complete")
|
||||||
nonce = Nonce(
|
nonce = Nonce(
|
||||||
redirect_uri=get_base_site_url() + reverse(
|
redirect_uri=redirect_uri,
|
||||||
"authentication:openid-login-complete"),
|
|
||||||
|
|
||||||
next_path=self.request.GET.get('next')
|
next_path=self.request.GET.get('next')
|
||||||
)
|
)
|
||||||
|
|
||||||
cache.set(str(nonce.state), nonce, 24*3600)
|
cache.set(str(nonce.state), nonce, 24*3600)
|
||||||
|
|
||||||
self.request.session['openid_state'] = str(nonce.state)
|
self.request.session['openid_state'] = str(nonce.state)
|
||||||
|
|
||||||
authorization_url = client.openid_connect_client.\
|
authorization_url = client.openid_connect_client.\
|
||||||
authorization_url(
|
authorization_url(
|
||||||
redirect_uri=nonce.redirect_uri, scope='code',
|
redirect_uri=nonce.redirect_uri, scope='code',
|
||||||
state=str(nonce.state)
|
state=str(nonce.state)
|
||||||
)
|
)
|
||||||
|
|
||||||
return authorization_url
|
return authorization_url
|
||||||
|
|
||||||
|
|
||||||
class LoginCompleteView(RedirectView):
|
class OpenIDLoginCompleteView(RedirectView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if 'error' in request.GET:
|
if 'error' in request.GET:
|
||||||
|
@ -79,24 +72,6 @@ class LoginCompleteView(RedirectView):
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
login(self.request, user)
|
login(self.request, user)
|
||||||
|
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||||
data = {
|
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_NOTHING,
|
|
||||||
'status': True
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
|
|
||||||
return HttpResponseRedirect(nonce.next_path or '/')
|
return HttpResponseRedirect(nonce.next_path or '/')
|
||||||
|
|
||||||
def write_login_log(self, data):
|
|
||||||
login_ip = get_request_ip(self.request)
|
|
||||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
tmp_data = {
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': 'W',
|
|
||||||
'user_agent': user_agent
|
|
||||||
}
|
|
||||||
data.update(tmp_data)
|
|
||||||
write_login_log_async.delay(**data)
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
|
||||||
|
from .common import *
|
||||||
|
from .django import *
|
||||||
|
from .encode import *
|
||||||
|
from .http import *
|
||||||
|
from .ipip import *
|
|
@ -1,104 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from six import string_types
|
|
||||||
import base64
|
|
||||||
import os
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import hashlib
|
|
||||||
from email.utils import formatdate
|
|
||||||
import calendar
|
|
||||||
import threading
|
|
||||||
from io import StringIO
|
|
||||||
import uuid
|
import uuid
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import copy
|
import copy
|
||||||
|
import ipaddress
|
||||||
import paramiko
|
|
||||||
import sshpubkeys
|
|
||||||
from itsdangerous import TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer, \
|
|
||||||
BadSignature, SignatureExpired
|
|
||||||
from django.shortcuts import reverse as dj_reverse
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
|
|
||||||
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||||
|
ipip_db = None
|
||||||
|
|
||||||
def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
|
||||||
current_app=None, external=False):
|
|
||||||
url = dj_reverse(view_name, urlconf=urlconf, args=args,
|
|
||||||
kwargs=kwargs, current_app=current_app)
|
|
||||||
|
|
||||||
if external:
|
|
||||||
site_url = settings.SITE_URL
|
|
||||||
url = site_url.strip('/') + url
|
|
||||||
return url
|
|
||||||
|
|
||||||
|
|
||||||
def get_object_or_none(model, **kwargs):
|
|
||||||
try:
|
|
||||||
obj = model.objects.get(**kwargs)
|
|
||||||
except model.DoesNotExist:
|
|
||||||
return None
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
|
||||||
def __init__(cls, *args, **kwargs):
|
|
||||||
cls.__instance = None
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls.__instance is None:
|
|
||||||
cls.__instance = super().__call__(*args, **kwargs)
|
|
||||||
return cls.__instance
|
|
||||||
else:
|
|
||||||
return cls.__instance
|
|
||||||
|
|
||||||
|
|
||||||
class Signer(metaclass=Singleton):
|
|
||||||
"""用来加密,解密,和基于时间戳的方式验证token"""
|
|
||||||
def __init__(self, secret_key=None):
|
|
||||||
self.secret_key = secret_key
|
|
||||||
|
|
||||||
def sign(self, value):
|
|
||||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
|
||||||
return s.dumps(value).decode()
|
|
||||||
|
|
||||||
def unsign(self, value):
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
|
||||||
try:
|
|
||||||
return s.loads(value)
|
|
||||||
except BadSignature:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def sign_t(self, value, expires_in=3600):
|
|
||||||
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
|
||||||
return str(s.dumps(value), encoding="utf8")
|
|
||||||
|
|
||||||
def unsign_t(self, value):
|
|
||||||
s = TimedJSONWebSignatureSerializer(self.secret_key)
|
|
||||||
try:
|
|
||||||
return s.loads(value)
|
|
||||||
except (BadSignature, SignatureExpired):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def date_expired_default():
|
|
||||||
try:
|
|
||||||
years = int(settings.DEFAULT_EXPIRED_YEARS)
|
|
||||||
except TypeError:
|
|
||||||
years = 70
|
|
||||||
return timezone.now() + timezone.timedelta(days=365*years)
|
|
||||||
|
|
||||||
|
|
||||||
def combine_seq(s1, s2, callback=None):
|
def combine_seq(s1, s2, callback=None):
|
||||||
|
@ -146,88 +60,6 @@ def timesince(dt, since='', default="just now"):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def ssh_key_string_to_obj(text, password=None):
|
|
||||||
key = None
|
|
||||||
try:
|
|
||||||
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
|
|
||||||
except paramiko.SSHException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
|
|
||||||
except paramiko.SSHException:
|
|
||||||
pass
|
|
||||||
return key
|
|
||||||
|
|
||||||
|
|
||||||
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
|
||||||
if isinstance(private_key, bytes):
|
|
||||||
private_key = private_key.decode("utf-8")
|
|
||||||
if isinstance(private_key, string_types):
|
|
||||||
private_key = ssh_key_string_to_obj(private_key, password=password)
|
|
||||||
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
|
||||||
raise IOError('Invalid private key')
|
|
||||||
|
|
||||||
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
|
||||||
'key_type': private_key.get_name(),
|
|
||||||
'key_content': private_key.get_base64(),
|
|
||||||
'username': username,
|
|
||||||
'hostname': hostname,
|
|
||||||
}
|
|
||||||
return public_key
|
|
||||||
|
|
||||||
|
|
||||||
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
|
|
||||||
"""Generate user ssh private and public key
|
|
||||||
|
|
||||||
Use paramiko RSAKey generate it.
|
|
||||||
:return private key str and public key str
|
|
||||||
"""
|
|
||||||
|
|
||||||
if hostname is None:
|
|
||||||
hostname = os.uname()[1]
|
|
||||||
|
|
||||||
f = StringIO()
|
|
||||||
try:
|
|
||||||
if type == 'rsa':
|
|
||||||
private_key_obj = paramiko.RSAKey.generate(length)
|
|
||||||
elif type == 'dsa':
|
|
||||||
private_key_obj = paramiko.DSSKey.generate(length)
|
|
||||||
else:
|
|
||||||
raise IOError('SSH private key must be `rsa` or `dsa`')
|
|
||||||
private_key_obj.write_private_key(f, password=password)
|
|
||||||
private_key = f.getvalue()
|
|
||||||
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
|
|
||||||
return private_key, public_key
|
|
||||||
except IOError:
|
|
||||||
raise IOError('These is error when generate ssh key.')
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ssh_private_key(text, password=None):
|
|
||||||
if isinstance(text, bytes):
|
|
||||||
try:
|
|
||||||
text = text.decode("utf-8")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
key = ssh_key_string_to_obj(text, password=password)
|
|
||||||
if key is None:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ssh_public_key(text):
|
|
||||||
ssh = sshpubkeys.SSHKey(text)
|
|
||||||
try:
|
|
||||||
ssh.parse()
|
|
||||||
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
|
|
||||||
return False
|
|
||||||
except NotImplementedError as e:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def setattr_bulk(seq, key, value):
|
def setattr_bulk(seq, key, value):
|
||||||
def set_attr(obj):
|
def set_attr(obj):
|
||||||
setattr(obj, key, value)
|
setattr(obj, key, value)
|
||||||
|
@ -243,70 +75,6 @@ def set_or_append_attr_bulk(seq, key, value):
|
||||||
setattr(obj, key, value)
|
setattr(obj, key, value)
|
||||||
|
|
||||||
|
|
||||||
def content_md5(data):
|
|
||||||
"""计算data的MD5值,经过Base64编码并返回str类型。
|
|
||||||
|
|
||||||
返回值可以直接作为HTTP Content-Type头部的值
|
|
||||||
"""
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = hashlib.md5(data.encode('utf-8'))
|
|
||||||
value = base64.b64encode(data.hexdigest().encode('utf-8'))
|
|
||||||
return value.decode('utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
_STRPTIME_LOCK = threading.Lock()
|
|
||||||
|
|
||||||
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
|
||||||
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
|
|
||||||
|
|
||||||
|
|
||||||
def to_unixtime(time_string, format_string):
|
|
||||||
time_string = time_string.decode("ascii")
|
|
||||||
with _STRPTIME_LOCK:
|
|
||||||
return int(calendar.timegm(time.strptime(time_string, format_string)))
|
|
||||||
|
|
||||||
|
|
||||||
def http_date(timeval=None):
|
|
||||||
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
|
|
||||||
但不能使用strftime,因为strftime的结果是和locale相关的。
|
|
||||||
"""
|
|
||||||
return formatdate(timeval, usegmt=True)
|
|
||||||
|
|
||||||
|
|
||||||
def http_to_unixtime(time_string):
|
|
||||||
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
|
||||||
|
|
||||||
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
|
||||||
"""
|
|
||||||
return to_unixtime(time_string, _GMT_FORMAT)
|
|
||||||
|
|
||||||
|
|
||||||
def iso8601_to_unixtime(time_string):
|
|
||||||
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
|
|
||||||
return to_unixtime(time_string, _ISO8601_FORMAT)
|
|
||||||
|
|
||||||
|
|
||||||
def make_signature(access_key_secret, date=None):
|
|
||||||
if isinstance(date, bytes):
|
|
||||||
date = bytes.decode(date)
|
|
||||||
if isinstance(date, int):
|
|
||||||
date_gmt = http_date(date)
|
|
||||||
elif date is None:
|
|
||||||
date_gmt = http_date(int(time.time()))
|
|
||||||
else:
|
|
||||||
date_gmt = date
|
|
||||||
|
|
||||||
data = str(access_key_secret) + "\n" + date_gmt
|
|
||||||
return content_md5(data)
|
|
||||||
|
|
||||||
|
|
||||||
def encrypt_password(password, salt=None):
|
|
||||||
from passlib.hash import sha512_crypt
|
|
||||||
if password:
|
|
||||||
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def capacity_convert(size, expect='auto', rate=1000):
|
def capacity_convert(size, expect='auto', rate=1000):
|
||||||
"""
|
"""
|
||||||
:param size: '100MB', '1G'
|
:param size: '100MB', '1G'
|
||||||
|
@ -374,11 +142,6 @@ def is_uuid(seq):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_signer():
|
|
||||||
signer = Signer(settings.SECRET_KEY)
|
|
||||||
return signer
|
|
||||||
|
|
||||||
|
|
||||||
def get_request_ip(request):
|
def get_request_ip(request):
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
|
||||||
if x_forwarded_for and x_forwarded_for[0]:
|
if x_forwarded_for and x_forwarded_for[0]:
|
||||||
|
@ -388,22 +151,13 @@ def get_request_ip(request):
|
||||||
return login_ip
|
return login_ip
|
||||||
|
|
||||||
|
|
||||||
def get_command_storage_setting():
|
def validate_ip(ip):
|
||||||
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
try:
|
||||||
value = settings.TERMINAL_COMMAND_STORAGE
|
ipaddress.ip_address(ip)
|
||||||
if not value:
|
return True
|
||||||
return default
|
except ValueError:
|
||||||
value.update(default)
|
pass
|
||||||
return value
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_replay_storage_setting():
|
|
||||||
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
|
||||||
value = settings.TERMINAL_REPLAY_STORAGE
|
|
||||||
if not value:
|
|
||||||
return default
|
|
||||||
value.update(default)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def with_cache(func):
|
def with_cache(func):
|
|
@ -0,0 +1,54 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import re
|
||||||
|
from django.shortcuts import reverse as dj_reverse
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||||
|
|
||||||
|
|
||||||
|
def reverse(view_name, urlconf=None, args=None, kwargs=None,
|
||||||
|
current_app=None, external=False):
|
||||||
|
url = dj_reverse(view_name, urlconf=urlconf, args=args,
|
||||||
|
kwargs=kwargs, current_app=current_app)
|
||||||
|
|
||||||
|
if external:
|
||||||
|
site_url = settings.SITE_URL
|
||||||
|
url = site_url.strip('/') + url
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def get_object_or_none(model, **kwargs):
|
||||||
|
try:
|
||||||
|
obj = model.objects.get(**kwargs)
|
||||||
|
except model.DoesNotExist:
|
||||||
|
return None
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def date_expired_default():
|
||||||
|
try:
|
||||||
|
years = int(settings.DEFAULT_EXPIRED_YEARS)
|
||||||
|
except TypeError:
|
||||||
|
years = 70
|
||||||
|
return timezone.now() + timezone.timedelta(days=365*years)
|
||||||
|
|
||||||
|
|
||||||
|
def get_command_storage_setting():
|
||||||
|
default = settings.DEFAULT_TERMINAL_COMMAND_STORAGE
|
||||||
|
value = settings.TERMINAL_COMMAND_STORAGE
|
||||||
|
if not value:
|
||||||
|
return default
|
||||||
|
value.update(default)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_replay_storage_setting():
|
||||||
|
default = settings.DEFAULT_TERMINAL_REPLAY_STORAGE
|
||||||
|
value = settings.TERMINAL_REPLAY_STORAGE
|
||||||
|
if not value:
|
||||||
|
return default
|
||||||
|
value.update(default)
|
||||||
|
return value
|
|
@ -0,0 +1,184 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import re
|
||||||
|
from six import string_types
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
import sshpubkeys
|
||||||
|
from itsdangerous import (
|
||||||
|
TimedJSONWebSignatureSerializer, JSONWebSignatureSerializer,
|
||||||
|
BadSignature, SignatureExpired
|
||||||
|
)
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from .http import http_date
|
||||||
|
|
||||||
|
|
||||||
|
UUID_PATTERN = re.compile(r'[0-9a-zA-Z\-]{36}')
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton(type):
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
cls.__instance = None
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls.__instance is None:
|
||||||
|
cls.__instance = super().__call__(*args, **kwargs)
|
||||||
|
return cls.__instance
|
||||||
|
else:
|
||||||
|
return cls.__instance
|
||||||
|
|
||||||
|
|
||||||
|
class Signer(metaclass=Singleton):
|
||||||
|
"""用来加密,解密,和基于时间戳的方式验证token"""
|
||||||
|
def __init__(self, secret_key=None):
|
||||||
|
self.secret_key = secret_key
|
||||||
|
|
||||||
|
def sign(self, value):
|
||||||
|
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||||
|
return s.dumps(value).decode()
|
||||||
|
|
||||||
|
def unsign(self, value):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256')
|
||||||
|
try:
|
||||||
|
return s.loads(value)
|
||||||
|
except BadSignature:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def sign_t(self, value, expires_in=3600):
|
||||||
|
s = TimedJSONWebSignatureSerializer(self.secret_key, expires_in=expires_in)
|
||||||
|
return str(s.dumps(value), encoding="utf8")
|
||||||
|
|
||||||
|
def unsign_t(self, value):
|
||||||
|
s = TimedJSONWebSignatureSerializer(self.secret_key)
|
||||||
|
try:
|
||||||
|
return s.loads(value)
|
||||||
|
except (BadSignature, SignatureExpired):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_key_string_to_obj(text, password=None):
|
||||||
|
key = None
|
||||||
|
try:
|
||||||
|
key = paramiko.RSAKey.from_private_key(StringIO(text), password=password)
|
||||||
|
except paramiko.SSHException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = paramiko.DSSKey.from_private_key(StringIO(text), password=password)
|
||||||
|
except paramiko.SSHException:
|
||||||
|
pass
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_pubkey_gen(private_key=None, username='jumpserver', hostname='localhost', password=None):
|
||||||
|
if isinstance(private_key, bytes):
|
||||||
|
private_key = private_key.decode("utf-8")
|
||||||
|
if isinstance(private_key, string_types):
|
||||||
|
private_key = ssh_key_string_to_obj(private_key, password=password)
|
||||||
|
if not isinstance(private_key, (paramiko.RSAKey, paramiko.DSSKey)):
|
||||||
|
raise IOError('Invalid private key')
|
||||||
|
|
||||||
|
public_key = "%(key_type)s %(key_content)s %(username)s@%(hostname)s" % {
|
||||||
|
'key_type': private_key.get_name(),
|
||||||
|
'key_content': private_key.get_base64(),
|
||||||
|
'username': username,
|
||||||
|
'hostname': hostname,
|
||||||
|
}
|
||||||
|
return public_key
|
||||||
|
|
||||||
|
|
||||||
|
def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', hostname=None):
|
||||||
|
"""Generate user ssh private and public key
|
||||||
|
|
||||||
|
Use paramiko RSAKey generate it.
|
||||||
|
:return private key str and public key str
|
||||||
|
"""
|
||||||
|
|
||||||
|
if hostname is None:
|
||||||
|
hostname = os.uname()[1]
|
||||||
|
|
||||||
|
f = StringIO()
|
||||||
|
try:
|
||||||
|
if type == 'rsa':
|
||||||
|
private_key_obj = paramiko.RSAKey.generate(length)
|
||||||
|
elif type == 'dsa':
|
||||||
|
private_key_obj = paramiko.DSSKey.generate(length)
|
||||||
|
else:
|
||||||
|
raise IOError('SSH private key must be `rsa` or `dsa`')
|
||||||
|
private_key_obj.write_private_key(f, password=password)
|
||||||
|
private_key = f.getvalue()
|
||||||
|
public_key = ssh_pubkey_gen(private_key_obj, username=username, hostname=hostname)
|
||||||
|
return private_key, public_key
|
||||||
|
except IOError:
|
||||||
|
raise IOError('These is error when generate ssh key.')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ssh_private_key(text, password=None):
|
||||||
|
if isinstance(text, bytes):
|
||||||
|
try:
|
||||||
|
text = text.decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
key = ssh_key_string_to_obj(text, password=password)
|
||||||
|
if key is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_ssh_public_key(text):
|
||||||
|
ssh = sshpubkeys.SSHKey(text)
|
||||||
|
try:
|
||||||
|
ssh.parse()
|
||||||
|
except (sshpubkeys.InvalidKeyException, UnicodeDecodeError):
|
||||||
|
return False
|
||||||
|
except NotImplementedError as e:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def content_md5(data):
|
||||||
|
"""计算data的MD5值,经过Base64编码并返回str类型。
|
||||||
|
|
||||||
|
返回值可以直接作为HTTP Content-Type头部的值
|
||||||
|
"""
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = hashlib.md5(data.encode('utf-8'))
|
||||||
|
value = base64.b64encode(data.hexdigest().encode('utf-8'))
|
||||||
|
return value.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def make_signature(access_key_secret, date=None):
|
||||||
|
if isinstance(date, bytes):
|
||||||
|
date = bytes.decode(date)
|
||||||
|
if isinstance(date, int):
|
||||||
|
date_gmt = http_date(date)
|
||||||
|
elif date is None:
|
||||||
|
date_gmt = http_date(int(time.time()))
|
||||||
|
else:
|
||||||
|
date_gmt = date
|
||||||
|
|
||||||
|
data = str(access_key_secret) + "\n" + date_gmt
|
||||||
|
return content_md5(data)
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_password(password, salt=None):
|
||||||
|
from passlib.hash import sha512_crypt
|
||||||
|
if password:
|
||||||
|
return sha512_crypt.using(rounds=5000).hash(password, salt=salt)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_signer():
|
||||||
|
signer = Signer(settings.SECRET_KEY)
|
||||||
|
return signer
|
|
@ -0,0 +1,37 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import time
|
||||||
|
from email.utils import formatdate
|
||||||
|
import calendar
|
||||||
|
import threading
|
||||||
|
|
||||||
|
_STRPTIME_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
_GMT_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
|
||||||
|
_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
|
||||||
|
|
||||||
|
|
||||||
|
def to_unixtime(time_string, format_string):
|
||||||
|
time_string = time_string.decode("ascii")
|
||||||
|
with _STRPTIME_LOCK:
|
||||||
|
return int(calendar.timegm(time.strptime(time_string, format_string)))
|
||||||
|
|
||||||
|
|
||||||
|
def http_date(timeval=None):
|
||||||
|
"""返回符合HTTP标准的GMT时间字符串,用strftime的格式表示就是"%a, %d %b %Y %H:%M:%S GMT"。
|
||||||
|
但不能使用strftime,因为strftime的结果是和locale相关的。
|
||||||
|
"""
|
||||||
|
return formatdate(timeval, usegmt=True)
|
||||||
|
|
||||||
|
|
||||||
|
def http_to_unixtime(time_string):
|
||||||
|
"""把HTTP Date格式的字符串转换为UNIX时间(自1970年1月1日UTC零点的秒数)。
|
||||||
|
|
||||||
|
HTTP Date形如 `Sat, 05 Dec 2015 11:10:29 GMT` 。
|
||||||
|
"""
|
||||||
|
return to_unixtime(time_string, _GMT_FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
def iso8601_to_unixtime(time_string):
|
||||||
|
"""把ISO8601时间字符串(形如,2012-02-24T06:07:48.000Z)转换为UNIX时间,精确到秒。"""
|
||||||
|
return to_unixtime(time_string, _ISO8601_FORMAT)
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
from .ipdb import *
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ipdb
|
||||||
|
|
||||||
|
ipip_db = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip_city(ip):
|
||||||
|
global ipip_db
|
||||||
|
if ipip_db is None:
|
||||||
|
ipip_db_path = os.path.join(os.path.dirname(__file__), 'ipipfree.ipdb')
|
||||||
|
ipip_db = ipdb.City(ipip_db_path)
|
||||||
|
info = list(set(ipip_db.find(ip, 'CN')))
|
||||||
|
if '' in info:
|
||||||
|
info.remove('')
|
||||||
|
return ' '.join(info)
|
Binary file not shown.
|
@ -14,8 +14,8 @@ from rest_framework.views import APIView
|
||||||
from common.utils import get_logger, get_request_ip
|
from common.utils import get_logger, get_request_ip
|
||||||
from common.permissions import IsOrgAdminOrAppUser
|
from common.permissions import IsOrgAdminOrAppUser
|
||||||
from orgs.mixins import RootOrgViewMixin
|
from orgs.mixins import RootOrgViewMixin
|
||||||
|
from authentication.signals import post_auth_success, post_auth_failed
|
||||||
from ..serializers import UserSerializer
|
from ..serializers import UserSerializer
|
||||||
from ..tasks import write_login_log_async
|
|
||||||
from ..models import User, LoginLog
|
from ..models import User, LoginLog
|
||||||
from ..utils import check_user_valid, check_otp_code, \
|
from ..utils import check_user_valid, check_otp_code, \
|
||||||
increase_login_failed_count, is_block_login, \
|
increase_login_failed_count, is_block_login, \
|
||||||
|
@ -46,37 +46,22 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
||||||
username = request.data.get('username', '')
|
username = request.data.get('username', '')
|
||||||
exist = User.objects.filter(username=username).first()
|
exist = User.objects.filter(username=username).first()
|
||||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
||||||
data = {
|
self.send_auth_signal(success=False, username=username, reason=reason)
|
||||||
'username': username,
|
|
||||||
'mfa': LoginLog.MFA_UNKNOWN,
|
|
||||||
'reason': reason,
|
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(request, data)
|
|
||||||
increase_login_failed_count(username, ip)
|
increase_login_failed_count(username, ip)
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
if user.password_has_expired:
|
if user.password_has_expired:
|
||||||
data = {
|
self.send_auth_signal(
|
||||||
'username': user.username,
|
success=False, username=username,
|
||||||
'mfa': int(user.otp_enabled),
|
reason=LoginLog.REASON_PASSWORD_EXPIRED
|
||||||
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
|
)
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(request, data)
|
|
||||||
msg = _("The user {} password has expired, please update.".format(
|
msg = _("The user {} password has expired, please update.".format(
|
||||||
user.username))
|
user.username))
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
if not user.otp_enabled:
|
if not user.otp_enabled:
|
||||||
data = {
|
self.send_auth_signal(success=True, user=user)
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_NOTHING,
|
|
||||||
'status': True
|
|
||||||
}
|
|
||||||
self.write_login_log(request, data)
|
|
||||||
# 登陆成功,清除原来的缓存计数
|
# 登陆成功,清除原来的缓存计数
|
||||||
clean_failed_count(username, ip)
|
clean_failed_count(username, ip)
|
||||||
token = user.create_bearer_token(request)
|
token = user.create_bearer_token(request)
|
||||||
|
@ -108,22 +93,14 @@ class UserAuthApi(RootOrgViewMixin, APIView):
|
||||||
)
|
)
|
||||||
return user, msg
|
return user, msg
|
||||||
|
|
||||||
@staticmethod
|
def send_auth_signal(self, success=True, user=None, username='', reason=''):
|
||||||
def write_login_log(request, data):
|
if success:
|
||||||
login_ip = request.data.get('remote_addr', None)
|
post_auth_success.send(sender=self.__class__, user=user, request=self.request)
|
||||||
login_type = request.data.get('login_type', '')
|
else:
|
||||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
post_auth_failed.send(
|
||||||
|
sender=self.__class__, username=username,
|
||||||
if not login_ip:
|
request=self.request, reason=reason
|
||||||
login_ip = get_request_ip(request)
|
)
|
||||||
|
|
||||||
tmp_data = {
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': login_type,
|
|
||||||
'user_agent': user_agent,
|
|
||||||
}
|
|
||||||
data.update(tmp_data)
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
||||||
|
|
||||||
class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
class UserConnectionTokenApi(RootOrgViewMixin, APIView):
|
||||||
|
@ -197,52 +174,25 @@ class UserOtpAuthApi(RootOrgViewMixin, APIView):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
otp_code = request.data.get('otp_code', '')
|
otp_code = request.data.get('otp_code', '')
|
||||||
seed = request.data.get('seed', '')
|
seed = request.data.get('seed', '')
|
||||||
|
|
||||||
user = cache.get(seed, None)
|
user = cache.get(seed, None)
|
||||||
if not user:
|
if not user:
|
||||||
return Response(
|
return Response(
|
||||||
{'msg': _('Please verify the user name and password first')},
|
{'msg': _('Please verify the user name and password first')},
|
||||||
status=401
|
status=401
|
||||||
)
|
)
|
||||||
|
|
||||||
if not check_otp_code(user.otp_secret_key, otp_code):
|
if not check_otp_code(user.otp_secret_key, otp_code):
|
||||||
data = {
|
self.send_auth_signal(success=False, username=user.username, reason=LoginLog.REASON_MFA)
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_MFA,
|
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(request, data)
|
|
||||||
return Response({'msg': _('MFA certification failed')}, status=401)
|
return Response({'msg': _('MFA certification failed')}, status=401)
|
||||||
|
self.send_auth_signal(success=True, user=user)
|
||||||
data = {
|
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_NOTHING,
|
|
||||||
'status': True
|
|
||||||
}
|
|
||||||
self.write_login_log(request, data)
|
|
||||||
token = user.create_bearer_token(request)
|
token = user.create_bearer_token(request)
|
||||||
return Response(
|
data = {'token': token, 'user': self.serializer_class(user).data}
|
||||||
{
|
return Response(data)
|
||||||
'token': token,
|
|
||||||
'user': self.serializer_class(user).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
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def write_login_log(request, data):
|
|
||||||
login_ip = request.data.get('remote_addr', None)
|
|
||||||
login_type = request.data.get('login_type', '')
|
|
||||||
user_agent = request.data.get('HTTP_USER_AGENT', '')
|
|
||||||
|
|
||||||
if not login_ip:
|
|
||||||
login_ip = get_request_ip(request)
|
|
||||||
|
|
||||||
tmp_data = {
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': login_type,
|
|
||||||
'user_agent': user_agent
|
|
||||||
}
|
|
||||||
data.update(tmp_data)
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from .user import User
|
from .user import User
|
||||||
|
@ -82,7 +83,7 @@ class LoginLog(models.Model):
|
||||||
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
mfa = models.SmallIntegerField(default=MFA_UNKNOWN, choices=MFA_CHOICE, verbose_name=_('MFA'))
|
||||||
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
reason = models.SmallIntegerField(default=REASON_NOTHING, choices=REASON_CHOICE, verbose_name=_('Reason'))
|
||||||
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
status = models.BooleanField(max_length=2, default=True, choices=STATUS_CHOICE, verbose_name=_('Status'))
|
||||||
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date login'))
|
datetime = models.DateTimeField(default=timezone.now, verbose_name=_('Date login'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-datetime', 'username']
|
ordering = ['-datetime', 'username']
|
||||||
|
|
|
@ -2,4 +2,3 @@ from django.dispatch import Signal
|
||||||
|
|
||||||
|
|
||||||
post_user_create = Signal(providing_args=('user',))
|
post_user_create = Signal(providing_args=('user',))
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,12 @@ from ops.celery.utils import create_or_update_celery_periodic_tasks
|
||||||
from ops.celery.decorator import after_app_ready_start
|
from ops.celery.decorator import after_app_ready_start
|
||||||
from .models import User
|
from .models import User
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from .utils import write_login_log, send_password_expiration_reminder_mail
|
from .utils import send_password_expiration_reminder_mail
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
|
||||||
def write_login_log_async(*args, **kwargs):
|
|
||||||
write_login_log(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def check_password_expired():
|
def check_password_expired():
|
||||||
users = User.objects.exclude(role=User.ROLE_APP)
|
users = User.objects.exclude(role=User.ROLE_APP)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import pyotp
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -18,7 +17,7 @@ from django.core.cache import cache
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from common.tasks import send_mail_async
|
from common.tasks import send_mail_async
|
||||||
from common.utils import reverse, get_object_or_none
|
from common.utils import reverse, get_object_or_none, get_ip_city
|
||||||
from .models import User, LoginLog
|
from .models import User, LoginLog
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,51 +198,6 @@ def check_user_valid(**kwargs):
|
||||||
return None, _('Password or SSH public key invalid')
|
return None, _('Password or SSH public key invalid')
|
||||||
|
|
||||||
|
|
||||||
def validate_ip(ip):
|
|
||||||
try:
|
|
||||||
ipaddress.ip_address(ip)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def write_login_log(*args, **kwargs):
|
|
||||||
ip = kwargs.get('ip', '')
|
|
||||||
if not (ip and validate_ip(ip)):
|
|
||||||
ip = ip[:15]
|
|
||||||
city = "Unknown"
|
|
||||||
else:
|
|
||||||
city = get_ip_city(ip)
|
|
||||||
kwargs.update({'ip': ip, 'city': city})
|
|
||||||
LoginLog.objects.create(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def get_ip_city(ip, timeout=10):
|
|
||||||
# Taobao ip api: http://ip.taobao.com/service/getIpInfo.php?ip=8.8.8.8
|
|
||||||
# Sina ip api: http://int.dpool.sina.com.cn/iplookup/iplookup.php?ip=8.8.8.8&format=json
|
|
||||||
|
|
||||||
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=%s' % ip
|
|
||||||
try:
|
|
||||||
r = requests.get(url, timeout=timeout)
|
|
||||||
except:
|
|
||||||
r = None
|
|
||||||
city = 'Unknown'
|
|
||||||
if r and r.status_code == 200:
|
|
||||||
try:
|
|
||||||
data = r.json()
|
|
||||||
if not isinstance(data, int) and data['code'] == 0:
|
|
||||||
country = data['data']['country']
|
|
||||||
_city = data['data']['city']
|
|
||||||
if country == 'XX':
|
|
||||||
city = _city
|
|
||||||
else:
|
|
||||||
city = ' '.join([country, _city])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return city
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_or_tmp_user(request):
|
def get_user_or_tmp_user(request):
|
||||||
user = request.user
|
user = request.user
|
||||||
tmp_user = get_tmp_user_from_cache(request)
|
tmp_user = get_tmp_user_from_cache(request)
|
||||||
|
|
|
@ -1,244 +1,35 @@
|
||||||
# ~*~ coding: utf-8 ~*~
|
# ~*~ coding: utf-8 ~*~
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import os
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.contrib.auth import login as auth_login, logout as auth_logout
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic import ListView
|
from django.views.generic import RedirectView
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import reverse, redirect
|
from django.shortcuts import reverse, redirect
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
from django.views.decorators.csrf import csrf_protect
|
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.views.generic.edit import FormView
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from formtools.wizard.views import SessionWizardView
|
from formtools.wizard.views import SessionWizardView
|
||||||
|
|
||||||
from common.utils import get_object_or_none, get_request_ip
|
from common.utils import get_object_or_none
|
||||||
from ..models import User, LoginLog
|
from ..models import User
|
||||||
from ..utils import send_reset_password_mail, check_otp_code, \
|
from ..utils import (
|
||||||
redirect_user_first_login_or_index, get_user_or_tmp_user, \
|
send_reset_password_mail, get_password_check_rules, check_password_rules
|
||||||
set_tmp_user_to_cache, get_password_check_rules, check_password_rules, \
|
)
|
||||||
is_block_login, increase_login_failed_count, clean_failed_count
|
|
||||||
from ..tasks import write_login_log_async
|
|
||||||
from .. import forms
|
from .. import forms
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'UserLoginView', 'UserLoginOtpView', 'UserLogoutView',
|
'UserLoginView', 'UserForgotPasswordSendmailSuccessView',
|
||||||
'UserForgotPasswordView', 'UserForgotPasswordSendmailSuccessView',
|
'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView',
|
||||||
'UserResetPasswordView', 'UserResetPasswordSuccessView',
|
'UserResetPasswordView', 'UserForgotPasswordView',
|
||||||
'UserFirstLoginView', 'LoginLogListView'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(sensitive_post_parameters(), name='dispatch')
|
class UserLoginView(RedirectView):
|
||||||
@method_decorator(csrf_protect, name='dispatch')
|
urls = reverse_lazy('authentication:login')
|
||||||
@method_decorator(never_cache, name='dispatch')
|
|
||||||
class UserLoginView(FormView):
|
|
||||||
form_class = forms.UserLoginForm
|
|
||||||
form_class_captcha = forms.UserLoginCaptchaForm
|
|
||||||
redirect_field_name = 'next'
|
|
||||||
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
|
||||||
|
|
||||||
def get_template_names(self):
|
|
||||||
template_name = 'users/login.html'
|
|
||||||
if not settings.XPACK_ENABLED:
|
|
||||||
return template_name
|
|
||||||
|
|
||||||
from xpack.plugins.license.models import License
|
|
||||||
if not License.has_valid_license():
|
|
||||||
return template_name
|
|
||||||
|
|
||||||
template_name = 'users/new_login.html'
|
|
||||||
return template_name
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
if request.user.is_staff:
|
|
||||||
return redirect(redirect_user_first_login_or_index(
|
|
||||||
request, self.redirect_field_name)
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
data = {
|
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
|
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
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)
|
|
||||||
return redirect(self.get_success_url())
|
|
||||||
|
|
||||||
def form_invalid(self, form):
|
|
||||||
# write login failed log
|
|
||||||
username = form.cleaned_data.get('username')
|
|
||||||
exist = User.objects.filter(username=username).first()
|
|
||||||
reason = LoginLog.REASON_PASSWORD if exist else LoginLog.REASON_NOT_EXIST
|
|
||||||
data = {
|
|
||||||
'username': username,
|
|
||||||
'mfa': LoginLog.MFA_UNKNOWN,
|
|
||||||
'reason': reason,
|
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
|
|
||||||
# limit user login failed count
|
|
||||||
ip = get_request_ip(self.request)
|
|
||||||
increase_login_failed_count(username, ip)
|
|
||||||
|
|
||||||
# show captcha
|
|
||||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
|
||||||
old_form = form
|
|
||||||
form = self.form_class_captcha(data=form.data)
|
|
||||||
form._errors = old_form.errors
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
def get_form_class(self):
|
|
||||||
ip = get_request_ip(self.request)
|
|
||||||
if cache.get(self.key_prefix_captcha.format(ip)):
|
|
||||||
return self.form_class_captcha
|
|
||||||
else:
|
|
||||||
return self.form_class
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
user = get_user_or_tmp_user(self.request)
|
|
||||||
|
|
||||||
if user.otp_enabled and user.otp_secret_key:
|
|
||||||
# 1,2,mfa_setting & T
|
|
||||||
return reverse('users:login-otp')
|
|
||||||
elif user.otp_enabled and not user.otp_secret_key:
|
|
||||||
# 1,2,mfa_setting & F
|
|
||||||
return reverse('users:user-otp-enable-authentication')
|
|
||||||
elif not user.otp_enabled:
|
|
||||||
# 0 & T,F
|
|
||||||
auth_login(self.request, user)
|
|
||||||
data = {
|
|
||||||
'username': self.request.user.username,
|
|
||||||
'mfa': int(self.request.user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_NOTHING,
|
|
||||||
'status': True
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = {
|
|
||||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
|
||||||
'AUTH_OPENID': settings.AUTH_OPENID,
|
|
||||||
}
|
|
||||||
kwargs.update(context)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
def write_login_log(self, data):
|
|
||||||
login_ip = get_request_ip(self.request)
|
|
||||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
tmp_data = {
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': 'W',
|
|
||||||
'user_agent': user_agent
|
|
||||||
}
|
|
||||||
data.update(tmp_data)
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoginOtpView(FormView):
|
|
||||||
template_name = 'users/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):
|
|
||||||
auth_login(self.request, user)
|
|
||||||
data = {
|
|
||||||
'username': self.request.user.username,
|
|
||||||
'mfa': int(self.request.user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_NOTHING,
|
|
||||||
'status': True
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
return redirect(self.get_success_url())
|
|
||||||
else:
|
|
||||||
data = {
|
|
||||||
'username': user.username,
|
|
||||||
'mfa': int(user.otp_enabled),
|
|
||||||
'reason': LoginLog.REASON_MFA,
|
|
||||||
'status': False
|
|
||||||
}
|
|
||||||
self.write_login_log(data)
|
|
||||||
form.add_error('otp_code', _('MFA code invalid, or ntp sync server time'))
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return redirect_user_first_login_or_index(self.request, self.redirect_field_name)
|
|
||||||
|
|
||||||
def write_login_log(self, data):
|
|
||||||
login_ip = get_request_ip(self.request)
|
|
||||||
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
|
|
||||||
tmp_data = {
|
|
||||||
'ip': login_ip,
|
|
||||||
'type': 'W',
|
|
||||||
'user_agent': user_agent
|
|
||||||
}
|
|
||||||
data.update(tmp_data)
|
|
||||||
write_login_log_async.delay(**data)
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(never_cache, name='dispatch')
|
|
||||||
class UserLogoutView(TemplateView):
|
|
||||||
template_name = 'flash_message_standalone.html'
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
auth_logout(request)
|
|
||||||
next_uri = request.COOKIES.get("next")
|
|
||||||
if next_uri:
|
|
||||||
return redirect(next_uri)
|
|
||||||
response = super().get(request, *args, **kwargs)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = {
|
|
||||||
'title': _('Logout success'),
|
|
||||||
'messages': _('Logout success, return login page'),
|
|
||||||
'interval': 1,
|
|
||||||
'redirect_url': reverse('users:login'),
|
|
||||||
'auto_redirect': True,
|
|
||||||
}
|
|
||||||
kwargs.update(context)
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class UserForgotPasswordView(TemplateView):
|
class UserForgotPasswordView(TemplateView):
|
||||||
|
@ -386,8 +177,3 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
|
||||||
form.fields["otp_level"].initial = self.request.user.otp_level
|
form.fields["otp_level"].initial = self.request.user.otp_level
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
class LoginLogListView(ListView):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
return redirect(reverse('audits:login-log-list'))
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ decorator==4.1.2
|
||||||
Django==2.1.7
|
Django==2.1.7
|
||||||
django-auth-ldap==1.7.0
|
django-auth-ldap==1.7.0
|
||||||
django-bootstrap3==9.1.0
|
django-bootstrap3==9.1.0
|
||||||
django-celery-beat==1.1.1
|
django-celery-beat==1.4.0
|
||||||
django-filter==2.0.0
|
django-filter==2.0.0
|
||||||
django-formtools==2.1
|
django-formtools==2.1
|
||||||
django-ranged-response==0.2.0
|
django-ranged-response==0.2.0
|
||||||
|
@ -79,3 +79,4 @@ rest_condition==1.0.3
|
||||||
python-ldap==3.1.0
|
python-ldap==3.1.0
|
||||||
tencentcloud-sdk-python==3.0.40
|
tencentcloud-sdk-python==3.0.40
|
||||||
django-radius==1.3.3
|
django-radius==1.3.3
|
||||||
|
ipip-ipdb==1.2.1
|
||||||
|
|
Loading…
Reference in New Issue