From 7f0ad7e27f17d526f977f0785bfd3b1d8da98ce1 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Nov 2021 13:20:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20oidc=20cas=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=97=B6=E8=B7=B3=E8=BD=AC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit perf: 优化一波,容易debug perf: 还原回来的世界 --- apps/authentication/middleware.py | 29 ++++++++++++--- apps/authentication/mixins.py | 9 +++-- apps/authentication/views/mfa.py | 17 ++++++--- apps/authentication/views/utils.py | 4 +-- apps/static/css/otp.css | 4 +++ .../users/user_otp_check_password.html | 36 +++---------------- .../templates/users/user_otp_enable_bind.html | 8 +++-- apps/users/views/profile/otp.py | 12 +++++-- 8 files changed, 65 insertions(+), 54 deletions(-) diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index ff050f815..46aa46ded 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -1,17 +1,36 @@ -from django.shortcuts import redirect +from django.shortcuts import redirect, reverse +from django.http import HttpResponse class MFAMiddleware: + """ + 这个 中间件 是用来全局拦截开启了 MFA 却没有认证的,如 OIDC, CAS,使用第三方库做的登录,直接 login 了, + 所以只能在 Middleware 中控制 + """ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) + # 没有校验 + if not request.session.get('auth_mfa_required'): + return response + # 没有认证过,证明不是从 第三方 来的 + if request.user.is_anonymous: + return response - white_urls = ['login/mfa', 'mfa/select', 'jsi18n/', '/static/'] + # 这个是 mfa 登录页需要的请求, 也得放出来, 用户其实已经在 CAS/OIDC 中完成登录了 + white_urls = [ + 'login/mfa', 'mfa/select', 'jsi18n/', '/static/', '/profile/otp', + '/logout/', '/login/' + ] for url in white_urls: if request.path.find(url) > -1: return response - if request.session.get('auth_mfa_required'): - return redirect('authentication:login-mfa') - return response + + # 因为使用 CAS/OIDC 登录的,不小心去了别的页面就回不来了 + if request.path.find('users/profile') > -1: + return HttpResponse('', status=401) + + url = reverse('authentication:login-mfa') + '?_=middleware' + return redirect(url) diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 88437dd16..2167ab198 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -257,7 +257,8 @@ class MFAMixin: def _check_login_page_mfa_if_need(self, user): if not settings.SECURITY_MFA_IN_LOGIN_PAGE: return - self._check_if_no_active_mfa(user) + if not user.active_mfa_backends: + return request = self.request data = request.data if hasattr(request, 'data') else request.POST @@ -274,10 +275,8 @@ class MFAMixin: if not user.mfa_enabled: return - self._check_if_no_active_mfa(user) - - active_mfa_mapper = user.active_mfa_backends_mapper - raise errors.MFARequiredError(mfa_types=tuple(active_mfa_mapper.keys())) + active_mfa_names = user.active_mfa_backends_mapper.keys() + raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names)) def mark_mfa_ok(self, mfa_type): self.request.session['auth_mfa'] = 1 diff --git a/apps/authentication/views/mfa.py b/apps/authentication/views/mfa.py index ec51ed63c..fd8b80e32 100644 --- a/apps/authentication/views/mfa.py +++ b/apps/authentication/views/mfa.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.views.generic.edit import FormView +from django.shortcuts import redirect from common.utils import get_logger from .. import forms, errors, mixins @@ -19,9 +20,15 @@ class UserLoginMFAView(mixins.AuthMixin, FormView): def get(self, *args, **kwargs): try: - self.get_user_from_session() + user = self.get_user_from_session() except errors.SessionEmptyError: - return redirect_to_guard_view() + return redirect_to_guard_view('session_empty') + + try: + self._check_if_no_active_mfa(user) + except errors.MFAUnsetError as e: + return redirect(e.url + '?_=login_mfa') + return super().get(*args, **kwargs) def form_valid(self, form): @@ -30,17 +37,17 @@ class UserLoginMFAView(mixins.AuthMixin, FormView): try: self._do_check_user_mfa(code, mfa_type) - return redirect_to_guard_view() + return redirect_to_guard_view('mfa_ok') except (errors.MFAFailedError, errors.BlockMFAError) as e: form.add_error('code', e.msg) return super().form_invalid(form) except errors.SessionEmptyError: - return redirect_to_guard_view() + return redirect_to_guard_view('session_empty') except Exception as e: logger.error(e) import traceback traceback.print_exc() - return redirect_to_guard_view() + return redirect_to_guard_view('unexpect') def get_context_data(self, **kwargs): user = self.get_user_from_session() diff --git a/apps/authentication/views/utils.py b/apps/authentication/views/utils.py index 182d7390b..63a1d76c6 100644 --- a/apps/authentication/views/utils.py +++ b/apps/authentication/views/utils.py @@ -3,6 +3,6 @@ from django.shortcuts import reverse, redirect -def redirect_to_guard_view(): - continue_url = reverse('authentication:login-guard') +def redirect_to_guard_view(comment=''): + continue_url = reverse('authentication:login-guard') + '?_=' + comment return redirect(continue_url) diff --git a/apps/static/css/otp.css b/apps/static/css/otp.css index 4c9ed2606..f78916ac1 100644 --- a/apps/static/css/otp.css +++ b/apps/static/css/otp.css @@ -136,6 +136,10 @@ article ul li:last-child{ border-radius: 6px; color: white; } + +.next:hover { + color: white; +} /*绑定TOTP*/ /*版权信息*/ diff --git a/apps/users/templates/users/user_otp_check_password.html b/apps/users/templates/users/user_otp_check_password.html index edb9b2519..501d6eb77 100644 --- a/apps/users/templates/users/user_otp_check_password.html +++ b/apps/users/templates/users/user_otp_check_password.html @@ -7,36 +7,8 @@ {% endblock %} {% block content %} -
{% trans 'Please enter the password of' %} {% trans 'account' %} {{ user.username }} {% trans 'to complete the binding operation' %}
-
-
- {% csrf_token %} -
- - -
- - {% if 'password' in form.errors %} -

{{ form.password.errors.as_text }}

- {% endif %} -
- - +
+ {% endblock %} diff --git a/apps/users/templates/users/user_otp_enable_bind.html b/apps/users/templates/users/user_otp_enable_bind.html index e43f079da..11e4e01b9 100644 --- a/apps/users/templates/users/user_otp_enable_bind.html +++ b/apps/users/templates/users/user_otp_enable_bind.html @@ -16,12 +16,12 @@
Secret: {{ otp_secret_key }}
-
+ {% csrf_token %}
- + {% if 'otp_code' in form.errors %}

{{ form.otp_code.errors.as_text }}

{% endif %} @@ -33,6 +33,10 @@ $('.change-color li:eq(1) i').css('color', '#1ab394'); $('.change-color li:eq(2) i').css('color', '#1ab394'); + function submitForm() { + $('#bind-form').submit() + } + $(document).ready(function() { // 生成用户绑定otp的二维码 var qrcode = new QRCode(document.getElementById('qr_code'), { diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index eb75d335f..b26af285a 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -6,10 +6,12 @@ from django.utils.translation import ugettext as _ from django.views.generic.base import TemplateView from django.views.generic.edit import FormView from django.contrib.auth import logout as auth_logout +from django.shortcuts import redirect from django.http.response import HttpResponseRedirect from authentication.mixins import AuthMixin from authentication.mfa import MFAOtp, otp_failed_msg +from authentication.errors import SessionEmptyError from common.utils import get_logger, FlashMessageUtil from common.mixins.views import PermissionsMixin from common.permissions import IsValidUser @@ -30,11 +32,15 @@ __all__ = [ logger = get_logger(__name__) -class UserOtpEnableStartView(UserVerifyPasswordView): +class UserOtpEnableStartView(AuthMixin, TemplateView): template_name = 'users/user_otp_check_password.html' - def get_success_url(self): - return reverse('authentication:user-otp-enable-install-app') + def get(self, request, *args, **kwargs): + try: + self.get_user_from_session() + except SessionEmptyError: + return redirect('authentication:login') + '?_=otp_enable_start' + return super().get(request, *args, **kwargs) class UserOtpEnableInstallAppView(TemplateView):