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