diff --git a/apps/users/views/profile/otp.py b/apps/users/views/profile/otp.py index 83918114e..e5724d506 100644 --- a/apps/users/views/profile/otp.py +++ b/apps/users/views/profile/otp.py @@ -1,4 +1,5 @@ # ~*~ coding: utf-8 ~*~ +import time from django.urls import reverse_lazy, reverse from django.utils.translation import ugettext as _ @@ -6,8 +7,12 @@ 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.conf import settings +from django.shortcuts import redirect +from authentication.mixins import AuthMixin +from users.models import User from common.utils import get_logger +from common.utils import get_object_or_none from common.permissions import IsValidUser from ... import forms from .password import UserVerifyPasswordView @@ -46,11 +51,59 @@ class UserOtpEnableInstallAppView(TemplateView): return super().get_context_data(**kwargs) -class UserOtpEnableBindView(TemplateView, FormView): +class UserOtpEnableBindView(AuthMixin, TemplateView, FormView): template_name = 'users/user_otp_enable_bind.html' form_class = forms.UserCheckOtpCodeForm success_url = reverse_lazy('users:user-otp-settings-success') + def get(self, request, *args, **kwargs): + return self._check_can_bind() or super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + return self._check_can_bind() or super().post(request, *args, **kwargs) + + def _check_can_bind(self): + """ + :return: + - `None` 表示验证成功 + - `Response` 验证失败,调用函数需直接返回该 `Response` + """ + + request = self.request + request_user = request.user + session_user = None + + if not self.request.session.is_empty(): + user_id = self.request.session.get('user_id') + session_user = get_object_or_none(User, pk=user_id) + + auth_password = request.session.get('auth_password') + if request_user.is_authenticated: + # 用户已登录,在 `mfa_enabled` 启用,而且 `otp_secret_key` 不为空的情况,跳转到 + # otp 认证界面 + if request_user.mfa_enabled and request_user.otp_secret_key: + logger.warn(f'OPT_BIND-> authenticated ' + f'request_user.username={request_user.username}, ' + f'request_user.mfa_enabled={request_user.mfa_enabled}, ' + f'request_user.otp_secret_key={request_user.otp_secret_key}') + return redirect(reverse('authentication:user-otp-update')) + return None + elif session_user: + # 未登录,但是验证过了密码,如果是 `reset` 流程,需要 `mfa_enabled` 启用,`otp_secret_key` 为空 + if not all((auth_password, session_user.mfa_enabled, not session_user.otp_secret_key)): + logger.warn(f'OPT_BIND-> auth_password ' + f'session_user.username={session_user.username}, ' + f'auth_password={auth_password}, ' + f'session_user.mfa_enabled={session_user.mfa_enabled}, ' + f'session_user.otp_secret_key={session_user.otp_secret_key}') + return redirect(reverse('authentication:login')) + return None + else: + # 未登录,没有验证过密码,直接跳转到登录界面 + logger.warn(f'OPT_BIND-> anonymous ' + f'REMOTE_ADDR={request.META.get("HTTP_X_FORWARDED_HOST") or request.META.get("REMOTE_ADDR")}') + return redirect(reverse('authentication:login')) + def form_valid(self, form): otp_code = form.cleaned_data.get('otp_code') otp_secret_key = self.request.session.get('otp_secret_key', '') @@ -116,6 +169,8 @@ class UserOtpUpdateView(FormView): valid = user.check_mfa(otp_code) if valid: + user.otp_secret_key = '' + user.save() return super().form_valid(form) else: error = _('MFA code invalid, or ntp sync server time')