diff --git a/apps/chat/serializers/chat_authentication.py b/apps/chat/serializers/chat_authentication.py index e3a45bf6381..7db073c478f 100644 --- a/apps/chat/serializers/chat_authentication.py +++ b/apps/chat/serializers/chat_authentication.py @@ -15,7 +15,7 @@ from rest_framework import serializers from application.models import ApplicationAccessToken, Application, ApplicationVersion -from common.auth.common import FileToken, ChatToken +from common.auth.common import ChatToken from common.auth.constants.operate_constants import Operate from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version @@ -48,14 +48,10 @@ def auth(self, request): if application_access_token is None or not application_access_token.is_active: raise AppApiException(500, _("Invalid application_id")) application_id = str(application_id) - return ( - ChatToken(chat_user_id, _type, str(Operate.ANNOTATION_AUTH), application_id=application_id).to_token(), - FileToken(chat_user_id, _type, application_id=application_id).to_token(), - ) - return ( - ChatToken(chat_user_id, _type, str(Operate.ANNOTATION_AUTH)).to_token(), - FileToken(chat_user_id, _type).to_token(), - ) + return ChatToken( + chat_user_id, _type, str(Operate.ANNOTATION_AUTH), application_id=application_id + ).to_token() + return (ChatToken(chat_user_id, _type, str(Operate.ANNOTATION_AUTH)).to_token(),) class AnonymousAuthenticationV2Serializer(serializers.Serializer): diff --git a/apps/chat/views/v2/chat.py b/apps/chat/views/v2/chat.py index a816df8a9e1..df7add2982f 100644 --- a/apps/chat/views/v2/chat.py +++ b/apps/chat/views/v2/chat.py @@ -43,11 +43,9 @@ ) from common.auth import ChatTokenAuth from common.auth.authentication import has_permissions -from common.auth.common import FileToken from common.auth.constants.chat_permission_constants import ChatPermissionConstants from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version -from common.auth.common import ChatAuthentication from common.exception.app_exception import AppAuthenticationFailed, AppApiException from common.log.log import _get_ip_address, log from common.result import result @@ -443,7 +441,7 @@ def create_token_and_cache(access_token, user, request): token = ChatUserAccessTokenSerializer.create_token_and_cache(access_token, user, request) version, get_key = Cache_Version.CHAT_USER_TOKEN.value cache.set(get_key(token), user, timeout=60 * 60 * 2, version=version) - return token, FileToken(str(user.id), AuthenticationType.CHAT_USER.value).to_token() + return token @classmethod def generate(self, request, f_token: str, response: HttpResponse, path: str = "/chat"): @@ -474,9 +472,9 @@ class LocalLoginView(BaseAuthView): def post(self, request: Request, access_token: str = None): user = ChatUserAccessTokenSerializer.local_login(request.data, access_token) user.source = "LOCAL" - token, f_token = self.create_token_and_cache(access_token, user, request) + token = self.create_token_and_cache(access_token, user, request) response = result.success({"token": token}) - return self.generate(request, f_token, response, path=f"/chat/{access_token}/") + return self.generate(request, token, response, path=f"/chat/{access_token}/") class Logout(APIView): diff --git a/apps/chat/views/v3/chat.py b/apps/chat/views/v3/chat.py index a4c48b7535e..c61e88413d1 100644 --- a/apps/chat/views/v3/chat.py +++ b/apps/chat/views/v3/chat.py @@ -38,7 +38,7 @@ ) from common.auth import ChatTokenAuth from common.auth.authentication import has_permissions -from common.auth.common import FileToken, ChatToken +from common.auth.common import ChatToken from common.auth.constants.chat_permission_constants import ChatPermissionConstants from common.auth.constants.operate_constants import Operate from common.constants.authentication_type import AuthenticationType @@ -140,7 +140,7 @@ def options(self, request, *args, **kwargs): def post(self, request: Request): serializer = AnonymousAuthenticationSerializer(data=request.query_params) serializer.is_valid(raise_exception=True) - token, f_token = serializer.auth(request) + token = serializer.auth(request) response = result.success( token, headers={ @@ -156,7 +156,7 @@ def post(self, request: Request): cookie_path = f"{CONFIG.get_chat_path()}/{application_id}" if application_id else CONFIG.get_chat_path() response.set_cookie( key="mk_file_auth", - value=f_token, + value=token, max_age=7 * 24 * 3600, path=cookie_path, secure=is_https, @@ -450,7 +450,7 @@ def create_token_and_cache(user, access_token, operate): ).to_token() version, get_key = Cache_Version.CHAT_USER_TOKEN.value cache.set(get_key(token), user, timeout=60 * 60 * 2, version=version) - return token, FileToken(str(user.id), AuthenticationType.CHAT_USER.value).to_token() + return token @classmethod def generate(self, request, f_token: str, response: HttpResponse, path: str = "/chat"): @@ -482,9 +482,9 @@ def post(self, request: Request): user = ChatUserAccessTokenV3Serializer.local_login(request.data) user.source = "LOCAL" access_token = request.query_params.get("accessToken") - token, f_token = self.create_token_and_cache(user, access_token, Operate.LOCAL) + token = self.create_token_and_cache(user, access_token, Operate.LOCAL) response = result.success({"token": token}) - return self.generate(request, f_token, response, path=f"/chat/{access_token + '/' if access_token else ''}") + return self.generate(request, token, response, path=f"/chat/{access_token + '/' if access_token else ''}") class Logout(APIView): diff --git a/apps/common/auth/common.py b/apps/common/auth/common.py index 7ee23988e57..98832c885ef 100644 --- a/apps/common/auth/common.py +++ b/apps/common/auth/common.py @@ -7,138 +7,57 @@ @desc: """ -import hashlib -import json -import threading +from django.core import signing -from django.core import signing, cache - -from application.models import ChatUserType from common.constants.authentication_type import AuthenticationType -from common.constants.cache_version import Cache_Version -from common.utils.rsa_util import encrypt, decrypt - -authentication_cache = cache.cache -lock = threading.Lock() - - -def _decrypt(authentication: str): - cache_key = hashlib.sha256(authentication.encode()).hexdigest() - result = authentication_cache.get(key=cache_key, version=Cache_Version.CHAT.value) - if result is None: - with lock: - result = authentication_cache.get(cache_key, version=Cache_Version.CHAT.value) - if result is None: - result = decrypt(authentication) - authentication_cache.set(cache_key, result, version=Cache_Version.CHAT.value, timeout=60 * 60 * 2) - - return result - - -class ChatAuthentication: - def __init__(self, auth_type: str | None, **kwargs): - self.auth_type = auth_type - for k, v in kwargs.items(): - self.__setattr__(k, v) - - def to_dict(self): - return self.__dict__ - - def to_string(self): - value = json.dumps(self.to_dict()) - authentication = encrypt(value) - cache_key = hashlib.sha256(authentication.encode()).hexdigest() - authentication_cache.set(cache_key, value, version=Cache_Version.CHAT.get_version(), timeout=60 * 60 * 2) - return authentication - - @staticmethod - def new_instance(authentication: str): - auth = json.loads(_decrypt(authentication)) - return ChatAuthentication(**auth) +from common.exception.app_exception import AppAuthenticationFailed -class FileToken: - def __init__(self, user_id, _type, application_id: str = None): - self.user_id = user_id +class SystemToken: + def __init__(self, user_id, _type: AuthenticationType, **kwargs): + self.id = user_id self.type = _type - self.application_id = application_id - - def to_dict(self): - return ( - {"user_id": self.user_id, "type": str(self.type), "application_id": self.application_id} - if self.application_id - else {"user_id": self.user_id, "type": str(self.type)} - ) - - def to_token(self): - return signing.dumps(self.to_dict()) - - @staticmethod - def new_instance(token): - token_dict = signing.loads(token) - return FileToken(token_dict.get("user_id"), token_dict.get("type"), token_dict.get("application_id")) - - -class ChatUserToken: - def __init__( - self, - application_id, - user_id, - access_token, - _type, - chat_user_type, - chat_user_id, - authentication: ChatAuthentication, - ): - self.application_id = application_id - self.user_id = user_id - self.access_token = access_token - self.type = _type - self.chat_user_type = chat_user_type - self.chat_user_id = chat_user_id - self.authentication = authentication + self.kwargs = kwargs def to_dict(self): - return { - "application_id": str(self.application_id), - "user_id": str(self.user_id), - "access_token": self.access_token, - "type": str(self.type.value), - "chat_user_type": str(self.chat_user_type), - "chat_user_id": str(self.chat_user_id), - "authentication": self.authentication.to_string(), - } + if self.kwargs: + return {"user_id": self.id, "type": str(self.type.value), "kwargs": self.kwargs} + return {"id": str(self.id), "type": str(self.type.value)} def to_token(self): return signing.dumps(self.to_dict()) - @staticmethod - def new_instance(token_dict): - return ChatUserToken( - token_dict.get("application_id"), - token_dict.get("user_id"), - token_dict.get("access_token"), - token_dict.get("type"), - token_dict.get("chat_user_type"), - token_dict.get("chat_user_id"), - ChatAuthentication.new_instance(token_dict.get("authentication")), - ) - class ChatToken: def __init__(self, user_id, _type: AuthenticationType, login_type: str, **kwargs): - self.user_id = user_id + self.id = user_id self.type = _type self.login_type = login_type self.kwargs = kwargs def to_dict(self): + if self.kwargs: + return { + "id": str(self.id), + "type": str(self.type.value), + "login_type": str(self.login_type), + "kwargs": self.kwargs, + } return { - "user_id": str(self.user_id), + "id": str(self.id), "type": str(self.type.value), "login_type": str(self.login_type), - "kwargs": self.kwargs, } def to_token(self): return signing.dumps(self.to_dict()) + + +def parse_token(token): + details = signing.loads(token) + _type = details.get("type") + if _type: + if _type == AuthenticationType.SYSTEM_USER.value: + return SystemToken(details.get("id"), details.get("type"), **details.get("kwargs", {})) + return ChatToken(details.get("id"), details.get("type"), details.get("login_type"), **details.get("kwargs", {})) + raise AppAuthenticationFailed(1001, "") diff --git a/apps/common/auth/constants/chat_permission_constants.py b/apps/common/auth/constants/chat_permission_constants.py index 4a1d3e16ab9..160639d386a 100644 --- a/apps/common/auth/constants/chat_permission_constants.py +++ b/apps/common/auth/constants/chat_permission_constants.py @@ -1,11 +1,12 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎虎 - @file: chat_permission_constants.py - @date:2026/8/6 16:38 - @desc: +@project: MaxKB +@Author:虎虎虎 +@file: chat_permission_constants.py +@date:2026/8/6 16:38 +@desc: """ + from enum import Enum from common.auth.constants.group_constants import Group @@ -16,34 +17,36 @@ class ChatPermissionConstants(Enum): CHAT_USER_ANONYMOUS = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.ANNOTATION_AUTH, 0) - CHAT_USER_PASSWORD = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.PASSWORD, 1) - CHAT_USER_LOCAL = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LOCAL, 2) - CHAT_USER_CAS = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.CAS, 3) - CHAT_USER_DINGTALK = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.DINGTALK, 4) - CHAT_USER_WECOM = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.WECOM, 5) - CHAT_USER_LARK = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LARK, 6) - CHAT_USER_OIDC = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.OIDC, 7) - CHAT_USER_LDAP = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LDAP, 8) - CHAT_USER_OAUTH2 = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.OAUTH2, 9) + CHAT_USER_LOCAL = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LOCAL, 1) + CHAT_USER_CAS = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.CAS, 2) + CHAT_USER_DINGTALK = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.DINGTALK, 3) + CHAT_USER_WECOM = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.WECOM, 4) + CHAT_USER_LARK = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LARK, 5) + CHAT_USER_OIDC = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.OIDC, 6) + CHAT_USER_LDAP = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.LDAP, 7) + CHAT_USER_OAUTH2 = Permission(Group.CHAT_USER, Group.CHAT_USER, Operate.OAUTH2, 8) def get_permission(self): - return self._build_workspace_permission('application_id') + return self._build_workspace_permission("application_id") def _build_workspace_permission(self, resource_id_key=None): def permission_factory(_, **kwargs): - return Permission(group=self.value.group, - sub_group=self.value.sub_group, - operate=self.value.operate, - bit_index=self.value.bit_index, - workspace_id=kwargs.get('workspace_id'), - resource_id=kwargs.get(resource_id_key) if resource_id_key else None) + return Permission( + group=self.value.group, + sub_group=self.value.sub_group, + operate=self.value.operate, + bit_index=self.value.bit_index, + workspace_id=kwargs.get("workspace_id"), + resource_id=kwargs.get(resource_id_key) if resource_id_key else None, + ) return permission_factory @staticmethod def get_aggregate_permissions(): return AggregatePermission( - permissions=[_permission.get_permission() for _permission in ChatPermissionConstants]) + permissions=[_permission.get_permission() for _permission in ChatPermissionConstants] + ) # 权限字符串与权限对象的Map diff --git a/apps/common/auth/handle/impl/chat_user_token.py b/apps/common/auth/handle/impl/chat_user_token.py index e9c5c8c0eea..2dbc91d3edd 100644 --- a/apps/common/auth/handle/impl/chat_user_token.py +++ b/apps/common/auth/handle/impl/chat_user_token.py @@ -1,11 +1,12 @@ # coding=utf-8 """ - @project: MaxKB - @Author:虎虎 - @file: chat_anonymous_user_token.py - @date:2025/6/6 15:08 - @desc: +@project: MaxKB +@Author:虎虎 +@file: chat_anonymous_user_token.py +@date:2025/6/6 15:08 +@desc: """ + from functools import reduce from django.db.models import QuerySet, Q @@ -19,86 +20,111 @@ from common.auth.struct.auth import Principal, Auth from common.constants.authentication_type import AuthenticationType from common.exception.app_exception import AppUnauthorizedFailed -from system_manage.models import ResourceChatUserGroupAuthorize, ResourceType, ResourceChatUserAuthorize, \ - UserGroupRelation, ChatUser - -login_type_list = [Operate.LOCAL.value, Operate.CAS.value, Operate.DINGTALK.value, Operate.WECOM.value, - Operate.LARK.value, Operate.OIDC.value, Operate.LDAP.value, - Operate.OAUTH2.value] +from system_manage.models import ( + ResourceChatUserGroupAuthorize, + ResourceType, + ResourceChatUserAuthorize, + UserGroupRelation, + ChatUser, +) +login_type_list = [ + Operate.LOCAL.value, + Operate.CAS.value, + Operate.DINGTALK.value, + Operate.WECOM.value, + Operate.LARK.value, + Operate.OIDC.value, + Operate.LDAP.value, + Operate.OAUTH2.value, +] -class ChatUserToken(AuthBaseHandle): - def support(self, request, token: str, get_token_details): - token_details = get_token_details() - if token_details is None: - return False - return token_details.get('type') == AuthenticationType.CHAT_USER.value - def handle(self, request, token: str, get_token_details): - auth_details = get_token_details() - application_access_token_list = QuerySet(ApplicationAccessToken).filter( - is_active=True +def get_auth(login_type, user_id, application_id): + application_access_token_list = QuerySet(ApplicationAccessToken).filter(is_active=True) + if login_type.upper() == str(Operate.ANNOTATION_AUTH): + application_access_token_list = application_access_token_list.filter(authentication=False) + elif login_type.upper() == str(Operate.PASSWORD): + application_access_token_list = application_access_token_list.filter( + authentication=True, authentication_value__type="password" ) - _type = ChatUserType.ANONYMOUS_USER - login_type = auth_details.get('login_type') - user_id = auth_details.get('user_id') - application_id = (auth_details.get('kwargs') or {}).get('application_id') - if login_type.upper() == str(Operate.ANNOTATION_AUTH): - application_access_token_list = application_access_token_list.filter(authentication=False) - elif login_type.upper() == str(Operate.PASSWORD): - application_access_token_list = (application_access_token_list - .filter(authentication=True, authentication_value__type='password')) - elif login_type_list.__contains__(login_type.upper()): - _type = ChatUserType.CHAT_USER - user_group_ids = QuerySet(UserGroupRelation).filter( + elif login_type_list.__contains__(login_type.upper()): + user_group_ids = ( + QuerySet(UserGroupRelation) + .filter( user_id=user_id, - ).values_list('group_id', flat=True) + ) + .values_list("group_id", flat=True) + ) - group_qs = QuerySet(ResourceChatUserGroupAuthorize).filter( + group_qs = ( + QuerySet(ResourceChatUserGroupAuthorize) + .filter( resource_type=ResourceType.APPLICATION, is_auth=True, user_group_id__in=user_group_ids, - ).values_list('resource_id', flat=True) + ) + .values_list("resource_id", flat=True) + ) - user_qs = QuerySet(ResourceChatUserAuthorize).filter( + user_qs = ( + QuerySet(ResourceChatUserAuthorize) + .filter( resource_type=ResourceType.APPLICATION, is_auth=True, user_id=user_id, - ).values_list('resource_id', flat=True) - application_access_token_list = application_access_token_list.filter( - Q(authentication_value__type='login'), - Q(authentication_value__login_value__contains=login_type), - Q(application_id__in=group_qs) | Q(application_id__in=user_qs), ) - if application_id: - application_access_token_list = application_access_token_list.filter(application_id=application_id) - permissions = {} - for application_access_token in application_access_token_list: - permission_list = [] - if application_access_token.authentication: - authentication_value = application_access_token.authentication_value - if authentication_value.get('type') == 'password': - permission_list.append(ChatPermissionConstants.CHAT_USER_PASSWORD.value) - elif authentication_value.get('type') == 'login': - login_value = authentication_value.get('login_value') or [] - for _value in login_value: - permission_str = f'{Group.CHAT_USER}_{_value.upper()}' - permission = CHAT_PERMISSION_STR_MAP.get(permission_str) - if permission: - permission_list.append(permission.value) + .values_list("resource_id", flat=True) + ) + application_access_token_list = application_access_token_list.filter( + Q(authentication_value__type="login"), + Q(authentication_value__login_value__contains=login_type), + Q(application_id__in=group_qs) | Q(application_id__in=user_qs), + ) + if application_id: + application_access_token_list = application_access_token_list.filter(application_id=application_id) + permissions = {} + for application_access_token in application_access_token_list: + permission_list = [] + if application_access_token.authentication: + authentication_value = application_access_token.authentication_value + if authentication_value.get("type") == "login": + login_value = authentication_value.get("login_value") or [] + for _value in login_value: + permission_str = f"{Group.CHAT_USER}_{_value.upper()}" + permission = CHAT_PERMISSION_STR_MAP.get(permission_str) + if permission: + permission_list.append(permission.value) + + else: + permission_list.append(ChatPermissionConstants.CHAT_USER_ANONYMOUS.value) + k = f"{Group.CHAT_USER}:r:{application_access_token.application_id}" + permissions[k] = reduce(lambda x, y: x | y, [p.bit() for p in permission_list], 0) + return Auth(set(), permissions) + - else: - permission_list.append(ChatPermissionConstants.CHAT_USER_ANONYMOUS.value) - k = f"{Group.CHAT_USER}:r:{application_access_token.application_id}" - permissions[k] = reduce(lambda x, y: x | y, [p.bit() for p in permission_list], 0) +class ChatUserToken(AuthBaseHandle): + def support(self, request, token: str, get_token_details): + token_details = get_token_details() + if token_details is None: + return False + return token_details.get("type") == AuthenticationType.CHAT_USER.value + + def handle(self, request, token: str, get_token_details): + auth_details = get_token_details() + login_type = auth_details.get("login_type") + user_id = auth_details.get("id") + application_id = (auth_details.get("kwargs") or {}).get("application_id") + _type = ( + ChatUserType.CHAT_USER if login_type_list.__contains__(login_type.upper()) else ChatUserType.ANONYMOUS_USER + ) + auth = get_auth(login_type, user_id, application_id) chat_user = QuerySet(ChatUser).filter(id=user_id).first() if application_id: # 指定了 application_id(v2 流程)时,直接校验该应用是否有权限,无权限直接抛错, # 避免返回一个空权限的 Principal 造成静默失败。 - if not permissions.get(f"{Group.CHAT_USER}:r:{application_id}"): - raise AppUnauthorizedFailed(403, _('No permission to access')) - return Principal(auth_details.get('user_id'), _type, application_id=application_id, - profile=chat_user), Auth(set(), - permissions) - return Principal(auth_details.get('user_id'), _type, profile=chat_user), Auth(set(), permissions) + if not auth.permissions.get(f"{Group.CHAT_USER}:r:{application_id}"): + raise AppUnauthorizedFailed(403, _("No permission to access")) + return Principal(user_id, _type, application_id=application_id, profile=chat_user), auth + return Principal(user_id, _type, profile=chat_user), auth diff --git a/apps/oss/serializers/file.py b/apps/oss/serializers/file.py index df877c836be..bf6a1eafb03 100644 --- a/apps/oss/serializers/file.py +++ b/apps/oss/serializers/file.py @@ -3,13 +3,18 @@ import urllib import uuid_utils.compat as uuid +from django.db.models.functions import Cast + from application.models import Application, ApplicationAccessToken, ChatShareLink -from common.auth.common import FileToken +from common.auth.common import parse_token +from common.auth.constants.chat_permission_constants import ChatPermissionConstants +from common.auth.constants.operate_constants import Operate from common.auth.handle.impl.user_token import get_auth +from common.auth.handle.impl.chat_user_token import get_auth as get_chat_auth from common.constants.authentication_type import AuthenticationType from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException, AppUnauthorizedFailed, NotFound404 -from django.db.models import QuerySet +from django.db.models import QuerySet, CharField from django.http import HttpResponse from django.utils.translation import gettext from django.utils.translation import gettext_lazy as _ @@ -178,25 +183,37 @@ def auth(file, mk_file_auth): # 非公共文件,直接拒绝 if mk_file_auth is None: _deny() - token = FileToken.new_instance(mk_file_auth) + token = parse_token(mk_file_auth) user_type = AuthenticationType(token.type) - if user_type in (AuthenticationType.CHAT_USER, AuthenticationType.CHAT_ANONYMOUS_USER): - _auth_chat(file, token, user_type) + if user_type == AuthenticationType.CHAT_USER: + _auth_chat(file, token) elif user_type == AuthenticationType.SYSTEM_USER: - _auth_system(file, token.user_id) + _auth_system(file, token.id) else: # 默认拒绝,避免枚举扩展后静默放行 _deny() -def _auth_chat(file, token, user_type): - user_id = token.user_id +def _auth_chat(file, token): + user_id = token.id + application_id = token.kwargs.get("application_id") if file.source_type == FileSourceType.APPLICATION: - if not token.application_id == file.source_id: - _deny() + if application_id: + if not token.application_id == file.source_id: + _deny() else: - return + user_auth = get_chat_auth(token.login_type, token.id, application_id) + if not any( + [ + hasPermission( + user_auth, + _permission._build_workspace_permission("application_id")({"application_id": file.source_id}), + ) + for _permission in ChatPermissionConstants + ] + ): + _deny() if file.source_type == FileSourceType.CHAT: if file.meta.get("user_id") == user_id: return @@ -204,7 +221,7 @@ def _auth_chat(file, token, user_type): if not QuerySet(ChatShareLink).filter(chat_id=file.source_id).exists(): _deny() # 匿名用户还需满足应用的登录要求 - if user_type == AuthenticationType.CHAT_ANONYMOUS_USER: + if token.login_type.upper() == str(Operate.ANNOTATION_AUTH): _check_anonymous_login(file.source_id) return @@ -218,9 +235,9 @@ def _auth_chat(file, token, user_type): else: _deny() return - - if user_type == AuthenticationType.CHAT_ANONYMOUS_USER: - _check_knowledge_mapped_to_application(token.application_id, knowledge_id) + ## 如果是匿名的就要看可访问的应用是否 + if token.login_type.upper() == str(Operate.ANNOTATION_AUTH): + _check_knowledge_mapped_to_application(knowledge_id) return get_authorized = DatabaseModelManage.get_model("get_knowledge_list_of_authorized") @@ -237,14 +254,16 @@ def _check_anonymous_login(chat_id): _deny() -def _check_knowledge_mapped_to_application(application_id, knowledge_id): - if application_id is None or knowledge_id is None: +def _check_knowledge_mapped_to_application(knowledge_id): + if knowledge_id is None: _deny() exists = ( QuerySet(ResourceMapping) .filter( source_type=ResourceType.APPLICATION, - source_id=str(application_id), + source_id__in=QuerySet(ApplicationAccessToken) + .filter(is_active=True, authentication=False) + .values_list(Cast("application_id", output_field=CharField()), flat=True), target_type=ResourceType.KNOWLEDGE, target_id=str(knowledge_id), ) diff --git a/apps/oss/views/file.py b/apps/oss/views/file.py index 7e6a1df2bfc..7abe6e0a73e 100644 --- a/apps/oss/views/file.py +++ b/apps/oss/views/file.py @@ -7,7 +7,6 @@ from common.auth import TokenAuth, AllTokenAuth from common.auth.authentication import has_permissions -from common.auth.common import ChatAuthentication from common.auth.constants.role_constants import RoleConstants from common.exception.app_exception import AppUnauthorizedFailed from common.log.log import log @@ -68,7 +67,7 @@ def post(self, request: Request): "source_id": source_id, "source_type": source_type, } - ).upload(user_id=(str(request.user.id) if request.user else request.auth.chat_user_id)) + ).upload(user_id=str(request.user.id)) ) class Operate(APIView): @@ -108,11 +107,7 @@ class GetUrlView(APIView): tags=[_("Chat")], # type: ignore ) def get(self, request: Request, application_id: str): - if ( - isinstance(request.auth, ChatAuthentication) - and request.auth.application_id - and str(request.auth.application_id) != application_id - ): + if "application_id" in request.user.kwargs and str(request.user.kwargs.get("application_id")) != application_id: return result.error(_("No permission")) url = request.query_params.get("url") result_data = get_url_content(url, application_id) diff --git a/apps/portal/serializers/portal.py b/apps/portal/serializers/portal.py index ee11bfdd39d..08a022ad2cc 100644 --- a/apps/portal/serializers/portal.py +++ b/apps/portal/serializers/portal.py @@ -14,7 +14,6 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from common.auth.common import FileToken from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage from common.exception.app_exception import AppApiException @@ -189,7 +188,6 @@ def login(instance): version, get_key = Cache_Version.TOKEN.value timeout = CONFIG.get_session_timeout() cache.set(get_key(token), user, timeout=timeout, version=version) - f_token = FileToken(str(user.id), "PORTAL_USER").to_token() record_log( menu="Portal", operate="Log in", @@ -199,7 +197,7 @@ def login(instance): operation_object={"name": user.username}, workspace_id="default", ) - return {"token": token}, f_token + return {"token": token} @staticmethod def get_login_profile(): diff --git a/apps/portal/views/portal.py b/apps/portal/views/portal.py index a832e200d08..0e7b71a0db1 100644 --- a/apps/portal/views/portal.py +++ b/apps/portal/views/portal.py @@ -70,12 +70,12 @@ class PortalLoginView(APIView): responses=PortalAPI.Login.get_response(), ) def post(self, request: Request): - token_data, f_token = PortalLoginSerializer.login(request.data) + token_data = PortalLoginSerializer.login(request.data) response = result.success(token_data) secure = request.is_secure() response.set_cookie( "mk_file_auth", - value=f_token, + value=token_data.get("token"), max_age=7 * 24 * 3600, path="/portal/", domain=None, diff --git a/apps/system_manage/serializers/chat_user_serializer.py b/apps/system_manage/serializers/chat_user_serializer.py index 7ee0897db70..8e13da142fa 100644 --- a/apps/system_manage/serializers/chat_user_serializer.py +++ b/apps/system_manage/serializers/chat_user_serializer.py @@ -5,7 +5,8 @@ from rest_framework import serializers from application.models import ApplicationAccessToken, ChatUserType -from common.auth.common import ChatUserToken, ChatAuthentication +from common.auth.common import ChatToken +from common.auth.constants.operate_constants import Operate from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version from common.exception.app_exception import AppApiException @@ -13,35 +14,34 @@ from common.utils.common import password_encrypt from common.utils.common import password_verify, needs_password_upgrade from common.utils.rsa_util import decrypt -from system_manage.models import ResourceChatUserGroupAuthorize, ResourceType, \ - UserGroupRelation, ResourceChatUserAuthorize, ChatUser +from system_manage.models import ( + ResourceChatUserGroupAuthorize, + ResourceType, + UserGroupRelation, + ResourceChatUserAuthorize, + ChatUser, +) from users.serializers.login import LoginRequest system_version, system_get_key = Cache_Version.SYSTEM.value class ChatUserAccessTokenSerializer(serializers.Serializer): - @staticmethod def create_token_and_cache(access_token, user, request): status = 500 # 默认失败状态 - workspace_id = 'default' + workspace_id = "default" try: - application_access_token = ApplicationAccessToken.objects.filter( - access_token=access_token - ).first() + application_access_token = ApplicationAccessToken.objects.filter(access_token=access_token).first() if not application_access_token: - raise AppApiException(1005, _('Invalid access token')) + raise AppApiException(1005, _("Invalid access token")) application_id = application_access_token.application_id workspace_id = application_access_token.application.workspace_id # 检查用户是否有权限访问该应用 is_authorized = ResourceChatUserAuthorize.objects.filter( - resource_id=application_id, - resource_type=ResourceType.APPLICATION.value, - is_auth=True, - user_id=user.id + resource_id=application_id, resource_type=ResourceType.APPLICATION.value, is_auth=True, user_id=user.id ).exists() if not is_authorized: # 获取资源组授权的用户组ID @@ -49,43 +49,39 @@ def create_token_and_cache(access_token, user, request): resource_id=application_id, resource_type=ResourceType.APPLICATION.value, is_auth=True, - ).values_list('user_group_id', flat=True) + ).values_list("user_group_id", flat=True) # 如果有资源组授权,则检查用户是否属于这些用户组 if resource_group_ids.exists(): is_authorized = UserGroupRelation.objects.filter( - user_id=user.id, - group_id__in=resource_group_ids + user_id=user.id, group_id__in=resource_group_ids ).exists() if not is_authorized: - raise AppApiException(1005, _('The user does not have permission to access the application')) - token = ChatUserToken( - application_id, user.id, access_token, AuthenticationType.CHAT_USER, - ChatUserType.CHAT_USER, user.id, ChatAuthentication(user.source) + raise AppApiException(1005, _("The user does not have permission to access the application")) + token = ChatToken( + user.id, AuthenticationType.CHAT_USER, str(Operate.LOCAL).lower(), application_id=application_id ).to_token() status = 200 return token finally: record_log( - menu='Chat User/login', - operate='Log in', + menu="Chat User/login", + operate="Log in", request=request, - user={'username': user.username}, + user={"username": user.username}, status=status, - operation_object={'name': user.username}, - workspace_id=workspace_id + operation_object={"name": user.username}, + workspace_id=workspace_id, ) @staticmethod def get_auth_setting(access_token): auth_setting = {} - application_access_token = ApplicationAccessToken.objects.filter( - access_token=access_token - ).first() + application_access_token = ApplicationAccessToken.objects.filter(access_token=access_token).first() if not application_access_token: - raise AppApiException(1005, _('Invalid access token')) + raise AppApiException(1005, _("Invalid access token")) if application_access_token: auth_setting = application_access_token.authentication_value @@ -113,7 +109,7 @@ def local_login(instance, access_token): if max_attempts == -1: need_captcha = False elif max_attempts > 0: - fail_count = cache.get(system_get_key(f'chat_{username}'), version=system_version) or 0 + fail_count = cache.get(system_get_key(f"chat_{username}"), version=system_version) or 0 need_captcha = fail_count >= max_attempts if need_captcha: @@ -121,8 +117,7 @@ def local_login(instance, access_token): raise AppApiException(1005, _("Captcha is required")) captcha_cache = cache.get( - Cache_Version.CAPTCHA.get_key(captcha=f"chat_{username}"), - version=Cache_Version.CAPTCHA.get_version() + Cache_Version.CAPTCHA.get_key(captcha=f"chat_{username}"), version=Cache_Version.CAPTCHA.get_version() ) if captcha_cache is None or captcha.lower() != captcha_cache: raise AppApiException(1005, _("Captcha code error or expiration")) @@ -131,14 +126,14 @@ def local_login(instance, access_token): if not user or not password_verify(password, user.password): record_login_fail(username) - raise AppApiException(500, _('The username or password is incorrect')) + raise AppApiException(500, _("The username or password is incorrect")) if needs_password_upgrade(user.password): user.password = password_encrypt(password) - user.save(update_fields=['password']) + user.save(update_fields=["password"]) if not user.is_active: raise AppApiException(1005, _("The user has been disabled, please contact the administrator!")) - cache.delete(system_get_key(f'chat_{username}'), version=system_version) + cache.delete(system_get_key(f"chat_{username}"), version=system_version) return user @@ -146,7 +141,7 @@ def record_login_fail(username: str, expire: int = 600): """记录登录失败次数""" if not username: return - fail_key = system_get_key(f'chat_{username}') + fail_key = system_get_key(f"chat_{username}") fail_count = cache.get(fail_key, version=system_version) if fail_count is None: cache.set(fail_key, 1, timeout=expire, version=system_version) diff --git a/apps/users/serializers/login.py b/apps/users/serializers/login.py index cc1ac3959da..17a0baba64d 100644 --- a/apps/users/serializers/login.py +++ b/apps/users/serializers/login.py @@ -12,7 +12,7 @@ from application.models import ApplicationAccessToken from captcha.image import ImageCaptcha -from common.auth.common import FileToken +from common.auth.common import SystemToken from common.constants.authentication_type import AuthenticationType from common.constants.cache_version import Cache_Version from common.database_model_manage.database_model_manage import DatabaseModelManage @@ -117,14 +117,7 @@ def _authenticate(username: str, password: str) -> User | None: @staticmethod def _issue_token(user: User) -> str: """签发登录 token 并写入缓存""" - token = signing.dumps( - { - "username": user.username, - "id": str(user.id), - "email": user.email, - "type": AuthenticationType.SYSTEM_USER.value, - } - ) + token = SystemToken(str(user.id), AuthenticationType.SYSTEM_USER).to_token() version, get_key = Cache_Version.TOKEN.value cache.set(get_key(token), user, timeout=CONFIG.get_session_timeout(), version=version) return token @@ -174,7 +167,7 @@ def login(instance): cache.delete(system_get_key(f"system_{username}_lock"), version=system_version) token = LoginSerializer._issue_token(user) - return {"token": token}, FileToken(str(user.id), AuthenticationType.SYSTEM_USER.value).to_token() + return {"token": token} @staticmethod def _is_account_locked(username: str, failed_attempts: int) -> bool: diff --git a/apps/users/views/login.py b/apps/users/views/login.py index 3c9c80b6efe..817a65c25d3 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -48,13 +48,13 @@ class LoginView(APIView): get_operation_object=lambda r, k: {"name": r.data.get("username")}, ) def post(self, request: Request): - token, f_token = LoginSerializer().login(request.data) + token = LoginSerializer().login(request.data) response = result.success(token) is_https = request.scheme == "https" response.set_cookie( key="mk_file_auth", - value=f_token, + value=token.get("token"), max_age=7 * 24 * 3600, path=CONFIG.get_admin_path(), secure=is_https,