From f529be12d8e754fbe8d9859adb44b6312d9f3c55 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 2 Feb 2026 18:49:12 +0800 Subject: [PATCH] perf: update zip and agreement --- .../templates/authentication/agreement.html | 129 ++++++++++++++ .../authentication/agreement_zh.html | 165 ++++++++++++++++++ apps/authentication/urls/view_urls.py | 3 +- apps/authentication/views/__init__.py | 1 + apps/authentication/views/agreement.py | 13 ++ apps/common/utils/zip.py | 3 +- apps/i18n/lina/en.json | 8 +- apps/i18n/lina/zh.json | 8 +- apps/ops/api/playbook.py | 6 +- apps/terminal/api/applet/applet.py | 7 +- apps/terminal/api/virtualapp/virtualapp.py | 7 +- 11 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 apps/authentication/templates/authentication/agreement.html create mode 100644 apps/authentication/templates/authentication/agreement_zh.html create mode 100644 apps/authentication/views/agreement.py diff --git a/apps/authentication/templates/authentication/agreement.html b/apps/authentication/templates/authentication/agreement.html new file mode 100644 index 000000000..74b4d937b --- /dev/null +++ b/apps/authentication/templates/authentication/agreement.html @@ -0,0 +1,129 @@ + + + + + User Agreement & Privacy Policy + + + + + +
+

User Agreement & Privacy Policy

+ +

+ Version: 2026-01
+ Effective Date: Feb 1, 2026 +

+ +

1. User Agreement

+ +

1.1 Product Nature

+

+ This system is an enterprise-grade system deployed in a private environment. All + deployment environments and data are fully controlled by the customer. +

+ +

1.2 User Obligations

+ + +

1.3 Account Security

+

+ Users are responsible for safeguarding their credentials. Risks caused by improper + credential management shall be borne by the user or customer. +

+ +

2. Privacy Policy

+ +

2.1 Scope

+

+ This policy applies to the privately deployed version of this system. All personal + data is stored within the customer's own environment. The software provider does not + access, store, or transmit such data. +

+ +

2.2 Personal Information We Process

+ + +

2.3 Authentication Information

+

+ Passwords are never stored in plaintext. Only irreversible encrypted password hashes + are stored for authentication. +

+ +

2.4 Facial Recognition Data

+

+ Facial recognition is an optional premium feature and is disabled by default. If + enabled, facial image or feature data will be processed solely for identity + verification. +

+

+ Such data is considered sensitive personal information and is stored only within the + customer's local environment. +

+ +

2.5 Cookies and Similar Technologies

+

+ Necessary cookies and session identifiers are used to maintain login sessions and + system security. These technologies are not used for advertising or cross-site + tracking. +

+ +

2.6 Policy Updates

+

Material changes to this policy will require renewed user consent.

+
+ + diff --git a/apps/authentication/templates/authentication/agreement_zh.html b/apps/authentication/templates/authentication/agreement_zh.html new file mode 100644 index 000000000..bc96d00e5 --- /dev/null +++ b/apps/authentication/templates/authentication/agreement_zh.html @@ -0,0 +1,165 @@ + + + + + 用户协议与隐私政策 + + + + +
+

用户协议与隐私政策

+ +

+ 版本号:2026-01
+ 生效日期:2026-02-01 +

+ +
本软件为企业级私有化部署系统,仅供客户内部授权人员使用。
+ +

一、用户协议

+ +

1. 产品性质

+

+ 本系统为企业级运维与安全管理系统, + 采用私有化部署方式运行,系统的部署环境、运行环境及数据均由客户自行控制。 +

+ +

2. 用户行为规范

+ + +

3. 账号与凭证安全

+

+ 用户应妥善保管自身账号及认证凭证。 + 因用户个人原因导致的账号安全风险,由用户或客户自行承担。 +

+ +

4. 协议变更

+

如本协议内容发生实质性变更,系统将提示用户重新确认。

+ +
+ +

二、隐私政策

+ +

1. 适用范围

+

+ 本隐私政策适用于本系统私有化部署版本。 系统所产生的数据均存储于客户自有环境中, + 软件提供方不接触、不存储、不回传客户业务数据或个人信息。 +

+ +

2. 我们收集和使用的个人信息

+ +

2.1 基本账号信息

+ +

用于账号管理、身份识别与安全通知。

+ +

2.2 身份认证信息

+

系统不会以明文形式存储用户密码, 仅以不可逆加密方式保存密码摘要,用于身份校验。

+ +

2.3 日志与审计信息

+ +

用于安全审计、合规留痕与问题追溯。

+ +

3. 敏感个人信息的处理(人脸识别)

+

人脸识别功能为可选增值服务,默认不开启, 仅在客户或用户主动启用后生效。

+

+ 如启用该功能,系统将采集并处理用户的人脸图像或特征信息, + 用于身份认证与防止账号冒用。 +

+

+ 上述信息属于敏感个人信息, 仅存储和处理于客户自有部署环境中, + 不会传输至软件提供方服务器。 +

+

用户或客户管理员可随时关闭人脸识别功能, 并删除已采集的人脸信息。

+ +

4. Cookie 与同类技术

+

+ 为保障系统正常运行、登录状态维持及安全防护, 系统会在用户设备中存储必要的 Cookie + 或会话标识 (如 Session、CSRF Token)。 +

+

上述技术仅用于系统功能实现, 不用于广告投放或跨站追踪。

+ +

5. 数据存储位置与期限

+

系统中的个人信息均存储于客户自有环境中, 具体保存期限由客户根据自身管理要求决定。

+ +

6. 信息共享、转让与披露

+

+ 软件提供方不会共享、转让或披露系统中的个人信息, + 但法律法规或司法机关另有要求的除外。 +

+ +

7. 用户权利

+ + +

8. 安全措施

+ + +

9. 隐私政策更新

+

如本隐私政策发生实质性变更, 系统将要求用户重新确认后方可继续使用。

+
+ + diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 7c9e1a55b..aca5e60c5 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -84,5 +84,6 @@ urlpatterns = [ path('captcha/', include('captcha.urls')), - path('oauth2-provider/', include(('authentication.backends.oauth2_provider.urls', 'authentication'), namespace='oauth2-provider')) + path('oauth2-provider/', include(('authentication.backends.oauth2_provider.urls', 'authentication'), namespace='oauth2-provider')), + path('agreement/', views.AgreementView.as_view(), name='agreement'), ] diff --git a/apps/authentication/views/__init__.py b/apps/authentication/views/__init__.py index 3c51a1c85..557ad14b1 100644 --- a/apps/authentication/views/__init__.py +++ b/apps/authentication/views/__init__.py @@ -7,3 +7,4 @@ from .login import * from .mfa import * from .slack import * from .wecom import * +from .agreement import * \ No newline at end of file diff --git a/apps/authentication/views/agreement.py b/apps/authentication/views/agreement.py new file mode 100644 index 000000000..ddc6086b0 --- /dev/null +++ b/apps/authentication/views/agreement.py @@ -0,0 +1,13 @@ +from django.views.generic import TemplateView +from django.utils.translation import get_language + +__all__ = ['AgreementView'] + +class AgreementView(TemplateView): + + def get_template_names(self): + current_lang = get_language() or 'zh-cn' + if current_lang.startswith('zh'): + return 'authentication/agreement_zh.html' + else: + return 'authentication/agreement.html' diff --git a/apps/common/utils/zip.py b/apps/common/utils/zip.py index b11443d3d..4cac77c96 100644 --- a/apps/common/utils/zip.py +++ b/apps/common/utils/zip.py @@ -2,6 +2,7 @@ import os import stat import hashlib from pathlib import Path +from rest_framework.exceptions import ValidationError from zipfile import ZipFile, BadZipFile @@ -14,7 +15,7 @@ MAX_TOTAL_SIZE = 1 * 1024 * 1024 * 1024 # 1GB MAX_COMPRESSION_RATIO = 100 # 解压 / 压缩 -class ZipSecurityError(Exception): +class ZipSecurityError(ValidationError): pass diff --git a/apps/i18n/lina/en.json b/apps/i18n/lina/en.json index 5decec9a1..158b7c824 100644 --- a/apps/i18n/lina/en.json +++ b/apps/i18n/lina/en.json @@ -1639,5 +1639,9 @@ "AccountSecretReadDisabled": "Account secret reading has been disabled by administrator", "AccessToken": "Access tokens", "AccessTokenTip": "Access Token is a temporary credential generated through the OAuth2 (Authorization Code Grant) flow using the JumpServer client, which is used to access protected resources.", - "Revoke": "Revoke" -} \ No newline at end of file + "Revoke": "Revoke", + "ReadAgreeTo": "I have read and agree to the", + "TermsOfService": "Terms of Service", + "PrivacyPolicy": "Privacy Policy", + "and": "and" +} diff --git a/apps/i18n/lina/zh.json b/apps/i18n/lina/zh.json index f2f4b107d..fd786f577 100644 --- a/apps/i18n/lina/zh.json +++ b/apps/i18n/lina/zh.json @@ -1650,5 +1650,9 @@ "AccessTokenTip": "访问令牌是通过 JumpServer 客户端使用 OAuth2(授权码授权)流程生成的临时凭证,用于访问受保护的资源。", "Revoke": "撤销", "AccountSecretReadDisabled": "账号密码读取功能已被管理员禁用", - "Automations": "自动化" -} \ No newline at end of file + "Automations": "自动化", + "ReadAgreeTo": "我已阅读并同意", + "TermsOfService": "服务条款", + "PrivacyPolicy": "隐私政策", + "and": "和" +} diff --git a/apps/ops/api/playbook.py b/apps/ops/api/playbook.py index 073d1161d..25f3e6484 100644 --- a/apps/ops/api/playbook.py +++ b/apps/ops/api/playbook.py @@ -1,6 +1,5 @@ import os import shutil -import zipfile from django.conf import settings from django.core.exceptions import SuspiciousFileOperation @@ -13,6 +12,7 @@ from common.api.generic import JMSBulkModelViewSet from common.exceptions import JMSException from common.permissions import IsOwnerOrAdminWritable from common.utils.http import is_true +from common.utils.zip import safe_extract_zip from rbac.permissions import RBACPermission from ..const import Scope from ..exception import PlaybookNoValidEntry @@ -27,9 +27,7 @@ from django.utils._os import safe_join def unzip_playbook(src, dest): - fz = zipfile.ZipFile(src, 'r') - for file in fz.namelist(): - fz.extract(file, dest) + safe_extract_zip(src, dest) class PlaybookViewSet(JMSBulkModelViewSet): diff --git a/apps/terminal/api/applet/applet.py b/apps/terminal/api/applet/applet.py index 0bbdc8fa3..b86e119da 100644 --- a/apps/terminal/api/applet/applet.py +++ b/apps/terminal/api/applet/applet.py @@ -2,7 +2,6 @@ import os import os.path import re import shutil -import zipfile from typing import Callable from django.conf import settings @@ -22,6 +21,7 @@ from common.utils import is_uuid from common.utils.http import is_true from terminal import serializers from terminal.models import AppletPublication, Applet +from common.utils.zip import safe_extract_zip __all__ = ['AppletViewSet', 'AppletPublicationViewSet'] @@ -47,10 +47,7 @@ class DownloadUploadMixin: shutil.rmtree(extract_to) try: - with zipfile.ZipFile(path) as zp: - if zp.testzip() is not None: - raise ValidationError({'error': _('Invalid zip file')}) - zp.extractall(extract_to) + safe_extract_zip(path, extract_to) except RuntimeError as e: raise ValidationError({'error': _('Invalid zip file') + ': {}'.format(e)}) diff --git a/apps/terminal/api/virtualapp/virtualapp.py b/apps/terminal/api/virtualapp/virtualapp.py index 7b4c5b9b2..63e7476d3 100644 --- a/apps/terminal/api/virtualapp/virtualapp.py +++ b/apps/terminal/api/virtualapp/virtualapp.py @@ -1,6 +1,5 @@ import os.path import shutil -import zipfile from typing import Callable from django.core.files.storage import default_storage @@ -16,6 +15,7 @@ from common.api import JMSBulkModelViewSet from common.serializers import FileSerializer from terminal import serializers from terminal.models import VirtualAppPublication, VirtualApp +from common.utils.zip import safe_extract_zip __all__ = ['VirtualAppViewSet', 'VirtualAppPublicationViewSet'] @@ -38,10 +38,7 @@ class UploadMixin: if os.path.exists(extract_to): shutil.rmtree(extract_to) try: - with zipfile.ZipFile(path) as zp: - if zp.testzip() is not None: - raise ValidationError({'error': _('Invalid zip file')}) - zp.extractall(extract_to) + safe_extract_zip(path, extract_to) except RuntimeError as e: raise ValidationError({'error': _('Invalid zip file') + ': {}'.format(e)}) tmp_dir = safe_join(extract_to, file.name.replace('.zip', ''))