perf: 优化登录,cas, openid 自动登录

pull/6101/head
ibuler 2021-04-29 17:15:46 +08:00 committed by 老广
parent 7294f6e5e0
commit 4d6d4cbc22
14 changed files with 217 additions and 153 deletions

View File

@ -7,13 +7,14 @@ import time
from django.conf import settings
from django.contrib import auth
from django.utils.translation import ugettext as _
from django.contrib.auth import (
BACKEND_SESSION_KEY, _get_backends,
PermissionDenied, user_login_failed, _clean_credentials
)
from django.shortcuts import reverse
from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get
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
@ -204,41 +205,40 @@ class AuthMixin:
return user
@classmethod
def generate_reset_password_url_with_flash_msg(cls, user: User, flash_view_name):
def generate_reset_password_url_with_flash_msg(cls, user, message):
reset_passwd_url = reverse('authentication:reset-password')
query_str = urlencode({
'token': user.generate_reset_token()
})
reset_passwd_url = f'{reset_passwd_url}?{query_str}'
flash_page_url = reverse(flash_view_name)
query_str = urlencode({
'redirect_url': reset_passwd_url
})
return f'{flash_page_url}?{query_str}'
message_data = {
'title': _('Please change your password'),
'message': message,
'interval': 3,
'redirect_url': reset_passwd_url,
}
return FlashMessageUtil.gen_message_url(message_data)
@classmethod
def _check_passwd_is_too_simple(cls, user: User, password):
if user.is_superuser and password == 'admin':
url = cls.generate_reset_password_url_with_flash_msg(
user, 'authentication:passwd-too-simple-flash-msg'
)
message = _('Your password is too simple, please change it for security')
url = cls.generate_reset_password_url_with_flash_msg(user, message=message)
raise errors.PasswdTooSimple(url)
@classmethod
def _check_passwd_need_update(cls, user: User):
if user.need_update_password:
url = cls.generate_reset_password_url_with_flash_msg(
user, 'authentication:passwd-need-update-flash-msg'
)
message = _('You should to change your password before login')
url = cls.generate_reset_password_url_with_flash_msg(user, message)
raise errors.PasswdNeedUpdate(url)
@classmethod
def _check_password_require_reset_or_not(cls, user: User):
if user.password_has_expired:
url = cls.generate_reset_password_url_with_flash_msg(
user, 'authentication:passwd-has-expired-flash-msg'
)
message = _('Your password has expired, please reset before logging in')
url = cls.generate_reset_password_url_with_flash_msg(user, message)
raise errors.PasswordRequireResetError(url)
def check_user_auth_if_need(self, decrypt_passwd=False):

View File

@ -18,13 +18,7 @@ urlpatterns = [
# 原来在users中的
path('password/forgot/', users_view.UserForgotPasswordView.as_view(), name='forgot-password'),
path('password/forgot/sendmail-success/', users_view.UserForgotPasswordSendmailSuccessView.as_view(),
name='forgot-password-sendmail-success'),
path('password/reset/', users_view.UserResetPasswordView.as_view(), name='reset-password'),
path('password/too-simple-flash-msg/', views.FlashPasswdTooSimpleMsgView.as_view(), name='passwd-too-simple-flash-msg'),
path('password/need-update-flash-msg/', views.FlashPasswdNeedUpdateMsgView.as_view(), name='passwd-need-update-flash-msg'),
path('password/has-expired-msg/', views.FlashPasswdHasExpiredMsgView.as_view(), name='passwd-has-expired-flash-msg'),
path('password/reset/success/', users_view.UserResetPasswordSuccessView.as_view(), name='reset-password-success'),
path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'),
# Profile

View File

@ -19,7 +19,7 @@ from django.conf import settings
from django.urls import reverse_lazy
from django.contrib.auth import BACKEND_SESSION_KEY
from common.utils import get_request_ip, get_object_or_none
from common.utils import get_request_ip, FlashMessageUtil
from users.utils import (
redirect_user_first_login_or_index
)
@ -31,8 +31,6 @@ from ..forms import get_user_login_form_cls
__all__ = [
'UserLoginView', 'UserLogoutView',
'UserLoginGuardView', 'UserLoginWaitConfirmView',
'FlashPasswdTooSimpleMsgView', 'FlashPasswdHasExpiredMsgView',
'FlashPasswdNeedUpdateMsgView'
]
@ -44,12 +42,42 @@ class UserLoginView(mixins.AuthMixin, FormView):
redirect_field_name = 'next'
template_name = 'authentication/login.html'
def redirect_third_party_auth_if_need(self, request):
# show jumpserver login page if request http://{JUMP-SERVER}/?admin=1
if self.request.GET.get("admin", 0):
return None
auth_type = ''
auth_url = ''
if settings.AUTH_OPENID:
auth_type = 'OIDC'
auth_url = reverse(settings.AUTH_OPENID_AUTH_LOGIN_URL_NAME)
elif settings.AUTH_CAS:
auth_type = 'CAS'
auth_url = reverse(settings.CAS_LOGIN_URL_NAME)
if not auth_url:
return None
message_data = {
'title': _('Redirecting'),
'message': _("Redirecting to {} authentication").format(auth_type),
'redirect_url': auth_url,
'has_cancel': True,
'cancel_url': reverse('authentication:login') + '?admin=1'
}
redirect_url = FlashMessageUtil.gen_message_url(message_data)
query_string = request.GET.urlencode()
redirect_url = "{}&{}".format(redirect_url, query_string)
return redirect_url
def get(self, request, *args, **kwargs):
if request.user.is_staff:
first_login_url = redirect_user_first_login_or_index(
request, self.redirect_field_name
)
return redirect(first_login_url)
redirect_url = self.redirect_third_party_auth_if_need(request)
if redirect_url:
return redirect(redirect_url)
request.session.set_test_cookie()
return super().get(request, *args, **kwargs)
@ -225,50 +253,3 @@ class UserLogoutView(TemplateView):
}
kwargs.update(context)
return super().get_context_data(**kwargs)
@method_decorator(never_cache, name='dispatch')
class FlashPasswdTooSimpleMsgView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
context = {
'title': _('Please change your password'),
'messages': _('Your password is too simple, please change it for security'),
'interval': 3,
'redirect_url': request.GET.get('redirect_url'),
'auto_redirect': True,
}
return self.render_to_response(context)
@method_decorator(never_cache, name='dispatch')
class FlashPasswdNeedUpdateMsgView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
context = {
'title': _('Please change your password'),
'messages': _('You should to change your password before login'),
'interval': 3,
'redirect_url': request.GET.get('redirect_url'),
'auto_redirect': True,
'confirm_button': _('Confirm')
}
return self.render_to_response(context)
@method_decorator(never_cache, name='dispatch')
class FlashPasswdHasExpiredMsgView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
context = {
'title': _('Please change your password'),
'messages': _('Your password has expired, please reset before logging in'),
'interval': 3,
'redirect_url': request.GET.get('redirect_url'),
'auto_redirect': True,
'confirm_button': _('Confirm')
}
return self.render_to_response(context)

View File

@ -5,7 +5,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin
from django.utils import timezone
__all__ = ["DatetimeSearchMixin"]
__all__ = ["DatetimeSearchMixin", "PermissionsMixin"]
from rest_framework import permissions
@ -51,4 +51,4 @@ class PermissionsMixin(UserPassesTestMixin):
for permission_class in permission_classes:
if not permission_class().has_permission(self.request, self):
return False
return True
return True

View File

@ -0,0 +1,10 @@
from django.urls import path
from .. import views
app_name = 'common'
urlpatterns = [
# login
path('flash-message/', views.FlashMessageMsgView.as_view(), name='flash-message'),
]

View File

@ -8,3 +8,4 @@ from .http import *
from .ipip import *
from .crypto import *
from .random import *
from .jumpserver import *

View File

@ -0,0 +1,31 @@
from django.core.cache import cache
from django.shortcuts import reverse
from .random import random_string
__all__ = ['FlashMessageUtil']
class FlashMessageUtil:
@staticmethod
def get_key(code):
key = 'MESSAGE_{}'.format(code)
return key
@classmethod
def get_message_code(cls, message_data):
code = random_string(12)
key = cls.get_key(code)
cache.set(key, message_data, 60)
return code
@classmethod
def get_message_by_code(cls, code):
key = cls.get_key(code)
return cache.get(key)
@classmethod
def gen_message_url(cls, message_data):
code = cls.get_message_code(message_data)
return reverse('common:flash-message') + f'?code={code}'

40
apps/common/views.py Normal file
View File

@ -0,0 +1,40 @@
#
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.http import HttpResponse
from django.views.generic.base import TemplateView
from common.utils import bulk_get, FlashMessageUtil
@method_decorator(never_cache, name='dispatch')
class FlashMessageMsgView(TemplateView):
template_name = 'flash_message_standalone.html'
def get(self, request, *args, **kwargs):
code = request.GET.get('code')
if not code:
return HttpResponse('Not found the code')
message_data = FlashMessageUtil.get_message_by_code(code)
if not message_data:
return HttpResponse('Message code error')
title, message, redirect_url, confirm_button, cancel_url = bulk_get(
message_data, 'title', 'message', 'redirect_url', 'confirm_button', 'cancel_url'
)
interval = message_data.get('interval', 3)
auto_redirect = message_data.get('auto_redirect', True)
has_cancel = message_data.get('has_cancel', False)
context = {
'title': title,
'messages': message,
'interval': interval,
'redirect_url': redirect_url,
'auto_redirect': auto_redirect,
'confirm_button': confirm_button,
'has_cancel': has_cancel,
'cancel_url': cancel_url,
}
return self.render_to_response(context)

View File

@ -29,6 +29,7 @@ api_v1 = [
app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('ops/', include('ops.urls.view_urls'), name='ops'),
path('common/', include('common.urls.view_urls'), name='common'),
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
]

View File

@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
#
import re
import time
from django.http import HttpResponseRedirect, JsonResponse, Http404
from django.conf import settings
from django.views.generic import View
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from rest_framework.views import APIView
from common.http import HttpResponseTemporaryRedirect
@ -85,3 +83,4 @@ class KokoView(View):
"<div>Koko is a separately deployed program, you need to deploy Koko, configure nginx for url distribution,</div> "
"</div>If you see this page, prove that you are not accessing the nginx listening port. Good luck.</div>")
return HttpResponse(msg)

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-04-29 12:17+0800\n"
"POT-Creation-Date: 2021-04-29 16:20+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -354,7 +354,9 @@ msgid "You can't delete the root node ({})"
msgstr "不能删除根节点 ({})"
#: assets/api/node.py:75
msgid "Deletion failed and the node contains children or assets"
#, fuzzy
#| msgid "Deletion failed and the node contains children or assets"
msgid "Deletion failed and the node contains assets"
msgstr "删除失败,节点包含子节点或资产"
#: assets/backends/db.py:244
@ -650,31 +652,31 @@ msgstr "资产组"
msgid "Default asset group"
msgstr "默认资产组"
#: assets/models/label.py:19 assets/models/node.py:547 settings/models.py:30
#: assets/models/label.py:19 assets/models/node.py:546 settings/models.py:30
msgid "Value"
msgstr "值"
#: assets/models/node.py:152
#: assets/models/node.py:151
msgid "New node"
msgstr "新节点"
#: assets/models/node.py:475 users/templates/users/_granted_assets.html:130
#: assets/models/node.py:474 users/templates/users/_granted_assets.html:130
msgid "empty"
msgstr "空"
#: assets/models/node.py:546 perms/models/asset_permission.py:176
#: assets/models/node.py:545 perms/models/asset_permission.py:176
msgid "Key"
msgstr "键"
#: assets/models/node.py:548
#: assets/models/node.py:547
msgid "Full value"
msgstr "全称"
#: assets/models/node.py:551 perms/models/asset_permission.py:177
#: assets/models/node.py:550 perms/models/asset_permission.py:177
msgid "Parent key"
msgstr "ssh私钥"
#: assets/models/node.py:560 assets/serializers/system_user.py:191
#: assets/models/node.py:559 assets/serializers/system_user.py:191
#: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73
#: users/templates/users/user_asset_permission.html:158
@ -1360,15 +1362,15 @@ msgstr "来源 IP 不被允许登录"
msgid "SSO auth closed"
msgstr "SSO 认证关闭了"
#: authentication/errors.py:271 authentication/views/login.py:237
#: authentication/errors.py:271 authentication/views/login.py:267
msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改"
#: authentication/errors.py:280 authentication/views/login.py:252
#: authentication/errors.py:280 authentication/views/login.py:282
msgid "You should to change your password before login"
msgstr "登录完成前,请先修改密码"
#: authentication/errors.py:289 authentication/views/login.py:268
#: authentication/errors.py:289 authentication/views/login.py:298
msgid "Your password has expired, please reset before logging in"
msgstr "您的密码已过期,先修改再登录"
@ -1453,8 +1455,9 @@ msgid "Need MFA for view auth"
msgstr "需要多因子认证来查看账号信息"
#: authentication/templates/authentication/_mfa_confirm_modal.html:20
#: authentication/views/login.py:256 authentication/views/login.py:272
#: templates/_modal.html:23 users/templates/users/user_password_verify.html:20
#: authentication/views/login.py:286 authentication/views/login.py:302
#: authentication/views/login.py:318 templates/_modal.html:23
#: users/templates/users/user_password_verify.html:20
msgid "Confirm"
msgstr "确认"
@ -1519,7 +1522,6 @@ msgid "Copy link"
msgstr "复制链接"
#: authentication/templates/authentication/login_wait_confirm.html:51
#: templates/flash_message_standalone.html:38
msgid "Return"
msgstr "返回"
@ -1527,11 +1529,19 @@ msgstr "返回"
msgid "Copy success"
msgstr "复制成功"
#: authentication/views/login.py:58
#: authentication/views/login.py:63 authentication/views/login.py:313
msgid "Redirecting"
msgstr "跳转中"
#: authentication/views/login.py:64
msgid "Redirecting to {} authentication"
msgstr "正在跳转到 {} 认证"
#: authentication/views/login.py:88
msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:183
#: authentication/views/login.py:213
msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page"
@ -1539,23 +1549,27 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面"
#: authentication/views/login.py:188
#: authentication/views/login.py:218
msgid "No ticket found"
msgstr "没有发现工单"
#: authentication/views/login.py:220
#: authentication/views/login.py:250
msgid "Logout success"
msgstr "退出登录成功"
#: authentication/views/login.py:221
#: authentication/views/login.py:251
msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面"
#: authentication/views/login.py:236 authentication/views/login.py:251
#: authentication/views/login.py:267
#: authentication/views/login.py:266 authentication/views/login.py:281
#: authentication/views/login.py:297
msgid "Please change your password"
msgstr "请修改密码"
#: authentication/views/login.py:314
msgid "Redirect to third party auth"
msgstr ""
#: common/const/__init__.py:6
#, python-format
msgid "%(name)s was created successfully"
@ -1671,7 +1685,7 @@ msgstr "JumpServer 开源堡垒机"
msgid "<h1>Flow service unavailable, check it</h1>"
msgstr ""
#: jumpserver/views/other.py:27
#: jumpserver/views/other.py:25
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, koko, "
"configure nginx for url distribution,</div> </div>If you see this page, "
@ -1680,11 +1694,11 @@ msgstr ""
"<div>Luna是单独部署的一个程序你需要部署lunakoko, </div><div>如果你看到了"
"这个页面证明你访问的不是nginx监听的端口祝你好运</div>"
#: jumpserver/views/other.py:71
#: jumpserver/views/other.py:69
msgid "Websocket server run on port: {}, you should proxy it on nginx"
msgstr "Websocket 服务运行在端口: {}, 请检查nginx是否代理是否设置"
#: jumpserver/views/other.py:85
#: jumpserver/views/other.py:83
msgid ""
"<div>Koko is a separately deployed program, you need to deploy Koko, "
"configure nginx for url distribution,</div> </div>If you see this page, "
@ -2760,6 +2774,14 @@ msgstr "确认删除"
msgid "Are you sure delete"
msgstr "您确定删除吗?"
#: templates/flash_message_standalone.html:28
msgid "Cancel"
msgstr "取消"
#: templates/flash_message_standalone.html:37
msgid "Go"
msgstr "跳转"
#: templates/index.html:11
msgid "Total users"
msgstr "用户总数"
@ -4978,9 +5000,6 @@ msgstr "社区版"
#~ msgid "This will reset the user password and send a reset mail"
#~ msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#~ msgid "Cancel"
#~ msgstr "取消"
#~ msgid ""
#~ "The reset-ssh-public-key E-mail has been sent successfully. Please inform "
#~ "the user to update his new ssh public key."

View File

@ -4,14 +4,6 @@
{% block html_title %} {{ title }} {% endblock %}
{% block title %} {{ title }}{% endblock %}
{% block custom_head_css_js %}
<style>
.passwordBox {
max-width: 660px;
}
</style>
{% endblock %}
{% block content %}
<div>
{% if errors %}
@ -30,12 +22,19 @@
</p>
{% endif %}
<div class="row">
<div class="col-lg-3">
{% if has_cancel %}
<div class="col-lg-2">
<a href="{{ cancel_url }}" class="btn btn-default block full-width m-b">
{% trans 'Cancel' %}
</a>
</div>
{% endif %}
<div class="col-lg-2">
<a href="{{ redirect_url }}" class="btn btn-primary block full-width m-b">
{% if confirm_button %}
{{ confirm_button }}
{% else %}
{% trans 'Return' %}
{% trans 'Go' %}
{% endif %}
</a>
</div>

View File

@ -1,18 +1,15 @@
# ~*~ coding: utf-8 ~*~
from __future__ import unicode_literals
from django.shortcuts import render
from django.views.generic import RedirectView
from django.core.files.storage import default_storage
from django.shortcuts import reverse, redirect
from django.utils.translation import ugettext as _
from django.views.generic.base import TemplateView
from django.conf import settings
from django.urls import reverse_lazy
from formtools.wizard.views import SessionWizardView
from django.views.generic import FormView
from common.utils import get_object_or_none
from common.utils import get_object_or_none, FlashMessageUtil
from common.permissions import IsValidUser
from common.mixins.views import PermissionsMixin
from ...models import User
@ -24,9 +21,7 @@ from ... import forms
__all__ = [
'UserLoginView', 'UserForgotPasswordSendmailSuccessView',
'UserResetPasswordSuccessView', 'UserResetPasswordSuccessView',
'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView',
'UserLoginView', 'UserResetPasswordView', 'UserForgotPasswordView', 'UserFirstLoginView',
]
@ -39,6 +34,17 @@ class UserForgotPasswordView(FormView):
template_name = 'users/forgot_password.html'
form_class = forms.UserForgotPasswordForm
@staticmethod
def get_redirect_message_url():
message_data = {
'title': _('Send reset password message'),
'message': _('Send reset password mail success, '
'login your mail box and follow it '),
'redirect_url': reverse('authentication:login'),
}
url = FlashMessageUtil.gen_message_url(message_data)
return url
def form_valid(self, form):
email = form.cleaned_data['email']
user = get_object_or_none(User, email=email)
@ -53,37 +59,9 @@ class UserForgotPasswordView(FormView):
).format(user.get_source_display())
form.add_error('email', error)
return self.form_invalid(form)
send_reset_password_mail(user)
return redirect('authentication:forgot-password-sendmail-success')
class UserForgotPasswordSendmailSuccessView(TemplateView):
template_name = 'flash_message_standalone.html'
def get_context_data(self, **kwargs):
context = {
'title': _('Send reset password message'),
'messages': _('Send reset password mail success, '
'login your mail box and follow it '),
'redirect_url': reverse('authentication:login'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserResetPasswordSuccessView(TemplateView):
template_name = 'flash_message_standalone.html'
def get_context_data(self, **kwargs):
context = {
'title': _('Reset password success'),
'messages': _('Reset password success, return to login page'),
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
url = self.get_redirect_message_url()
return redirect(url)
class UserResetPasswordView(FormView):
@ -139,7 +117,18 @@ class UserResetPasswordView(FormView):
user.reset_password(password)
User.expired_reset_password_token(token)
send_reset_password_success_mail(self.request, user)
return redirect('authentication:reset-password-success')
url = self.get_redirect_url()
return redirect(url)
@staticmethod
def get_redirect_url():
message_data = {
'title': _('Reset password success'),
'message': _('Reset password success, return to login page'),
'redirect_url': reverse('authentication:login'),
'auto_redirect': True,
}
return FlashMessageUtil.gen_message_url(message_data)
class UserFirstLoginView(PermissionsMixin, TemplateView):