diff --git a/apps/authentication/models.py b/apps/authentication/models.py index e68fb8298..21fb2aafd 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,10 +1,12 @@ import uuid from django.db import models +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from django.conf import settings from common.mixins.models import CommonModelMixin +from common.utils import get_object_or_none, get_request_ip, get_ip_city class AccessKey(models.Model): @@ -42,3 +44,32 @@ class LoginConfirmSetting(CommonModelMixin): reviewers = models.ManyToManyField('users.User', verbose_name=_("Reviewers"), related_name=_("review_login_confirmation_settings")) is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + @classmethod + def get_user_confirm_setting(cls, user): + return get_object_or_none(cls, user=user) + + def create_confirm_order(self, request=None): + from orders.models import Order + title = _('User login request confirm: {}'.format(self.user)) + if request: + remote_addr = get_request_ip(request) + city = get_ip_city(remote_addr) + body = _("User: {}\nIP: {}\nCity: {}\nDate: {}\n").format( + self.user, remote_addr, city, timezone.now() + ) + else: + body = '' + reviewer = self.reviewers.all() + reviewer_names = ','.join([u.name for u in reviewer]) + order = Order.objects.create( + user=self.user, user_display=str(self.user), + title=title, body=body, + assignees_display=reviewer_names, + type=Order.TYPE_LOGIN_REQUEST, + ) + order.assignees.set(reviewer) + return order + + def __str__(self): + return '{} confirm'.format(self.user.username) + diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html new file mode 100644 index 000000000..54526427e --- /dev/null +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -0,0 +1,88 @@ +{% load i18n %} +{% load static %} + + + + + + + + {{ title }} + + {% include '_head_css_js.html' %} + + + + + + + +
+
+
+
+
+ +

+ {{ JMS_TITLE }} +

+
+

+
+ Wait for Guanghongwei confirm, You also can copy link to her/his
+ Don't close .... +
+
+
+
+
+ +
+
+
+
+
+
+ {% include '_copyright.html' %} +
+
+ 2014-2019 +
+
+
+ + + diff --git a/apps/authentication/templates/authentication/new_login.html b/apps/authentication/templates/authentication/xpack_login.html similarity index 100% rename from apps/authentication/templates/authentication/new_login.html rename to apps/authentication/templates/authentication/xpack_login.html diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 8602daca5..0981f1b09 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -16,5 +16,7 @@ urlpatterns = [ # login path('login/', views.UserLoginView.as_view(), name='login'), path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'), + path('login/continue/', views.UserLoginContinueView.as_view(), name='login-continue'), + path('login/wait/', views.UserLoginWaitConfirmView.as_view(), name='login-wait'), path('logout/', views.UserLogoutView.as_view(), name='logout'), ] diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 2a7098ae7..afff9dd45 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -12,13 +12,12 @@ 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, View, RedirectView from django.views.generic.edit import FormView from django.conf import settings from common.utils import get_request_ip from users.models import User -from audits.models import UserLoginLog as LoginLog 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, @@ -31,6 +30,7 @@ from .. import const __all__ = [ 'UserLoginView', 'UserLoginOtpView', 'UserLogoutView', + 'UserLoginContinueView', 'UserLoginWaitConfirmView', ] @@ -40,7 +40,6 @@ __all__ = [ 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): @@ -52,7 +51,7 @@ class UserLoginView(FormView): if not License.has_valid_license(): return template_name - template_name = 'authentication/new_login.html' + template_name = 'authentication/xpack_login.html' return template_name def get(self, request, *args, **kwargs): @@ -91,7 +90,8 @@ class UserLoginView(FormView): ip = get_request_ip(self.request) # 登陆成功,清除缓存计数 clean_failed_count(username, ip) - return redirect(self.get_success_url()) + self.request.session['auth_password'] = '1' + return self.redirect_to_continue_view() def form_invalid(self, form): # write login failed log @@ -111,6 +111,11 @@ class UserLoginView(FormView): form._errors = old_form.errors return super().form_invalid(form) + @staticmethod + def redirect_to_continue_view(): + continue_url = reverse('authentication:login-continue') + return redirect(continue_url) + def get_form_class(self): ip = get_request_ip(self.request) if cache.get(self.key_prefix_captcha.format(ip)): @@ -118,21 +123,6 @@ class UserLoginView(FormView): 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('authentication: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"), @@ -141,15 +131,6 @@ class UserLoginView(FormView): 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 = 'authentication/login_otp.html' @@ -162,9 +143,8 @@ class UserLoginOtpView(FormView): 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()) + self.request.session['auth_otp'] = '1' + return UserLoginView.redirect_to_continue_view() else: self.send_auth_signal( success=False, username=user.username, @@ -175,8 +155,40 @@ class UserLoginOtpView(FormView): ) 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 + ) + + +class UserLoginContinueView(RedirectView): + redirect_field_name = 'next' + + 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) + if user.otp_enabled and user.otp_secret_key and \ + not self.request.session.get('auth_otp'): + return reverse('authentication:login-otp') + + self.login_success(user) + if user.otp_enabled and not user.otp_secret_key: + # 1,2,mfa_setting & F + return reverse('users:user-otp-enable-authentication') + url = redirect_user_first_login_or_index( + self.request, self.redirect_field_name + ) + return url + + def login_success(self, user): + auth_login(self.request, user) + self.send_auth_signal(success=True, user=user) def send_auth_signal(self, success=True, user=None, username='', reason=''): if success: @@ -188,6 +200,13 @@ class UserLoginOtpView(FormView): ) +class UserLoginWaitConfirmView(TemplateView): + template_name = 'authentication/login_wait_confirm.html' + + def get_context_data(self, **kwargs): + return super().get_context_data(**kwargs) + + @method_decorator(never_cache, name='dispatch') class UserLogoutView(TemplateView): template_name = 'flash_message_standalone.html' diff --git a/apps/orders/models.py b/apps/orders/models.py index dd2e1624d..b2614bd17 100644 --- a/apps/orders/models.py +++ b/apps/orders/models.py @@ -10,8 +10,9 @@ class Order(CommonModelMixin): ('rejected', _("Rejected")), ('pending', _("Pending")) ) + TYPE_LOGIN_REQUEST = 'login_request' TYPE_CHOICES = ( - ('login_request', _("Login request")), + (TYPE_LOGIN_REQUEST, _("Login request")), ) user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name=_("User")) user_display = models.CharField(max_length=128, verbose_name=_("User display name")) @@ -22,7 +23,10 @@ class Order(CommonModelMixin): assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True) type = models.CharField(choices=TYPE_CHOICES, max_length=64) - status = models.CharField(choices=STATUS_CHOICES, max_length=16) + status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='pending') + + def __str__(self): + return '{}: {}'.format(self.user_display, self.title) class Meta: ordering = ('date_created',) diff --git a/apps/users/models/user.py b/apps/users/models/user.py index f58422fcd..4c7c54def 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -10,7 +10,9 @@ from django.conf import settings from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser from django.core.cache import cache +from django.core.exceptions import ObjectDoesNotExist from django.db import models + from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse