From a30e27e4bcd072b68919ceed3975833c89e3551d Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 14 Aug 2026 00:33:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(msync):=20=E7=94=A8=E6=88=B7=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E7=AD=BE=E5=8F=91=E7=AB=AF=E7=82=B9=EF=BC=88UserToken?= =?UTF-8?q?IssueView=EF=BC=89+=20Market=20=E8=BF=94=E5=9B=9E=20Source/Serv?= =?UTF-8?q?iceName/ServiceOnline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gvsds/apps/gerp/app_manager.py | 5 ++++ gvsds/apps/gerp/views/app.py | 51 ++++++++++++++++++++++++++++++++++ gvsds/urls.py | 1 + 3 files changed, 57 insertions(+) diff --git a/gvsds/apps/gerp/app_manager.py b/gvsds/apps/gerp/app_manager.py index f088c843..73d87ddc 100644 --- a/gvsds/apps/gerp/app_manager.py +++ b/gvsds/apps/gerp/app_manager.py @@ -72,6 +72,11 @@ def _AppFlat(app, tenant_installed=None, user_record=None, company_installed=Non 'AuthRequired': app.AuthRequired, 'MenuRootUUID': UUIDObj(app.MenuRootUUID).str if app.MenuRootUUID else '', 'AppSort': app.AppSort or 0, + # 远端注入(MSYNC):来源/提供方服务/在线状态(前端"服务离线"提示用) + 'Source': app.Source or 'local', + 'ServiceName': app.ServiceName or '', + 'ServiceDomain': app.ServiceDomain or '', + 'ServiceOnline': app.ServiceOnline if app.ServiceOnline is not None else 1, # 前端入口(真应用注册协议:安装后"打开应用"跳转目标) 'FrontendRoute': app.FrontendRoute or '', 'FrontendType': app.FrontendType or 'vue', diff --git a/gvsds/apps/gerp/views/app.py b/gvsds/apps/gerp/views/app.py index b02dc0bc..3f2f3b25 100644 --- a/gvsds/apps/gerp/views/app.py +++ b/gvsds/apps/gerp/views/app.py @@ -74,6 +74,17 @@ def _GetCompanyBytes(request): return None +def _ResolveRoleUUID(request): + """当前角色 UUID bytes(Cookie CurrentRoleUUID)""" + role_uuid_str = request.COOKIES.get('CurrentRoleUUID', '') + if not role_uuid_str: + return None + try: + return UUIDObj(role_uuid_str).bytes + except Exception: + return None + + def _ParseUUIDList(values): """解析公司 UUID 列表(str/bytes 混合 → bytes 列表),过滤无效""" result = [] @@ -532,3 +543,43 @@ class AppConfigCopyView(ELTAuthenticatedAPIView): if not ok: return Response({'code': 1, 'msg': err}, status=400) return Response({'code': 0, 'msg': f'已复制 {copied} 项配置到 {len(targets)} 家公司', 'data': {'Copied': copied}}) + + +class UserTokenIssueView(ELTAuthenticatedAPIView): + """签发用户级短期令牌——员工 iframe 访问子服务器远端 App 时透传用户身份 + + POST /API/MSync/UserToken/Issue + Body: { AudienceService: 目标服务名(子服务器 MSYNC 注册名) } + 返回: { AccessToken, ExpiresAt(ts), AudienceService } + + 令牌由主服务器 MASTER_SECRET 签发(HMAC-SHA256,typ=user,TTL 10min), + 附带当前用户/角色/公司/租户上下文;子服务器通过 + /api/msync/user/token/introspect/ 自省获取用户身份。 + """ + + def post(self, request): + audience = (request.data.get('AudienceService') or '').strip() + if not audience: + return Response({'code': 1, 'msg': '缺少目标服务名 AudienceService'}, status=400) + user_uuid = _GetUserBytes(request) + if not user_uuid: + return Response({'code': 401, 'msg': '无法识别当前用户'}, status=401) + try: + from gvsdsdk.msync.auth import IssueUserToken + token, expires = IssueUserToken( + user_uuid, + RoleUUID=_ResolveRoleUUID(request), + CompanyUUID=_GetCompanyBytes(request), + TenantUUID=_GetTenantBytes(request), + AudienceService=audience, + ) + except Exception as e: + logger.error(f'用户令牌签发异常: {e}') + return Response({'code': 500, 'msg': '用户令牌签发失败'}, status=500) + if not token: + return Response({'code': 500, 'msg': '用户令牌签发失败'}, status=500) + return Response({'code': 0, 'data': { + 'AccessToken': token, + 'ExpiresAt': expires, + 'AudienceService': audience, + }}) diff --git a/gvsds/urls.py b/gvsds/urls.py index be70271f..ceaf7422 100644 --- a/gvsds/urls.py +++ b/gvsds/urls.py @@ -159,6 +159,7 @@ urlpatterns = [ path('API/App/Pending', gerp_views.AppPendingView.as_view(), name='app_pending'), path('API/App/Config', gerp_views.AppConfigView.as_view(), name='app_config'), path('API/App/Config/Copy', gerp_views.AppConfigCopyView.as_view(), name='app_config_copy'), + path('API/MSync/UserToken/Issue', gerp_views.UserTokenIssueView.as_view(), name='msync_user_token_issue'), # 官网社区(discuss.gvsds.com 前端调用) path('API/Community/', include('gvsds.apps.community.urls')),