From bc4258256ad6814b6ed4e85d870977eb27216dfe Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 20 May 2021 13:06:37 +0800 Subject: [PATCH 01/33] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- apps/templates/_copyright.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f5da3631..05d18ac6e 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - [Lina](https://github.com/jumpserver/lina) JumpServer Web UI 项目 - [Luna](https://github.com/jumpserver/luna) JumpServer Web Terminal 项目 - [KoKo](https://github.com/jumpserver/koko) JumpServer 字符协议 Connector 项目,替代原来 Python 版本的 [Coco](https://github.com/jumpserver/coco) -- [Guacamole](https://github.com/jumpserver/docker-guacamole) JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) +- [Lion](https://github.com/jumpserver/lion-release) JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) ## 贡献 如果有你好的想法创意,或者帮助我们修复了 Bug, 欢迎提交 Pull Request diff --git a/apps/templates/_copyright.html b/apps/templates/_copyright.html index 772c24cc5..241a5cfe4 100644 --- a/apps/templates/_copyright.html +++ b/apps/templates/_copyright.html @@ -1,2 +1,2 @@ -{% load i18n %} -Copyright {{ COPYRIGHT }} \ No newline at end of file +{#{% load i18n %}#} +{#Copyright {{ COPYRIGHT }}#} \ No newline at end of file From 572d0e3f276d53453909550d8c915933abc8c14c Mon Sep 17 00:00:00 2001 From: Michael Bai Date: Sat, 22 May 2021 00:09:54 +0800 Subject: [PATCH 02/33] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dparser=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=A4=84=E7=90=86int=E7=B1=BB=E5=9E=8B=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/parsers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/drf/parsers/base.py b/apps/common/drf/parsers/base.py index acffcfef8..32f93a1bf 100644 --- a/apps/common/drf/parsers/base.py +++ b/apps/common/drf/parsers/base.py @@ -94,7 +94,7 @@ class BaseFileParser(BaseParser): new_row_data = {} serializer_fields = self.serializer_fields for k, v in row_data.items(): - if isinstance(v, list) or isinstance(v, dict) or isinstance(v, str) and k.strip() and v.strip(): + if type(v) in [list, dict, int] or (isinstance(v, str) and k.strip() and v.strip()): # 解决类似disk_info为字符串的'{}'的问题 if not isinstance(v, str) and isinstance(serializer_fields[k], serializers.CharField): v = str(v) From f8b4259a8c1ec9a48b6310d2a2d314391c13523f Mon Sep 17 00:00:00 2001 From: Michael Bai Date: Sat, 22 May 2021 17:00:01 +0800 Subject: [PATCH 03/33] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA/=E6=9B=B4=E6=96=B0=E7=94=A8=E6=88=B7=E6=97=B6?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E7=AD=96=E7=95=A5=E7=9B=B8=E5=85=B3=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 75321 -> 75313 bytes apps/locale/zh/LC_MESSAGES/django.po | 93 ++++++++++++++------------- apps/users/models/user.py | 4 +- apps/users/serializers/user.py | 21 +++--- 4 files changed, 62 insertions(+), 56 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index b3bde6600c0bb872d8d1b75fc24aa8530be39c0b..53adc123954e272a9f8001bda791d4b7afcd7cc8 100644 GIT binary patch delta 14868 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 U`O}eW4%|N;xqfT7@696r16Ith($ delta 14867 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 O)3$8d80H(1_x}Omea-Ix diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index a64dde7cd..8f5f38858 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-21 11:08+0800\n" +"POT-Creation-Date: 2021-05-22 16:56+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -99,7 +99,7 @@ msgstr "动作" #: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 #: tickets/models/comment.py:17 users/models/user.py:176 -#: users/models/user.py:738 users/models/user.py:764 +#: users/models/user.py:740 users/models/user.py:766 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -184,7 +184,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models.py:47 #: xpack/plugins/change_auth_plan/models.py:278 -#: xpack/plugins/cloud/serializers.py:65 +#: xpack/plugins/cloud/serializers.py:51 msgid "Username" msgstr "用户名" @@ -285,7 +285,7 @@ msgid "Cluster" msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:63 +#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:49 msgid "Host" msgstr "主机" @@ -295,7 +295,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11 #: assets/models/asset.py:188 assets/models/domain.py:53 -#: xpack/plugins/cloud/serializers.py:64 +#: xpack/plugins/cloud/serializers.py:50 msgid "Port" msgstr "端口" @@ -325,7 +325,7 @@ msgstr "目标URL" #: xpack/plugins/change_auth_plan/models.py:68 #: xpack/plugins/change_auth_plan/models.py:190 #: xpack/plugins/change_auth_plan/models.py:285 -#: xpack/plugins/cloud/serializers.py:67 +#: xpack/plugins/cloud/serializers.py:53 msgid "Password" msgstr "密码" @@ -407,7 +407,7 @@ msgstr "激活" #: assets/models/asset.py:196 assets/models/cluster.py:19 #: assets/models/user.py:66 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:160 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:146 msgid "Admin user" msgstr "管理用户" @@ -497,7 +497,7 @@ msgstr "创建者" #: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25 #: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18 -#: users/models/user.py:765 xpack/plugins/cloud/models.py:107 +#: users/models/user.py:767 xpack/plugins/cloud/models.py:107 msgid "Date created" msgstr "创建日期" @@ -569,7 +569,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:750 +#: users/models/user.py:752 msgid "System" msgstr "系统" @@ -678,7 +678,7 @@ msgstr "ssh私钥" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:161 +#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:147 msgid "Node" msgstr "节点" @@ -2100,8 +2100,8 @@ msgid "" msgstr "应用列表中包含与授权类型不同的应用。({})" #: perms/serializers/asset/permission.py:45 -#: perms/serializers/asset/permission.py:69 users/serializers/user.py:34 -#: users/serializers/user.py:82 +#: perms/serializers/asset/permission.py:69 users/serializers/user.py:33 +#: users/serializers/user.py:81 msgid "Is expired" msgstr "是否过期" @@ -2121,7 +2121,7 @@ msgstr "资产名称" msgid "System users name" msgstr "系统用户名称" -#: perms/serializers/asset/permission.py:70 users/serializers/user.py:81 +#: perms/serializers/asset/permission.py:70 users/serializers/user.py:80 msgid "Is valid" msgstr "账户是否有效" @@ -3897,11 +3897,15 @@ msgstr "用户来源" msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:746 +#: users/models/user.py:603 +msgid "Need update password" +msgstr "需要更新密码" + +#: users/models/user.py:748 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:749 +#: users/models/user.py:751 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -3909,7 +3913,7 @@ msgstr "Administrator是初始的超级管理员" msgid "The old password is incorrect" msgstr "旧密码错误" -#: users/serializers/profile.py:36 users/serializers/user.py:125 +#: users/serializers/profile.py:36 users/serializers/user.py:126 msgid "Password does not match security rules" msgstr "密码不满足安全规则" @@ -3921,76 +3925,76 @@ msgstr "新密码不能是最近 {} 次的密码" msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:119 users/serializers/user.py:80 +#: users/serializers/profile.py:119 users/serializers/user.py:79 msgid "Is first login" msgstr "首次登录" -#: users/serializers/user.py:20 +#: users/serializers/user.py:22 msgid "Reset link will be generated and sent to the user" msgstr "生成重置密码链接,通过邮件发送给用户" -#: users/serializers/user.py:21 +#: users/serializers/user.py:23 msgid "Set password" msgstr "设置密码" -#: users/serializers/user.py:28 xpack/plugins/change_auth_plan/models.py:61 +#: users/serializers/user.py:27 xpack/plugins/change_auth_plan/models.py:61 #: xpack/plugins/change_auth_plan/serializers.py:30 msgid "Password strategy" msgstr "密码策略" -#: users/serializers/user.py:30 +#: users/serializers/user.py:29 msgid "MFA enabled" msgstr "是否开启多因子认证" -#: users/serializers/user.py:31 +#: users/serializers/user.py:30 msgid "MFA force enabled" msgstr "强制启用多因子认证" -#: users/serializers/user.py:32 +#: users/serializers/user.py:31 msgid "MFA level for display" msgstr "多因子认证等级(显示名称)" -#: users/serializers/user.py:33 +#: users/serializers/user.py:32 msgid "Login blocked" msgstr "登录被阻塞" -#: users/serializers/user.py:35 +#: users/serializers/user.py:34 msgid "Can update" msgstr "是否可更新" -#: users/serializers/user.py:36 +#: users/serializers/user.py:35 msgid "Can delete" msgstr "是否可删除" -#: users/serializers/user.py:39 users/serializers/user.py:87 +#: users/serializers/user.py:38 users/serializers/user.py:86 msgid "Organization role name" msgstr "组织角色名称" -#: users/serializers/user.py:83 +#: users/serializers/user.py:82 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/user.py:85 +#: users/serializers/user.py:84 msgid "Groups name" msgstr "用户组名" -#: users/serializers/user.py:86 +#: users/serializers/user.py:85 msgid "Source name" msgstr "用户来源名" -#: users/serializers/user.py:88 +#: users/serializers/user.py:87 msgid "Super role name" msgstr "超级角色名称" -#: users/serializers/user.py:89 +#: users/serializers/user.py:88 msgid "Total role name" msgstr "汇总角色名称" -#: users/serializers/user.py:113 +#: users/serializers/user.py:112 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/user.py:210 +#: users/serializers/user.py:211 msgid "name not unique" msgstr "名称重复" @@ -3999,7 +4003,7 @@ msgid "Security token validation" msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78 -#: xpack/plugins/cloud/serializers.py:159 +#: xpack/plugins/cloud/serializers.py:145 msgid "Account" msgstr "账户" @@ -4740,7 +4744,7 @@ msgstr "云服务商" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:140 +#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:126 msgid "Regions" msgstr "地域" @@ -4748,7 +4752,7 @@ msgstr "地域" msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:163 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:149 msgid "Always update" msgstr "总是更新" @@ -4940,24 +4944,20 @@ msgstr "" msgid "Subscription ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:49 -msgid "This field is required" -msgstr "这个字段是必填项" - -#: xpack/plugins/cloud/serializers.py:138 +#: xpack/plugins/cloud/serializers.py:124 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers.py:139 +#: xpack/plugins/cloud/serializers.py:125 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers.py:162 +#: xpack/plugins/cloud/serializers.py:148 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" -#: xpack/plugins/cloud/utils.py:65 +#: xpack/plugins/cloud/utils.py:64 msgid "Account unavailable" msgstr "账户无效" @@ -5045,6 +5045,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "This field is required" +#~ msgstr "这个字段是必填项" + #~ msgid "{} is required" #~ msgstr "{} 字段是必填项" diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 97a9e3d6d..6f5b52f14 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -599,7 +599,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): auto_now_add=True, blank=True, null=True, verbose_name=_('Date password last updated') ) - need_update_password = models.BooleanField(default=False) + need_update_password = models.BooleanField( + default=False, verbose_name=_('Need update password') + ) wecom_id = models.CharField(null=True, default=None, unique=True, max_length=128) dingtalk_id = models.CharField(null=True, default=None, unique=True, max_length=128) diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index d7591360b..46e4ca64a 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -2,6 +2,7 @@ # from django.core.cache import cache from django.utils.translation import ugettext_lazy as _ +from django.db.models import TextChoices from rest_framework import serializers from common.mixins import CommonBulkSerializerMixin @@ -17,15 +18,13 @@ __all__ = [ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): - EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user') - CUSTOM_PASSWORD = _('Set password') - PASSWORD_STRATEGY_CHOICES = ( - (0, EMAIL_SET_PASSWORD), - (1, CUSTOM_PASSWORD) - ) + class PasswordStrategy(TextChoices): + email = 'email', _('Reset link will be generated and sent to the user') + custom = 'custom', _('Set password') + password_strategy = serializers.ChoiceField( - choices=PASSWORD_STRATEGY_CHOICES, required=False, - label=_('Password strategy'), write_only=True, default=0 + choices=PasswordStrategy.choices, default=PasswordStrategy.email, required=False, + write_only=True, label=_('Password strategy') ) mfa_enabled = serializers.BooleanField(read_only=True, label=_('MFA enabled')) mfa_force_enabled = serializers.BooleanField(read_only=True, label=_('MFA force enabled')) @@ -117,9 +116,11 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer): def validate_password(self, password): from ..utils import check_password_rules password_strategy = self.initial_data.get('password_strategy') - if password_strategy == '0': + if self.instance is None and password_strategy != self.PasswordStrategy.custom: + # 创建用户,使用邮件设置密码 return - if password_strategy is None and not password: + if self.instance and not password: + # 更新用户, 未设置密码 return if not check_password_rules(password): msg = _('Password does not match security rules') From 7edc9c37f8e0bb29de5c822732e388656bfa571f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= Date: Fri, 21 May 2021 11:29:20 +0800 Subject: [PATCH 04/33] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8e86a2c7..3a3d4d47c 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 - [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/) - [完整文档](https://docs.jumpserver.org) - [演示视频](https://www.bilibili.com/video/BV1ZV41127GB) +- [手动安装](https://github.com/jumpserver/installer) ## 组件项目 - [Lina](https://github.com/jumpserver/lina) JumpServer Web UI 项目 From 33fb063f78da12e220a294b5e5ac260abe0abe75 Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 24 May 2021 10:48:46 +0800 Subject: [PATCH 05/33] =?UTF-8?q?perf:=20=E6=9A=82=E6=97=B6=E7=A6=81?= =?UTF-8?q?=E7=94=A8xrdp=E5=AE=9E=E6=97=B6=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/models/session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/terminal/models/session.py b/apps/terminal/models/session.py index 86843433e..b3202a9d9 100644 --- a/apps/terminal/models/session.py +++ b/apps/terminal/models/session.py @@ -109,8 +109,11 @@ class Session(OrgModelMixin): _PROTOCOL = self.PROTOCOL if self.is_finished: return False + if self.login_from == self.LOGIN_FROM.RT: + return False if self.protocol in [ - _PROTOCOL.SSH, _PROTOCOL.VNC, _PROTOCOL.RDP, _PROTOCOL.TELNET, _PROTOCOL.K8S + _PROTOCOL.SSH, _PROTOCOL.VNC, _PROTOCOL.RDP, + _PROTOCOL.TELNET, _PROTOCOL.K8S ]: return True else: From 6b46f5b48eb1f1e11d6950ffb6f24bb1ec90732e Mon Sep 17 00:00:00 2001 From: Bai Date: Mon, 24 May 2021 19:11:47 +0800 Subject: [PATCH 06/33] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0ApplicationUser?= =?UTF-8?q?List=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 37 ++++++++++++++++++++-- apps/applications/urls/api_urls.py | 1 + apps/perms/utils/application/permission.py | 4 +++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index cb51ff023..b4255b426 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -2,18 +2,49 @@ # from orgs.mixins.api import OrgBulkModelViewSet +from rest_framework import generics -from ..hands import IsOrgAdminOrAppUser +from ..hands import IsOrgAdminOrAppUser, IsOrgAdmin from .. import models, serializers +from ..models import Application +from assets.models import SystemUser +from assets.serializers import SystemUserListSerializer +from perms.models import ApplicationPermission +from ..const import ApplicationCategoryChoices -__all__ = ['ApplicationViewSet'] +__all__ = ['ApplicationViewSet', 'ApplicationUserListApi'] class ApplicationViewSet(OrgBulkModelViewSet): - model = models.Application + model = Application filterset_fields = ('name', 'type', 'category') search_fields = filterset_fields permission_classes = (IsOrgAdminOrAppUser,) serializer_class = serializers.ApplicationSerializer + +class ApplicationUserListApi(generics.ListAPIView): + permission_classes = (IsOrgAdmin, ) + serializer_class = SystemUserListSerializer + + def get_application(self): + application = None + app_id = self.request.query_params.get('application_id') + if app_id: + application = Application.objects.get(id=app_id) + return application + + def get_queryset(self): + queryset = SystemUser.objects.none() + application = self.get_application() + if not application: + return queryset + if application.category == ApplicationCategoryChoices.remote_app: + return queryset + system_user_ids = ApplicationPermission.objects.filter(applications=application)\ + .values_list('system_users', flat=True) + if not system_user_ids: + return queryset + queryset = SystemUser.objects.filter(id__in=system_user_ids) + return queryset diff --git a/apps/applications/urls/api_urls.py b/apps/applications/urls/api_urls.py index ab463a401..9ca50d32c 100644 --- a/apps/applications/urls/api_urls.py +++ b/apps/applications/urls/api_urls.py @@ -14,6 +14,7 @@ router.register(r'applications', api.ApplicationViewSet, 'application') urlpatterns = [ path('remote-apps//connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'), + path('application-users/', api.ApplicationUserListApi.as_view(), name='application-user') ] diff --git a/apps/perms/utils/application/permission.py b/apps/perms/utils/application/permission.py index c4ebb5bdb..939a8e53c 100644 --- a/apps/perms/utils/application/permission.py +++ b/apps/perms/utils/application/permission.py @@ -72,3 +72,7 @@ def get_application_system_user_ids(user, application): def has_application_system_permission(user, application, system_user): system_user_ids = get_application_system_user_ids(user, application) return system_user.id in system_user_ids + + +def get_application_system_users_ids(application): + return system_users_id From b82e9f860b60940fdb0cd3597c026d1174599234 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 26 May 2021 15:25:02 +0800 Subject: [PATCH 07/33] =?UTF-8?q?fix:=20users=20=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0035_auto_20210526_1100.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/users/migrations/0035_auto_20210526_1100.py diff --git a/apps/users/migrations/0035_auto_20210526_1100.py b/apps/users/migrations/0035_auto_20210526_1100.py new file mode 100644 index 000000000..4d4357a2b --- /dev/null +++ b/apps/users/migrations/0035_auto_20210526_1100.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2021-05-26 03:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0034_auto_20210506_1448'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='need_update_password', + field=models.BooleanField(default=False, verbose_name='Need update password'), + ), + ] From 4eef25982dd74158438b61da69d8ded67b500dd9 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 27 May 2021 18:42:43 +0800 Subject: [PATCH 08/33] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20ApplicationU?= =?UTF-8?q?serList=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index b4255b426..93caa97d1 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -26,6 +26,8 @@ class ApplicationViewSet(OrgBulkModelViewSet): class ApplicationUserListApi(generics.ListAPIView): permission_classes = (IsOrgAdmin, ) + filterset_fields = ('name', 'username') + search_fields = filterset_fields serializer_class = SystemUserListSerializer def get_application(self): @@ -40,8 +42,8 @@ class ApplicationUserListApi(generics.ListAPIView): application = self.get_application() if not application: return queryset - if application.category == ApplicationCategoryChoices.remote_app: - return queryset + # if application.category == ApplicationCategoryChoices.remote_app: + # return queryset system_user_ids = ApplicationPermission.objects.filter(applications=application)\ .values_list('system_users', flat=True) if not system_user_ids: From 4ef3b2630a1c866c4a634040b412ea6a3cc3ada2 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Mon, 31 May 2021 17:20:38 +0800 Subject: [PATCH 09/33] =?UTF-8?q?feat:=20=E7=AB=99=E5=86=85=E4=BF=A1=20(#6?= =?UTF-8?q?183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加站内信 * s * s * 添加接口 * fix * fix * 重构了一些 * 完成 * 完善 * s * s * s * s * s * s * 测试ok * 替换业务中发送消息的方式 * 修改 * s * 去掉 update 兼容 create * 添加 unread total 接口 * 调整json字段 Co-authored-by: xinwen --- .gitignore | 1 + apps/jumpserver/settings/base.py | 1 + apps/jumpserver/urls.py | 1 + apps/notifications/__init__.py | 0 apps/notifications/api/__init__.py | 2 + apps/notifications/api/notifications.py | 72 +++++++++ apps/notifications/api/site_msgs.py | 59 ++++++++ apps/notifications/apps.py | 5 + apps/notifications/backends/__init__.py | 36 +++++ apps/notifications/backends/base.py | 32 ++++ apps/notifications/backends/dingtalk.py | 20 +++ apps/notifications/backends/email.py | 14 ++ apps/notifications/backends/site_msg.py | 14 ++ apps/notifications/backends/wecom.py | 20 +++ apps/notifications/migrations/0001_initial.py | 92 ++++++++++++ apps/notifications/migrations/__init__.py | 0 apps/notifications/models/__init__.py | 2 + apps/notifications/models/notification.py | 50 ++++++ apps/notifications/models/site_msg.py | 29 ++++ apps/notifications/notifications.py | 141 +++++++++++++++++ apps/notifications/serializers/__init__.py | 2 + .../serializers/notifications.py | 29 ++++ apps/notifications/serializers/site_msgs.py | 28 ++++ apps/notifications/site_msg.py | 84 +++++++++++ apps/notifications/tests.py | 3 + apps/notifications/urls.py | 15 ++ apps/ops/apps.py | 1 + apps/ops/models/command.py | 6 +- apps/ops/notifications.py | 26 ++++ apps/ops/tasks.py | 4 +- apps/ops/utils.py | 10 -- apps/terminal/api/command.py | 10 +- apps/terminal/apps.py | 1 + apps/terminal/notifications.py | 142 ++++++++++++++++++ apps/terminal/utils.py | 72 --------- apps/users/models/user.py | 6 + 36 files changed, 936 insertions(+), 94 deletions(-) create mode 100644 apps/notifications/__init__.py create mode 100644 apps/notifications/api/__init__.py create mode 100644 apps/notifications/api/notifications.py create mode 100644 apps/notifications/api/site_msgs.py create mode 100644 apps/notifications/apps.py create mode 100644 apps/notifications/backends/__init__.py create mode 100644 apps/notifications/backends/base.py create mode 100644 apps/notifications/backends/dingtalk.py create mode 100644 apps/notifications/backends/email.py create mode 100644 apps/notifications/backends/site_msg.py create mode 100644 apps/notifications/backends/wecom.py create mode 100644 apps/notifications/migrations/0001_initial.py create mode 100644 apps/notifications/migrations/__init__.py create mode 100644 apps/notifications/models/__init__.py create mode 100644 apps/notifications/models/notification.py create mode 100644 apps/notifications/models/site_msg.py create mode 100644 apps/notifications/notifications.py create mode 100644 apps/notifications/serializers/__init__.py create mode 100644 apps/notifications/serializers/notifications.py create mode 100644 apps/notifications/serializers/site_msgs.py create mode 100644 apps/notifications/site_msg.py create mode 100644 apps/notifications/tests.py create mode 100644 apps/notifications/urls.py create mode 100644 apps/ops/notifications.py create mode 100644 apps/terminal/notifications.py diff --git a/.gitignore b/.gitignore index cb931287b..5d5eb57db 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ dump.rdb .tox .cache/ .idea/ +.vscode/ db.sqlite3 config.py config.yml diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 4a2e59062..1d4b2f995 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -48,6 +48,7 @@ INSTALLED_APPS = [ 'applications.apps.ApplicationsConfig', 'tickets.apps.TicketsConfig', 'acls.apps.AclsConfig', + 'notifications', 'common.apps.CommonConfig', 'jms_oidc_rp', 'rest_framework', diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 687b7f2ae..510654048 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -23,6 +23,7 @@ api_v1 = [ path('applications/', include('applications.urls.api_urls', namespace='api-applications')), path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')), + path('notifications/', include('notifications.urls', namespace='api-notifications')), path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()), ] diff --git a/apps/notifications/__init__.py b/apps/notifications/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/notifications/api/__init__.py b/apps/notifications/api/__init__.py new file mode 100644 index 000000000..bde5ef849 --- /dev/null +++ b/apps/notifications/api/__init__.py @@ -0,0 +1,2 @@ +from .notifications import * +from .site_msgs import * diff --git a/apps/notifications/api/notifications.py b/apps/notifications/api/notifications.py new file mode 100644 index 000000000..7d176e7ae --- /dev/null +++ b/apps/notifications/api/notifications.py @@ -0,0 +1,72 @@ +from django.http import Http404 +from rest_framework.mixins import ListModelMixin, UpdateModelMixin +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status + +from common.drf.api import JmsGenericViewSet +from notifications.notifications import system_msgs +from notifications.models import SystemMsgSubscription +from notifications.backends import BACKEND +from notifications.serializers import ( + SystemMsgSubscriptionSerializer, SystemMsgSubscriptionByCategorySerializer +) + +__all__ = ('BackendListView', 'SystemMsgSubscriptionViewSet') + + +class BackendListView(APIView): + def get(self, request): + data = [ + { + 'name': backend, + 'name_display': backend.label + } + for backend in BACKEND + if backend.is_enable + ] + return Response(data=data) + + +class SystemMsgSubscriptionViewSet(ListModelMixin, + UpdateModelMixin, + JmsGenericViewSet): + lookup_field = 'message_type' + queryset = SystemMsgSubscription.objects.all() + serializer_classes = { + 'list': SystemMsgSubscriptionByCategorySerializer, + 'update': SystemMsgSubscriptionSerializer, + 'partial_update': SystemMsgSubscriptionSerializer + } + + def list(self, request, *args, **kwargs): + data = [] + category_children_mapper = {} + + subscriptions = self.get_queryset() + msgtype_sub_mapper = {} + for sub in subscriptions: + msgtype_sub_mapper[sub.message_type] = sub + + for msg in system_msgs: + message_type = msg['message_type'] + message_type_label = msg['message_type_label'] + category = msg['category'] + category_label = msg['category_label'] + + if category not in category_children_mapper: + children = [] + + data.append({ + 'category': category, + 'category_label': category_label, + 'children': children + }) + category_children_mapper[category] = children + + sub = msgtype_sub_mapper[message_type] + sub.message_type_label = message_type_label + category_children_mapper[category].append(sub) + + serializer = self.get_serializer(data, many=True) + return Response(data=serializer.data) diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py new file mode 100644 index 000000000..e64ac23e2 --- /dev/null +++ b/apps/notifications/api/site_msgs.py @@ -0,0 +1,59 @@ +from rest_framework.response import Response +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin +from rest_framework.decorators import action + +from common.permissions import IsValidUser +from common.const.http import GET, PATCH, POST +from common.drf.api import JmsGenericViewSet +from ..serializers import ( + SiteMessageListSerializer, SiteMessageRetrieveSerializer, SiteMessageIdsSerializer, + SiteMessageSendSerializer, +) +from ..site_msg import SiteMessage + +__all__ = ('SiteMessageViewSet', ) + + +class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): + permission_classes = (IsValidUser,) + serializer_classes = { + 'retrieve': SiteMessageRetrieveSerializer, + 'unread': SiteMessageListSerializer, + 'list': SiteMessageListSerializer, + 'mark_as_read': SiteMessageIdsSerializer, + 'send': SiteMessageSendSerializer, + } + + def get_queryset(self): + user = self.request.user + msgs = SiteMessage.get_user_all_msgs(user.id) + return msgs + + @action(methods=[GET], detail=False) + def unread(self, request, **kwargs): + user = request.user + msgs = SiteMessage.get_user_unread_msgs(user.id) + msgs = self.filter_queryset(msgs) + return self.get_paginated_response_with_query_set(msgs) + + @action(methods=[GET], detail=False, url_path='unread-total') + def unread_total(self, request, **kwargs): + user = request.user + msgs = SiteMessage.get_user_unread_msgs(user.id) + return Response(data={'total': msgs.count()}) + + @action(methods=[PATCH], detail=False) + def mark_as_read(self, request, **kwargs): + user = request.user + seri = self.get_serializer(data=request.data) + seri.is_valid(raise_exception=True) + ids = seri.validated_data['ids'] + SiteMessage.mark_msgs_as_read(user.id, ids) + return Response({'detail': 'ok'}) + + @action(methods=[POST], detail=False) + def send(self, request, **kwargs): + seri = self.get_serializer(data=request.data) + seri.is_valid(raise_exception=True) + SiteMessage.send_msg(**seri.validated_data, sender=request.user) + return Response({'detail': 'ok'}) diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py new file mode 100644 index 000000000..9c260e0b1 --- /dev/null +++ b/apps/notifications/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + name = 'notifications' diff --git a/apps/notifications/backends/__init__.py b/apps/notifications/backends/__init__.py new file mode 100644 index 000000000..4e2633072 --- /dev/null +++ b/apps/notifications/backends/__init__.py @@ -0,0 +1,36 @@ +from django.utils.translation import gettext_lazy as _ +from django.db import models + +from .dingtalk import DingTalk +from .email import Email +from .site_msg import SiteMessage +from .wecom import WeCom + + +class BACKEND(models.TextChoices): + EMAIL = 'email', _('Email') + WECOM = 'wecom', _('WeCom') + DINGTALK = 'dingtalk', _('DingTalk') + SITE_MSG = 'site_msg', _('Site message') + + @property + def client(self): + client = { + self.EMAIL: Email, + self.WECOM: WeCom, + self.DINGTALK: DingTalk, + self.SITE_MSG: SiteMessage + }[self] + return client + + def get_account(self, user): + return self.client.get_account(user) + + @property + def is_enable(self): + return self.client.is_enable() + + @classmethod + def filter_enable_backends(cls, backends): + enable_backends = [b for b in backends if cls(b).is_enable] + return enable_backends diff --git a/apps/notifications/backends/base.py b/apps/notifications/backends/base.py new file mode 100644 index 000000000..67a2d5b03 --- /dev/null +++ b/apps/notifications/backends/base.py @@ -0,0 +1,32 @@ +from django.conf import settings + + +class BackendBase: + # User 表中的字段 + account_field = None + + # Django setting 中的字段名 + is_enable_field_in_settings = None + + def get_accounts(self, users): + accounts = [] + unbound_users = [] + account_user_mapper = {} + + for user in users: + account = getattr(user, self.account_field, None) + if account: + account_user_mapper[account] = user + accounts.append(account) + else: + unbound_users.append(user) + return accounts, unbound_users, account_user_mapper + + @classmethod + def get_account(cls, user): + return getattr(user, cls.account_field) + + @classmethod + def is_enable(cls): + enable = getattr(settings, cls.is_enable_field_in_settings) + return bool(enable) diff --git a/apps/notifications/backends/dingtalk.py b/apps/notifications/backends/dingtalk.py new file mode 100644 index 000000000..ef5e9a9c6 --- /dev/null +++ b/apps/notifications/backends/dingtalk.py @@ -0,0 +1,20 @@ +from django.conf import settings + +from common.message.backends.dingtalk import DingTalk as Client +from .base import BackendBase + + +class DingTalk(BackendBase): + account_field = 'dingtalk_id' + is_enable_field_in_settings = 'AUTH_DINGTALK' + + def __init__(self): + self.dingtalk = Client( + appid=settings.DINGTALK_APPKEY, + appsecret=settings.DINGTALK_APPSECRET, + agentid=settings.DINGTALK_AGENTID + ) + + def send_msg(self, users, msg): + accounts, __, __ = self.get_accounts(users) + return self.dingtalk.send_text(accounts, msg) diff --git a/apps/notifications/backends/email.py b/apps/notifications/backends/email.py new file mode 100644 index 000000000..b1cdec755 --- /dev/null +++ b/apps/notifications/backends/email.py @@ -0,0 +1,14 @@ +from django.conf import settings +from django.core.mail import send_mail + +from .base import BackendBase + + +class Email(BackendBase): + account_field = 'email' + is_enable_field_in_settings = 'EMAIL_HOST_USER' + + def send_msg(self, users, subject, message): + from_email = settings.EMAIL_FROM or settings.EMAIL_HOST_USER + accounts, __, __ = self.get_accounts(users) + send_mail(subject, message, from_email, accounts) diff --git a/apps/notifications/backends/site_msg.py b/apps/notifications/backends/site_msg.py new file mode 100644 index 000000000..33032843a --- /dev/null +++ b/apps/notifications/backends/site_msg.py @@ -0,0 +1,14 @@ +from notifications.site_msg import SiteMessage as Client +from .base import BackendBase + + +class SiteMessage(BackendBase): + account_field = 'id' + + def send_msg(self, users, subject, message): + accounts, __, __ = self.get_accounts(users) + Client.send_msg(subject, message, user_ids=accounts) + + @classmethod + def is_enable(cls): + return True diff --git a/apps/notifications/backends/wecom.py b/apps/notifications/backends/wecom.py new file mode 100644 index 000000000..80b6f1a22 --- /dev/null +++ b/apps/notifications/backends/wecom.py @@ -0,0 +1,20 @@ +from django.conf import settings + +from common.message.backends.wecom import WeCom as Client +from .base import BackendBase + + +class WeCom(BackendBase): + account_field = 'wecom_id' + is_enable_field_in_settings = 'AUTH_WECOM' + + def __init__(self): + self.wecom = Client( + corpid=settings.WECOM_CORPID, + corpsecret=settings.WECOM_SECRET, + agentid=settings.WECOM_AGENTID + ) + + def send_msg(self, users, msg): + accounts, __, __ = self.get_accounts(users) + return self.wecom.send_text(accounts, msg) diff --git a/apps/notifications/migrations/0001_initial.py b/apps/notifications/migrations/0001_initial.py new file mode 100644 index 000000000..ebe79f304 --- /dev/null +++ b/apps/notifications/migrations/0001_initial.py @@ -0,0 +1,92 @@ +# Generated by Django 3.1 on 2021-05-31 08:59 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0035_auto_20210526_1100'), + ] + + operations = [ + migrations.CreateModel( + name='SiteMessage', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('subject', models.CharField(max_length=1024)), + ('message', models.TextField()), + ('is_broadcast', models.BooleanField(default=False)), + ('groups', models.ManyToManyField(to='users.UserGroup')), + ('sender', models.ForeignKey(db_constraint=False, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='send_site_message', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='UserMsgSubscription', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('message_type', models.CharField(max_length=128)), + ('receive_backends', models.JSONField(default=list)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_msg_subscriptions', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SystemMsgSubscription', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('message_type', models.CharField(max_length=128, unique=True)), + ('receive_backends', models.JSONField(default=list)), + ('groups', models.ManyToManyField(related_name='system_msg_subscriptions', to='users.UserGroup')), + ('users', models.ManyToManyField(related_name='system_msg_subscriptions', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SiteMessageUsers', + fields=[ + ('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')), + ('updated_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Updated by')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('has_read', models.BooleanField(default=False)), + ('read_at', models.DateTimeField(default=None, null=True)), + ('sitemessage', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='m2m_sitemessageusers', to='notifications.sitemessage')), + ('user', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='m2m_sitemessageusers', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='sitemessage', + name='users', + field=models.ManyToManyField(related_name='recv_site_messages', through='notifications.SiteMessageUsers', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/notifications/migrations/__init__.py b/apps/notifications/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/notifications/models/__init__.py b/apps/notifications/models/__init__.py new file mode 100644 index 000000000..dede7511d --- /dev/null +++ b/apps/notifications/models/__init__.py @@ -0,0 +1,2 @@ +from .notification import * +from .site_msg import * diff --git a/apps/notifications/models/notification.py b/apps/notifications/models/notification.py new file mode 100644 index 000000000..94bd1ad7d --- /dev/null +++ b/apps/notifications/models/notification.py @@ -0,0 +1,50 @@ +from django.db import models + +from common.db.models import JMSModel + +__all__ = ('SystemMsgSubscription', 'UserMsgSubscription') + + +class UserMsgSubscription(JMSModel): + message_type = models.CharField(max_length=128) + user = models.ForeignKey('users.User', related_name='user_msg_subscriptions', on_delete=models.CASCADE) + receive_backends = models.JSONField(default=list) + + def __str__(self): + return f'{self.message_type}' + + +class SystemMsgSubscription(JMSModel): + message_type = models.CharField(max_length=128, unique=True) + users = models.ManyToManyField('users.User', related_name='system_msg_subscriptions') + groups = models.ManyToManyField('users.UserGroup', related_name='system_msg_subscriptions') + receive_backends = models.JSONField(default=list) + + message_type_label = '' + + def __str__(self): + return f'{self.message_type}' + + def __repr__(self): + return self.__str__() + + @property + def receivers(self): + from notifications.backends import BACKEND + + users = [user for user in self.users.all()] + + for group in self.groups.all(): + for user in group.users.all(): + users.append(user) + + receive_backends = self.receive_backends + receviers = [] + + for user in users: + recevier = {'name': str(user), 'id': user.id} + for backend in receive_backends: + recevier[backend] = bool(BACKEND(backend).get_account(user)) + receviers.append(recevier) + + return receviers diff --git a/apps/notifications/models/site_msg.py b/apps/notifications/models/site_msg.py new file mode 100644 index 000000000..3e3c09baa --- /dev/null +++ b/apps/notifications/models/site_msg.py @@ -0,0 +1,29 @@ +from django.db import models + +from common.db.models import JMSModel + +__all__ = ('SiteMessageUsers', 'SiteMessage') + + +class SiteMessageUsers(JMSModel): + sitemessage = models.ForeignKey('notifications.SiteMessage', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') + user = models.ForeignKey('users.User', on_delete=models.CASCADE, db_constraint=False, related_name='m2m_sitemessageusers') + has_read = models.BooleanField(default=False) + read_at = models.DateTimeField(default=None, null=True) + + +class SiteMessage(JMSModel): + subject = models.CharField(max_length=1024) + message = models.TextField() + users = models.ManyToManyField( + 'users.User', through=SiteMessageUsers, related_name='recv_site_messages' + ) + groups = models.ManyToManyField('users.UserGroup') + is_broadcast = models.BooleanField(default=False) + sender = models.ForeignKey( + 'users.User', db_constraint=False, on_delete=models.DO_NOTHING, null=True, default=None, + related_name='send_site_message' + ) + + has_read = False + read_at = None diff --git a/apps/notifications/notifications.py b/apps/notifications/notifications.py new file mode 100644 index 000000000..8563fd214 --- /dev/null +++ b/apps/notifications/notifications.py @@ -0,0 +1,141 @@ +from typing import Iterable +import traceback +from itertools import chain + +from django.db.utils import ProgrammingError +from celery import shared_task + +from notifications.backends import BACKEND +from .models import SystemMsgSubscription + +__all__ = ('SystemMessage', 'UserMessage') + + +system_msgs = [] +user_msgs = [] + + +class MessageType(type): + def __new__(cls, name, bases, attrs: dict): + clz = type.__new__(cls, name, bases, attrs) + + if 'message_type_label' in attrs \ + and 'category' in attrs \ + and 'category_label' in attrs: + message_type = clz.get_message_type() + + msg = { + 'message_type': message_type, + 'message_type_label': attrs['message_type_label'], + 'category': attrs['category'], + 'category_label': attrs['category_label'], + } + if issubclass(clz, SystemMessage): + system_msgs.append(msg) + try: + if not SystemMsgSubscription.objects.filter(message_type=message_type).exists(): + sub = SystemMsgSubscription.objects.create(message_type=message_type) + clz.post_insert_to_db(sub) + except ProgrammingError as e: + if e.args[0] == 1146: + # 表不存在 + pass + else: + raise + elif issubclass(clz, UserMessage): + user_msgs.append(msg) + + return clz + + +@shared_task +def publish_task(msg): + msg.publish() + + +class Message(metaclass=MessageType): + """ + 这里封装了什么? + 封装不同消息的模板,提供统一的发送消息的接口 + - publish 该方法的实现与消息订阅的表结构有关 + - send_msg + """ + + message_type_label: str + category: str + category_label: str + + @classmethod + def get_message_type(cls): + return cls.__name__ + + def publish_async(self): + return publish_task.delay(self) + + def publish(self): + raise NotImplementedError + + def send_msg(self, users: Iterable, backends: Iterable = BACKEND): + for backend in backends: + try: + backend = BACKEND(backend) + + get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg) + msg = get_msg_method() + client = backend.client() + + if isinstance(msg, dict): + client.send_msg(users, **msg) + else: + client.send_msg(users, msg) + except: + traceback.print_exc() + + def get_common_msg(self) -> str: + raise NotImplementedError + + def get_dingtalk_msg(self) -> str: + return self.get_common_msg() + + def get_wecom_msg(self) -> str: + return self.get_common_msg() + + def get_email_msg(self) -> dict: + msg = self.get_common_msg() + return { + 'subject': msg, + 'message': msg + } + + def get_site_msg_msg(self) -> dict: + msg = self.get_common_msg() + return { + 'subject': msg, + 'message': msg + } + + +class SystemMessage(Message): + def publish(self): + subscription = SystemMsgSubscription.objects.get( + message_type=self.get_message_type() + ) + + # 只发送当前有效后端 + receive_backends = subscription.receive_backends + receive_backends = BACKEND.filter_enable_backends(receive_backends) + + users = [ + *subscription.users.all(), + *chain(*[g.users.all() for g in subscription.groups.all()]) + ] + + self.send_msg(users, receive_backends) + + @classmethod + def post_insert_to_db(cls, subscription: SystemMsgSubscription): + pass + + +class UserMessage(Message): + pass diff --git a/apps/notifications/serializers/__init__.py b/apps/notifications/serializers/__init__.py new file mode 100644 index 000000000..bde5ef849 --- /dev/null +++ b/apps/notifications/serializers/__init__.py @@ -0,0 +1,2 @@ +from .notifications import * +from .site_msgs import * diff --git a/apps/notifications/serializers/notifications.py b/apps/notifications/serializers/notifications.py new file mode 100644 index 000000000..7415d46f7 --- /dev/null +++ b/apps/notifications/serializers/notifications.py @@ -0,0 +1,29 @@ +from rest_framework import serializers + +from common.drf.serializers import BulkModelSerializer +from notifications.models import SystemMsgSubscription + + +class SystemMsgSubscriptionSerializer(BulkModelSerializer): + receive_backends = serializers.ListField(child=serializers.CharField()) + + class Meta: + model = SystemMsgSubscription + fields = ( + 'message_type', 'message_type_label', + 'users', 'groups', 'receive_backends', 'receivers' + ) + read_only_fields = ( + 'message_type', 'message_type_label', 'receivers' + ) + extra_kwargs = { + 'users': {'allow_empty': True}, + 'groups': {'allow_empty': True}, + 'receive_backends': {'required': True} + } + + +class SystemMsgSubscriptionByCategorySerializer(serializers.Serializer): + category = serializers.CharField() + category_label = serializers.CharField() + children = SystemMsgSubscriptionSerializer(many=True) diff --git a/apps/notifications/serializers/site_msgs.py b/apps/notifications/serializers/site_msgs.py new file mode 100644 index 000000000..8d76205e1 --- /dev/null +++ b/apps/notifications/serializers/site_msgs.py @@ -0,0 +1,28 @@ +from rest_framework.serializers import ModelSerializer +from rest_framework import serializers + +from ..models import SiteMessage + + +class SiteMessageListSerializer(ModelSerializer): + class Meta: + model = SiteMessage + fields = ['id', 'subject', 'has_read', 'read_at'] + + +class SiteMessageRetrieveSerializer(ModelSerializer): + class Meta: + model = SiteMessage + fields = ['id', 'subject', 'message', 'has_read', 'read_at'] + + +class SiteMessageIdsSerializer(serializers.Serializer): + ids = serializers.ListField(child=serializers.UUIDField()) + + +class SiteMessageSendSerializer(serializers.Serializer): + subject = serializers.CharField() + message = serializers.CharField() + user_ids = serializers.ListField(child=serializers.UUIDField(), required=False) + group_ids = serializers.ListField(child=serializers.UUIDField(), required=False) + is_broadcast = serializers.BooleanField(default=False) diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py new file mode 100644 index 000000000..944a8ea3c --- /dev/null +++ b/apps/notifications/site_msg.py @@ -0,0 +1,84 @@ +from django.db.models import F + +from common.utils.timezone import now +from users.models import User +from .models import SiteMessage as SiteMessageModel, SiteMessageUsers + + +class SiteMessage: + + @classmethod + def send_msg(cls, subject, message, user_ids=(), group_ids=(), sender=None, is_broadcast=False): + if not any((user_ids, group_ids, is_broadcast)): + raise ValueError('No recipient is specified') + + site_msg = SiteMessageModel.objects.create( + subject=subject, message=message, + is_broadcast=is_broadcast, sender=sender + ) + + if is_broadcast: + user_ids = User.objects.all().values_list('id', flat=True) + else: + if group_ids: + site_msg.groups.add(*group_ids) + + user_ids_from_group = User.groups.through.objects.filter( + usergroup_id__in=group_ids + ).values_list('user_id', flat=True) + + user_ids = [*user_ids, *user_ids_from_group] + + site_msg.users.add(*user_ids) + + @classmethod + def get_user_all_msgs(cls, user_id): + site_msgs = SiteMessageModel.objects.filter( + m2m_sitemessageusers__user_id=user_id + ).distinct().annotate( + has_read=F('m2m_sitemessageusers__has_read'), + read_at=F('m2m_sitemessageusers__read_at') + ).order_by('-date_created') + + return site_msgs + + @classmethod + def get_user_all_msgs_count(cls, user_id): + site_msgs_count = SiteMessageModel.objects.filter( + m2m_sitemessageusers__user_id=user_id + ).distinct().count() + return site_msgs_count + + @classmethod + def get_user_unread_msgs(cls, user_id): + site_msgs = SiteMessageModel.objects.filter( + m2m_sitemessageusers__user_id=user_id, + m2m_sitemessageusers__has_read=False + ).distinct().annotate( + has_read=F('m2m_sitemessageusers__has_read'), + read_at=F('m2m_sitemessageusers__read_at') + ).order_by('-date_created') + + return site_msgs + + @classmethod + def get_user_unread_msgs_count(cls, user_id): + site_msgs_count = SiteMessageModel.objects.filter( + m2m_sitemessageusers__user_id=user_id, + m2m_sitemessageusers__has_read=False + ).distinct().count() + return site_msgs_count + + @classmethod + def mark_msgs_as_read(cls, user_id, msg_ids): + sitemsg_users = SiteMessageUsers.objects.filter( + user_id=user_id, sitemessage_id__in=msg_ids, + has_read=False + ) + + for sitemsg_user in sitemsg_users: + sitemsg_user.has_read = True + sitemsg_user.read_at = now() + + SiteMessageUsers.objects.bulk_update( + sitemsg_users, fields=('has_read', 'read_at')) diff --git a/apps/notifications/tests.py b/apps/notifications/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/apps/notifications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/notifications/urls.py b/apps/notifications/urls.py new file mode 100644 index 000000000..ad05c4aca --- /dev/null +++ b/apps/notifications/urls.py @@ -0,0 +1,15 @@ + +from rest_framework_bulk.routes import BulkRouter +from django.urls import path + +from . import api + +app_name = 'notifications' + +router = BulkRouter() +router.register('system-msg-subscription', api.SystemMsgSubscriptionViewSet, 'system-msg-subscription') +router.register('site-message', api.SiteMessageViewSet, 'site-message') + +urlpatterns = [ + path('backends/', api.BackendListView.as_view(), name='backends') +] + router.urls diff --git a/apps/ops/apps.py b/apps/ops/apps.py index 8bdc04ce8..5133c6655 100644 --- a/apps/ops/apps.py +++ b/apps/ops/apps.py @@ -13,4 +13,5 @@ class OpsConfig(AppConfig): from orgs.utils import set_current_org set_current_org(Organization.root()) from .celery import signal_handler + from . import notifications super().ready() diff --git a/apps/ops/models/command.py b/apps/ops/models/command.py index 0a2012e73..e89520390 100644 --- a/apps/ops/models/command.py +++ b/apps/ops/models/command.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from django.db import models -from terminal.utils import send_command_execution_alert_mail +from terminal.notifications import CommandExecutionAlert from common.utils import lazyproperty from orgs.models import Organization from orgs.mixins.models import OrgModelMixin @@ -99,12 +99,12 @@ class CommandExecution(OrgModelMixin): else: msg = _("Command `{}` is forbidden ........").format(self.command) print('\033[31m' + msg + '\033[0m') - send_command_execution_alert_mail({ + CommandExecutionAlert({ 'input': self.command, 'assets': self.hosts.all(), 'user': str(self.user), 'risk_level': 5, - }) + }).publish_async() self.result = {"error": msg} self.org_id = self.run_as.org_id self.is_finished = True diff --git a/apps/ops/notifications.py b/apps/ops/notifications.py new file mode 100644 index 000000000..61e9d5630 --- /dev/null +++ b/apps/ops/notifications.py @@ -0,0 +1,26 @@ +from django.utils.translation import gettext_lazy as _ + +from notifications.notifications import SystemMessage +from notifications.models import SystemMsgSubscription +from users.models import User + +__all__ = ('ServerPerformanceMessage',) + + +class ServerPerformanceMessage(SystemMessage): + category = 'Operations' + category_label = _('Operations') + message_type_label = _('Server performance') + + def __init__(self, path, usage): + self.path = path + self.usage = usage + + def get_common_msg(self): + msg = _("Disk used more than 80%: {} => {}").format(self.path, self.usage.percent) + return msg + + @classmethod + def post_insert_to_db(cls, subscription: SystemMsgSubscription): + admins = User.objects.filter(role=User.ROLE.ADMIN) + subscription.users.add(*admins) diff --git a/apps/ops/tasks.py b/apps/ops/tasks.py index 02cc9290e..60f639668 100644 --- a/apps/ops/tasks.py +++ b/apps/ops/tasks.py @@ -20,7 +20,7 @@ from .celery.utils import ( disable_celery_periodic_task, delete_celery_periodic_task ) from .models import Task, CommandExecution, CeleryTask -from .utils import send_server_performance_mail +from .notifications import ServerPerformanceMessage logger = get_logger(__file__) @@ -143,7 +143,7 @@ def check_server_performance_period(): if path.startswith(uncheck_path): need_check = False if need_check and usage.percent > 80: - send_server_performance_mail(path, usage, usages) + ServerPerformanceMessage(path=path, usage=usage).publish() @shared_task(queue="ansible") diff --git a/apps/ops/utils.py b/apps/ops/utils.py index 5ce4494a6..9993ea2cb 100644 --- a/apps/ops/utils.py +++ b/apps/ops/utils.py @@ -69,16 +69,6 @@ def update_or_create_ansible_task( return task, created -def send_server_performance_mail(path, usage, usages): - from users.models import User - subject = _("Disk used more than 80%: {} => {}").format(path, usage.percent) - message = subject - admins = User.objects.filter(role=User.ROLE.ADMIN) - recipient_list = [u.email for u in admins if u.email] - logger.info(subject) - send_mail_async(subject, message, recipient_list, html_message=message) - - def get_task_log_path(base_path, task_id, level=2): task_id = str(task_id) try: diff --git a/apps/terminal/api/command.py b/apps/terminal/api/command.py index 497e40fbe..b43910e26 100644 --- a/apps/terminal/api/command.py +++ b/apps/terminal/api/command.py @@ -4,28 +4,24 @@ import time from django.conf import settings from django.utils import timezone from django.shortcuts import HttpResponse -from rest_framework import viewsets from rest_framework import generics from rest_framework.fields import DateTimeField from rest_framework.response import Response -from rest_framework.decorators import action from django.template import loader -from common.http import is_true -from terminal.models import CommandStorage, Command +from terminal.models import CommandStorage from terminal.filters import CommandFilter from orgs.utils import current_org from common.permissions import IsOrgAdminOrAppUser, IsOrgAuditor, IsAppUser -from common.const.http import GET from common.drf.api import JMSBulkModelViewSet from common.utils import get_logger -from terminal.utils import send_command_alert_mail from terminal.serializers import InsecureCommandAlertSerializer from terminal.exceptions import StorageInvalid from ..backends import ( get_command_storage, get_multi_command_storage, SessionCommandSerializer, ) +from ..notifications import CommandAlertMessage logger = get_logger(__name__) __all__ = ['CommandViewSet', 'CommandExportApi', 'InsecureCommandAlertAPI'] @@ -211,5 +207,5 @@ class InsecureCommandAlertAPI(generics.CreateAPIView): if command['risk_level'] >= settings.SECURITY_INSECURE_COMMAND_LEVEL and \ settings.SECURITY_INSECURE_COMMAND and \ settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER: - send_command_alert_mail(command) + CommandAlertMessage(command).publish_async() return Response() diff --git a/apps/terminal/apps.py b/apps/terminal/apps.py index f0cb05bf2..edaa38cef 100644 --- a/apps/terminal/apps.py +++ b/apps/terminal/apps.py @@ -10,4 +10,5 @@ class TerminalConfig(AppConfig): def ready(self): from . import signals_handler + from . import notifications return super().ready() diff --git a/apps/terminal/notifications.py b/apps/terminal/notifications.py new file mode 100644 index 000000000..fb70e3535 --- /dev/null +++ b/apps/terminal/notifications.py @@ -0,0 +1,142 @@ +from django.utils.translation import gettext_lazy as _ +from django.conf import settings + +from users.models import User +from common.utils import get_logger, reverse +from notifications.notifications import SystemMessage +from terminal.models import Session, Command +from notifications.models import SystemMsgSubscription + +logger = get_logger(__name__) + +__all__ = ('CommandAlertMessage', 'CommandExecutionAlert') + +CATEGORY = 'terminal' +CATEGORY_LABEL = _('Terminal') + + +class CommandAlertMixin: + @classmethod + def post_insert_to_db(cls, subscription: SystemMsgSubscription): + """ + 兼容操作,试图用 `settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER` 的邮件地址找到 + 用户,把用户设置为默认接收者 + """ + emails = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',') + emails = [email.strip() for email in emails] + + users = User.objects.filter(email__in=emails) + subscription.users.add(*users) + + +class CommandAlertMessage(CommandAlertMixin, SystemMessage): + category = CATEGORY + category_label = CATEGORY_LABEL + message_type_label = _('Terminal command alert') + + def __init__(self, command): + self.command = command + + def _get_message(self): + command = self.command + session_obj = Session.objects.get(id=command['session']) + + message = _(""" + Command: %(command)s +
+ Asset: %(host_name)s (%(host_ip)s) +
+ User: %(user)s +
+ Level: %(risk_level)s +
+ Session: session detail +
+ """) % { + 'command': command['input'], + 'host_name': command['asset'], + 'host_ip': session_obj.asset_obj.ip, + 'user': command['user'], + 'risk_level': Command.get_risk_level_str(command['risk_level']), + 'session_detail_url': reverse('api-terminal:session-detail', + kwargs={'pk': command['session']}, + external=True, api_to_ui=True), + } + + return message + + def get_common_msg(self): + return self._get_message() + + def get_email_msg(self): + command = self.command + session_obj = Session.objects.get(id=command['session']) + + input = command['input'] + if isinstance(input, str): + input = input.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ') + + subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % { + 'name': command['user'], + 'login_from': session_obj.get_login_from_display(), + 'remote_addr': session_obj.remote_addr, + 'command': input + } + + message = self._get_message(command) + + return { + 'subject': subject, + 'message': message + } + + +class CommandExecutionAlert(CommandAlertMixin, SystemMessage): + category = CATEGORY + category_label = CATEGORY_LABEL + message_type_label = _('Batch command alert') + + def __init__(self, command): + self.command = command + + def _get_message(self): + command = self.command + input = command['input'] + input = input.replace('\n', '
') + + assets = ', '.join([str(asset) for asset in command['assets']]) + message = _(""" +
+ Assets: %(assets)s +
+ User: %(user)s +
+ Level: %(risk_level)s +
+ + ----------------- Commands ----------------
+ %(command)s
+ ----------------- Commands ----------------
+ """) % { + 'command': input, + 'assets': assets, + 'user': command['user'], + 'risk_level': Command.get_risk_level_str(command['risk_level']), + } + return message + + def get_common_msg(self): + return self._get_message() + + def get_email_msg(self): + command = self.command + + subject = _("Insecure Web Command Execution Alert: [%(name)s]") % { + 'name': command['user'], + } + message = self._get_message(command) + + return { + 'subject': subject, + 'message': message + } diff --git a/apps/terminal/utils.py b/apps/terminal/utils.py index b13383fba..68b09bcd0 100644 --- a/apps/terminal/utils.py +++ b/apps/terminal/utils.py @@ -68,78 +68,6 @@ def get_session_replay_url(session): return local_path, url -def send_command_alert_mail(command): - session_obj = Session.objects.get(id=command['session']) - - input = command['input'] - if isinstance(input, str): - input = input.replace('\r\n', ' ').replace('\r', ' ').replace('\n', ' ') - - subject = _("Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s") % { - 'name': command['user'], - 'login_from': session_obj.get_login_from_display(), - 'remote_addr': session_obj.remote_addr, - 'command': input - } - - recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',') - message = _(""" - Command: %(command)s -
- Asset: %(host_name)s (%(host_ip)s) -
- User: %(user)s -
- Level: %(risk_level)s -
- Session: session detail -
- """) % { - 'command': command['input'], - 'host_name': command['asset'], - 'host_ip': session_obj.asset_obj.ip, - 'user': command['user'], - 'risk_level': Command.get_risk_level_str(command['risk_level']), - 'session_detail_url': reverse('api-terminal:session-detail', - kwargs={'pk': command['session']}, - external=True, api_to_ui=True), - } - logger.debug(message) - - send_mail_async.delay(subject, message, recipient_list, html_message=message) - - -def send_command_execution_alert_mail(command): - subject = _("Insecure Web Command Execution Alert: [%(name)s]") % { - 'name': command['user'], - } - input = command['input'] - input = input.replace('\n', '
') - recipient_list = settings.SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER.split(',') - - assets = ', '.join([str(asset) for asset in command['assets']]) - message = _(""" -
- Assets: %(assets)s -
- User: %(user)s -
- Level: %(risk_level)s -
- - ----------------- Commands ----------------
- %(command)s
- ----------------- Commands ----------------
- """) % { - 'command': input, - 'assets': assets, - 'user': command['user'], - 'risk_level': Command.get_risk_level_str(command['risk_level']), - } - - send_mail_async.delay(subject, message, recipient_list, html_message=message) - - class ComputeStatUtil: # system status @staticmethod diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 6f5b52f14..f362e60ac 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -608,6 +608,12 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): def __str__(self): return '{0.name}({0.username})'.format(self) + @classmethod + def get_group_ids_by_user_id(cls, user_id): + group_ids = cls.groups.through.objects.filter(user_id=user_id).distinct().values_list('usergroup_id', flat=True) + group_ids = list(group_ids) + return group_ids + @property def is_wecom_bound(self): return bool(self.wecom_id) From bdab93260f05cf7a02cd7ca51dc9204041edcfc4 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 2 Jun 2021 17:00:31 +0800 Subject: [PATCH 10/33] =?UTF-8?q?feat:=20=E8=B5=84=E4=BA=A7=E7=94=A8?= =?UTF-8?q?=E6=88=B7API=E8=BF=94=E5=9B=9E=20BackendDisplay=20=E5=92=8C=20N?= =?UTF-8?q?ame=20=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/backends/base.py | 4 +- apps/assets/backends/db.py | 15 +++- apps/assets/models/asset_user.py | 1 + apps/assets/serializers/asset_user.py | 5 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 75313 -> 75515 bytes apps/locale/zh/LC_MESSAGES/django.po | 94 ++++++++++++++------------ 6 files changed, 72 insertions(+), 47 deletions(-) diff --git a/apps/assets/backends/base.py b/apps/assets/backends/base.py index 3b27a57af..17115afaa 100644 --- a/apps/assets/backends/base.py +++ b/apps/assets/backends/base.py @@ -31,11 +31,11 @@ class BaseBackend: def qs_to_values(qs): values = qs.values( 'hostname', 'ip', "asset_id", - 'username', 'password', 'private_key', 'public_key', + 'name', 'username', 'password', 'private_key', 'public_key', 'score', 'version', "asset_username", "union_id", 'date_created', 'date_updated', - 'org_id', 'backend', + 'org_id', 'backend', 'backend_display' ) return values diff --git a/apps/assets/backends/db.py b/apps/assets/backends/db.py index 386f0ee29..aa3e1ef78 100644 --- a/apps/assets/backends/db.py +++ b/apps/assets/backends/db.py @@ -106,6 +106,7 @@ class DBBackend(BaseBackend): class SystemUserBackend(DBBackend): model = SystemUser.assets.through backend = 'system_user' + backend_display = _('System user') prefer = backend base_score = 0 union_id_length = 2 @@ -138,6 +139,7 @@ class SystemUserBackend(DBBackend): kwargs = dict( hostname=F("asset__hostname"), ip=F("asset__ip"), + name=F("systemuser__name"), username=F("systemuser__username"), password=F("systemuser__password"), private_key=F("systemuser__private_key"), @@ -152,7 +154,8 @@ class SystemUserBackend(DBBackend): union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"), output_field=CharField()), org_id=F("asset__org_id"), - backend=Value(self.backend, CharField()) + backend=Value(self.backend, CharField()), + backend_display=Value(self.backend_display, CharField()), ) return kwargs @@ -174,12 +177,17 @@ class SystemUserBackend(DBBackend): class DynamicSystemUserBackend(SystemUserBackend): backend = 'system_user_dynamic' + backend_display = _('System user(Dynamic)') prefer = 'system_user' union_id_length = 3 def get_annotate(self): kwargs = super().get_annotate() kwargs.update(dict( + name=Concat( + F("systemuser__users__name"), Value('('), F("systemuser__name"), Value(')'), + output_field=CharField() + ), username=F("systemuser__users__username"), asset_username=Concat( F("asset__id"), Value("_"), @@ -221,6 +229,7 @@ class DynamicSystemUserBackend(SystemUserBackend): class AdminUserBackend(DBBackend): model = Asset backend = 'admin_user' + backend_display = _('Admin user') prefer = backend base_score = 200 @@ -246,6 +255,7 @@ class AdminUserBackend(DBBackend): def all(self): qs = self.model.objects.all().annotate( asset_id=F("id"), + name=F("admin_user__name"), username=F("admin_user__username"), password=F("admin_user__password"), private_key=F("admin_user__private_key"), @@ -256,6 +266,7 @@ class AdminUserBackend(DBBackend): asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()), union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()), backend=Value(self.backend, CharField()), + backend_display=Value(self.backend_display, CharField()), ) qs = self.qs_to_values(qs) return qs @@ -264,6 +275,7 @@ class AdminUserBackend(DBBackend): class AuthbookBackend(DBBackend): model = AuthBook backend = 'db' + backend_display = _('Database') prefer = backend base_score = 400 @@ -313,6 +325,7 @@ class AuthbookBackend(DBBackend): asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()), union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()), backend=Value(self.backend, CharField()), + backend_display=Value(self.backend_display, CharField()), ) qs = self.qs_to_values(qs) return qs diff --git a/apps/assets/models/asset_user.py b/apps/assets/models/asset_user.py index 118d4549b..ac9112427 100644 --- a/apps/assets/models/asset_user.py +++ b/apps/assets/models/asset_user.py @@ -7,6 +7,7 @@ class AssetUser(AuthBook): hostname = "" ip = "" backend = "" + backend_display = "" union_id = "" asset_username = "" diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index 19cb2adc7..90145399f 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -47,16 +47,17 @@ class AssetUserReadSerializer(AssetUserWriteSerializer): ip = serializers.CharField(read_only=True, label=_("IP")) asset = serializers.CharField(source='asset_id', label=_('Asset')) backend = serializers.CharField(read_only=True, label=_("Backend")) + backend_display = serializers.CharField(read_only=True, label=_("Backend (Display)")) class Meta(AssetUserWriteSerializer.Meta): read_only_fields = ( 'date_created', 'date_updated', 'created_by', 'version', ) - fields_mini = ['id', 'username'] + fields_mini = ['id', 'name', 'username'] fields_write_only = ['password', 'private_key', "public_key"] fields_small = fields_mini + fields_write_only + [ - 'backend', 'version', + 'backend', 'backend_display', 'version', 'date_created', "date_updated", 'comment' ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 53adc123954e272a9f8001bda791d4b7afcd7cc8..562853c5ed03958ff5cc4444f0131527efcd9509 100644 GIT binary patch delta 22171 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\n" "Language-Team: JumpServer team\n" @@ -121,7 +121,7 @@ msgstr "系统用户" #: applications/serializers/attrs/application_category/remote_app.py:33 #: assets/models/asset.py:355 assets/models/authbook.py:26 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:34 -#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:89 +#: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:90 #: assets/serializers/system_user.py:201 audits/models.py:38 #: perms/models/asset_permission.py:99 templates/index.html:82 #: terminal/backends/command/models.py:19 @@ -184,7 +184,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models.py:47 #: xpack/plugins/change_auth_plan/models.py:278 -#: xpack/plugins/cloud/serializers.py:51 +#: xpack/plugins/cloud/serializers.py:65 msgid "Username" msgstr "用户名" @@ -233,6 +233,7 @@ msgstr "所有复核人都不属于组织 `{}`" #: applications/const.py:9 #: applications/serializers/attrs/application_category/db.py:14 #: applications/serializers/attrs/application_type/mysql_workbench.py:26 +#: assets/backends/db.py:278 msgid "Database" msgstr "数据库" @@ -285,7 +286,7 @@ msgid "Cluster" msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:49 +#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:63 msgid "Host" msgstr "主机" @@ -295,7 +296,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11 #: assets/models/asset.py:188 assets/models/domain.py:53 -#: xpack/plugins/cloud/serializers.py:50 +#: xpack/plugins/cloud/serializers.py:64 msgid "Port" msgstr "端口" @@ -315,7 +316,7 @@ msgstr "目标URL" #: applications/serializers/attrs/application_type/custom.py:25 #: applications/serializers/attrs/application_type/mysql_workbench.py:34 #: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:252 assets/serializers/asset_user.py:76 +#: assets/models/base.py:252 assets/serializers/asset_user.py:77 #: audits/signals_handler.py:58 authentication/forms.py:22 #: authentication/templates/authentication/login.html:164 #: settings/serializers/settings.py:93 users/forms/profile.py:21 @@ -325,7 +326,7 @@ msgstr "目标URL" #: xpack/plugins/change_auth_plan/models.py:68 #: xpack/plugins/change_auth_plan/models.py:190 #: xpack/plugins/change_auth_plan/models.py:285 -#: xpack/plugins/cloud/serializers.py:53 +#: xpack/plugins/cloud/serializers.py:67 msgid "Password" msgstr "密码" @@ -357,11 +358,35 @@ msgstr "不能删除根节点 ({})" msgid "Deletion failed and the node contains assets" msgstr "删除失败,节点包含资产" -#: assets/backends/db.py:244 +#: assets/backends/db.py:109 assets/models/user.py:228 audits/models.py:39 +#: perms/models/application_permission.py:31 +#: perms/models/asset_permission.py:101 templates/_nav.html:45 +#: terminal/backends/command/models.py:20 +#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 +#: users/templates/users/_granted_assets.html:27 +#: users/templates/users/user_asset_permission.html:42 +#: users/templates/users/user_asset_permission.html:76 +#: users/templates/users/user_asset_permission.html:159 +#: users/templates/users/user_database_app_permission.html:40 +#: users/templates/users/user_database_app_permission.html:67 +msgid "System user" +msgstr "系统用户" + +#: assets/backends/db.py:180 +msgid "System user(Dynamic)" +msgstr "系统用户(动态)" + +#: assets/backends/db.py:232 assets/models/asset.py:196 +#: assets/models/cluster.py:19 assets/models/user.py:66 templates/_nav.html:44 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:160 +msgid "Admin user" +msgstr "管理用户" + +#: assets/backends/db.py:253 msgid "Could not remove asset admin user" msgstr "不能移除资产的管理用户账号" -#: assets/backends/db.py:305 +#: assets/backends/db.py:317 msgid "Latest version could not be delete" msgstr "最新版本的不能被删除" @@ -405,12 +430,6 @@ msgstr "节点" msgid "Is active" msgstr "激活" -#: assets/models/asset.py:196 assets/models/cluster.py:19 -#: assets/models/user.py:66 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:146 -msgid "Admin user" -msgstr "管理用户" - #: assets/models/asset.py:199 msgid "Public IP" msgstr "公网IP" @@ -678,7 +697,7 @@ msgstr "ssh私钥" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:147 +#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:161 msgid "Node" msgstr "节点" @@ -740,20 +759,6 @@ msgstr "家目录" msgid "System groups" msgstr "用户组" -#: assets/models/user.py:228 audits/models.py:39 -#: perms/models/application_permission.py:31 -#: perms/models/asset_permission.py:101 templates/_nav.html:45 -#: terminal/backends/command/models.py:20 -#: terminal/backends/command/serializers.py:14 terminal/models/session.py:42 -#: users/templates/users/_granted_assets.html:27 -#: users/templates/users/user_asset_permission.html:42 -#: users/templates/users/user_asset_permission.html:76 -#: users/templates/users/user_asset_permission.html:159 -#: users/templates/users/user_database_app_permission.html:40 -#: users/templates/users/user_database_app_permission.html:67 -msgid "System user" -msgstr "系统用户" - #: assets/models/utils.py:35 #, python-format msgid "%(value)s is not an even number" @@ -813,12 +818,16 @@ msgstr "ID" msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:80 users/forms/profile.py:160 +#: assets/serializers/asset_user.py:50 +msgid "Backend (Display)" +msgstr "后端 (显示名称)" + +#: assets/serializers/asset_user.py:81 users/forms/profile.py:160 #: users/models/user.py:580 users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" -#: assets/serializers/asset_user.py:84 users/models/user.py:577 +#: assets/serializers/asset_user.py:85 users/models/user.py:577 msgid "Private key" msgstr "ssh私钥" @@ -4003,7 +4012,7 @@ msgid "Security token validation" msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78 -#: xpack/plugins/cloud/serializers.py:145 +#: xpack/plugins/cloud/serializers.py:159 msgid "Account" msgstr "账户" @@ -4744,7 +4753,7 @@ msgstr "云服务商" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:126 +#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:140 msgid "Regions" msgstr "地域" @@ -4752,7 +4761,7 @@ msgstr "地域" msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:149 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:163 msgid "Always update" msgstr "总是更新" @@ -4944,20 +4953,24 @@ msgstr "" msgid "Subscription ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:124 +#: xpack/plugins/cloud/serializers.py:49 +msgid "This field is required" +msgstr "这个字段是必填项" + +#: xpack/plugins/cloud/serializers.py:138 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers.py:125 +#: xpack/plugins/cloud/serializers.py:139 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers.py:148 +#: xpack/plugins/cloud/serializers.py:162 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" -#: xpack/plugins/cloud/utils.py:64 +#: xpack/plugins/cloud/utils.py:65 msgid "Account unavailable" msgstr "账户无效" @@ -5045,9 +5058,6 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" -#~ msgid "This field is required" -#~ msgstr "这个字段是必填项" - #~ msgid "{} is required" #~ msgstr "{} 字段是必填项" From a809eac2b8eea9b703a5fa6a34c9bb91dc41d2a4 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 2 Jun 2021 17:36:47 +0800 Subject: [PATCH 11/33] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=20Metadata=20=E6=97=B6=EF=BC=8C=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=9A=84=E6=80=BB=E6=98=AF=20action=20=E4=B8=BA=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/metadata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index cc2903d2f..3a0e98c50 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -18,7 +18,7 @@ from rest_framework.request import clone_request class SimpleMetadataWithFilters(SimpleMetadata): """Override SimpleMetadata, adding info about filters""" - methods = {"PUT", "POST", "GET"} + methods = {"PUT", "POST", "GET", "PATCH"} attrs = [ 'read_only', 'label', 'help_text', 'min_length', 'max_length', @@ -32,6 +32,7 @@ class SimpleMetadataWithFilters(SimpleMetadata): """ actions = {} for method in self.methods & set(view.allowed_methods): + view.action = view.action_map.get(method.lower(), view.action) view.request = clone_request(request, method) try: # Test global permissions From a9bdbcf7c6415ce8ff63c47da7b8a37f002d2bd6 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 2 Jun 2021 19:11:08 +0800 Subject: [PATCH 12/33] =?UTF-8?q?fix:=20metadata=20api=20view=20=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/common/drf/metadata.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/common/drf/metadata.py b/apps/common/drf/metadata.py index 3a0e98c50..afd7389bb 100644 --- a/apps/common/drf/metadata.py +++ b/apps/common/drf/metadata.py @@ -32,7 +32,9 @@ class SimpleMetadataWithFilters(SimpleMetadata): """ actions = {} for method in self.methods & set(view.allowed_methods): - view.action = view.action_map.get(method.lower(), view.action) + if hasattr(view, 'action_map'): + view.action = view.action_map.get(method.lower(), view.action) + view.request = clone_request(request, method) try: # Test global permissions From adae509bc08c45da488ad629924ce77e122378dc Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 3 Jun 2021 11:16:54 +0800 Subject: [PATCH 13/33] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/api.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/orgs/api.py b/apps/orgs/api.py index ace14112b..14ad9cb20 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -48,7 +48,6 @@ class OrgViewSet(BulkModelViewSet): queryset = Organization.objects.all() serializer_class = OrgSerializer permission_classes = (IsSuperUserOrAppUser,) - org = None def get_serializer_class(self): mapper = { @@ -58,32 +57,36 @@ class OrgViewSet(BulkModelViewSet): return mapper.get(self.action, super().get_serializer_class()) @tmp_to_root_org() - def get_data_from_model(self, model): + def get_data_from_model(self, org, model): if model == User: data = model.objects.filter( - orgs__id=self.org.id, - m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR] + orgs__id=org.id, m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR] ) elif model == Node: - # 跟节点不能手动删除,所以排除检查 - data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') + # 根节点不能手动删除,所以排除检查 + data = model.objects.filter(org_id=org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') else: - data = model.objects.filter(org_id=self.org.id) + data = model.objects.filter(org_id=org.id) return data - def destroy(self, request, *args, **kwargs): - self.org = self.get_object() + def allow_bulk_destroy(self, qs, filtered): + return False + + def perform_destroy(self, instance): + if str(current_org) == str(instance): + msg = _('The current organization ({}) cannot be deleted'.format(current_org)) + raise PermissionDenied(detail=msg) + for model in org_related_models: - data = self.get_data_from_model(model) - if data: - msg = _('Have {} exists, Please delete').format(model._meta.verbose_name) - return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN) - else: - if str(current_org) == str(self.org): - msg = _('The current organization cannot be deleted') - return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN) - self.org.delete() - return Response({'msg': True}, status=status.HTTP_200_OK) + data = self.get_data_from_model(instance, model) + if not data: + continue + msg = _( + 'The organization have resource ({}) cannot be deleted' + ).format(model._meta.verbose_name) + raise PermissionDenied(detail=msg) + + super().perform_destroy(instance) class OrgMemberRelationBulkViewSet(JMSBulkRelationModelViewSet): From 1e99be17753ed0941e3c5d56ae998de7c154fc08 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 3 Jun 2021 13:59:44 +0800 Subject: [PATCH 14/33] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=BA=94=E7=94=A8=E7=94=A8=E6=88=B7API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/applications/api/application.py | 2 -- apps/perms/utils/application/permission.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/apps/applications/api/application.py b/apps/applications/api/application.py index 93caa97d1..84426b6b2 100644 --- a/apps/applications/api/application.py +++ b/apps/applications/api/application.py @@ -42,8 +42,6 @@ class ApplicationUserListApi(generics.ListAPIView): application = self.get_application() if not application: return queryset - # if application.category == ApplicationCategoryChoices.remote_app: - # return queryset system_user_ids = ApplicationPermission.objects.filter(applications=application)\ .values_list('system_users', flat=True) if not system_user_ids: diff --git a/apps/perms/utils/application/permission.py b/apps/perms/utils/application/permission.py index 939a8e53c..c4ebb5bdb 100644 --- a/apps/perms/utils/application/permission.py +++ b/apps/perms/utils/application/permission.py @@ -72,7 +72,3 @@ def get_application_system_user_ids(user, application): def has_application_system_permission(user, application, system_user): system_user_ids = get_application_system_user_ids(user, application) return system_user.id in system_user_ids - - -def get_application_system_users_ids(application): - return system_users_id From e8b3ee4565176efa118b642edeeaf3f20d37d37d Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 31 May 2021 15:01:14 +0800 Subject: [PATCH 15/33] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=94=A8=E6=88=B7=EF=BC=8C=E6=94=AF=E6=8C=81=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=AE=BE=E7=BD=AE=E4=B8=B4=E6=97=B6=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit perf: 优化rdp file下载 perf: 修改密码途观选项 perf: 优化api获取 --- apps/assets/api/system_user.py | 59 ++++++++++----- apps/assets/models/base.py | 3 +- apps/assets/models/user.py | 80 ++++++++++++++++++++- apps/assets/serializers/system_user.py | 8 +++ apps/assets/urls/api_urls.py | 4 +- apps/authentication/api/connection_token.py | 14 +++- apps/authentication/serializers.py | 4 +- apps/jumpserver/conf.py | 4 +- apps/jumpserver/settings/custom.py | 2 + apps/settings/serializers/settings.py | 7 +- 10 files changed, 156 insertions(+), 29 deletions(-) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 8ec151285..d0cc5662d 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -1,16 +1,15 @@ # ~*~ coding: utf-8 ~*~ from django.shortcuts import get_object_or_404 from rest_framework.response import Response +from rest_framework.exceptions import ValidationError from common.utils import get_logger -from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser -from common.drf.filters import CustomFilter +from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics -from orgs.utils import tmp_to_org from ..models import SystemUser, Asset from .. import serializers -from ..serializers import SystemUserWithAuthInfoSerializer +from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer from ..tasks import ( push_system_user_to_assets_manual, test_system_user_connectivity_manual, push_system_user_to_assets @@ -21,6 +20,7 @@ logger = get_logger(__file__) __all__ = [ 'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi', 'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi', 'SystemUserAssetsListView', + 'SystemUserTempAuthInfoApi', 'SystemUserAppAuthInfoApi', ] @@ -57,6 +57,23 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): return Response(status=204) +class SystemUserTempAuthInfoApi(generics.CreateAPIView): + model = SystemUser + permission_classes = (IsValidUser,) + serializer_class = SystemUserTempAuthSerializer + + def create(self, request, *args, **kwargs): + serializer = super().get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + pk = kwargs.get('pk') + instance = get_object_or_404(SystemUser, pk=pk) + data = serializer.validated_data + user = self.request.user + instance_id = data.get('instance_id') + instance.set_temp_auth(instance_id, user, data) + return Response(serializer.data, status=201) + + class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): """ Get system user with asset auth info @@ -65,22 +82,30 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView): permission_classes = (IsOrgAdminOrAppUser,) serializer_class = SystemUserWithAuthInfoSerializer - def get_exception_handler(self): - def handler(e, context): - return Response({"error": str(e)}, status=400) - return handler + def get_object(self): + instance = super().get_object() + asset_id = self.kwargs.get('asset_id') + user_id = self.request.query_params.get("user_id") + username = self.request.query_params.get("username") + instance.load_asset_more_auth(asset_id=asset_id, user_id=user_id, username=username) + return instance + + +class SystemUserAppAuthInfoApi(generics.RetrieveAPIView): + """ + Get system user with asset auth info + """ + model = SystemUser + permission_classes = (IsOrgAdminOrAppUser,) + serializer_class = SystemUserWithAuthInfoSerializer def get_object(self): instance = super().get_object() - username = instance.username - if instance.username_same_with_user: - username = self.request.query_params.get("username") - asset_id = self.kwargs.get('aid') - asset = get_object_or_404(Asset, pk=asset_id) - - with tmp_to_org(asset.org_id): - instance.load_asset_special_auth(asset=asset, username=username) - return instance + app_id = self.kwargs.get('app_id') + user_id = self.request.query_params.get("user_id") + if user_id: + instance.load_app_more_auth(app_id, user_id) + return instance class SystemUserTaskApi(generics.CreateAPIView): diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 5ed16741f..6623cc6a7 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -11,8 +11,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings -from common.db.models import ChoiceSet -from common.utils import random_string +from common.utils import random_string, signer from common.utils import ( ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty ) diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index 37af8ea86..b64aeb758 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -7,9 +7,10 @@ import logging from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.cache import cache -from common.utils import signer -from common.fields.model import JsonListCharField +from common.utils import signer, get_object_or_none +from common.exceptions import JMSException from .base import BaseUser from .asset import Asset @@ -185,6 +186,81 @@ class SystemUser(BaseUser): if self.username_same_with_user: self.username = other.username + def set_temp_auth(self, asset_or_app_id, user_id, auth, ttl=300): + if not auth: + raise ValueError('Auth not set') + key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id) + logger.debug(f'Set system user temp auth: {key}') + cache.set(key, auth, ttl) + + def get_temp_auth(self, asset_or_app_id, user_id): + key = 'TEMP_PASSWORD_{}_{}_{}'.format(self.id, asset_or_app_id, user_id) + logger.debug(f'Get system user temp auth: {key}') + password = cache.get(key) + return password + + def load_tmp_auth_if_has(self, asset_or_app_id, user): + if not asset_or_app_id or not user: + return + if self.login_mode != self.LOGIN_MANUAL: + pass + + auth = self.get_temp_auth(asset_or_app_id, user) + if not auth: + return + username = auth.get('username') + password = auth.get('password') + + if username: + self.username = username + if password: + self.password = password + + def load_app_more_auth(self, app_id=None, user_id=None): + from users.models import User + + if self.login_mode == self.LOGIN_MANUAL: + self.password = '' + self.private_key = '' + if not user_id: + return + user = get_object_or_none(User, pk=user_id) + if not user: + return + self.load_tmp_auth_if_has(app_id, user) + + def load_asset_more_auth(self, asset_id=None, username=None, user_id=None): + from users.models import User + + if self.login_mode == self.LOGIN_MANUAL: + self.password = '' + self.private_key = '' + + asset = None + if asset_id: + asset = get_object_or_none(Asset, pk=asset_id) + # 没有资产就没有必要继续了 + if not asset: + logger.debug('Asset not found, pass') + return + + user = None + if user_id: + user = get_object_or_none(User, pk=user_id) + + if self.username_same_with_user: + if user and not username: + username = user.username + + # 加载某个资产的特殊配置认证信息 + try: + self.load_asset_special_auth(asset, username) + except Exception as e: + logger.error('Load special auth Error: ', e) + pass + + self.load_tmp_auth_if_has(asset_id, user) + @property def cmd_filter_rules(self): from .cmd_filter import CommandFilterRule diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index 7f5befaed..726f53e34 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -14,6 +14,7 @@ __all__ = [ 'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer', 'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer', 'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer', + 'SystemUserTempAuthSerializer', ] @@ -272,3 +273,10 @@ class SystemUserTaskSerializer(serializers.Serializer): many=True ) task = serializers.CharField(read_only=True) + + +class SystemUserTempAuthSerializer(SystemUserSerializer): + instance_id = serializers.CharField() + + class Meta(SystemUserSerializer.Meta): + fields = ['instance_id', 'username', 'password'] diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index c8413f83e..8bc20a162 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -46,7 +46,9 @@ urlpatterns = [ path('system-users//auth-info/', api.SystemUserAuthInfoApi.as_view(), name='system-user-auth-info'), path('system-users//assets/', api.SystemUserAssetsListView.as_view(), name='system-user-assets'), - path('system-users//assets//auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), + path('system-users//assets//auth-info/', api.SystemUserAssetAuthInfoApi.as_view(), name='system-user-asset-auth-info'), + path('system-users//applications//auth-info/', api.SystemUserAppAuthInfoApi.as_view(), name='system-user-app-auth-info'), + path('system-users//temp-auth/', api.SystemUserTempAuthInfoApi.as_view(), name='system-user-asset-temp-info'), path('system-users//tasks/', api.SystemUserTaskApi.as_view(), name='system-user-task-create'), path('system-users//cmd-filter-rules/', api.SystemUserCommandFilterRuleListApi.as_view(), name='system-user-cmd-filter-rule-list'), diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index ceebf3bde..ef908ed71 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -93,7 +93,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView token = self.create_token(user, asset, application, system_user) return Response({"token": token}, status=201) - @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file') + @action(methods=['POST', 'GET'], detail=False, url_path='rdp/file', permission_classes=[IsValidUser]) def get_rdp_file(self, request, *args, **kwargs): options = { 'full address:s': '', @@ -134,13 +134,15 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView asset = serializer.validated_data.get('asset') application = serializer.validated_data.get('application') system_user = serializer.validated_data['system_user'] - user = serializer.validated_data.get('user') height = serializer.validated_data.get('height') width = serializer.validated_data.get('width') + user = request.user token = self.create_token(user, asset, application, system_user) # Todo: 上线后地址是 JumpServerAddr:3389 - address = self.request.query_params.get('address') or '1.1.1.1' + address = settings.RDP_ADDR + if address == 'localhost:3389': + address = request.get_host().split(':')[0] + ':3389' options['full address:s'] = address options['username:s'] = '{}|{}'.format(user.username, token) options['desktopwidth:i'] = width @@ -217,10 +219,16 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView if value.get('type') == 'asset': asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user) + asset = asset_detail.get('asset') + if asset: + system_user.load_asset_more_auth(asset.id, user.username, user.id) data['type'] = 'asset' data.update(asset_detail) else: app_detail = self._get_application_secret_detail(value) + app = app_detail.get("application") + if app: + system_user.load_app_more_auth(app.id, user.id) data['type'] = 'application' data.update(app_detail) diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index 72b54e3ee..9265755d6 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -199,5 +199,5 @@ class ConnectionTokenSecretSerializer(serializers.Serializer): class RDPFileSerializer(ConnectionTokenSerializer): - width = serializers.IntegerField(default=1280) - height = serializers.IntegerField(default=800) + width = serializers.IntegerField(default=1600) + height = serializers.IntegerField(default=900) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 68be2776b..f3b59d2d9 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -300,7 +300,9 @@ class Config(dict): 'SESSION_SAVE_EVERY_REQUEST': True, 'SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE': False, 'FORGOT_PASSWORD_URL': '', - 'HEALTH_CHECK_TOKEN': '' + 'HEALTH_CHECK_TOKEN': '', + + 'RDP_ADDR': 'localhost:3389' } def compatible_auth_openid_of_key(self): diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index ed37ea49c..d68195792 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -125,3 +125,5 @@ FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL # 自定义默认组织名 GLOBAL_ORG_DISPLAY_NAME = CONFIG.GLOBAL_ORG_DISPLAY_NAME HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN + +RDP_ADDR = CONFIG.RDP_ADDR diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index f26679c29..a997c5466 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -13,7 +13,12 @@ __all__ = [ class BasicSettingSerializer(serializers.Serializer): SITE_URL = serializers.URLField( required=True, label=_("Site url"), - help_text=_('eg: http://demo.jumpserver.org:8080') + help_text=_('eg: http://dev.jumpserver.org:8080') + ) + RDP_ADDR = serializers.CharField( + required=True, label=_("RDP address"), + max_length=1024, + help_text=_('RDP visit address, eg: dev.jumpserver.org:3389') ) USER_GUIDE_URL = serializers.URLField( required=False, allow_blank=True, allow_null=True, label=_("User guide url"), From 95eb11422aeae49e446f7e944c13263554908b6e Mon Sep 17 00:00:00 2001 From: liubo Date: Mon, 12 Apr 2021 19:37:04 +0800 Subject: [PATCH 16/33] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20OBS=20=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/terminal/const.py | 1 + .../migrations/0035_auto_20210412_1905.py | 18 ++++++++++++++++++ apps/terminal/serializers/storage.py | 13 ++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 apps/terminal/migrations/0035_auto_20210412_1905.py diff --git a/apps/terminal/const.py b/apps/terminal/const.py index ff638325d..c2512e024 100644 --- a/apps/terminal/const.py +++ b/apps/terminal/const.py @@ -16,6 +16,7 @@ class ReplayStorageTypeChoices(TextChoices): swift = 'swift', 'Swift' oss = 'oss', 'OSS' azure = 'azure', 'Azure' + obs = 'obs', 'OBS' class CommandStorageTypeChoices(TextChoices): diff --git a/apps/terminal/migrations/0035_auto_20210412_1905.py b/apps/terminal/migrations/0035_auto_20210412_1905.py new file mode 100644 index 000000000..251287935 --- /dev/null +++ b/apps/terminal/migrations/0035_auto_20210412_1905.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.6 on 2021-04-12 11:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('terminal', '0034_auto_20210406_1434'), + ] + + operations = [ + migrations.AlterField( + model_name='replaystorage', + name='type', + field=models.CharField(choices=[('null', 'Null'), ('server', 'Server'), ('s3', 'S3'), ('ceph', 'Ceph'), ('swift', 'Swift'), ('oss', 'OSS'), ('azure', 'Azure'), ('obs', 'OBS')], default='server', max_length=16, verbose_name='Type'), + ), + ] diff --git a/apps/terminal/serializers/storage.py b/apps/terminal/serializers/storage.py index ff794eef9..cdd6e75a3 100644 --- a/apps/terminal/serializers/storage.py +++ b/apps/terminal/serializers/storage.py @@ -82,6 +82,16 @@ class ReplayStorageTypeOSSSerializer(ReplayStorageTypeBaseSerializer): ) +class ReplayStorageTypeOBSSerializer(ReplayStorageTypeBaseSerializer): + endpoint_help_text = ''' + OBS format: obs.{REGION_NAME}.myhuaweicloud.com + Such as: obs.cn-north-4.myhuaweicloud.com + ''' + ENDPOINT = serializers.CharField( + max_length=1024, label=_('Endpoint'), help_text=_(endpoint_help_text), allow_null=True, + ) + + class ReplayStorageTypeAzureSerializer(serializers.Serializer): class EndpointSuffixChoices(TextChoices): china = 'core.chinacloudapi.cn', 'core.chinacloudapi.cn' @@ -105,7 +115,8 @@ replay_storage_type_serializer_classes_mapping = { const.ReplayStorageTypeChoices.ceph.value: ReplayStorageTypeCephSerializer, const.ReplayStorageTypeChoices.swift.value: ReplayStorageTypeSwiftSerializer, const.ReplayStorageTypeChoices.oss.value: ReplayStorageTypeOSSSerializer, - const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer + const.ReplayStorageTypeChoices.azure.value: ReplayStorageTypeAzureSerializer, + const.ReplayStorageTypeChoices.obs.value: ReplayStorageTypeOBSSerializer } # ReplayStorageSerializer From 6d83dd0e3ae5371ba73e6d86c1de3e128f89dfee Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 3 Jun 2021 14:54:41 +0800 Subject: [PATCH 17/33] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0002_auto_20180105_1807.py | 35 ++++++++ .../migrations/0003_auto_20180109_2331.py | 22 +++++ .../migrations/0004_auto_20180125_1218.py | 20 +++++ .../migrations/0005_auto_20180126_1637.py | 40 +++++++++ .../migrations/0006_auto_20180130_1502.py | 39 +++++++++ .../migrations/0007_auto_20180225_1815.py | 60 +++++++++++++ .../migrations/0008_auto_20180306_1804.py | 40 +++++++++ .../migrations/0009_auto_20180307_1212.py | 20 +++++ .../migrations/0010_auto_20180307_1749.py | 20 +++++ .../migrations/0011_auto_20180326_0957.py | 55 ++++++++++++ .../migrations/0012_auto_20180404_1302.py | 21 +++++ .../migrations/0013_auto_20180411_1135.py | 25 ++++++ .../migrations/0014_auto_20180427_1245.py | 31 +++++++ .../migrations/0015_auto_20180510_1235.py | 31 +++++++ .../migrations/0016_auto_20180511_1203.py | 20 +++++ .../migrations/0017_auto_20180702_1415.py | 58 +++++++++++++ .../migrations/0018_auto_20180807_1116.py | 84 +++++++++++++++++++ .../migrations/0019_auto_20180816_1320.py | 22 +++++ 18 files changed, 643 insertions(+) create mode 100644 apps/assets/migrations/0002_auto_20180105_1807.py create mode 100644 apps/assets/migrations/0003_auto_20180109_2331.py create mode 100644 apps/assets/migrations/0004_auto_20180125_1218.py create mode 100644 apps/assets/migrations/0005_auto_20180126_1637.py create mode 100644 apps/assets/migrations/0006_auto_20180130_1502.py create mode 100644 apps/assets/migrations/0007_auto_20180225_1815.py create mode 100644 apps/assets/migrations/0008_auto_20180306_1804.py create mode 100644 apps/assets/migrations/0009_auto_20180307_1212.py create mode 100644 apps/assets/migrations/0010_auto_20180307_1749.py create mode 100644 apps/assets/migrations/0011_auto_20180326_0957.py create mode 100644 apps/assets/migrations/0012_auto_20180404_1302.py create mode 100644 apps/assets/migrations/0013_auto_20180411_1135.py create mode 100644 apps/assets/migrations/0014_auto_20180427_1245.py create mode 100644 apps/assets/migrations/0015_auto_20180510_1235.py create mode 100644 apps/assets/migrations/0016_auto_20180511_1203.py create mode 100644 apps/assets/migrations/0017_auto_20180702_1415.py create mode 100644 apps/assets/migrations/0018_auto_20180807_1116.py create mode 100644 apps/assets/migrations/0019_auto_20180816_1320.py diff --git a/apps/assets/migrations/0002_auto_20180105_1807.py b/apps/assets/migrations/0002_auto_20180105_1807.py new file mode 100644 index 000000000..bf1f022ac --- /dev/null +++ b/apps/assets/migrations/0002_auto_20180105_1807.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-05 10:07 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='adminuser', + options={'ordering': ['name'], 'verbose_name': 'Admin user'}, + ), + migrations.AlterModelOptions( + name='asset', + options={'verbose_name': 'Asset'}, + ), + migrations.AlterModelOptions( + name='assetgroup', + options={'ordering': ['name'], 'verbose_name': 'Asset group'}, + ), + migrations.AlterModelOptions( + name='cluster', + options={'ordering': ['name'], 'verbose_name': 'Cluster'}, + ), + migrations.AlterModelOptions( + name='systemuser', + options={'ordering': ['name'], 'verbose_name': 'System user'}, + ), + ] diff --git a/apps/assets/migrations/0003_auto_20180109_2331.py b/apps/assets/migrations/0003_auto_20180109_2331.py new file mode 100644 index 000000000..254de6236 --- /dev/null +++ b/apps/assets/migrations/0003_auto_20180109_2331.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-09 15:31 +from __future__ import unicode_literals + +import assets.models.asset +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0002_auto_20180105_1807'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='cluster', + field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'), + ), + ] diff --git a/apps/assets/migrations/0004_auto_20180125_1218.py b/apps/assets/migrations/0004_auto_20180125_1218.py new file mode 100644 index 000000000..1886fa499 --- /dev/null +++ b/apps/assets/migrations/0004_auto_20180125_1218.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-25 04:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0003_auto_20180109_2331'), + ] + + operations = [ + migrations.AlterField( + model_name='assetgroup', + name='created_by', + field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'), + ), + ] diff --git a/apps/assets/migrations/0005_auto_20180126_1637.py b/apps/assets/migrations/0005_auto_20180126_1637.py new file mode 100644 index 000000000..8db19e482 --- /dev/null +++ b/apps/assets/migrations/0005_auto_20180126_1637.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-26 08:37 +from __future__ import unicode_literals + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0004_auto_20180125_1218'), + ] + + operations = [ + migrations.CreateModel( + name='Label', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, verbose_name='Name')), + ('value', models.CharField(max_length=128, verbose_name='Value')), + ('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')), + ('is_active', models.BooleanField(default=True, verbose_name='Is active')), + ('comment', models.TextField(blank=True, null=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ], + options={ + 'db_table': 'assets_label', + }, + ), + migrations.AlterUniqueTogether( + name='label', + unique_together=set([('name', 'value')]), + ), + migrations.AddField( + model_name='asset', + name='labels', + field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'), + ), + ] diff --git a/apps/assets/migrations/0006_auto_20180130_1502.py b/apps/assets/migrations/0006_auto_20180130_1502.py new file mode 100644 index 000000000..b77470d27 --- /dev/null +++ b/apps/assets/migrations/0006_auto_20180130_1502.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-01-30 07:02 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0005_auto_20180126_1637'), + ] + + operations = [ + migrations.RemoveField( + model_name='asset', + name='cabinet_no', + ), + migrations.RemoveField( + model_name='asset', + name='cabinet_pos', + ), + migrations.RemoveField( + model_name='asset', + name='env', + ), + migrations.RemoveField( + model_name='asset', + name='remote_card_ip', + ), + migrations.RemoveField( + model_name='asset', + name='status', + ), + migrations.RemoveField( + model_name='asset', + name='type', + ), + ] diff --git a/apps/assets/migrations/0007_auto_20180225_1815.py b/apps/assets/migrations/0007_auto_20180225_1815.py new file mode 100644 index 000000000..009381bcb --- /dev/null +++ b/apps/assets/migrations/0007_auto_20180225_1815.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-02-25 10:15 +from __future__ import unicode_literals + +import assets.models.asset +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0006_auto_20180130_1502'), + ] + + operations = [ + migrations.CreateModel( + name='Node', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('key', models.CharField(max_length=64, unique=True, verbose_name='Key')), + ('value', models.CharField(max_length=128, unique=True, verbose_name='Value')), + ('child_mark', models.IntegerField(default=0)), + ('date_create', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.RemoveField( + model_name='asset', + name='cluster', + ), + migrations.RemoveField( + model_name='asset', + name='groups', + ), + migrations.RemoveField( + model_name='systemuser', + name='cluster', + ), + migrations.AlterField( + model_name='asset', + name='admin_user', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'), + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + migrations.AddField( + model_name='asset', + name='nodes', + field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'), + ), + migrations.AddField( + model_name='systemuser', + name='nodes', + field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'), + ), + ] diff --git a/apps/assets/migrations/0008_auto_20180306_1804.py b/apps/assets/migrations/0008_auto_20180306_1804.py new file mode 100644 index 000000000..48d352619 --- /dev/null +++ b/apps/assets/migrations/0008_auto_20180306_1804.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-03-06 10:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0007_auto_20180225_1815'), + ] + + operations = [ + migrations.AlterField( + model_name='adminuser', + name='created_by', + field=models.CharField(max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='adminuser', + name='username', + field=models.CharField(max_length=128, verbose_name='Username'), + ), + migrations.AlterField( + model_name='asset', + name='platform', + field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'), + ), + migrations.AlterField( + model_name='systemuser', + name='created_by', + field=models.CharField(max_length=128, null=True, verbose_name='Created by'), + ), + migrations.AlterField( + model_name='systemuser', + name='username', + field=models.CharField(max_length=128, verbose_name='Username'), + ), + ] diff --git a/apps/assets/migrations/0009_auto_20180307_1212.py b/apps/assets/migrations/0009_auto_20180307_1212.py new file mode 100644 index 000000000..08d770642 --- /dev/null +++ b/apps/assets/migrations/0009_auto_20180307_1212.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-03-07 04:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0008_auto_20180306_1804'), + ] + + operations = [ + migrations.AlterField( + model_name='node', + name='value', + field=models.CharField(max_length=128, verbose_name='Value'), + ), + ] diff --git a/apps/assets/migrations/0010_auto_20180307_1749.py b/apps/assets/migrations/0010_auto_20180307_1749.py new file mode 100644 index 000000000..5e6be0943 --- /dev/null +++ b/apps/assets/migrations/0010_auto_20180307_1749.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-03-07 09:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0009_auto_20180307_1212'), + ] + + operations = [ + migrations.AlterField( + model_name='node', + name='value', + field=models.CharField(max_length=128, unique=True, verbose_name='Value'), + ), + ] diff --git a/apps/assets/migrations/0011_auto_20180326_0957.py b/apps/assets/migrations/0011_auto_20180326_0957.py new file mode 100644 index 000000000..07b9055dc --- /dev/null +++ b/apps/assets/migrations/0011_auto_20180326_0957.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-03-26 01:57 +from __future__ import unicode_literals + +import assets.models.utils +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0010_auto_20180307_1749'), + ] + + operations = [ + migrations.CreateModel( + name='Domain', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), + ('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')), + ], + ), + migrations.CreateModel( + name='Gateway', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), + ('username', models.CharField(max_length=128, verbose_name='Username')), + ('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')), + ('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')), + ('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_updated', models.DateTimeField(auto_now=True)), + ('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')), + ('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')), + ('port', models.IntegerField(default=22, verbose_name='Port')), + ('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')), + ('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')), + ('is_active', models.BooleanField(default=True, verbose_name='Is active')), + ('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='asset', + name='domain', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'), + ), + ] diff --git a/apps/assets/migrations/0012_auto_20180404_1302.py b/apps/assets/migrations/0012_auto_20180404_1302.py new file mode 100644 index 000000000..0ccb63e27 --- /dev/null +++ b/apps/assets/migrations/0012_auto_20180404_1302.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-04 05:02 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0011_auto_20180326_0957'), + ] + + operations = [ + migrations.AlterField( + model_name='asset', + name='domain', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'), + ), + ] diff --git a/apps/assets/migrations/0013_auto_20180411_1135.py b/apps/assets/migrations/0013_auto_20180411_1135.py new file mode 100644 index 000000000..baaf789bd --- /dev/null +++ b/apps/assets/migrations/0013_auto_20180411_1135.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-11 03:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0012_auto_20180404_1302'), + ] + + operations = [ + migrations.AddField( + model_name='systemuser', + name='assets', + field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'), + ), + migrations.AlterField( + model_name='systemuser', + name='sudo', + field=models.TextField(default='/bin/whoami', verbose_name='Sudo'), + ), + ] diff --git a/apps/assets/migrations/0014_auto_20180427_1245.py b/apps/assets/migrations/0014_auto_20180427_1245.py new file mode 100644 index 000000000..735a50879 --- /dev/null +++ b/apps/assets/migrations/0014_auto_20180427_1245.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-04-27 04:45 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0013_auto_20180411_1135'), + ] + + operations = [ + migrations.AlterField( + model_name='adminuser', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='gateway', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='systemuser', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'), + ), + ] diff --git a/apps/assets/migrations/0015_auto_20180510_1235.py b/apps/assets/migrations/0015_auto_20180510_1235.py new file mode 100644 index 000000000..81d12d2e4 --- /dev/null +++ b/apps/assets/migrations/0015_auto_20180510_1235.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-05-10 04:35 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0014_auto_20180427_1245'), + ] + + operations = [ + migrations.AlterField( + model_name='adminuser', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='gateway', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='systemuser', + name='username', + field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + ] diff --git a/apps/assets/migrations/0016_auto_20180511_1203.py b/apps/assets/migrations/0016_auto_20180511_1203.py new file mode 100644 index 000000000..32f79a3c6 --- /dev/null +++ b/apps/assets/migrations/0016_auto_20180511_1203.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-05-11 04:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0015_auto_20180510_1235'), + ] + + operations = [ + migrations.AlterField( + model_name='node', + name='value', + field=models.CharField(max_length=128, verbose_name='Value'), + ), + ] diff --git a/apps/assets/migrations/0017_auto_20180702_1415.py b/apps/assets/migrations/0017_auto_20180702_1415.py new file mode 100644 index 000000000..9950424a6 --- /dev/null +++ b/apps/assets/migrations/0017_auto_20180702_1415.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2018-07-02 06:15 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +def migrate_win_to_ssh_protocol(apps, schema_editor): + asset_model = apps.get_model("assets", "Asset") + db_alias = schema_editor.connection.alias + asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp') + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0016_auto_20180511_1203'), + ] + + operations = [ + migrations.AddField( + model_name='asset', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'), + ), + migrations.AddField( + model_name='systemuser', + name='login_mode', + field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'), + ), + migrations.AlterField( + model_name='adminuser', + name='username', + field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='asset', + name='platform', + field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'), + ), + migrations.AlterField( + model_name='gateway', + name='username', + field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.AlterField( + model_name='systemuser', + name='protocol', + field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'), + ), + migrations.AlterField( + model_name='systemuser', + name='username', + field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'), + ), + migrations.RunPython(migrate_win_to_ssh_protocol), + ] diff --git a/apps/assets/migrations/0018_auto_20180807_1116.py b/apps/assets/migrations/0018_auto_20180807_1116.py new file mode 100644 index 000000000..c4e848b43 --- /dev/null +++ b/apps/assets/migrations/0018_auto_20180807_1116.py @@ -0,0 +1,84 @@ +# Generated by Django 2.0.7 on 2018-08-07 03:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0017_auto_20180702_1415'), + ] + + operations = [ + migrations.AddField( + model_name='adminuser', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='asset', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='domain', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='gateway', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='label', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='node', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AddField( + model_name='systemuser', + name='org_id', + field=models.CharField(blank=True, default=None, max_length=36, null=True), + ), + migrations.AlterField( + model_name='adminuser', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterField( + model_name='asset', + name='hostname', + field=models.CharField(max_length=128, verbose_name='Hostname'), + ), + migrations.AlterField( + model_name='gateway', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterField( + model_name='systemuser', + name='name', + field=models.CharField(max_length=128, verbose_name='Name'), + ), + migrations.AlterUniqueTogether( + name='adminuser', + unique_together={('name', 'org_id')}, + ), + migrations.AlterUniqueTogether( + name='asset', + unique_together={('org_id', 'hostname')}, + ), + migrations.AlterUniqueTogether( + name='gateway', + unique_together={('name', 'org_id')}, + ), + migrations.AlterUniqueTogether( + name='systemuser', + unique_together={('name', 'org_id')}, + ), + ] diff --git a/apps/assets/migrations/0019_auto_20180816_1320.py b/apps/assets/migrations/0019_auto_20180816_1320.py new file mode 100644 index 000000000..0d468e511 --- /dev/null +++ b/apps/assets/migrations/0019_auto_20180816_1320.py @@ -0,0 +1,22 @@ +# Generated by Django 2.0.7 on 2018-08-16 05:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0018_auto_20180807_1116'), + ] + + operations = [ + migrations.AddField( + model_name='asset', + name='cpu_vcpus', + field=models.IntegerField(null=True, verbose_name='CPU vcpus'), + ), + migrations.AlterUniqueTogether( + name='label', + unique_together={('name', 'value', 'org_id')}, + ), + ] From 0bced39f0819c5747c5264892d75c8f9e13b8721 Mon Sep 17 00:00:00 2001 From: Bai Date: Thu, 3 Jun 2021 19:23:47 +0800 Subject: [PATCH 18/33] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dredis=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=BC=82=E5=B8=B8=E6=97=B6(=E5=A6=82:=20=E4=B8=BB?= =?UTF-8?q?=E4=BB=8E=E5=88=87=E6=8D=A2),=20=E7=94=A8=E6=88=B7session?= =?UTF-8?q?=E7=AB=8B=E5=8D=B3=E8=BF=87=E6=9C=9F=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jumpserver/rewriting/__init__.py | 0 apps/jumpserver/rewriting/session.py | 18 ++++++++++++++++++ apps/jumpserver/settings/base.py | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/jumpserver/rewriting/__init__.py create mode 100644 apps/jumpserver/rewriting/session.py diff --git a/apps/jumpserver/rewriting/__init__.py b/apps/jumpserver/rewriting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/jumpserver/rewriting/session.py b/apps/jumpserver/rewriting/session.py new file mode 100644 index 000000000..d0e698070 --- /dev/null +++ b/apps/jumpserver/rewriting/session.py @@ -0,0 +1,18 @@ +from redis_sessions.session import force_unicode, SessionStore as RedisSessionStore +from redis import exceptions + + +class SessionStore(RedisSessionStore): + + def load(self): + try: + session_data = self.server.get( + self.get_real_stored_key(self._get_or_create_session_key()) + ) + return self.decode(force_unicode(session_data)) + except exceptions.ConnectionError as e: + # 解决redis服务异常(如: 主从切换时),用户session立即过期的问题 + raise + except: + self._session_key = None + return {} diff --git a/apps/jumpserver/settings/base.py b/apps/jumpserver/settings/base.py index 1d4b2f995..268bafa44 100644 --- a/apps/jumpserver/settings/base.py +++ b/apps/jumpserver/settings/base.py @@ -126,7 +126,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 自定义的配置,SESSION_EXPIRE_AT_BROWSER_CLOSE 始终为 True, 下面这个来控制是否强制关闭后过期 cookie SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE_FORCE SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST -SESSION_ENGINE = 'redis_sessions.session' +SESSION_ENGINE = 'jumpserver.rewriting.session' SESSION_REDIS = { 'host': CONFIG.REDIS_HOST, 'port': CONFIG.REDIS_PORT, From 5daca6592bd25c1bb40ea22ca191f687a28b8b37 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 4 Jun 2021 11:14:53 +0800 Subject: [PATCH 19/33] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=96=87?= =?UTF-8?q?=E6=A1=88=20=E5=90=8E=E7=AB=AF=20->=20=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset_user.py | 2 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 75515 -> 75494 bytes apps/locale/zh/LC_MESSAGES/django.po | 35 +++----------------------- apps/users/models/user.py | 2 +- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index 90145399f..6552281c6 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -47,7 +47,7 @@ class AssetUserReadSerializer(AssetUserWriteSerializer): ip = serializers.CharField(read_only=True, label=_("IP")) asset = serializers.CharField(source='asset_id', label=_('Asset')) backend = serializers.CharField(read_only=True, label=_("Backend")) - backend_display = serializers.CharField(read_only=True, label=_("Backend (Display)")) + backend_display = serializers.CharField(read_only=True, label=_("Source")) class Meta(AssetUserWriteSerializer.Meta): read_only_fields = ( diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 562853c5ed03958ff5cc4444f0131527efcd9509..d5940b932d0d1b4e0a655e25c4605507d71bae46 100644 GIT binary patch delta 20409 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 HF3JA^D3R?_ delta 20438 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 46b53a6ae..af2cbcc87 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-06-02 16:58+0800\n" +"POT-Creation-Date: 2021-06-04 11:11+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -819,8 +819,8 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:50 -msgid "Backend (Display)" -msgstr "后端 (显示名称)" +msgid "Source" +msgstr "来源" #: assets/serializers/asset_user.py:81 users/forms/profile.py:160 #: users/models/user.py:580 users/templates/users/user_password_update.html:48 @@ -3899,7 +3899,7 @@ msgid "Wechat" msgstr "微信" #: users/models/user.py:596 -msgid "Source" +msgid "User source" msgstr "用户来源" #: users/models/user.py:600 @@ -5057,30 +5057,3 @@ msgstr "旗舰版" #: xpack/plugins/license/models.py:77 msgid "Community edition" msgstr "社区版" - -#~ msgid "{} is required" -#~ msgstr "{} 字段是必填项" - -#~ msgid "AppSecret is required" -#~ msgstr "AppSecret 是必须的" - -#~ msgid "Corporation ID(corpid)" -#~ msgstr "企业 ID(CorpId)" - -#~ msgid "Agent ID(agentid)" -#~ msgstr "应用 ID(AgentId)" - -#~ msgid "Secret(secret)" -#~ msgstr "秘钥(secret)" - -#~ msgid "AgentId" -#~ msgstr "应用 ID(AgentId)" - -#~ msgid "AppKey" -#~ msgstr "应用 Key(AppKey)" - -#~ msgid "AppSecret" -#~ msgstr "应用密文(AppSecret)" - -#~ msgid "No" -#~ msgstr "无" diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 6f5b52f14..69d0318eb 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -593,7 +593,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): source = models.CharField( max_length=30, default=Source.local, choices=Source.choices, - verbose_name=_('Source') + verbose_name=_('User source') ) date_password_last_updated = models.DateTimeField( auto_now_add=True, blank=True, null=True, From 0c7838d0e36a3f41163ec12fb02db81f0584e0f2 Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 4 Jun 2021 11:26:52 +0800 Subject: [PATCH 20/33] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0412_1905.py => 0036_auto_20210604_1124.py} | 4 ++-- .../migrations/0036_auto_20210604_1124.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) rename apps/terminal/migrations/{0035_auto_20210412_1905.py => 0036_auto_20210604_1124.py} (83%) create mode 100644 apps/users/migrations/0036_auto_20210604_1124.py diff --git a/apps/terminal/migrations/0035_auto_20210412_1905.py b/apps/terminal/migrations/0036_auto_20210604_1124.py similarity index 83% rename from apps/terminal/migrations/0035_auto_20210412_1905.py rename to apps/terminal/migrations/0036_auto_20210604_1124.py index 251287935..31746dfba 100644 --- a/apps/terminal/migrations/0035_auto_20210412_1905.py +++ b/apps/terminal/migrations/0036_auto_20210604_1124.py @@ -1,4 +1,4 @@ -# Generated by Django 3.1.6 on 2021-04-12 11:05 +# Generated by Django 3.1.6 on 2021-06-04 03:24 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('terminal', '0034_auto_20210406_1434'), + ('terminal', '0035_auto_20210517_1448'), ] operations = [ diff --git a/apps/users/migrations/0036_auto_20210604_1124.py b/apps/users/migrations/0036_auto_20210604_1124.py new file mode 100644 index 000000000..1bdc17943 --- /dev/null +++ b/apps/users/migrations/0036_auto_20210604_1124.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.6 on 2021-06-04 03:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0035_auto_20210526_1100'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='source', + field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='local', max_length=30, verbose_name='User source'), + ), + ] From a8d84fc6e166982aef6ce6ee32bf7438a914ee9a Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 4 Jun 2021 11:38:00 +0800 Subject: [PATCH 21/33] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 75494 -> 76178 bytes apps/locale/zh/LC_MESSAGES/django.po | 494 +++++++++--------- .../migrations/0036_auto_20210604_1124.py | 18 - apps/users/models/user.py | 2 +- 4 files changed, 261 insertions(+), 253 deletions(-) delete mode 100644 apps/users/migrations/0036_auto_20210604_1124.py diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index d5940b932d0d1b4e0a655e25c4605507d71bae46..a3df15a78817b6aa1bf9cb981eb14789748326e6 100644 GIT binary patch delta 22781 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 M0SVIzLR delta 22305 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index af2cbcc87..c6787d382 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-06-04 11:11+0800\n" +"POT-Creation-Date: 2021-06-04 11:29+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -19,7 +19,7 @@ msgstr "" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: applications/models/application.py:11 assets/models/asset.py:142 -#: assets/models/base.py:250 assets/models/cluster.py:18 +#: assets/models/base.py:249 assets/models/cluster.py:18 #: assets/models/cmd_filter.py:21 assets/models/domain.py:21 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29 @@ -35,12 +35,12 @@ msgid "Name" msgstr "名称" #: acls/models/base.py:27 assets/models/cmd_filter.py:54 -#: assets/models/user.py:122 +#: assets/models/user.py:123 msgid "Priority" msgstr "优先级" #: acls/models/base.py:28 assets/models/cmd_filter.py:54 -#: assets/models/user.py:122 +#: assets/models/user.py:123 msgid "1-100, the lower the value will be match first" msgstr "优先级可选范围为 1-100 (数值越小越优先)" @@ -54,7 +54,7 @@ msgstr "激活中" # msgstr "创建日期" #: acls/models/base.py:32 applications/models/application.py:24 #: assets/models/asset.py:147 assets/models/asset.py:223 -#: assets/models/base.py:255 assets/models/cluster.py:29 +#: assets/models/base.py:254 assets/models/cluster.py:29 #: assets/models/cmd_filter.py:23 assets/models/cmd_filter.py:64 #: assets/models/domain.py:22 assets/models/domain.py:56 #: assets/models/group.py:23 assets/models/label.py:23 ops/models/adhoc.py:37 @@ -99,7 +99,7 @@ msgstr "动作" #: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 #: tickets/models/comment.py:17 users/models/user.py:176 -#: users/models/user.py:740 users/models/user.py:766 +#: users/models/user.py:746 users/models/user.py:772 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -122,7 +122,7 @@ msgstr "系统用户" #: assets/models/asset.py:355 assets/models/authbook.py:26 #: assets/models/gathered_user.py:14 assets/serializers/admin_user.py:34 #: assets/serializers/asset_user.py:48 assets/serializers/asset_user.py:90 -#: assets/serializers/system_user.py:201 audits/models.py:38 +#: assets/serializers/system_user.py:202 audits/models.py:38 #: perms/models/asset_permission.py:99 templates/index.html:82 #: terminal/backends/command/models.py:19 #: terminal/backends/command/serializers.py:13 terminal/models/session.py:40 @@ -158,7 +158,7 @@ msgstr "" #: acls/serializers/login_acl.py:30 acls/serializers/login_asset_acl.py:31 #: applications/serializers/attrs/application_type/mysql_workbench.py:18 #: assets/models/asset.py:183 assets/models/domain.py:52 -#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:112 +#: assets/serializers/asset_user.py:47 settings/serializers/settings.py:117 #: users/templates/users/_granted_assets.html:26 #: users/templates/users/user_asset_permission.html:156 msgid "IP" @@ -178,7 +178,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: applications/serializers/attrs/application_type/custom.py:21 #: applications/serializers/attrs/application_type/mysql_workbench.py:30 #: applications/serializers/attrs/application_type/vmware_client.py:26 -#: assets/models/base.py:251 assets/models/gathered_user.py:15 +#: assets/models/base.py:250 assets/models/gathered_user.py:15 #: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17 #: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:548 #: users/templates/users/_select_user_modal.html:14 @@ -199,7 +199,7 @@ msgstr "" #: acls/serializers/login_asset_acl.py:35 assets/models/asset.py:184 #: assets/serializers/asset_user.py:46 assets/serializers/gathered_user.py:23 -#: settings/serializers/settings.py:111 +#: settings/serializers/settings.py:116 #: users/templates/users/_granted_assets.html:25 #: users/templates/users/user_asset_permission.html:157 msgid "Hostname" @@ -212,7 +212,7 @@ msgid "" msgstr "格式为逗号分隔的字符串, * 表示匹配所有. 可选的协议有: {}" #: acls/serializers/login_asset_acl.py:55 assets/models/asset.py:187 -#: assets/models/domain.py:54 assets/models/user.py:123 +#: assets/models/domain.py:54 assets/models/user.py:124 #: terminal/serializers/session.py:32 terminal/serializers/storage.py:69 msgid "Protocol" msgstr "协议" @@ -316,10 +316,10 @@ msgstr "目标URL" #: applications/serializers/attrs/application_type/custom.py:25 #: applications/serializers/attrs/application_type/mysql_workbench.py:34 #: applications/serializers/attrs/application_type/vmware_client.py:30 -#: assets/models/base.py:252 assets/serializers/asset_user.py:77 +#: assets/models/base.py:251 assets/serializers/asset_user.py:77 #: audits/signals_handler.py:58 authentication/forms.py:22 #: authentication/templates/authentication/login.html:164 -#: settings/serializers/settings.py:93 users/forms/profile.py:21 +#: settings/serializers/settings.py:98 users/forms/profile.py:21 #: users/templates/users/user_otp_check_password.html:13 #: users/templates/users/user_password_update.html:43 #: users/templates/users/user_password_verify.html:18 @@ -358,7 +358,7 @@ msgstr "不能删除根节点 ({})" msgid "Deletion failed and the node contains assets" msgstr "删除失败,节点包含资产" -#: assets/backends/db.py:109 assets/models/user.py:228 audits/models.py:39 +#: assets/backends/db.py:109 assets/models/user.py:304 audits/models.py:39 #: perms/models/application_permission.py:31 #: perms/models/asset_permission.py:101 templates/_nav.html:45 #: terminal/backends/command/models.py:20 @@ -377,7 +377,7 @@ msgid "System user(Dynamic)" msgstr "系统用户(动态)" #: assets/backends/db.py:232 assets/models/asset.py:196 -#: assets/models/cluster.py:19 assets/models/user.py:66 templates/_nav.html:44 +#: assets/models/cluster.py:19 assets/models/user.py:67 templates/_nav.html:44 #: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:160 msgid "Admin user" msgstr "管理用户" @@ -417,7 +417,7 @@ msgstr "系统平台" msgid "Protocols" msgstr "协议组" -#: assets/models/asset.py:192 assets/models/user.py:118 +#: assets/models/asset.py:192 assets/models/user.py:119 #: perms/models/asset_permission.py:100 #: xpack/plugins/change_auth_plan/models.py:56 #: xpack/plugins/gathered_user/models.py:24 @@ -498,7 +498,7 @@ msgstr "主机名原始" msgid "Labels" msgstr "标签管理" -#: assets/models/asset.py:221 assets/models/base.py:258 +#: assets/models/asset.py:221 assets/models/base.py:257 #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:67 assets/models/group.py:21 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24 @@ -510,13 +510,13 @@ msgstr "创建者" # msgid "Created by" # msgstr "创建者" -#: assets/models/asset.py:222 assets/models/base.py:256 +#: assets/models/asset.py:222 assets/models/base.py:255 #: assets/models/cluster.py:26 assets/models/domain.py:24 #: assets/models/gathered_user.py:19 assets/models/group.py:22 #: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25 #: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18 -#: users/models/user.py:767 xpack/plugins/cloud/models.py:107 +#: users/models/user.py:773 xpack/plugins/cloud/models.py:107 msgid "Date created" msgstr "创建日期" @@ -536,19 +536,19 @@ msgstr "版本" msgid "AuthBook" msgstr "" -#: assets/models/base.py:253 xpack/plugins/change_auth_plan/models.py:72 +#: assets/models/base.py:252 xpack/plugins/change_auth_plan/models.py:72 #: xpack/plugins/change_auth_plan/models.py:197 #: xpack/plugins/change_auth_plan/models.py:292 msgid "SSH private key" msgstr "SSH密钥" -#: assets/models/base.py:254 xpack/plugins/change_auth_plan/models.py:75 +#: assets/models/base.py:253 xpack/plugins/change_auth_plan/models.py:75 #: xpack/plugins/change_auth_plan/models.py:193 #: xpack/plugins/change_auth_plan/models.py:288 msgid "SSH public key" msgstr "SSH公钥" -#: assets/models/base.py:257 assets/models/gathered_user.py:20 +#: assets/models/base.py:256 assets/models/gathered_user.py:20 #: common/db/models.py:73 common/mixins/models.py:51 ops/models/adhoc.py:39 #: orgs/models.py:421 msgid "Date updated" @@ -588,7 +588,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:752 +#: users/models/user.py:758 msgid "System" msgstr "系统" @@ -596,7 +596,7 @@ msgstr "系统" msgid "Default Cluster" msgstr "默认Cluster" -#: assets/models/cmd_filter.py:33 assets/models/user.py:128 +#: assets/models/cmd_filter.py:33 assets/models/user.py:129 msgid "Command filter" msgstr "命令过滤器" @@ -693,7 +693,7 @@ msgstr "全称" msgid "Parent key" msgstr "ssh私钥" -#: assets/models/node.py:559 assets/serializers/system_user.py:200 +#: assets/models/node.py:559 assets/serializers/system_user.py:201 #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 @@ -701,61 +701,61 @@ msgstr "ssh私钥" msgid "Node" msgstr "节点" -#: assets/models/user.py:114 +#: assets/models/user.py:115 msgid "Automatic login" msgstr "自动登录" -#: assets/models/user.py:115 +#: assets/models/user.py:116 msgid "Manually login" msgstr "手动登录" -#: assets/models/user.py:117 +#: assets/models/user.py:118 msgid "Username same with user" msgstr "用户名与用户相同" -#: assets/models/user.py:119 assets/serializers/domain.py:30 +#: assets/models/user.py:120 assets/serializers/domain.py:30 #: templates/_nav.html:39 xpack/plugins/change_auth_plan/models.py:52 msgid "Assets" msgstr "资产" -#: assets/models/user.py:120 templates/_nav.html:17 +#: assets/models/user.py:121 templates/_nav.html:17 #: users/views/profile/password.py:43 users/views/profile/pubkey.py:37 msgid "Users" msgstr "用户管理" -#: assets/models/user.py:121 +#: assets/models/user.py:122 msgid "User groups" msgstr "用户组" -#: assets/models/user.py:124 +#: assets/models/user.py:125 msgid "Auto push" msgstr "自动推送" -#: assets/models/user.py:125 +#: assets/models/user.py:126 msgid "Sudo" msgstr "Sudo" -#: assets/models/user.py:126 +#: assets/models/user.py:127 msgid "Shell" msgstr "Shell" -#: assets/models/user.py:127 +#: assets/models/user.py:128 msgid "Login mode" msgstr "登录模式" -#: assets/models/user.py:129 +#: assets/models/user.py:130 msgid "SFTP Root" msgstr "SFTP根路径" -#: assets/models/user.py:130 authentication/models.py:95 +#: assets/models/user.py:131 authentication/models.py:95 msgid "Token" msgstr "" -#: assets/models/user.py:131 +#: assets/models/user.py:132 msgid "Home" msgstr "家目录" -#: assets/models/user.py:132 +#: assets/models/user.py:133 msgid "System groups" msgstr "用户组" @@ -818,7 +818,7 @@ msgstr "ID" msgid "Backend" msgstr "后端" -#: assets/serializers/asset_user.py:50 +#: assets/serializers/asset_user.py:50 users/models/user.py:596 msgid "Source" msgstr "来源" @@ -847,8 +847,8 @@ msgstr "应用数量" msgid "Gateways count" msgstr "网关数量" -#: assets/serializers/label.py:13 assets/serializers/system_user.py:47 -#: assets/serializers/system_user.py:175 +#: assets/serializers/label.py:13 assets/serializers/system_user.py:48 +#: assets/serializers/system_user.py:176 #: perms/serializers/asset/permission.py:74 msgid "Assets amount" msgstr "资产数量" @@ -870,33 +870,33 @@ msgstr "不能包含: /" msgid "The same level node name cannot be the same" msgstr "同级别节点名字不能重复" -#: assets/serializers/system_user.py:46 assets/serializers/system_user.py:174 +#: assets/serializers/system_user.py:47 assets/serializers/system_user.py:175 #: perms/serializers/asset/permission.py:75 msgid "Nodes amount" msgstr "节点数量" -#: assets/serializers/system_user.py:48 assets/serializers/system_user.py:176 -#: assets/serializers/system_user.py:202 +#: assets/serializers/system_user.py:49 assets/serializers/system_user.py:177 +#: assets/serializers/system_user.py:203 msgid "Login mode display" msgstr "登录模式(显示名称)" -#: assets/serializers/system_user.py:50 assets/serializers/system_user.py:178 +#: assets/serializers/system_user.py:51 assets/serializers/system_user.py:179 msgid "Ad domain" msgstr "Ad 网域" -#: assets/serializers/system_user.py:89 +#: assets/serializers/system_user.py:90 msgid "Username same with user with protocol {} only allow 1" msgstr "用户名和用户相同的一种协议只允许存在一个" -#: assets/serializers/system_user.py:102 +#: assets/serializers/system_user.py:103 msgid "* Automatic login mode must fill in the username." msgstr "自动登录模式,必须填写用户名" -#: assets/serializers/system_user.py:116 +#: assets/serializers/system_user.py:117 msgid "Path should starts with /" msgstr "路径应该以 / 开头" -#: assets/serializers/system_user.py:127 +#: assets/serializers/system_user.py:128 msgid "Password or private key required" msgstr "密码或密钥密码需要一个" @@ -1189,7 +1189,7 @@ msgstr "主机 (显示名称)" msgid "Result" msgstr "结果" -#: audits/serializers.py:92 terminal/serializers/storage.py:178 +#: audits/serializers.py:92 terminal/serializers/storage.py:189 msgid "Hosts" msgstr "主机" @@ -1215,11 +1215,13 @@ msgstr "" #: audits/signals_handler.py:60 #: authentication/templates/authentication/login.html:210 +#: notifications/backends/__init__.py:12 msgid "WeCom" msgstr "企业微信" #: audits/signals_handler.py:61 #: authentication/templates/authentication/login.html:215 +#: notifications/backends/__init__.py:13 msgid "DingTalk" msgstr "钉钉" @@ -1824,6 +1826,15 @@ msgstr "" "div>
如果你看到了这个页面,证明你访问的不是nginx监听的端口,祝你好运" +#: notifications/backends/__init__.py:11 users/forms/profile.py:101 +#: users/models/user.py:552 +msgid "Email" +msgstr "邮件" + +#: notifications/backends/__init__.py:14 +msgid "Site message" +msgstr "" + #: ops/api/celery.py:61 ops/api/celery.py:76 msgid "Waiting task start" msgstr "等待任务开始" @@ -1832,7 +1843,7 @@ msgstr "等待任务开始" msgid "Not has host {} permission" msgstr "没有该主机 {} 权限" -#: ops/apps.py:9 +#: ops/apps.py:9 ops/notifications.py:12 msgid "Operations" msgstr "运维" @@ -1967,6 +1978,14 @@ msgstr "命令 `{}` 不允许被执行 ......." msgid "Task end" msgstr "任务结束" +#: ops/notifications.py:13 +msgid "Server performance" +msgstr "" + +#: ops/notifications.py:20 +msgid "Disk used more than 80%: {} => {}" +msgstr "磁盘使用率超过 80%: {} => {}" + #: ops/tasks.py:71 msgid "Clean task history period" msgstr "定期清除任务历史" @@ -1983,17 +2002,13 @@ msgstr "任务列表" msgid "Update task content: {}" msgstr "更新任务内容: {}" -#: ops/utils.py:74 -msgid "Disk used more than 80%: {} => {}" -msgstr "磁盘使用率超过 80%: {} => {}" +#: orgs/api.py:77 +msgid "The current organization ({}) cannot be deleted" +msgstr "当前组织 ({}) 不能被删除" -#: orgs/api.py:79 -msgid "Have {} exists, Please delete" -msgstr "{} 存在数据, 请先删除" - -#: orgs/api.py:83 -msgid "The current organization cannot be deleted" -msgstr "当前组织不能被删除" +#: orgs/api.py:85 +msgid "The organization have resource ({}) cannot be deleted" +msgstr "组织存在资源 ({}) 不能被删除" #: orgs/mixins/models.py:45 orgs/mixins/serializers.py:25 orgs/models.py:36 #: orgs/models.py:417 orgs/serializers.py:108 @@ -2034,7 +2049,7 @@ msgstr "应用程序" msgid "Application permission" msgstr "应用管理" -#: perms/models/asset_permission.py:37 settings/serializers/settings.py:116 +#: perms/models/asset_permission.py:37 settings/serializers/settings.py:121 msgid "All" msgstr "全部" @@ -2176,22 +2191,30 @@ msgid "Site url" msgstr "当前站点URL" #: settings/serializers/settings.py:16 -msgid "eg: http://demo.jumpserver.org:8080" -msgstr "如: http://demo.jumpserver.org:8080" +msgid "eg: http://dev.jumpserver.org:8080" +msgstr "如: http://dev.jumpserver.org:8080" #: settings/serializers/settings.py:19 +msgid "RDP address" +msgstr "RDP 地址" + +#: settings/serializers/settings.py:21 +msgid "RDP visit address, eg: dev.jumpserver.org:3389" +msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389" + +#: settings/serializers/settings.py:24 msgid "User guide url" msgstr "用户向导URL" -#: settings/serializers/settings.py:20 +#: settings/serializers/settings.py:25 msgid "User first login update profile done redirect to it" msgstr "用户第一次登录,修改profile后重定向到地址, 可以是 wiki 或 其他说明文档" -#: settings/serializers/settings.py:23 +#: settings/serializers/settings.py:28 msgid "Forgot password url" msgstr "忘记密码URL" -#: settings/serializers/settings.py:24 +#: settings/serializers/settings.py:29 msgid "" "The forgot password url on login page, If you use ldap or cas external " "authentication, you can set it" @@ -2199,138 +2222,138 @@ msgstr "" "登录页面忘记密码URL, 如果使用了 LDAP, OPENID 等外部认证系统,可以自定义用户重" "置密码访问的地址" -#: settings/serializers/settings.py:28 +#: settings/serializers/settings.py:33 msgid "Global organization name" msgstr "全局组织名" -#: settings/serializers/settings.py:29 +#: settings/serializers/settings.py:34 msgid "The name of global organization to display" msgstr "全局组织的显示名称,默认为 全局组织" -#: settings/serializers/settings.py:36 +#: settings/serializers/settings.py:41 msgid "SMTP host" msgstr "SMTP 主机" -#: settings/serializers/settings.py:37 +#: settings/serializers/settings.py:42 msgid "SMTP port" msgstr "SMTP 端口" -#: settings/serializers/settings.py:38 +#: settings/serializers/settings.py:43 msgid "SMTP account" msgstr "SMTP 账号" -#: settings/serializers/settings.py:40 +#: settings/serializers/settings.py:45 msgid "SMTP password" msgstr "SMTP 密码" -#: settings/serializers/settings.py:41 +#: settings/serializers/settings.py:46 msgid "Tips: Some provider use token except password" msgstr "提示:一些邮件提供商需要输入的是授权码" -#: settings/serializers/settings.py:44 +#: settings/serializers/settings.py:49 msgid "Send user" msgstr "发件人" -#: settings/serializers/settings.py:45 +#: settings/serializers/settings.py:50 msgid "Tips: Send mail account, default SMTP account as the send account" msgstr "提示:发送邮件账号,默认使用 SMTP 账号作为发送账号" -#: settings/serializers/settings.py:48 +#: settings/serializers/settings.py:53 msgid "Test recipient" msgstr "测试收件人" -#: settings/serializers/settings.py:49 +#: settings/serializers/settings.py:54 msgid "Tips: Used only as a test mail recipient" msgstr "提示:仅用来作为测试邮件收件人" -#: settings/serializers/settings.py:52 +#: settings/serializers/settings.py:57 msgid "Use SSL" msgstr "使用 SSL" -#: settings/serializers/settings.py:53 +#: settings/serializers/settings.py:58 msgid "If SMTP port is 465, may be select" msgstr "如果SMTP端口是465,通常需要启用 SSL" -#: settings/serializers/settings.py:56 +#: settings/serializers/settings.py:61 msgid "Use TLS" msgstr "使用 TLS" -#: settings/serializers/settings.py:57 +#: settings/serializers/settings.py:62 msgid "If SMTP port is 587, may be select" msgstr "如果SMTP端口是587,通常需要启用 TLS" -#: settings/serializers/settings.py:60 +#: settings/serializers/settings.py:65 msgid "Subject prefix" msgstr "主题前缀" -#: settings/serializers/settings.py:67 +#: settings/serializers/settings.py:72 msgid "Create user email subject" msgstr "邮件主题" -#: settings/serializers/settings.py:68 +#: settings/serializers/settings.py:73 msgid "" "Tips: When creating a user, send the subject of the email (eg:Create account " "successfully)" msgstr "提示: 创建用户时,发送设置密码邮件的主题 (例如: 创建用户成功)" -#: settings/serializers/settings.py:72 +#: settings/serializers/settings.py:77 msgid "Create user honorific" msgstr "邮件的敬语" -#: settings/serializers/settings.py:73 +#: settings/serializers/settings.py:78 msgid "Tips: When creating a user, send the honorific of the email (eg:Hello)" msgstr "提示: 创建用户时,发送设置密码邮件的敬语 (例如: 您好)" -#: settings/serializers/settings.py:77 +#: settings/serializers/settings.py:82 msgid "Create user email content" msgstr "邮件的内容" -#: settings/serializers/settings.py:78 +#: settings/serializers/settings.py:83 msgid "Tips:When creating a user, send the content of the email" msgstr "提示: 创建用户时,发送设置密码邮件的内容" -#: settings/serializers/settings.py:81 +#: settings/serializers/settings.py:86 msgid "Signature" msgstr "署名" -#: settings/serializers/settings.py:82 +#: settings/serializers/settings.py:87 msgid "Tips: Email signature (eg:jumpserver)" msgstr "邮件署名 (如:jumpserver)" -#: settings/serializers/settings.py:90 +#: settings/serializers/settings.py:95 msgid "LDAP server" msgstr "LDAP 地址" -#: settings/serializers/settings.py:90 +#: settings/serializers/settings.py:95 msgid "eg: ldap://localhost:389" msgstr "" -#: settings/serializers/settings.py:92 +#: settings/serializers/settings.py:97 msgid "Bind DN" msgstr "绑定 DN" -#: settings/serializers/settings.py:95 +#: settings/serializers/settings.py:100 msgid "User OU" msgstr "用户 OU" -#: settings/serializers/settings.py:96 +#: settings/serializers/settings.py:101 msgid "Use | split multi OUs" msgstr "多个 OU 使用 | 分割" -#: settings/serializers/settings.py:99 +#: settings/serializers/settings.py:104 msgid "User search filter" msgstr "用户过滤器" -#: settings/serializers/settings.py:100 +#: settings/serializers/settings.py:105 #, python-format msgid "Choice may be (cn|uid|sAMAccountName)=%(user)s)" msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)" -#: settings/serializers/settings.py:103 +#: settings/serializers/settings.py:108 msgid "User attr map" msgstr "用户属性映射" -#: settings/serializers/settings.py:104 +#: settings/serializers/settings.py:109 msgid "" "User attr map present how to map LDAP user attr to jumpserver, username,name," "email is jumpserver attr" @@ -2338,23 +2361,23 @@ msgstr "" "用户属性映射代表怎样将LDAP中用户属性映射到jumpserver用户上,username, name," "email 是jumpserver的用户需要属性" -#: settings/serializers/settings.py:106 +#: settings/serializers/settings.py:111 msgid "Enable LDAP auth" msgstr "启用 LDAP 认证" -#: settings/serializers/settings.py:117 +#: settings/serializers/settings.py:122 msgid "Auto" msgstr "自动" -#: settings/serializers/settings.py:123 +#: settings/serializers/settings.py:128 msgid "Password auth" msgstr "密码认证" -#: settings/serializers/settings.py:125 +#: settings/serializers/settings.py:130 msgid "Public key auth" msgstr "密钥认证" -#: settings/serializers/settings.py:126 +#: settings/serializers/settings.py:131 msgid "" "Tips: If use other auth method, like AD/LDAP, you should disable this to " "avoid being able to log in after deleting" @@ -2362,19 +2385,19 @@ msgstr "" "提示:如果你使用其它认证方式,如 AD/LDAP,你应该禁用此项,以避免第三方系统删" "除后,还可以登录" -#: settings/serializers/settings.py:129 +#: settings/serializers/settings.py:134 msgid "List sort by" msgstr "资产列表排序" -#: settings/serializers/settings.py:130 +#: settings/serializers/settings.py:135 msgid "List page size" msgstr "资产列表每页数量" -#: settings/serializers/settings.py:132 +#: settings/serializers/settings.py:137 msgid "Session keep duration" msgstr "会话日志保存时间" -#: settings/serializers/settings.py:133 +#: settings/serializers/settings.py:138 msgid "" "Units: days, Session, record, command will be delete if more than duration, " "only in database" @@ -2382,64 +2405,64 @@ msgstr "" "单位:天。 会话、录像、命令记录超过该时长将会被删除(仅影响数据库存储, oss等不" "受影响)" -#: settings/serializers/settings.py:135 +#: settings/serializers/settings.py:140 msgid "Telnet login regex" msgstr "Telnet 成功正则表达式" -#: settings/serializers/settings.py:140 +#: settings/serializers/settings.py:145 msgid "Global MFA auth" msgstr "全局启用 MFA 认证" -#: settings/serializers/settings.py:141 +#: settings/serializers/settings.py:146 msgid "All user enable MFA" msgstr "强制所有用户启用多因子认证" -#: settings/serializers/settings.py:144 +#: settings/serializers/settings.py:149 msgid "Batch command execution" msgstr "批量命令执行" -#: settings/serializers/settings.py:145 +#: settings/serializers/settings.py:150 msgid "Allow user run batch command or not using ansible" msgstr "是否允许用户使用 ansible 执行批量命令" -#: settings/serializers/settings.py:148 +#: settings/serializers/settings.py:153 msgid "Enable terminal register" msgstr "终端注册" -#: settings/serializers/settings.py:149 +#: settings/serializers/settings.py:154 msgid "" "Allow terminal register, after all terminal setup, you should disable this " "for security" msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭" -#: settings/serializers/settings.py:153 +#: settings/serializers/settings.py:158 msgid "Limit the number of login failures" msgstr "限制登录失败次数" -#: settings/serializers/settings.py:157 +#: settings/serializers/settings.py:162 msgid "Block logon interval" msgstr "禁止登录时间间隔" -#: settings/serializers/settings.py:158 +#: settings/serializers/settings.py:163 msgid "" "Tip: (unit/minute) if the user has failed to log in for a limited number of " "times, no login is allowed during this time interval." msgstr "" "提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录" -#: settings/serializers/settings.py:162 +#: settings/serializers/settings.py:167 msgid "Connection max idle time" msgstr "连接最大空闲时间" -#: settings/serializers/settings.py:163 +#: settings/serializers/settings.py:168 msgid "If idle time more than it, disconnect connection Unit: minute" msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)" -#: settings/serializers/settings.py:167 +#: settings/serializers/settings.py:172 msgid "User password expiration" msgstr "用户密码过期时间" -#: settings/serializers/settings.py:168 +#: settings/serializers/settings.py:173 msgid "" "Tip: (unit: day) If the user does not update the password during the time, " "the user password will expire failure;The password expiration reminder mail " @@ -2449,53 +2472,53 @@ msgstr "" "提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期" "提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户" -#: settings/serializers/settings.py:172 +#: settings/serializers/settings.py:177 msgid "Number of repeated historical passwords" msgstr "不能设置近几次密码" -#: settings/serializers/settings.py:173 +#: settings/serializers/settings.py:178 msgid "" "Tip: When the user resets the password, it cannot be the previous n " "historical passwords of the user" msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" -#: settings/serializers/settings.py:177 +#: settings/serializers/settings.py:182 msgid "Password minimum length" msgstr "密码最小长度" -#: settings/serializers/settings.py:180 +#: settings/serializers/settings.py:185 msgid "Must contain capital" msgstr "必须包含大写字符" -#: settings/serializers/settings.py:182 +#: settings/serializers/settings.py:187 msgid "Must contain lowercase" msgstr "必须包含小写字符" -#: settings/serializers/settings.py:183 +#: settings/serializers/settings.py:188 msgid "Must contain numeric" msgstr "必须包含数字" -#: settings/serializers/settings.py:184 +#: settings/serializers/settings.py:189 msgid "Must contain special" msgstr "必须包含特殊字符" -#: settings/serializers/settings.py:185 +#: settings/serializers/settings.py:190 msgid "Insecure command alert" msgstr "危险命令告警" -#: settings/serializers/settings.py:187 +#: settings/serializers/settings.py:192 msgid "Email recipient" msgstr "邮件收件人" -#: settings/serializers/settings.py:188 +#: settings/serializers/settings.py:193 msgid "Multiple user using , split" msgstr "多个用户,使用 , 分割" -#: settings/serializers/settings.py:196 +#: settings/serializers/settings.py:201 msgid "Enable WeCom Auth" msgstr "启用企业微信认证" -#: settings/serializers/settings.py:203 +#: settings/serializers/settings.py:208 msgid "Enable DingTalk Auth" msgstr "启用钉钉认证" @@ -2805,7 +2828,7 @@ msgstr "Web终端" msgid "File manager" msgstr "文件管理" -#: templates/_nav.html:110 terminal/apps.py:9 +#: templates/_nav.html:110 terminal/apps.py:9 terminal/notifications.py:15 #: terminal/serializers/session.py:40 msgid "Terminal" msgstr "终端" @@ -3143,20 +3166,20 @@ msgstr "风险等级(显示名称)" msgid "Timestamp" msgstr "时间戳" -#: terminal/const.py:31 +#: terminal/const.py:32 msgid "Critical" msgstr "严重" -#: terminal/const.py:32 +#: terminal/const.py:33 msgid "High" msgstr "较高" -#: terminal/const.py:33 users/templates/users/reset_password.html:50 +#: terminal/const.py:34 users/templates/users/reset_password.html:50 #: users/templates/users/user_password_update.html:104 msgid "Normal" msgstr "正常" -#: terminal/const.py:34 +#: terminal/const.py:35 msgid "Offline" msgstr "离线" @@ -3236,6 +3259,89 @@ msgstr "命令存储" msgid "Replay storage" msgstr "录像存储" +#: terminal/notifications.py:35 +msgid "Terminal command alert" +msgstr "终端命令告警" + +#: terminal/notifications.py:44 +#, python-format +msgid "" +"\n" +" Command: %(command)s\n" +"
\n" +" Asset: %(host_name)s (%(host_ip)s)\n" +"
\n" +" User: %(user)s\n" +"
\n" +" Level: %(risk_level)s\n" +"
\n" +" Session: session " +"detail\n" +"
\n" +" " +msgstr "" +"\n" +" 命令: %(command)s\n" +"
\n" +" 资产: %(host_name)s (%(host_ip)s)\n" +"
\n" +" 用户: %(user)s\n" +"
\n" +" 等级: %(risk_level)s\n" +"
\n" +" 会话: 会话详情\n" +"
\n" +" " + +#: terminal/notifications.py:79 +#, python-format +msgid "" +"Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $" +"%(command)s" +msgstr "危险命令告警: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s" + +#: terminal/notifications.py:97 +msgid "Batch command alert" +msgstr "批量命令告警" + +#: terminal/notifications.py:108 +#, python-format +msgid "" +"\n" +"
\n" +" Assets: %(assets)s\n" +"
\n" +" User: %(user)s\n" +"
\n" +" Level: %(risk_level)s\n" +"
\n" +"\n" +" ----------------- Commands ---------------- " +"
\n" +" %(command)s
\n" +" ----------------- Commands ---------------- " +"
\n" +" " +msgstr "" +"\n" +"
\n" +" 资产: %(assets)s\n" +"
\n" +" 用户: %(user)s\n" +"
\n" +" 等级: %(risk_level)s\n" +"
\n" +"\n" +" ----------------- 命令 ----------------
\n" +" %(command)s
\n" +" ----------------- 命令 ----------------
\n" +" " + +#: terminal/notifications.py:134 +#, python-format +msgid "Insecure Web Command Execution Alert: [%(name)s]" +msgstr "Web页面-> 命令执行 告警: [%(name)s]" + #: terminal/serializers/session.py:33 msgid "User ID" msgstr "用户 ID" @@ -3277,7 +3383,7 @@ msgid "Secret key" msgstr "" #: terminal/serializers/storage.py:39 terminal/serializers/storage.py:51 -#: terminal/serializers/storage.py:81 +#: terminal/serializers/storage.py:81 terminal/serializers/storage.py:91 msgid "Endpoint" msgstr "端点" @@ -3285,43 +3391,43 @@ msgstr "端点" msgid "Region" msgstr "地域" -#: terminal/serializers/storage.py:91 +#: terminal/serializers/storage.py:101 msgid "Container name" msgstr "容器名称" -#: terminal/serializers/storage.py:93 +#: terminal/serializers/storage.py:103 msgid "Account name" msgstr "账户名称" -#: terminal/serializers/storage.py:94 +#: terminal/serializers/storage.py:104 msgid "Account key" msgstr "账户密钥" -#: terminal/serializers/storage.py:97 +#: terminal/serializers/storage.py:107 msgid "Endpoint suffix" msgstr "端点后缀" -#: terminal/serializers/storage.py:155 +#: terminal/serializers/storage.py:166 msgid "The address format is incorrect" msgstr "地址格式不正确" -#: terminal/serializers/storage.py:162 +#: terminal/serializers/storage.py:173 msgid "Host invalid" msgstr "主机无效" -#: terminal/serializers/storage.py:165 +#: terminal/serializers/storage.py:176 msgid "Port invalid" msgstr "端口无效" -#: terminal/serializers/storage.py:181 +#: terminal/serializers/storage.py:192 msgid "Index" msgstr "索引" -#: terminal/serializers/storage.py:183 +#: terminal/serializers/storage.py:194 msgid "Doc type" msgstr "文档类型" -#: terminal/serializers/storage.py:185 +#: terminal/serializers/storage.py:196 msgid "Ignore Certificate Verification" msgstr "忽略证书认证" @@ -3329,78 +3435,6 @@ msgstr "忽略证书认证" msgid "Not found" msgstr "没有发现" -#: terminal/utils.py:78 -#, python-format -msgid "" -"Insecure Command Alert: [%(name)s->%(login_from)s@%(remote_addr)s] $" -"%(command)s" -msgstr "危险命令告警: [%(name)s->%(login_from)s@%(remote_addr)s] $%(command)s" - -#: terminal/utils.py:86 -#, python-format -msgid "" -"\n" -" Command: %(command)s\n" -"
\n" -" Asset: %(host_name)s (%(host_ip)s)\n" -"
\n" -" User: %(user)s\n" -"
\n" -" Level: %(risk_level)s\n" -"
\n" -" Session: session detail\n" -"
\n" -" " -msgstr "" -"\n" -" 命令: %(command)s\n" -"
\n" -" 资产: %(host_name)s (%(host_ip)s)\n" -"
\n" -" 用户: %(user)s\n" -"
\n" -" 等级: %(risk_level)s\n" -"
\n" -" 会话: 会话详情\n" -"
\n" -" " - -#: terminal/utils.py:113 -#, python-format -msgid "Insecure Web Command Execution Alert: [%(name)s]" -msgstr "Web页面-> 命令执行 告警: [%(name)s]" - -#: terminal/utils.py:121 -#, python-format -msgid "" -"\n" -"
\n" -" Assets: %(assets)s\n" -"
\n" -" User: %(user)s\n" -"
\n" -" Level: %(risk_level)s\n" -"
\n" -"\n" -" ----------------- Commands ----------------
\n" -" %(command)s
\n" -" ----------------- Commands ----------------
\n" -" " -msgstr "" -"\n" -"
\n" -" 资产: %(assets)s\n" -"
\n" -" 用户: %(user)s\n" -"
\n" -" 等级: %(risk_level)s\n" -"
\n" -"\n" -" ----------------- 命令 ----------------
\n" -" %(command)s
\n" -" ----------------- 命令 ----------------
\n" -" " - #: tickets/const.py:8 msgid "General" msgstr "一般" @@ -3837,10 +3871,6 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" -#: users/forms/profile.py:101 users/models/user.py:552 -msgid "Email" -msgstr "邮件" - #: users/forms/profile.py:108 msgid "Old password" msgstr "原来密码" @@ -3898,10 +3928,6 @@ msgstr "头像" msgid "Wechat" msgstr "微信" -#: users/models/user.py:596 -msgid "User source" -msgstr "用户来源" - #: users/models/user.py:600 msgid "Date password last updated" msgstr "最后更新密码日期" @@ -3910,11 +3936,11 @@ msgstr "最后更新密码日期" msgid "Need update password" msgstr "需要更新密码" -#: users/models/user.py:748 +#: users/models/user.py:754 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:751 +#: users/models/user.py:757 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" diff --git a/apps/users/migrations/0036_auto_20210604_1124.py b/apps/users/migrations/0036_auto_20210604_1124.py deleted file mode 100644 index 1bdc17943..000000000 --- a/apps/users/migrations/0036_auto_20210604_1124.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.6 on 2021-06-04 03:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0035_auto_20210526_1100'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='source', - field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS')], default='local', max_length=30, verbose_name='User source'), - ), - ] diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 40bb165e9..f362e60ac 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -593,7 +593,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser): source = models.CharField( max_length=30, default=Source.local, choices=Source.choices, - verbose_name=_('User source') + verbose_name=_('Source') ) date_password_last_updated = models.DateTimeField( auto_now_add=True, blank=True, null=True, From f91bef41058d70e917e4bb38f5c7f8799079221c Mon Sep 17 00:00:00 2001 From: Bai Date: Fri, 4 Jun 2021 14:32:15 +0800 Subject: [PATCH 22/33] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=8C=85=E7=89=88=E6=9C=AC=E5=8F=B7:=20jms-storage-sd?= =?UTF-8?q?k=3D=3D0.0.37?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 9dbb87017..97dac7a49 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -62,7 +62,7 @@ pytz==2018.3 PyYAML==5.1 redis==3.5.3 requests==2.22.0 -jms-storage==0.0.35 +jms-storage==0.0.37 s3transfer==0.3.3 simplejson==3.13.2 six==1.11.0 From daf7d98f0e7cd7cca3867a7b337ffee1f9d0f268 Mon Sep 17 00:00:00 2001 From: xinwen Date: Fri, 4 Jun 2021 17:26:34 +0800 Subject: [PATCH 23/33] =?UTF-8?q?fix:=20=E5=85=B6=E4=BB=96=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E4=B8=AD=E5=88=9B=E5=BB=BA=E7=9A=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E6=B7=BB=E5=8A=A0=E5=88=B0=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E7=BB=84=E7=BB=87=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/orgs/signals_handler/common.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/orgs/signals_handler/common.py b/apps/orgs/signals_handler/common.py index 6beecd5cb..fb8b2a2d1 100644 --- a/apps/orgs/signals_handler/common.py +++ b/apps/orgs/signals_handler/common.py @@ -167,10 +167,3 @@ def on_org_user_changed(action, instance, reverse, pk_set, **kwargs): leaved_users = set(pk_set) - set(org.members.filter(id__in=user_pk_set).values_list('id', flat=True)) _clear_users_from_org(org, leaved_users) - - -@receiver(post_save, sender=User) -def on_user_create_refresh_cache(sender, instance, created, **kwargs): - if created: - default_org = Organization.default() - default_org.members.add(instance) From 391a5cb7d0b545314ccdcc6e10bc42426d6a3c6b Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 7 Jun 2021 10:40:07 +0800 Subject: [PATCH 24/33] =?UTF-8?q?perf:=20=E4=BF=AE=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E8=AE=BE=E7=BD=AE=E5=AF=86=E7=A0=81=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/system_user.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index d0cc5662d..25c3f58f4 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -1,12 +1,12 @@ # ~*~ coding: utf-8 ~*~ from django.shortcuts import get_object_or_404 from rest_framework.response import Response -from rest_framework.exceptions import ValidationError from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsValidUser from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins import generics +from orgs.utils import tmp_to_root_org from ..models import SystemUser, Asset from .. import serializers from ..serializers import SystemUserWithAuthInfoSerializer, SystemUserTempAuthSerializer @@ -66,11 +66,13 @@ class SystemUserTempAuthInfoApi(generics.CreateAPIView): serializer = super().get_serializer(data=request.data) serializer.is_valid(raise_exception=True) pk = kwargs.get('pk') - instance = get_object_or_404(SystemUser, pk=pk) - data = serializer.validated_data user = self.request.user + data = serializer.validated_data instance_id = data.get('instance_id') - instance.set_temp_auth(instance_id, user, data) + + with tmp_to_root_org(): + instance = get_object_or_404(SystemUser, pk=pk) + instance.set_temp_auth(instance_id, user, data) return Response(serializer.data, status=201) From af92271a52f5ec43d68c5dfad58748c764b7b899 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 8 Jun 2021 11:11:27 +0800 Subject: [PATCH 25/33] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E7=AB=99?= =?UTF-8?q?=E5=86=85=E4=BF=A1=E6=8E=A5=E5=8F=A3=20(#6228)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 调整站内信接口 * 添加 websockt * 添加信息类型字段 * 添加 has_read 过滤参数 * feat: 调整站内信接口 * 添加 websockt * 添加信息类型字段 * 添加 has_read 过滤参数 * 去掉type websocket * perf: 去掉type Co-authored-by: xinwen Co-authored-by: ibuler --- apps/jumpserver/urls.py | 2 +- apps/notifications/api/site_msgs.py | 17 +++++----------- apps/notifications/filters.py | 11 ++++++++++ apps/notifications/models/site_msg.py | 1 + apps/notifications/serializers/site_msgs.py | 20 +++++++++++++------ apps/notifications/site_msg.py | 5 +++-- apps/notifications/urls/__init__.py | 0 .../{urls.py => urls/notifications.py} | 2 +- 8 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 apps/notifications/filters.py create mode 100644 apps/notifications/urls/__init__.py rename apps/notifications/{urls.py => urls/notifications.py} (93%) diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 510654048..c2ffea6ec 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -23,7 +23,7 @@ api_v1 = [ path('applications/', include('applications.urls.api_urls', namespace='api-applications')), path('tickets/', include('tickets.urls.api_urls', namespace='api-tickets')), path('acls/', include('acls.urls.api_urls', namespace='api-acls')), - path('notifications/', include('notifications.urls', namespace='api-notifications')), + path('notifications/', include('notifications.urls.notifications', namespace='api-notifications')), path('prometheus/metrics/', api.PrometheusMetricsApi.as_view()), ] diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py index e64ac23e2..5618a70b4 100644 --- a/apps/notifications/api/site_msgs.py +++ b/apps/notifications/api/site_msgs.py @@ -6,10 +6,11 @@ from common.permissions import IsValidUser from common.const.http import GET, PATCH, POST from common.drf.api import JmsGenericViewSet from ..serializers import ( - SiteMessageListSerializer, SiteMessageRetrieveSerializer, SiteMessageIdsSerializer, + SiteMessageDetailSerializer, SiteMessageIdsSerializer, SiteMessageSendSerializer, ) from ..site_msg import SiteMessage +from ..filters import SiteMsgFilter __all__ = ('SiteMessageViewSet', ) @@ -17,32 +18,24 @@ __all__ = ('SiteMessageViewSet', ) class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): permission_classes = (IsValidUser,) serializer_classes = { - 'retrieve': SiteMessageRetrieveSerializer, - 'unread': SiteMessageListSerializer, - 'list': SiteMessageListSerializer, + 'default': SiteMessageDetailSerializer, 'mark_as_read': SiteMessageIdsSerializer, 'send': SiteMessageSendSerializer, } + filterset_class = SiteMsgFilter def get_queryset(self): user = self.request.user msgs = SiteMessage.get_user_all_msgs(user.id) return msgs - @action(methods=[GET], detail=False) - def unread(self, request, **kwargs): - user = request.user - msgs = SiteMessage.get_user_unread_msgs(user.id) - msgs = self.filter_queryset(msgs) - return self.get_paginated_response_with_query_set(msgs) - @action(methods=[GET], detail=False, url_path='unread-total') def unread_total(self, request, **kwargs): user = request.user msgs = SiteMessage.get_user_unread_msgs(user.id) return Response(data={'total': msgs.count()}) - @action(methods=[PATCH], detail=False) + @action(methods=[PATCH], detail=False, url_path='mark-as-read') def mark_as_read(self, request, **kwargs): user = request.user seri = self.get_serializer(data=request.data) diff --git a/apps/notifications/filters.py b/apps/notifications/filters.py new file mode 100644 index 000000000..b14d47943 --- /dev/null +++ b/apps/notifications/filters.py @@ -0,0 +1,11 @@ +import django_filters + +from .models import SiteMessage + + +class SiteMsgFilter(django_filters.FilterSet): + has_read = django_filters.BooleanFilter(field_name='m2m_sitemessageusers__has_read') + + class Meta: + model = SiteMessage + fields = ('has_read',) diff --git a/apps/notifications/models/site_msg.py b/apps/notifications/models/site_msg.py index 3e3c09baa..556c00607 100644 --- a/apps/notifications/models/site_msg.py +++ b/apps/notifications/models/site_msg.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils.translation import gettext_lazy as _ from common.db.models import JMSModel diff --git a/apps/notifications/serializers/site_msgs.py b/apps/notifications/serializers/site_msgs.py index 8d76205e1..1f157add5 100644 --- a/apps/notifications/serializers/site_msgs.py +++ b/apps/notifications/serializers/site_msgs.py @@ -4,16 +4,24 @@ from rest_framework import serializers from ..models import SiteMessage -class SiteMessageListSerializer(ModelSerializer): - class Meta: - model = SiteMessage - fields = ['id', 'subject', 'has_read', 'read_at'] +class SenderMixin(ModelSerializer): + sender = serializers.SerializerMethodField() + + def get_sender(self, site_msg): + sender = site_msg.sender + if sender: + return str(sender) + else: + return '' -class SiteMessageRetrieveSerializer(ModelSerializer): +class SiteMessageDetailSerializer(SenderMixin, ModelSerializer): class Meta: model = SiteMessage - fields = ['id', 'subject', 'message', 'has_read', 'read_at'] + fields = [ + 'id', 'subject', 'message', 'has_read', 'read_at', + 'date_created', 'date_updated', 'sender', + ] class SiteMessageIdsSerializer(serializers.Serializer): diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py index 944a8ea3c..3767cc322 100644 --- a/apps/notifications/site_msg.py +++ b/apps/notifications/site_msg.py @@ -8,13 +8,14 @@ from .models import SiteMessage as SiteMessageModel, SiteMessageUsers class SiteMessage: @classmethod - def send_msg(cls, subject, message, user_ids=(), group_ids=(), sender=None, is_broadcast=False): + def send_msg(cls, subject, message, user_ids=(), group_ids=(), + sender=None, is_broadcast=False): if not any((user_ids, group_ids, is_broadcast)): raise ValueError('No recipient is specified') site_msg = SiteMessageModel.objects.create( subject=subject, message=message, - is_broadcast=is_broadcast, sender=sender + is_broadcast=is_broadcast, sender=sender, ) if is_broadcast: diff --git a/apps/notifications/urls/__init__.py b/apps/notifications/urls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/notifications/urls.py b/apps/notifications/urls/notifications.py similarity index 93% rename from apps/notifications/urls.py rename to apps/notifications/urls/notifications.py index ad05c4aca..60aaee873 100644 --- a/apps/notifications/urls.py +++ b/apps/notifications/urls/notifications.py @@ -2,7 +2,7 @@ from rest_framework_bulk.routes import BulkRouter from django.urls import path -from . import api +from notifications import api app_name = 'notifications' From 8fbea2f70265e2d412bb1b8179c865f143ba04fb Mon Sep 17 00:00:00 2001 From: Bai Date: Tue, 8 Jun 2021 11:29:47 +0800 Subject: [PATCH 26/33] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E8=B4=A6=E5=8F=B7name=E4=B8=BA=E9=9D=9E=E5=BF=85?= =?UTF-8?q?=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/serializers/asset_user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/assets/serializers/asset_user.py b/apps/assets/serializers/asset_user.py index 6552281c6..cd098537a 100644 --- a/apps/assets/serializers/asset_user.py +++ b/apps/assets/serializers/asset_user.py @@ -64,6 +64,7 @@ class AssetUserReadSerializer(AssetUserWriteSerializer): fields_fk = ['asset', 'hostname', 'ip'] fields = fields_small + fields_fk extra_kwargs = { + 'name': {'required': False}, 'username': {'required': True}, 'password': {'write_only': True}, 'private_key': {'write_only': True}, From a2eb431015b08d35fb2d8637391a4660ab576467 Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 8 Jun 2021 12:45:36 +0800 Subject: [PATCH 27/33] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=88=86=E8=BE=A8=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 12 ++++++++---- apps/authentication/serializers.py | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index ef908ed71..7add2d804 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -99,8 +99,8 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView 'full address:s': '', 'username:s': '', 'screen mode id:i': '0', - 'desktopwidth:i': '1280', - 'desktopheight:i': '800', + # 'desktopwidth:i': '1280', + # 'desktopheight:i': '800', 'use multimon:i': '1', 'session bpp:i': '32', 'audiomode:i': '0', @@ -120,6 +120,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView 'autoreconnection enabled:i': '1', 'bookmarktype:i': '3', 'use redirection server name:i': '0', + 'smart sizing:i': '0' # 'alternate shell:s:': '||MySQLWorkbench', # 'remoteapplicationname:s': 'Firefox', # 'remoteapplicationcmdline:s': '', @@ -145,8 +146,11 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView address = request.get_host().split(':')[0] + ':3389' options['full address:s'] = address options['username:s'] = '{}|{}'.format(user.username, token) - options['desktopwidth:i'] = width - options['desktopheight:i'] = height + if width and height: + options['desktopwidth:i'] = width + options['desktopheight:i'] = height + else: + options['smart sizing:i'] = '1' data = '' for k, v in options.items(): data += f'{k}:{v}\n' diff --git a/apps/authentication/serializers.py b/apps/authentication/serializers.py index 9265755d6..11381c4cb 100644 --- a/apps/authentication/serializers.py +++ b/apps/authentication/serializers.py @@ -199,5 +199,5 @@ class ConnectionTokenSecretSerializer(serializers.Serializer): class RDPFileSerializer(ConnectionTokenSerializer): - width = serializers.IntegerField(default=1600) - height = serializers.IntegerField(default=900) + width = serializers.IntegerField(allow_null=True, max_value=3112, min_value=100, required=False) + height = serializers.IntegerField(allow_null=True, max_value=4096, min_value=100, required=False) From 33a29ae788d9f411c3bc6d7f4cdaa038eb70337e Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 8 Jun 2021 15:23:40 +0800 Subject: [PATCH 28/33] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20xrdp=20setti?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 4 ++-- apps/jumpserver/conf.py | 2 +- apps/jumpserver/settings/custom.py | 2 +- apps/settings/serializers/settings.py | 11 ++++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 7add2d804..a86ce8b8d 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -141,8 +141,8 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView token = self.create_token(user, asset, application, system_user) # Todo: 上线后地址是 JumpServerAddr:3389 - address = settings.RDP_ADDR - if address == 'localhost:3389': + address = settings.TERMINAL_RDP_ADDR + if not address or address == 'localhost:3389': address = request.get_host().split(':')[0] + ':3389' options['full address:s'] = address options['username:s'] = '{}|{}'.format(user.username, token) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index f3b59d2d9..89b790929 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -302,7 +302,7 @@ class Config(dict): 'FORGOT_PASSWORD_URL': '', 'HEALTH_CHECK_TOKEN': '', - 'RDP_ADDR': 'localhost:3389' + 'TERMINAL_RDP_ADDR': '' } def compatible_auth_openid_of_key(self): diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index d68195792..ae965d343 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -126,4 +126,4 @@ FORGOT_PASSWORD_URL = CONFIG.FORGOT_PASSWORD_URL GLOBAL_ORG_DISPLAY_NAME = CONFIG.GLOBAL_ORG_DISPLAY_NAME HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN -RDP_ADDR = CONFIG.RDP_ADDR +TERMINAL_RDP_ADDR = CONFIG.TERMINAL_RDP_ADDR diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index a997c5466..99bdc8c09 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -15,11 +15,7 @@ class BasicSettingSerializer(serializers.Serializer): required=True, label=_("Site url"), help_text=_('eg: http://dev.jumpserver.org:8080') ) - RDP_ADDR = serializers.CharField( - required=True, label=_("RDP address"), - max_length=1024, - help_text=_('RDP visit address, eg: dev.jumpserver.org:3389') - ) + USER_GUIDE_URL = serializers.URLField( required=False, allow_blank=True, allow_null=True, label=_("User guide url"), help_text=_('User first login update profile done redirect to it') @@ -138,6 +134,11 @@ class TerminalSettingSerializer(serializers.Serializer): help_text=_('Units: days, Session, record, command will be delete if more than duration, only in database') ) TERMINAL_TELNET_REGEX = serializers.CharField(allow_blank=True, max_length=1024, required=False, label=_('Telnet login regex')) + TERMINAL_RDP_ADDR = serializers.CharField( + required=False, label=_("RDP address"), + max_length=1024, + help_text=_('RDP visit address, eg: dev.jumpserver.org:3389') + ) class SecuritySettingSerializer(serializers.Serializer): From d6527e3b02e2b9df77be36eaeff3b67f98b061a6 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Tue, 8 Jun 2021 20:50:15 +0800 Subject: [PATCH 29/33] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=AE=B0=E5=BD=95=E5=AF=86=E7=A0=81=20(#6247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 优化 xrdp setting * perf: 优化支持记录密码 Co-authored-by: ibuler --- apps/jumpserver/conf.py | 1 + apps/jumpserver/settings/custom.py | 1 + apps/settings/api/common.py | 1 + apps/settings/serializers/settings.py | 1 + 4 files changed, 4 insertions(+) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index 89b790929..81188eb8a 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -259,6 +259,7 @@ class Config(dict): 'SECURITY_INSECURE_COMMAND': False, 'SECURITY_INSECURE_COMMAND_LEVEL': 5, 'SECURITY_INSECURE_COMMAND_EMAIL_RECEIVER': '', + 'SECURITY_LUNA_REMEMBER_AUTH': True, 'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_LISTEN_PORT': 8080, diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index ae965d343..c60c53788 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -127,3 +127,4 @@ GLOBAL_ORG_DISPLAY_NAME = CONFIG.GLOBAL_ORG_DISPLAY_NAME HEALTH_CHECK_TOKEN = CONFIG.HEALTH_CHECK_TOKEN TERMINAL_RDP_ADDR = CONFIG.TERMINAL_RDP_ADDR +SECURITY_LUNA_REMEMBER_AUTH = CONFIG.SECURITY_LUNA_REMEMBER_AUTH diff --git a/apps/settings/api/common.py b/apps/settings/api/common.py index bb3107dfd..03c885d78 100644 --- a/apps/settings/api/common.py +++ b/apps/settings/api/common.py @@ -115,6 +115,7 @@ class PublicSettingApi(generics.RetrieveAPIView): "OLD_PASSWORD_HISTORY_LIMIT_COUNT": settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT, "SECURITY_COMMAND_EXECUTION": settings.SECURITY_COMMAND_EXECUTION, "SECURITY_PASSWORD_EXPIRATION_TIME": settings.SECURITY_PASSWORD_EXPIRATION_TIME, + "SECURITY_LUNA_REMEMBER_AUTH": settings.SECURITY_LUNA_REMEMBER_AUTH, "XPACK_LICENSE_IS_VALID": has_valid_xpack_license(), "LOGIN_TITLE": self.get_login_title(), "LOGO_URLS": self.get_logo_urls(), diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 99bdc8c09..f2e11c17f 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -137,6 +137,7 @@ class TerminalSettingSerializer(serializers.Serializer): TERMINAL_RDP_ADDR = serializers.CharField( required=False, label=_("RDP address"), max_length=1024, + allow_blank=True, help_text=_('RDP visit address, eg: dev.jumpserver.org:3389') ) From de6908e5a6a2609bc67db2626c9e6fd193f122bb Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 9 Jun 2021 10:39:03 +0800 Subject: [PATCH 30/33] =?UTF-8?q?perf:=20rdp=20file=E6=B7=BB=E5=8A=A0domai?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 禁用的用户不返回信息 perf: 优化token,禁用的资产无法链接 --- apps/authentication/api/connection_token.py | 65 +++++++++++++-------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index a86ce8b8d..6c65ec20f 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -10,6 +10,7 @@ from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied +from rest_framework import serializers from common.utils import get_logger, random_string from common.drf.api import SerializerMixin2 @@ -120,7 +121,8 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView 'autoreconnection enabled:i': '1', 'bookmarktype:i': '3', 'use redirection server name:i': '0', - 'smart sizing:i': '0' + 'smart sizing:i': '0', + # 'domain:s': '' # 'alternate shell:s:': '||MySQLWorkbench', # 'remoteapplicationname:s': 'Firefox', # 'remoteapplicationcmdline:s': '', @@ -140,12 +142,13 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView user = request.user token = self.create_token(user, asset, application, system_user) - # Todo: 上线后地址是 JumpServerAddr:3389 address = settings.TERMINAL_RDP_ADDR if not address or address == 'localhost:3389': address = request.get_host().split(':')[0] + ':3389' options['full address:s'] = address options['username:s'] = '{}|{}'.format(user.username, token) + if system_user.ad_domain: + options['domain:s'] = system_user.ad_domain if width and height: options['desktopwidth:i'] = width options['desktopheight:i'] = height @@ -161,10 +164,9 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView return response @staticmethod - def _get_application_secret_detail(value): + def _get_application_secret_detail(application): from applications.models import Application from perms.models import Action - application = get_object_or_404(Application, id=value.get('application')) gateway = None if not application.category_remote_app: @@ -190,15 +192,15 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView } @staticmethod - def _get_asset_secret_detail(value, user, system_user): - from assets.models import Asset + def _get_asset_secret_detail(asset, user, system_user): from perms.utils.asset import get_asset_system_user_ids_with_actions_by_user - asset = get_object_or_404(Asset, id=value.get('asset')) systemuserid_actions_mapper = get_asset_system_user_ids_with_actions_by_user(user, asset) actions = systemuserid_actions_mapper.get(system_user.id, []) + gateway = None if asset and asset.domain and asset.domain.has_gateway(): gateway = asset.domain.random_gateway() + return { 'asset': asset, 'application': None, @@ -207,32 +209,49 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView 'actions': actions, } - @action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') - def get_secret_detail(self, request, *args, **kwargs): + def valid_token(self, token): from users.models import User - from assets.models import SystemUser + from assets.models import SystemUser, Asset + from applications.models import Application - token = request.data.get('token', '') key = self.CACHE_KEY_PREFIX.format(token) value = cache.get(key, None) if not value: - return Response(status=404) - user = get_object_or_404(User, id=value.get('user')) - system_user = get_object_or_404(SystemUser, id=value.get('system_user')) - data = dict(user=user, system_user=system_user) + raise serializers.ValidationError('Token not found') + user = get_object_or_404(User, id=value.get('user')) + if user.is_valid: + raise serializers.ValidationError("User not valid, disabled or expired") + + system_user = get_object_or_404(SystemUser, id=value.get('system_user')) + if system_user.ad_domain: + system_user.username = '{0.username}@{0.ad_domain}'.format(system_user) + + asset = None + app = None if value.get('type') == 'asset': - asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user) - asset = asset_detail.get('asset') - if asset: - system_user.load_asset_more_auth(asset.id, user.username, user.id) + asset = get_object_or_404(Asset, id=value.get('asset')) + else: + app = get_object_or_404(Application, id=value.get('application')) + + if asset and not asset.is_active: + raise serializers.ValidationError("Asset disabled") + return value, user, system_user, asset, app + + @action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail') + def get_secret_detail(self, request, *args, **kwargs): + token = request.data.get('token', '') + value, user, system_user, asset, app = self.valid_token(token) + + data = dict(user=user, system_user=system_user) + if asset: + asset_detail = self._get_asset_secret_detail(asset, user=user, system_user=system_user) + system_user.load_asset_more_auth(asset.id, user.username, user.id) data['type'] = 'asset' data.update(asset_detail) else: - app_detail = self._get_application_secret_detail(value) - app = app_detail.get("application") - if app: - system_user.load_app_more_auth(app.id, user.id) + app_detail = self._get_application_secret_detail(app) + system_user.load_app_more_auth(app.id, user.id) data['type'] = 'application' data.update(app_detail) From 34b2a5fe0b76c0674e80d8d1b5f1ddf50024d54a Mon Sep 17 00:00:00 2001 From: ibuler Date: Wed, 9 Jun 2021 15:46:32 +0800 Subject: [PATCH 31/33] =?UTF-8?q?perf:=20=E4=BF=AE=E5=A4=8D=E4=B8=8A?= =?UTF-8?q?=E6=AC=A1=E5=BC=95=E8=B5=B7=E7=9A=84=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/api/connection_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index 6c65ec20f..a50de0371 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -220,7 +220,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView raise serializers.ValidationError('Token not found') user = get_object_or_404(User, id=value.get('user')) - if user.is_valid: + if not user.is_valid: raise serializers.ValidationError("User not valid, disabled or expired") system_user = get_object_or_404(SystemUser, id=value.get('system_user')) From 891a5157a75c8b5123349c27750897ebde14a5c4 Mon Sep 17 00:00:00 2001 From: fit2bot <68588906+fit2bot@users.noreply.github.com> Date: Wed, 9 Jun 2021 20:10:34 +0800 Subject: [PATCH 32/33] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96token=E6=97=B6?= =?UTF-8?q?=E9=97=B4=20(#6252)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 修复上次引起的小bug * perf: 优化token时间 Co-authored-by: ibuler --- apps/authentication/api/connection_token.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/authentication/api/connection_token.py b/apps/authentication/api/connection_token.py index a50de0371..76d6e934d 100644 --- a/apps/authentication/api/connection_token.py +++ b/apps/authentication/api/connection_token.py @@ -50,7 +50,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView raise PermissionDenied(error) return True - def create_token(self, user, asset, application, system_user): + def create_token(self, user, asset, application, system_user, ttl=5*60): if not settings.CONNECTION_TOKEN_ENABLED: raise PermissionDenied('Connection token disabled') if not user: @@ -80,7 +80,7 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView }) key = self.CACHE_KEY_PREFIX.format(token) - cache.set(key, value, timeout=30*60) + cache.set(key, value, timeout=ttl) return token def create(self, request, *args, **kwargs): @@ -165,7 +165,6 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView @staticmethod def _get_application_secret_detail(application): - from applications.models import Application from perms.models import Action gateway = None @@ -224,8 +223,6 @@ class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericView raise serializers.ValidationError("User not valid, disabled or expired") system_user = get_object_or_404(SystemUser, id=value.get('system_user')) - if system_user.ad_domain: - system_user.username = '{0.username}@{0.ad_domain}'.format(system_user) asset = None app = None From 7dddf0c3c281897002e070104b261d4172b139a0 Mon Sep 17 00:00:00 2001 From: xinwen Date: Wed, 9 Jun 2021 20:51:31 +0800 Subject: [PATCH 33/33] =?UTF-8?q?fix:=20=E7=AB=99=E5=86=85=E4=BF=A1?= =?UTF-8?q?=E6=9C=AA=E8=AF=BB=E4=BF=A1=E6=81=AF=E8=AE=A1=E6=95=B0=E4=B8=8D?= =?UTF-8?q?=E5=87=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/notifications/api/site_msgs.py | 10 ++++++++-- apps/notifications/filters.py | 11 +++++++++-- apps/notifications/site_msg.py | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/notifications/api/site_msgs.py b/apps/notifications/api/site_msgs.py index 5618a70b4..6ee856922 100644 --- a/apps/notifications/api/site_msgs.py +++ b/apps/notifications/api/site_msgs.py @@ -2,6 +2,7 @@ from rest_framework.response import Response from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.decorators import action +from common.http import is_true from common.permissions import IsValidUser from common.const.http import GET, PATCH, POST from common.drf.api import JmsGenericViewSet @@ -26,13 +27,18 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JmsGenericViewSet): def get_queryset(self): user = self.request.user - msgs = SiteMessage.get_user_all_msgs(user.id) + has_read = self.request.query_params.get('has_read') + + if has_read is None: + msgs = SiteMessage.get_user_all_msgs(user.id) + else: + msgs = SiteMessage.filter_user_msgs(user.id, has_read=is_true(has_read)) return msgs @action(methods=[GET], detail=False, url_path='unread-total') def unread_total(self, request, **kwargs): user = request.user - msgs = SiteMessage.get_user_unread_msgs(user.id) + msgs = SiteMessage.filter_user_msgs(user.id, has_read=False) return Response(data={'total': msgs.count()}) @action(methods=[PATCH], detail=False, url_path='mark-as-read') diff --git a/apps/notifications/filters.py b/apps/notifications/filters.py index b14d47943..c28b8a3f2 100644 --- a/apps/notifications/filters.py +++ b/apps/notifications/filters.py @@ -1,10 +1,17 @@ import django_filters +from common.drf.filters import BaseFilterSet from .models import SiteMessage -class SiteMsgFilter(django_filters.FilterSet): - has_read = django_filters.BooleanFilter(field_name='m2m_sitemessageusers__has_read') +class SiteMsgFilter(BaseFilterSet): + # 不用 Django 的关联表过滤,有个小bug,会重复关联相同表 + # SELECT DISTINCT * FROM `notifications_sitemessage` + # INNER JOIN `notifications_sitemessageusers` ON (`notifications_sitemessage`.`id` = `notifications_sitemessageusers`.`sitemessage_id`) + # INNER JOIN `notifications_sitemessageusers` T4 ON (`notifications_sitemessage`.`id` = T4.`sitemessage_id`) + # WHERE (`notifications_sitemessageusers`.`user_id` = '40c8f140dfa246d4861b80f63cf4f6e3' AND NOT T4.`has_read`) + # ORDER BY `notifications_sitemessage`.`date_created` DESC LIMIT 15; + has_read = django_filters.BooleanFilter(method='do_nothing') class Meta: model = SiteMessage diff --git a/apps/notifications/site_msg.py b/apps/notifications/site_msg.py index 3767cc322..b78d3c7f4 100644 --- a/apps/notifications/site_msg.py +++ b/apps/notifications/site_msg.py @@ -51,10 +51,10 @@ class SiteMessage: return site_msgs_count @classmethod - def get_user_unread_msgs(cls, user_id): + def filter_user_msgs(cls, user_id, has_read=False): site_msgs = SiteMessageModel.objects.filter( m2m_sitemessageusers__user_id=user_id, - m2m_sitemessageusers__has_read=False + m2m_sitemessageusers__has_read=has_read ).distinct().annotate( has_read=F('m2m_sitemessageusers__has_read'), read_at=F('m2m_sitemessageusers__read_at')