diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index 30e5d63cd..715be0aae 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -14,14 +14,15 @@ from django.contrib.auth import ( PermissionDenied, user_login_failed, _clean_credentials ) from django.shortcuts import reverse, redirect +from django.views.generic.edit import FormView from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get, FlashMessageUtil from users.models import User from users.utils import LoginBlockUtil, MFABlockUtils from . import errors -from .utils import rsa_decrypt +from .utils import rsa_decrypt, gen_key_pair from .signals import post_auth_success, post_auth_failed -from .const import RSA_PRIVATE_KEY +from .const import RSA_PRIVATE_KEY, RSA_PUBLIC_KEY logger = get_logger(__name__) @@ -79,7 +80,70 @@ def authenticate(request=None, **credentials): auth.authenticate = authenticate -class AuthMixin: +class PasswordEncryptionViewMixin: + request = None + + def get_decrypted_password(self, password=None, username=None): + request = self.request + if hasattr(request, 'data'): + data = request.data + else: + data = request.POST + + username = username or data.get('username') + password = password or data.get('password') + + password = self.decrypt_passwd(password) + if not password: + self.raise_password_decrypt_failed(username=username) + return password + + def raise_password_decrypt_failed(self, username): + ip = self.get_request_ip() + raise errors.CredentialError( + error=errors.reason_password_decrypt_failed, + username=username, ip=ip, request=self.request + ) + + def decrypt_passwd(self, raw_passwd): + # 获取解密密钥,对密码进行解密 + rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) + if rsa_private_key is not None: + try: + return rsa_decrypt(raw_passwd, rsa_private_key) + except Exception as e: + logger.error(e, exc_info=True) + logger.error( + f'Decrypt password failed: password[{raw_passwd}] ' + f'rsa_private_key[{rsa_private_key}]' + ) + return None + return raw_passwd + + def get_request_ip(self): + ip = '' + if hasattr(self.request, 'data'): + ip = self.request.data.get('remote_addr', '') + ip = ip or get_request_ip(self.request) + return ip + + def get_context_data(self, **kwargs): + # 生成加解密密钥对,public_key传递给前端,private_key存入session中供解密使用 + rsa_public_key = self.request.session.get(RSA_PUBLIC_KEY) + rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) + if not all((rsa_private_key, rsa_public_key)): + rsa_private_key, rsa_public_key = gen_key_pair() + rsa_public_key = rsa_public_key.replace('\n', '\\n') + self.request.session[RSA_PRIVATE_KEY] = rsa_private_key + self.request.session[RSA_PUBLIC_KEY] = rsa_public_key + + kwargs.update({ + 'rsa_public_key': rsa_public_key, + }) + return super().get_context_data(**kwargs) + + +class AuthMixin(PasswordEncryptionViewMixin): request = None partial_credential_error = None @@ -106,13 +170,6 @@ class AuthMixin: user.backend = self.request.session.get("auth_backend") return user - def get_request_ip(self): - ip = '' - if hasattr(self.request, 'data'): - ip = self.request.data.get('remote_addr', '') - ip = ip or get_request_ip(self.request) - return ip - def _check_is_block(self, username, raise_exception=True): ip = self.get_request_ip() if LoginBlockUtil(username, ip).is_block(): @@ -130,19 +187,6 @@ class AuthMixin: username = self.request.POST.get("username") self._check_is_block(username, raise_exception) - def decrypt_passwd(self, raw_passwd): - # 获取解密密钥,对密码进行解密 - rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) - if rsa_private_key is not None: - try: - return rsa_decrypt(raw_passwd, rsa_private_key) - except Exception as e: - logger.error(e, exc_info=True) - logger.error(f'Decrypt password failed: password[{raw_passwd}] ' - f'rsa_private_key[{rsa_private_key}]') - return None - return raw_passwd - def raise_credential_error(self, error): raise self.partial_credential_error(error=error) @@ -158,14 +202,12 @@ class AuthMixin: items = ['username', 'password', 'challenge', 'public_key', 'auto_login'] username, password, challenge, public_key, auto_login = bulk_get(data, *items, default='') - password = password + challenge.strip() ip = self.get_request_ip() self._set_partial_credential_error(username=username, ip=ip, request=request) + password = password + challenge.strip() if decrypt_passwd: - password = self.decrypt_passwd(password) - if not password: - self.raise_credential_error(errors.reason_password_decrypt_failed) + password = self.get_decrypted_password() return username, password, public_key, ip, auto_login def _check_only_allow_exists_user_auth(self, username): diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index 3fc62e08d..cf5474d55 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -137,15 +137,6 @@ class UserLoginView(mixins.AuthMixin, FormView): self.request.session[RSA_PUBLIC_KEY] = None def get_context_data(self, **kwargs): - # 生成加解密密钥对,public_key传递给前端,private_key存入session中供解密使用 - rsa_private_key = self.request.session.get(RSA_PRIVATE_KEY) - rsa_public_key = self.request.session.get(RSA_PUBLIC_KEY) - if not all((rsa_private_key, rsa_public_key)): - rsa_private_key, rsa_public_key = utils.gen_key_pair() - rsa_public_key = rsa_public_key.replace('\n', '\\n') - self.request.session[RSA_PRIVATE_KEY] = rsa_private_key - self.request.session[RSA_PUBLIC_KEY] = rsa_public_key - forgot_password_url = reverse('authentication:forgot-password') has_other_auth_backend = settings.AUTHENTICATION_BACKENDS[0] != settings.AUTH_BACKEND_MODEL if has_other_auth_backend and settings.FORGOT_PASSWORD_URL: @@ -158,7 +149,6 @@ class UserLoginView(mixins.AuthMixin, FormView): 'AUTH_WECOM': settings.AUTH_WECOM, 'AUTH_DINGTALK': settings.AUTH_DINGTALK, 'AUTH_FEISHU': settings.AUTH_FEISHU, - 'rsa_public_key': rsa_public_key, 'forgot_password_url': forgot_password_url } kwargs.update(context) diff --git a/apps/users/forms/profile.py b/apps/users/forms/profile.py index 63c1078d6..d2b005e12 100644 --- a/apps/users/forms/profile.py +++ b/apps/users/forms/profile.py @@ -19,7 +19,7 @@ __all__ = [ class UserCheckPasswordForm(forms.Form): password = forms.CharField( label=_('Password'), widget=forms.PasswordInput, - max_length=128, strip=False + max_length=1024, strip=False ) diff --git a/apps/users/templates/users/user_otp_check_password.html b/apps/users/templates/users/user_otp_check_password.html index f83e03b78..2f04c4b33 100644 --- a/apps/users/templates/users/user_otp_check_password.html +++ b/apps/users/templates/users/user_otp_check_password.html @@ -7,14 +7,34 @@ {% endblock %} {% block content %} -
+ + {% endblock %} diff --git a/apps/users/views/profile/password.py b/apps/users/views/profile/password.py index e2dde8e92..8c92487c1 100644 --- a/apps/users/views/profile/password.py +++ b/apps/users/views/profile/password.py @@ -6,6 +6,8 @@ from django.contrib.auth import authenticate from django.shortcuts import redirect from django.utils.translation import ugettext as _ from django.views.generic.edit import FormView +from authentication.mixins import PasswordEncryptionViewMixin +from authentication import errors from common.utils import get_logger from ... import forms @@ -18,13 +20,17 @@ __all__ = ['UserVerifyPasswordView'] logger = get_logger(__name__) -class UserVerifyPasswordView(FormView): +class UserVerifyPasswordView(PasswordEncryptionViewMixin, FormView): template_name = 'users/user_password_verify.html' form_class = forms.UserCheckPasswordForm def form_valid(self, form): user = get_user_or_pre_auth_user(self.request) - password = form.cleaned_data.get('password') + try: + password = self.get_decrypted_password(username=user.username) + except errors.AuthFailedError as e: + form.add_error("password", _(f"Password invalid") + f'({e.msg})') + return self.form_invalid(form) user = authenticate(request=self.request, username=user.username, password=password) if not user: form.add_error("password", _("Password invalid"))