You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jumpserver/apps/authentication/middleware.py

170 lines
6.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import base64
from django.conf import settings
from django.contrib.auth import logout as auth_logout
from django.core.cache import cache
from django.http import HttpResponse
from django.shortcuts import redirect, reverse, render
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import gettext as _
from apps.authentication import mixins
from audits.signal_handlers import send_login_info_to_reviewers
from authentication.signals import post_auth_failed
from common.utils import gen_key_pair
from common.utils import get_request_ip
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
# 这个是 mfa 登录页需要的请求, 也得放出来, 用户其实已经在 CAS/OIDC 中完成登录了
white_urls = [
'login/mfa', 'mfa/select', 'mfa/face','jsi18n/', '/static/',
'/profile/otp', '/logout/',
]
for url in white_urls:
if request.path.find(url) > -1:
return response
# 因为使用 CAS/OIDC 登录的,不小心去了别的页面就回不来了
if request.path.find('users/profile') > -1:
return HttpResponse('', status=401)
url = reverse('authentication:login-mfa') + '?_=middleware'
return redirect(url)
class ThirdPartyLoginMiddleware(mixins.AuthMixin):
"""OpenID、CAS、SAML2登录规则设置验证"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# 没有认证过,证明不是从 第三方 来的
if request.user.is_anonymous:
return response
if not request.session.get('auth_third_party_required'):
return response
white_urls = [
'jsi18n/', '/static/',
'login/guard', 'login/wait-confirm',
'login-confirm-ticket/status',
'settings/public/open',
'core/auth/login', 'core/auth/logout'
]
for url in white_urls:
if request.path.find(url) > -1:
return response
ip = get_request_ip(request)
try:
self.request = request
self._check_third_party_login_acl()
self._check_login_acl(request.user, ip)
except Exception as e:
if getattr(request, 'user_need_delete', False):
request.user.delete()
else:
error_message = getattr(e, 'msg', None)
error_message = error_message or str(e)
post_auth_failed.send(
sender=self.__class__, username=request.user.username,
request=self.request, reason=error_message
)
auth_logout(request)
context = {
'title': _('Authentication failed'),
'message': _('Authentication failed (before login check failed): {}').format(e),
'interval': 10,
'redirect_url': reverse('authentication:login') + '?admin=1',
'auto_redirect': True,
}
response = render(request, 'authentication/auth_fail_flash_message_standalone.html', context)
else:
if not self.request.session.get('auth_confirm_required'):
return response
guard_url = reverse('authentication:login-guard')
args = request.META.get('QUERY_STRING', '')
if args:
guard_url = "%s?%s" % (guard_url, args)
response = redirect(guard_url)
finally:
if request.session.get('can_send_notifications') and \
self.request.session.get('auth_notice_required'):
request.session['can_send_notifications'] = False
user_log_id = self.request.session.get('user_log_id')
auth_acl_id = self.request.session.get('auth_acl_id')
send_login_info_to_reviewers(user_log_id, auth_acl_id)
return response
class SessionCookieMiddleware(MiddlewareMixin):
USER_LOGIN_ENCRYPTION_KEY_PAIR = 'user_login_encryption_key_pair'
def set_cookie_public_key(self, request, response):
if request.path.startswith('/api'):
return
session_public_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
session_private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
session_public_key = request.session.get(session_public_key_name)
cookie_public_key = request.COOKIES.get(session_public_key_name)
if session_public_key and session_public_key == cookie_public_key:
return
private_key, public_key = self.get_key_pair()
public_key_decode = base64.b64encode(public_key.encode()).decode()
request.session[session_public_key_name] = public_key_decode
request.session[session_private_key_name] = private_key
response.set_cookie(session_public_key_name, public_key_decode)
def get_key_pair(self):
key_pair = cache.get(self.USER_LOGIN_ENCRYPTION_KEY_PAIR)
if key_pair:
return key_pair['private_key'], key_pair['public_key']
private_key, public_key = gen_key_pair()
key_pair = {
'private_key': private_key,
'public_key': public_key
}
cache.set(self.USER_LOGIN_ENCRYPTION_KEY_PAIR, key_pair, None)
return private_key, public_key
@staticmethod
def set_cookie_session_prefix(request, response):
key = settings.SESSION_COOKIE_NAME_PREFIX_KEY
value = settings.SESSION_COOKIE_NAME_PREFIX
if request.COOKIES.get(key) == value:
return response
response.set_cookie(key, value)
def process_response(self, request, response: HttpResponse):
self.set_cookie_session_prefix(request, response)
self.set_cookie_public_key(request, response)
return response