diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index ab07533d0..278a99d87 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -5,6 +5,7 @@ from django.db.models import Count from common.mixins.serializers import BulkSerializerMixin from common.utils import ssh_pubkey_gen from common.drf.fields import EncryptedField +from common.drf.serializers import SecretReadableMixin from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from orgs.mixins.serializers import BulkOrgResourceModelSerializer from ..models import SystemUser, Asset @@ -252,7 +253,7 @@ class MiniSystemUserSerializer(serializers.ModelSerializer): fields = SystemUserSerializer.Meta.fields_mini -class SystemUserWithAuthInfoSerializer(SystemUserSerializer): +class SystemUserWithAuthInfoSerializer(SecretReadableMixin, SystemUserSerializer): class Meta(SystemUserSerializer.Meta): fields_mini = ['id', 'name', 'username'] fields_write_only = ['password', 'public_key', 'private_key'] @@ -268,6 +269,9 @@ class SystemUserWithAuthInfoSerializer(SystemUserSerializer): 'assets_amount': {'label': _('Asset')}, 'login_mode_display': {'label': _('Login mode display')}, 'created_by': {'read_only': True}, + 'password': {'write_only': False}, + 'private_key': {'write_only': False}, + 'token': {'write_only': False} } diff --git a/apps/authentication/backends/oidc/backends.py b/apps/authentication/backends/oidc/backends.py index 8d4f2f629..daa614ec8 100644 --- a/apps/authentication/backends/oidc/backends.py +++ b/apps/authentication/backends/oidc/backends.py @@ -281,6 +281,11 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend): try: claims_response.raise_for_status() claims = claims_response.json() + preferred_username = claims.get('preferred_username') + if preferred_username and \ + preferred_username.lower() == username.lower() and \ + preferred_username != username: + return except Exception as e: error = "Json claims response error, claims response " \ "content is: {}, error is: {}".format(claims_response.content, str(e)) @@ -309,5 +314,3 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend): openid_user_login_failed.send( sender=self.__class__, request=request, username=username, reason="User is invalid" ) - return None - diff --git a/apps/authentication/middleware.py b/apps/authentication/middleware.py index d2b4ff19e..f1f60bbc5 100644 --- a/apps/authentication/middleware.py +++ b/apps/authentication/middleware.py @@ -45,21 +45,7 @@ class MFAMiddleware: class SessionCookieMiddleware(MiddlewareMixin): @staticmethod - def process_response(request, response: HttpResponse): - 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) - return response - - -class EncryptedMiddleware: - def __init__(self, get_response): - self.get_response = get_response - - @staticmethod - def check_key_pair(request, response): + def set_cookie_public_key(request, response): pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME public_key = request.session.get(pub_key_name) cookie_key = request.COOKIES.get(pub_key_name) @@ -73,7 +59,29 @@ class EncryptedMiddleware: request.session[pri_key_name] = private_key response.set_cookie(pub_key_name, public_key_decode) - def __call__(self, request): - response = self.get_response(request) - self.check_key_pair(request, response) + @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) + + @staticmethod + def set_cookie_session_expire(request, response): + if not request.session.get('auth_session_expiration_required'): + return + value = 'age' + if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE or \ + not request.session.get('auto_login', False): + value = 'close' + + age = request.session.get_expiry_age() + response.set_cookie('jms_session_expire', value, max_age=age) + request.session.pop('auth_session_expiration_required', None) + + def process_response(self, request, response: HttpResponse): + self.set_cookie_session_prefix(request, response) + self.set_cookie_public_key(request, response) + self.set_cookie_session_expire(request, response) return response diff --git a/apps/authentication/signal_handlers.py b/apps/authentication/signal_handlers.py index 0d2a617f9..ac155dcf0 100644 --- a/apps/authentication/signal_handlers.py +++ b/apps/authentication/signal_handlers.py @@ -35,6 +35,9 @@ def on_user_auth_login_success(sender, user, request, **kwargs): session.delete() cache.set(lock_key, request.session.session_key, None) + # 标记登录,设置 cookie,前端可以控制刷新, Middleware 会拦截这个生成 cookie + request.session['auth_session_expiration_required'] = 1 + @receiver(openid_user_login_success) def on_oidc_user_login_success(sender, request, user, create=False, **kwargs): diff --git a/apps/common/utils/crypto.py b/apps/common/utils/crypto.py index 4fd9360f6..f6a690a82 100644 --- a/apps/common/utils/crypto.py +++ b/apps/common/utils/crypto.py @@ -91,12 +91,13 @@ class AESCrypto: def encrypt(self, text): aes = self.aes() - return str(base64.encodebytes(aes.encrypt(self.to_16(text))), - encoding='utf8').replace('\n', '') # 加密 + cipher = base64.encodebytes(aes.encrypt(self.to_16(text))) + return str(cipher, encoding='utf8').replace('\n', '') # 加密 def decrypt(self, text): aes = self.aes() - return str(aes.decrypt(base64.decodebytes(bytes(text, encoding='utf8'))).rstrip(b'\0').decode("utf8")) # 解密 + text_decoded = base64.decodebytes(bytes(text, encoding='utf8')) + return str(aes.decrypt(text_decoded).rstrip(b'\0').decode("utf8")) class AESCryptoGCM: @@ -234,6 +235,8 @@ def rsa_decrypt(cipher_text, rsa_private_key=None): def rsa_decrypt_by_session_pkey(value): from jumpserver.utils import current_request + if not current_request: + return value private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME private_key = current_request.session.get(private_key_name) @@ -254,7 +257,11 @@ def decrypt_password(value): key_cipher, password_cipher = cipher aes_key = rsa_decrypt_by_session_pkey(key_cipher) aes = get_aes_crypto(aes_key, 'ECB') - password = aes.decrypt(password_cipher) + try: + password = aes.decrypt(password_cipher) + except UnicodeDecodeError as e: + logging.error("Decript password error: {}, {}".format(password_cipher, e)) + return value return password diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 327c3ea97..c1baf2882 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -95,7 +95,6 @@ MIDDLEWARE = [ 'authentication.backends.cas.middleware.CASMiddleware', 'authentication.middleware.MFAMiddleware', 'authentication.middleware.SessionCookieMiddleware', - 'authentication.middleware.EncryptedMiddleware', 'simple_history.middleware.HistoryRequestMiddleware', ] diff --git a/apps/orgs/signal_handlers/cache.py b/apps/orgs/signal_handlers/cache.py index 9de31bbb0..8bd2ca609 100644 --- a/apps/orgs/signal_handlers/cache.py +++ b/apps/orgs/signal_handlers/cache.py @@ -91,10 +91,11 @@ class OrgResourceStatisticsRefreshUtil: @classmethod def refresh_if_need(cls, instance): cache_field_name = cls.model_cache_field_mapper.get(type(instance)) - if cache_field_name: - org_cache = OrgResourceStatisticsCache(instance.org) - org_cache.expire(*cache_field_name) - OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name) + if not cache_field_name: + return + OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name) + if instance.org: + OrgResourceStatisticsCache(instance.org).expire(*cache_field_name) @receiver(post_save)