From 699b8d99804808db91e14dfd65cb31f06bc0bb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Mon, 15 Apr 2019 14:33:16 +0800 Subject: [PATCH 01/17] =?UTF-8?q?[Update]=20=E4=BF=AE=E6=94=B9=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=9B=B4=E6=96=B0=E8=B5=84=E4=BA=A7url=E8=BF=87?= =?UTF-8?q?=E9=95=BF=E5=AF=BC=E8=87=B4=E7=9A=84=E9=94=99=E8=AF=AF=20(#2571?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset.py | 23 +++++++++++++++++++- apps/assets/const.py | 3 +++ apps/assets/templates/assets/asset_list.html | 20 ++++++++++++++--- apps/assets/urls/api_urls.py | 2 ++ apps/assets/views/asset.py | 10 ++++----- 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index abb0243db..a734806fb 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -1,19 +1,25 @@ # -*- coding: utf-8 -*- # +import uuid import random from rest_framework import generics +from rest_framework.views import APIView from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView from rest_framework.pagination import LimitOffsetPagination +from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy +from django.core.cache import cache from django.db.models import Q from common.mixins import IDInFilterMixin from common.utils import get_logger from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser +from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from ..models import Asset, AdminUser, Node from .. import serializers from ..tasks import update_asset_hardware_info_manual, \ @@ -25,7 +31,7 @@ logger = get_logger(__file__) __all__ = [ 'AssetViewSet', 'AssetListUpdateApi', 'AssetRefreshHardwareApi', 'AssetAdminUserTestApi', - 'AssetGatewayApi' + 'AssetGatewayApi', 'AssetBulkUpdateSelectAPI' ] @@ -92,6 +98,21 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): permission_classes = (IsOrgAdmin,) +class AssetBulkUpdateSelectAPI(APIView): + permission_classes = (IsOrgAdmin,) + + def post(self, request, *args, **kwargs): + assets_id = request.data.get('assets_id', '') + if assets_id: + spm = uuid.uuid4().hex + key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm) + cache.set(key, assets_id, 300) + url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm + return Response({'url': url}) + error = _('Please select assets that need to be updated') + return Response({'error': error}, status=400) + + class AssetRefreshHardwareApi(generics.RetrieveAPIView): """ Refresh asset hardware info diff --git a/apps/assets/const.py b/apps/assets/const.py index 901ade1ff..a110683d0 100644 --- a/apps/assets/const.py +++ b/apps/assets/const.py @@ -48,3 +48,6 @@ TASK_OPTIONS = { 'timeout': 10, 'forks': 10, } + +CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}' + diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index edd82cf0f..8adc81de9 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -657,9 +657,23 @@ $(document).ready(function(){ }); } function doUpdate() { - var id_list_string = id_list.join(','); - var url = "{% url 'assets:asset-bulk-update' %}?assets_id=" + id_list_string; - location.href = url + var data = { + 'assets_id':id_list + }; + function error(data) { + toastr.error(JSON.parse(data).error) + } + function success(data) { + location.href = data.url; + } + APIUpdateAttr({ + 'url': "{% url 'api-assets:asset-bulk-update-select' %}", + 'method': 'POST', + 'body': JSON.stringify(data), + 'flash_message': false, + 'success': success, + 'error': error, + }) } function doRemove() { diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 6ad54cf7e..fec960dab 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -25,6 +25,8 @@ cmd_filter_router.register(r'rules', api.CommandFilterRuleViewSet, 'cmd-filter-r urlpatterns = [ path('assets-bulk/', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), + path('asset/update/select/', + api.AssetBulkUpdateSelectAPI.as_view(), name='asset-bulk-update-select'), path('assets//refresh/', api.AssetRefreshHardwareApi.as_view(), name='asset-refresh'), path('assets//alive/', diff --git a/apps/assets/views/asset.py b/apps/assets/views/asset.py index b7493e047..96bc451f0 100644 --- a/apps/assets/views/asset.py +++ b/apps/assets/views/asset.py @@ -28,6 +28,7 @@ from common.mixins import JSONResponseMixin from common.utils import get_object_or_none, get_logger from common.permissions import AdminUserRequiredMixin from common.const import create_success_msg, update_success_msg +from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX from orgs.utils import current_org from .. import forms from ..models import Asset, AdminUser, SystemUser, Label, Node, Domain @@ -120,15 +121,12 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView): form = None def get(self, request, *args, **kwargs): - assets_id = self.request.GET.get('assets_id', '') - self.id_list = [i for i in assets_id.split(',')] - + spm = request.GET.get('spm', '') + assets_id = cache.get(CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)) if kwargs.get('form'): self.form = kwargs['form'] elif assets_id: - self.form = self.form_class( - initial={'assets': self.id_list} - ) + self.form = self.form_class(initial={'assets': assets_id}) else: self.form = self.form_class() return super().get(request, *args, **kwargs) From 78e4e13fb997f92c648606591506c12678ee5733 Mon Sep 17 00:00:00 2001 From: jokimina Date: Mon, 15 Apr 2019 14:33:40 +0800 Subject: [PATCH 02/17] [Bugfix] IsSuperUserOrAppUser deny app user (#2558) --- apps/common/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/common/permissions.py b/apps/common/permissions.py index 67154b228..689444131 100644 --- a/apps/common/permissions.py +++ b/apps/common/permissions.py @@ -35,7 +35,7 @@ class IsSuperUser(IsValidUser): class IsSuperUserOrAppUser(IsSuperUser): def has_permission(self, request, view): return super(IsSuperUserOrAppUser, self).has_permission(request, view) \ - and (request.user.is_superuser or request.user.is_app) + or request.user.is_app class IsOrgAdmin(IsValidUser): From 56519354b68a332948d05cec3c8dc321d7f7c8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AB=E5=8D=83=E6=B5=81?= <40739051+jym503558564@users.noreply.github.com> Date: Mon, 15 Apr 2019 14:37:45 +0800 Subject: [PATCH 03/17] Email reset password (#2547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update]更改英文登录界面标题,可两行 * [Update] 更改用户通过邮箱修改密码后,该链接就失效 * [Update]更改页面左上角logo_text图片 * [Update] 优化发送邮箱改密连接失效代码 * [Update] 优化发送邮箱改密连接失效代码(2) * [Update] 优化发送邮箱改密连接失效代码(3) * [Update] 更新interface一键恢复默认的翻译 * [Update] 更改登录失败 用户名密码错误信息的翻译 * [Update] 优化生成token并设置缓存的代码 --- apps/authentication/forms.py | 8 ++ .../templates/authentication/new_login.html | 14 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 70257 -> 71036 bytes apps/locale/zh/LC_MESSAGES/django.po | 129 ++++++++++++------ apps/users/models/user.py | 52 ++++--- apps/users/views/login.py | 10 +- 6 files changed, 140 insertions(+), 73 deletions(-) diff --git a/apps/authentication/forms.py b/apps/authentication/forms.py index c722629db..61b073e21 100644 --- a/apps/authentication/forms.py +++ b/apps/authentication/forms.py @@ -14,6 +14,14 @@ class UserLoginForm(AuthenticationForm): max_length=128, strip=False ) + error_messages = { + 'invalid_login': _( + "Please enter a correct username and password. Note that both " + "fields may be case-sensitive." + ), + 'inactive': _("This account is inactive."), + } + def confirm_login_allowed(self, user): if not user.is_staff: raise forms.ValidationError( diff --git a/apps/authentication/templates/authentication/new_login.html b/apps/authentication/templates/authentication/new_login.html index 9bd0fe581..c644e6fad 100644 --- a/apps/authentication/templates/authentication/new_login.html +++ b/apps/authentication/templates/authentication/new_login.html @@ -52,7 +52,7 @@ - +
@@ -60,19 +60,19 @@
-
- {{ JMS_TITLE }} +
+ {{ JMS_TITLE }}
-
+
{% trans 'Welcome back, please enter username and password to login' %}
-
+
{% csrf_token %} -
+
{% if block_login %}

{% trans 'Log in frequently and try again later' %}

{% elif password_expired %} @@ -92,7 +92,7 @@
-
+
{{ form.captcha }}
diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 1de7cffcd887449575ee499e7ca415e4d32357bd..6dcc86b5ae0b4d544a8e9774cd17af9f2b670f34 100644 GIT binary patch delta 21418 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 delta 20692 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 X&jv;}c(pzLjC3DvZp{`@Kl%Rwe\n" "Language-Team: Jumpserver team\n" @@ -172,7 +172,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: settings/templates/settings/terminal_setting.html:102 terminal/models.py:22 #: terminal/models.py:241 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:54 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:61 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_list.html:12 @@ -208,7 +208,7 @@ msgstr "名称" #: ops/models/adhoc.py:164 perms/templates/perms/asset_permission_list.html:74 #: perms/templates/perms/asset_permission_user.html:55 #: settings/templates/settings/_ldap_list_users_modal.html:37 users/forms.py:13 -#: users/models/user.py:52 users/templates/users/_select_user_modal.html:14 +#: users/models/user.py:59 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:24 #: users/templates/users/user_profile.html:47 @@ -246,7 +246,7 @@ msgid "Password" msgstr "密码" #: assets/forms/user.py:29 assets/serializers/asset_user.py:27 -#: users/models/user.py:81 +#: users/models/user.py:88 msgid "Private key" msgstr "ssh私钥" @@ -437,7 +437,7 @@ msgstr "标签管理" #: assets/templates/assets/system_user_detail.html:100 #: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:36 #: perms/models.py:89 perms/templates/perms/asset_permission_detail.html:98 -#: users/models/user.py:95 users/templates/users/user_detail.html:111 +#: users/models/user.py:102 users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:103 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 #: xpack/plugins/cloud/models.py:55 xpack/plugins/cloud/models.py:127 @@ -482,7 +482,7 @@ msgstr "创建日期" #: orgs/models.py:17 perms/models.py:38 perms/models.py:91 #: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 #: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 -#: users/models/group.py:15 users/models/user.py:87 +#: users/models/group.py:15 users/models/user.py:94 #: users/templates/users/user_detail.html:127 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:14 @@ -554,7 +554,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:73 +#: assets/models/cluster.py:22 users/models/user.py:80 #: users/templates/users/user_detail.html:76 msgid "Phone" msgstr "手机" @@ -580,7 +580,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:457 +#: users/models/user.py:473 msgid "System" msgstr "系统" @@ -728,7 +728,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 -#: users/models/user.py:32 users/models/user.py:445 +#: users/models/user.py:36 users/models/user.py:461 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:386 #: xpack/plugins/orgs/forms.py:26 @@ -827,7 +827,7 @@ msgid "%(value)s is not an even number" msgstr "%(value)s is not an even number" #: assets/serializers/asset_user.py:23 users/forms.py:230 -#: users/models/user.py:84 users/templates/users/first_login.html:42 +#: users/models/user.py:91 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:46 #: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile_update.html:43 @@ -1026,7 +1026,7 @@ msgstr "其它" #: settings/templates/settings/command_storage_create.html:79 #: settings/templates/settings/email_setting.html:62 #: settings/templates/settings/ldap_setting.html:61 -#: settings/templates/settings/replay_storage_create.html:151 +#: settings/templates/settings/replay_storage_create.html:152 #: settings/templates/settings/security_setting.html:70 #: settings/templates/settings/terminal_setting.html:68 #: terminal/templates/terminal/terminal_update.html:45 @@ -1062,7 +1062,7 @@ msgstr "重置" #: settings/templates/settings/command_storage_create.html:80 #: settings/templates/settings/email_setting.html:63 #: settings/templates/settings/ldap_setting.html:64 -#: settings/templates/settings/replay_storage_create.html:152 +#: settings/templates/settings/replay_storage_create.html:153 #: settings/templates/settings/security_setting.html:71 #: settings/templates/settings/terminal_setting.html:70 #: terminal/templates/terminal/command_list.html:103 @@ -1076,7 +1076,7 @@ msgstr "重置" #: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_pubkey_update.html:77 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:73 +#: xpack/plugins/interface/templates/interface/interface.html:74 msgid "Submit" msgstr "提交" @@ -1161,7 +1161,7 @@ msgstr "更新认证" #: assets/templates/assets/system_user_asset.html:350 #: users/templates/users/user_detail.html:307 #: users/templates/users/user_detail.html:334 -#: xpack/plugins/interface/views.py:31 +#: xpack/plugins/interface/views.py:34 msgid "Update successfully!" msgstr "更新成功" @@ -1271,6 +1271,7 @@ msgstr "选择节点" #: users/templates/users/user_profile.html:238 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 +#: xpack/plugins/interface/templates/interface/interface.html:103 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 msgid "Confirm" msgstr "确认" @@ -1500,6 +1501,7 @@ msgstr "重命名失败,不能更改root节点的名称" #: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_list.html:82 #: users/templates/users/user_list.html:202 +#: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" @@ -1516,6 +1518,7 @@ msgstr "删除选择资产" #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:206 +#: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" @@ -1938,7 +1941,7 @@ msgid "User agent" msgstr "Agent" #: audits/models.py:99 audits/templates/audits/login_log_list.html:56 -#: users/forms.py:142 users/models/user.py:76 +#: users/forms.py:142 users/models/user.py:83 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2125,7 +2128,17 @@ msgstr "" msgid "Invalid token or cache refreshed." msgstr "" -#: authentication/forms.py:29 users/forms.py:21 +#: authentication/forms.py:19 +msgid "" +"Please enter a correct username and password. Note that both fields may be " +"case-sensitive." +msgstr "请输入正确的用户名和密码. 注意它们是区分大小写." + +#: authentication/forms.py:22 +msgid "This account is inactive." +msgstr "此账户无效" + +#: authentication/forms.py:37 users/forms.py:21 msgid "MFA code" msgstr "MFA 验证码" @@ -2687,7 +2700,7 @@ msgstr "组织管理" #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 #: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14 -#: users/forms.py:253 users/models/group.py:26 users/models/user.py:60 +#: users/forms.py:253 users/models/group.py:26 users/models/user.py:67 #: users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:213 #: users/templates/users/user_list.html:26 @@ -2705,7 +2718,7 @@ msgstr "资产和节点至少选一个" #: perms/models.py:35 perms/models.py:88 #: perms/templates/perms/asset_permission_detail.html:90 -#: users/models/user.py:92 users/templates/users/user_detail.html:107 +#: users/models/user.py:99 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" @@ -3123,7 +3136,7 @@ msgid "Please submit the LDAP configuration before import" msgstr "请先提交LDAP配置再进行导入" #: settings/templates/settings/_ldap_list_users_modal.html:39 -#: users/models/user.py:56 users/templates/users/user_detail.html:71 +#: users/models/user.py:63 users/templates/users/user_detail.html:71 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" @@ -3431,7 +3444,7 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:151 users/views/user.py:68 users/views/user.py:83 +#: users/views/login.py:153 users/views/user.py:68 users/views/user.py:83 #: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355 #: users/views/user.py:405 users/views/user.py:445 msgid "Users" @@ -3871,7 +3884,7 @@ msgstr "你可以使用ssh客户端工具连接终端" msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:32 users/models/user.py:64 +#: users/forms.py:32 users/models/user.py:71 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:25 @@ -3964,53 +3977,53 @@ msgstr "复制你的公钥到这里" msgid "Select users" msgstr "选择用户" -#: users/models/user.py:31 users/models/user.py:453 +#: users/models/user.py:35 users/models/user.py:469 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:33 +#: users/models/user.py:37 msgid "Application" msgstr "应用程序" -#: users/models/user.py:36 users/templates/users/user_profile.html:92 +#: users/models/user.py:40 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:159 #: users/templates/users/user_profile.html:162 msgid "Disable" msgstr "禁用" -#: users/models/user.py:37 users/templates/users/user_profile.html:90 +#: users/models/user.py:41 users/templates/users/user_profile.html:90 #: users/templates/users/user_profile.html:166 msgid "Enable" msgstr "启用" -#: users/models/user.py:38 users/templates/users/user_profile.html:88 +#: users/models/user.py:42 users/templates/users/user_profile.html:88 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:67 +#: users/models/user.py:74 msgid "Avatar" msgstr "头像" -#: users/models/user.py:70 users/templates/users/user_detail.html:82 +#: users/models/user.py:77 users/templates/users/user_detail.html:82 msgid "Wechat" msgstr "微信" -#: users/models/user.py:99 users/templates/users/user_detail.html:103 +#: users/models/user.py:106 users/templates/users/user_detail.html:103 #: users/templates/users/user_list.html:27 #: users/templates/users/user_profile.html:100 msgid "Source" msgstr "用户来源" -#: users/models/user.py:103 +#: users/models/user.py:110 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:129 users/templates/users/user_update.html:22 -#: users/views/login.py:45 users/views/login.py:104 users/views/user.py:418 +#: users/models/user.py:136 users/templates/users/user_update.html:22 +#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:418 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:456 +#: users/models/user.py:472 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -4658,40 +4671,40 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:42 +#: users/views/login.py:43 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:58 +#: users/views/login.py:59 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:59 +#: users/views/login.py:60 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:72 +#: users/views/login.py:73 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:73 +#: users/views/login.py:74 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:88 users/views/login.py:107 +#: users/views/login.py:89 users/views/login.py:105 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:100 +#: users/views/login.py:101 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:113 users/views/user.py:128 users/views/user.py:428 +#: users/views/login.py:114 users/views/user.py:128 users/views/user.py:428 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:151 +#: users/views/login.py:153 msgid "First login" msgstr "首次登录" @@ -5217,14 +5230,40 @@ msgid "Interface settings" msgstr "界面设置" #: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:21 +#: xpack/plugins/interface/views.py:24 msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/interface/views.py:20 +#: xpack/plugins/interface/templates/interface/interface.html:73 +#: xpack/plugins/interface/templates/interface/interface.html:108 +#: xpack/plugins/interface/templates/interface/interface.html:115 +msgid "Restore Default" +msgstr "恢复默认" + +#: xpack/plugins/interface/templates/interface/interface.html:98 +msgid "This will restore default Settings of the interface !!!" +msgstr "您确定要恢复默认初始化吗?" + +#: xpack/plugins/interface/templates/interface/interface.html:107 +msgid "Restore default successfully." +msgstr "恢复默认成功!" + +#: xpack/plugins/interface/templates/interface/interface.html:114 +msgid "Restore default failed." +msgstr "恢复默认失败!" + +#: xpack/plugins/interface/views.py:23 msgid "Interface" msgstr "界面" +#: xpack/plugins/interface/views.py:49 +msgid "It is already in the default setting state!" +msgstr "当前已经是初始化状态!" + +#: xpack/plugins/interface/views.py:53 +msgid "Restore default successfully!" +msgstr "恢复默认成功!" + #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/views.py:31 @@ -5233,7 +5272,7 @@ msgstr "许可证" #: xpack/plugins/license/models.py:74 msgid "Standard edition" -msgstr "" +msgstr "标准版" #: xpack/plugins/license/models.py:76 msgid "Enterprise edition" diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 86751e359..9d2fc1a08 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -3,24 +3,28 @@ # import uuid import base64 +import string +import random from collections import OrderedDict from django.conf import settings from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractUser -from django.core import signing from django.core.cache import cache from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.shortcuts import reverse -from common.utils import get_signer, date_expired_default +from common.utils import get_signer, date_expired_default, get_logger __all__ = ['User'] + signer = get_signer() +logger = get_logger(__file__) + class User(AbstractUser): ROLE_ADMIN = 'Admin' @@ -47,6 +51,9 @@ class User(AbstractUser): (SOURCE_OPENID, 'OpenID'), (SOURCE_RADIUS, 'Radius'), ) + + CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}" + id = models.UUIDField(default=uuid.uuid4, primary_key=True) username = models.CharField( max_length=128, unique=True, verbose_name=_('Username') @@ -346,9 +353,32 @@ class User(AbstractUser): return user_default def generate_reset_token(self): - return signer.sign_t( - {'reset': str(self.id), 'email': self.email}, expires_in=3600 - ) + letter = string.ascii_letters + string.digits + token =''.join([random.choice(letter) for _ in range(50)]) + self.set_cache(token) + return token + + def set_cache(self, token): + key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) + cache.set(key, {'id': self.id, 'email': self.email}, 3600) + + @classmethod + def validate_reset_password_token(cls, token): + try: + key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) + value = cache.get(key) + user_id = value.get('id', '') + email = value.get('email', '') + user = cls.objects.get(id=user_id, email=email) + except (AttributeError, cls.DoesNotExist) as e: + logger.error(e, exc_info=True) + user = None + return user + + @classmethod + def expired_reset_password_token(cls, token): + key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token) + cache.delete(key) @property def otp_enabled(self): @@ -400,18 +430,6 @@ class User(AbstractUser): access_key = app.create_access_key() return app, access_key - @classmethod - def validate_reset_token(cls, token): - try: - data = signer.unsign_t(token) - user_id = data.get('reset', None) - user_email = data.get('email', '') - user = cls.objects.get(id=user_id, email=user_email) - - except (signing.BadSignature, cls.DoesNotExist): - user = None - return user - def reset_password(self, new_password): self.set_password(new_password) self.date_password_last_updated = timezone.now() diff --git a/apps/users/views/login.py b/apps/users/views/login.py index ca8bbf1ac..8f79b6f43 100644 --- a/apps/users/views/login.py +++ b/apps/users/views/login.py @@ -1,6 +1,7 @@ # ~*~ coding: utf-8 ~*~ from __future__ import unicode_literals +from django.core.cache import cache from django.shortcuts import render from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import RedirectView @@ -84,7 +85,7 @@ class UserResetPasswordView(TemplateView): def get(self, request, *args, **kwargs): token = request.GET.get('token', '') - user = User.validate_reset_token(token) + user = User.validate_reset_password_token(token) if not user: kwargs.update({'errors': _('Token invalid or expired')}) else: @@ -100,12 +101,12 @@ class UserResetPasswordView(TemplateView): if password != password_confirm: return self.get(request, errors=_('Password not same')) - user = User.validate_reset_token(token) + user = User.validate_reset_password_token(token) + if not user: + return self.get(request, errors=_('Token invalid or expired')) if not user.can_update_password(): error = _('User auth from {}, go there change password'.format(user.source)) return self.get(request, errors=error) - if not user: - return self.get(request, errors=_('Token invalid or expired')) is_ok = check_password_rules(password) if not is_ok: @@ -115,6 +116,7 @@ class UserResetPasswordView(TemplateView): ) user.reset_password(password) + User.expired_reset_password_token(token) return HttpResponseRedirect(reverse('users:reset-password-success')) From fc1068a9dcfcfb0d9f0ce0c55224afb338630659 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 15 Apr 2019 16:34:56 +0800 Subject: [PATCH 04/17] =?UTF-8?q?[Update]=20=E6=9B=B4=E6=96=B0=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=20(#2599)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/locale/zh/LC_MESSAGES/django.mo | Bin 71036 -> 70722 bytes apps/locale/zh/LC_MESSAGES/django.po | 196 +++++++++++++-------------- 2 files changed, 97 insertions(+), 99 deletions(-) diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 6dcc86b5ae0b4d544a8e9774cd17af9f2b670f34..ddf7a8865876add6ab6f0e0c3c0adea06eedb89b 100644 GIT binary patch delta 20036 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 R#(e9~QE5Le7}6@s{{fXec?JLg delta 20360 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 W+qnO%CUM&A2fG$*{_Rz(jQ<6|su;Qe diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0f10c9492..776273e3f 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-03-29 17:11+0800\n" +"POT-Creation-Date: 2019-04-15 15:34+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: assets/api/asset.py:112 +msgid "Please select assets that need to be updated" +msgstr "请选择需要更新的资产" + #: assets/api/node.py:58 msgid "You can't update the root node name" msgstr "不能修改根节点名称" @@ -126,7 +130,7 @@ msgstr "选择资产" #: terminal/templates/terminal/session_list.html:41 #: terminal/templates/terminal/session_list.html:72 #: xpack/plugins/change_auth_plan/forms.py:114 -#: xpack/plugins/change_auth_plan/models.py:408 +#: xpack/plugins/change_auth_plan/models.py:409 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:13 @@ -214,7 +218,7 @@ msgstr "名称" #: users/templates/users/user_profile.html:47 #: xpack/plugins/change_auth_plan/forms.py:99 #: xpack/plugins/change_auth_plan/models.py:60 -#: xpack/plugins/change_auth_plan/models.py:404 +#: xpack/plugins/change_auth_plan/models.py:405 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:53 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:12 @@ -241,7 +245,7 @@ msgstr "密码或密钥密码" #: users/templates/users/user_pubkey_update.html:40 #: users/templates/users/user_update.html:20 #: xpack/plugins/change_auth_plan/models.py:90 -#: xpack/plugins/change_auth_plan/models.py:259 +#: xpack/plugins/change_auth_plan/models.py:260 msgid "Password" msgstr "密码" @@ -380,7 +384,7 @@ msgid "CPU model" msgstr "CPU型号" #: assets/models/asset.py:96 -#: xpack/plugins/license/templates/license/license_detail.html:71 +#: xpack/plugins/license/templates/license/license_detail.html:80 msgid "CPU count" msgstr "CPU数量" @@ -537,12 +541,12 @@ msgid "AuthBook" msgstr "" #: assets/models/base.py:29 xpack/plugins/change_auth_plan/models.py:94 -#: xpack/plugins/change_auth_plan/models.py:266 +#: xpack/plugins/change_auth_plan/models.py:267 msgid "SSH private key" msgstr "ssh密钥" #: assets/models/base.py:30 xpack/plugins/change_auth_plan/models.py:97 -#: xpack/plugins/change_auth_plan/models.py:262 +#: xpack/plugins/change_auth_plan/models.py:263 msgid "SSH public key" msgstr "ssh公钥" @@ -580,7 +584,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:473 +#: users/models/user.py:475 msgid "System" msgstr "系统" @@ -728,7 +732,7 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:71 users/forms.py:283 -#: users/models/user.py:36 users/models/user.py:461 +#: users/models/user.py:36 users/models/user.py:463 #: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:13 users/views/user.py:386 #: xpack/plugins/orgs/forms.py:26 @@ -768,9 +772,9 @@ msgstr "手动登录" #: assets/templates/assets/system_user_detail.html:22 #: assets/views/admin_user.py:29 assets/views/admin_user.py:47 #: assets/views/admin_user.py:63 assets/views/admin_user.py:78 -#: assets/views/admin_user.py:102 assets/views/asset.py:50 -#: assets/views/asset.py:66 assets/views/asset.py:103 assets/views/asset.py:147 -#: assets/views/asset.py:164 assets/views/asset.py:188 +#: assets/views/admin_user.py:102 assets/views/asset.py:51 +#: assets/views/asset.py:67 assets/views/asset.py:104 assets/views/asset.py:145 +#: assets/views/asset.py:162 assets/views/asset.py:186 #: assets/views/cmd_filter.py:30 assets/views/cmd_filter.py:46 #: assets/views/cmd_filter.py:62 assets/views/cmd_filter.py:78 #: assets/views/cmd_filter.py:97 assets/views/cmd_filter.py:130 @@ -954,7 +958,7 @@ msgstr "资产csv文件" msgid "If set id, will use this id update asset existed" msgstr "如果设置了id,则会使用该行信息更新该id的资产" -#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:51 +#: assets/templates/assets/_asset_list_modal.html:7 assets/views/asset.py:52 #: templates/_nav.html:22 xpack/plugins/change_auth_plan/views.py:110 msgid "Asset list" msgstr "资产列表" @@ -1076,13 +1080,13 @@ msgstr "重置" #: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_pubkey_update.html:77 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:74 +#: xpack/plugins/interface/templates/interface/interface.html:73 msgid "Submit" msgstr "提交" #: assets/templates/assets/_user_asset_detail_modal.html:11 #: assets/templates/assets/asset_asset_user_list.html:17 -#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:189 +#: assets/templates/assets/asset_detail.html:20 assets/views/asset.py:187 msgid "Asset detail" msgstr "资产详情" @@ -1161,7 +1165,7 @@ msgstr "更新认证" #: assets/templates/assets/system_user_asset.html:350 #: users/templates/users/user_detail.html:307 #: users/templates/users/user_detail.html:334 -#: xpack/plugins/interface/views.py:34 +#: xpack/plugins/interface/views.py:31 msgid "Update successfully!" msgstr "更新成功" @@ -1271,7 +1275,6 @@ msgstr "选择节点" #: users/templates/users/user_profile.html:238 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 -#: xpack/plugins/interface/templates/interface/interface.html:103 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 msgid "Confirm" msgstr "确认" @@ -1306,7 +1309,7 @@ msgid "Ratio" msgstr "比例" #: assets/templates/assets/asset_asset_user_list.html:20 -#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:67 +#: assets/templates/assets/asset_detail.html:23 assets/views/asset.py:68 msgid "Asset user list" msgstr "资产用户列表" @@ -1330,7 +1333,7 @@ msgstr "更新日期" #: users/templates/users/user_detail.html:138 #: users/templates/users/user_profile.html:146 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:128 -#: xpack/plugins/license/templates/license/license_detail.html:93 +#: xpack/plugins/license/templates/license/license_detail.html:102 msgid "Quick modify" msgstr "快速修改" @@ -1389,14 +1392,14 @@ msgstr "" "左侧是资产树,右击可以新建、删除、更改树节点,授权资产也是以节点方式组织的," "右侧是属于该节点下的资产" -#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:104 +#: assets/templates/assets/asset_list.html:69 assets/views/asset.py:105 msgid "Create asset" msgstr "创建资产" #: assets/templates/assets/asset_list.html:73 #: settings/templates/settings/_ldap_list_users_modal.html:97 #: users/templates/users/user_list.html:7 -#: xpack/plugins/license/templates/license/license_detail.html:101 +#: xpack/plugins/license/templates/license/license_detail.html:110 msgid "Import" msgstr "导入" @@ -1501,7 +1504,6 @@ msgstr "重命名失败,不能更改root节点的名称" #: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_list.html:82 #: users/templates/users/user_list.html:202 -#: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" @@ -1518,7 +1520,6 @@ msgstr "删除选择资产" #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:206 -#: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" @@ -1753,23 +1754,23 @@ msgstr "更新管理用户" msgid "Admin user detail" msgstr "管理用户详情" -#: assets/views/asset.py:78 templates/_nav_user.html:4 +#: assets/views/asset.py:79 templates/_nav_user.html:4 msgid "My assets" msgstr "我的资产" -#: assets/views/asset.py:118 +#: assets/views/asset.py:119 msgid "Bulk update asset success" msgstr "批量更新资产成功" -#: assets/views/asset.py:148 +#: assets/views/asset.py:146 msgid "Bulk update asset" msgstr "批量更新资产" -#: assets/views/asset.py:165 +#: assets/views/asset.py:163 msgid "Update asset" msgstr "更新资产" -#: assets/views/asset.py:306 +#: assets/views/asset.py:304 msgid "already exists" msgstr "已经存在" @@ -1947,7 +1948,7 @@ msgid "MFA" msgstr "MFA" #: audits/models.py:100 audits/templates/audits/login_log_list.html:57 -#: xpack/plugins/change_auth_plan/models.py:412 +#: xpack/plugins/change_auth_plan/models.py:413 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:15 #: xpack/plugins/cloud/models.py:172 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 @@ -1972,8 +1973,8 @@ msgstr "登录日期" #: ops/templates/ops/task_history.html:58 perms/models.py:34 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165 #: terminal/templates/terminal/session_list.html:78 -#: xpack/plugins/change_auth_plan/models.py:245 -#: xpack/plugins/change_auth_plan/models.py:415 +#: xpack/plugins/change_auth_plan/models.py:246 +#: xpack/plugins/change_auth_plan/models.py:416 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:59 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:17 msgid "Date start" @@ -2434,8 +2435,8 @@ msgstr "完成时间" #: ops/models/adhoc.py:326 ops/templates/ops/adhoc_history.html:57 #: ops/templates/ops/task_history.html:63 ops/templates/ops/task_list.html:33 -#: xpack/plugins/change_auth_plan/models.py:248 -#: xpack/plugins/change_auth_plan/models.py:418 +#: xpack/plugins/change_auth_plan/models.py:249 +#: xpack/plugins/change_auth_plan/models.py:419 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:58 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_subtask_list.html:16 msgid "Time" @@ -2774,7 +2775,7 @@ msgid "Validity period" msgstr "有效期" #: perms/templates/perms/asset_permission_detail.html:66 -#: xpack/plugins/license/templates/license/license_detail.html:67 +#: xpack/plugins/license/templates/license/license_detail.html:76 msgid "User count" msgstr "用户数量" @@ -2783,7 +2784,7 @@ msgid "User group count" msgstr "用户组列表" #: perms/templates/perms/asset_permission_detail.html:74 -#: xpack/plugins/license/templates/license/license_detail.html:63 +#: xpack/plugins/license/templates/license/license_detail.html:72 msgid "Asset count" msgstr "资产数量" @@ -3444,7 +3445,7 @@ msgstr "" #: templates/_nav.html:10 users/views/group.py:27 users/views/group.py:43 #: users/views/group.py:59 users/views/group.py:75 users/views/group.py:91 -#: users/views/login.py:153 users/views/user.py:68 users/views/user.py:83 +#: users/views/login.py:154 users/views/user.py:68 users/views/user.py:83 #: users/views/user.py:113 users/views/user.py:194 users/views/user.py:355 #: users/views/user.py:405 users/views/user.py:445 msgid "Users" @@ -3977,7 +3978,7 @@ msgstr "复制你的公钥到这里" msgid "Select users" msgstr "选择用户" -#: users/models/user.py:35 users/models/user.py:469 +#: users/models/user.py:35 users/models/user.py:471 msgid "Administrator" msgstr "管理员" @@ -4019,11 +4020,11 @@ msgid "Date password last updated" msgstr "最后更新密码日期" #: users/models/user.py:136 users/templates/users/user_update.html:22 -#: users/views/login.py:46 users/views/login.py:107 users/views/user.py:418 +#: users/views/login.py:47 users/views/login.py:108 users/views/user.py:418 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:472 +#: users/models/user.py:474 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -4671,40 +4672,40 @@ msgstr "更新用户组" msgid "User group granted asset" msgstr "用户组授权资产" -#: users/views/login.py:43 +#: users/views/login.py:44 msgid "Email address invalid, please input again" msgstr "邮箱地址错误,重新输入" -#: users/views/login.py:59 +#: users/views/login.py:60 msgid "Send reset password message" msgstr "发送重置密码邮件" -#: users/views/login.py:60 +#: users/views/login.py:61 msgid "Send reset password mail success, login your mail box and follow it " msgstr "" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" -#: users/views/login.py:73 +#: users/views/login.py:74 msgid "Reset password success" msgstr "重置密码成功" -#: users/views/login.py:74 +#: users/views/login.py:75 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" -#: users/views/login.py:89 users/views/login.py:105 +#: users/views/login.py:90 users/views/login.py:106 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/login.py:101 +#: users/views/login.py:102 msgid "Password not same" msgstr "密码不一致" -#: users/views/login.py:114 users/views/user.py:128 users/views/user.py:428 +#: users/views/login.py:115 users/views/user.py:128 users/views/user.py:428 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/login.py:153 +#: users/views/login.py:154 msgid "First login" msgstr "首次登录" @@ -4809,8 +4810,8 @@ msgstr "" "具)
注意: 如果同时设置了定期执行和周期执行,优先使用定期执行" #: xpack/plugins/change_auth_plan/meta.py:9 -#: xpack/plugins/change_auth_plan/models.py:110 -#: xpack/plugins/change_auth_plan/models.py:252 +#: xpack/plugins/change_auth_plan/models.py:111 +#: xpack/plugins/change_auth_plan/models.py:253 #: xpack/plugins/change_auth_plan/views.py:31 #: xpack/plugins/change_auth_plan/views.py:47 #: xpack/plugins/change_auth_plan/views.py:68 @@ -4834,13 +4835,13 @@ msgid "All assets use different random password" msgstr "所有资产使用不同的随机密码" #: xpack/plugins/change_auth_plan/models.py:73 -#: xpack/plugins/change_auth_plan/models.py:141 +#: xpack/plugins/change_auth_plan/models.py:142 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:100 msgid "Cycle perform" msgstr "周期执行" #: xpack/plugins/change_auth_plan/models.py:78 -#: xpack/plugins/change_auth_plan/models.py:139 +#: xpack/plugins/change_auth_plan/models.py:140 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:92 msgid "Regularly perform" msgstr "定期执行" @@ -4858,32 +4859,32 @@ msgstr "密码策略" msgid "Password rules" msgstr "密码规则" -#: xpack/plugins/change_auth_plan/models.py:209 +#: xpack/plugins/change_auth_plan/models.py:210 msgid "For security, do not change root user's password" msgstr "为了安全,禁止更改root用户的密码" -#: xpack/plugins/change_auth_plan/models.py:212 +#: xpack/plugins/change_auth_plan/models.py:213 msgid "Assets is empty, please add the asset" msgstr "资产为空,请添加资产" -#: xpack/plugins/change_auth_plan/models.py:256 +#: xpack/plugins/change_auth_plan/models.py:257 msgid "Change auth plan snapshot" msgstr "改密计划快照" -#: xpack/plugins/change_auth_plan/models.py:271 -#: xpack/plugins/change_auth_plan/models.py:422 +#: xpack/plugins/change_auth_plan/models.py:272 +#: xpack/plugins/change_auth_plan/models.py:423 msgid "Change auth plan execution" msgstr "改密计划执行" -#: xpack/plugins/change_auth_plan/models.py:431 +#: xpack/plugins/change_auth_plan/models.py:432 msgid "Change auth plan execution subtask" msgstr "改密计划执行子任务" -#: xpack/plugins/change_auth_plan/models.py:449 +#: xpack/plugins/change_auth_plan/models.py:450 msgid "Authentication failed" msgstr "认证失败" -#: xpack/plugins/change_auth_plan/models.py:451 +#: xpack/plugins/change_auth_plan/models.py:452 msgid "Connection timeout" msgstr "连接超时" @@ -5230,42 +5231,17 @@ msgid "Interface settings" msgstr "界面设置" #: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:24 +#: xpack/plugins/interface/views.py:21 msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/interface/templates/interface/interface.html:73 -#: xpack/plugins/interface/templates/interface/interface.html:108 -#: xpack/plugins/interface/templates/interface/interface.html:115 -msgid "Restore Default" -msgstr "恢复默认" - -#: xpack/plugins/interface/templates/interface/interface.html:98 -msgid "This will restore default Settings of the interface !!!" -msgstr "您确定要恢复默认初始化吗?" - -#: xpack/plugins/interface/templates/interface/interface.html:107 -msgid "Restore default successfully." -msgstr "恢复默认成功!" - -#: xpack/plugins/interface/templates/interface/interface.html:114 -msgid "Restore default failed." -msgstr "恢复默认失败!" - -#: xpack/plugins/interface/views.py:23 +#: xpack/plugins/interface/views.py:20 msgid "Interface" msgstr "界面" -#: xpack/plugins/interface/views.py:49 -msgid "It is already in the default setting state!" -msgstr "当前已经是初始化状态!" - -#: xpack/plugins/interface/views.py:53 -msgid "Restore default successfully!" -msgstr "恢复默认成功!" - #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 +#: xpack/plugins/license/templates/license/license_detail.html:55 #: xpack/plugins/license/views.py:31 msgid "License" msgstr "许可证" @@ -5279,7 +5255,7 @@ msgid "Enterprise edition" msgstr "企业版" #: xpack/plugins/license/templates/license/_license_import_modal.html:4 -#: xpack/plugins/license/templates/license/license_detail.html:99 +#: xpack/plugins/license/templates/license/license_detail.html:108 msgid "Import license" msgstr "导入许可证" @@ -5312,34 +5288,38 @@ msgstr "许可证详情" msgid "No license" msgstr "暂无许可证" -#: xpack/plugins/license/templates/license/license_detail.html:55 +#: xpack/plugins/license/templates/license/license_detail.html:60 +msgid "Subscription ID" +msgstr "订阅授权ID" + +#: xpack/plugins/license/templates/license/license_detail.html:64 msgid "Corporation" msgstr "公司" -#: xpack/plugins/license/templates/license/license_detail.html:59 +#: xpack/plugins/license/templates/license/license_detail.html:68 msgid "Expired" msgstr "过期时间" -#: xpack/plugins/license/templates/license/license_detail.html:64 -#: xpack/plugins/license/templates/license/license_detail.html:68 -#: xpack/plugins/license/templates/license/license_detail.html:72 -#: xpack/plugins/license/templates/license/license_detail.html:76 +#: xpack/plugins/license/templates/license/license_detail.html:73 +#: xpack/plugins/license/templates/license/license_detail.html:77 +#: xpack/plugins/license/templates/license/license_detail.html:81 +#: xpack/plugins/license/templates/license/license_detail.html:85 msgid "Unlimited" msgstr "无限制" -#: xpack/plugins/license/templates/license/license_detail.html:75 +#: xpack/plugins/license/templates/license/license_detail.html:84 msgid "Concurrent connections" msgstr "并发连接" -#: xpack/plugins/license/templates/license/license_detail.html:80 +#: xpack/plugins/license/templates/license/license_detail.html:89 msgid "Edition" msgstr "版本" -#: xpack/plugins/license/templates/license/license_detail.html:106 +#: xpack/plugins/license/templates/license/license_detail.html:115 msgid "Technology consulting" msgstr "技术咨询" -#: xpack/plugins/license/templates/license/license_detail.html:109 +#: xpack/plugins/license/templates/license/license_detail.html:118 msgid "Consult" msgstr "咨询" @@ -5395,6 +5375,24 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" +#~ msgid "Restore Default" +#~ msgstr "恢复默认" + +#~ msgid "This will restore default Settings of the interface !!!" +#~ msgstr "您确定要恢复默认初始化吗?" + +#~ msgid "Restore default successfully." +#~ msgstr "恢复默认成功!" + +#~ msgid "Restore default failed." +#~ msgstr "恢复默认失败!" + +#~ msgid "It is already in the default setting state!" +#~ msgstr "当前已经是初始化状态!" + +#~ msgid "Restore default successfully!" +#~ msgstr "恢复默认成功!" + #~ msgid "Sync User" #~ msgstr "同步用户" From f235e2015365634dba93e7800d7614710da6e411 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 22 Apr 2019 11:42:20 +0800 Subject: [PATCH 05/17] =?UTF-8?q?[Feature]=20=E6=8E=88=E6=9D=83=E8=A7=84?= =?UTF-8?q?=E5=88=99=E6=B7=BB=E5=8A=A0=20actions=20=E9=80=89=E9=A1=B9?= =?UTF-8?q?=EF=BC=8C=E6=8E=A7=E5=88=B6=E7=94=A8=E6=88=B7=E5=AF=B9=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=9A=84=E6=93=8D=E4=BD=9C=E8=A1=8C=E4=B8=BA=20(#2610?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Feature] 1. perms actions - 添加 Action Model * [Feature] 2. perms actions - 添加 Action API * [Feature] 3. perms actions - 授权规则: 添加actions字段 * [Feature] 4. perms actions - 授权规则创建页面: 设置 actions 默认 all * [Feature] 5. perms actions - 资产授权工具: 动态给system_user设置actions属性; 修改授权相关的API-serializer类: 添加actions字段值 * [Feature] 6. perms actions - 更新API(用户使用系统用户连接资产时权限校验): 添加actions校验 * [Feature] 7. perms actions - 迁移文件中为已经存在的perms添加默认的action * [Feature] 8. perms actions - 创建授权规则时设置默认action(如果actions字段值为空) * [Feature] 9. check actions - 修改校验用户资产权限API逻辑(添加actions校验) * [Feature] 10. check actions - 修改注释 * [Feature] 11. check actions - 添加API: 获取用户指定资产和系统用户被授权的actions * [Feature] 12. check actions - 添加翻译信息 --- apps/assets/serializers/system_user.py | 8 +- apps/locale/zh/LC_MESSAGES/django.mo | Bin 70722 -> 71390 bytes apps/locale/zh/LC_MESSAGES/django.po | 141 +++++++++++------- apps/perms/api/permission.py | 10 +- apps/perms/api/user_permission.py | 45 +++++- apps/perms/const.py | 22 +++ apps/perms/forms.py | 7 + apps/perms/migrations/0003_action.py | 33 ++++ .../0004_assetpermission_actions.py | 31 ++++ apps/perms/models.py | 21 +++ apps/perms/serializers.py | 10 +- apps/perms/signals_handler.py | 24 ++- .../perms/asset_permission_create_update.html | 3 + apps/perms/urls/api_urls.py | 3 + apps/perms/utils.py | 43 +++++- apps/perms/views.py | 5 +- 16 files changed, 332 insertions(+), 74 deletions(-) create mode 100644 apps/perms/const.py create mode 100644 apps/perms/migrations/0003_action.py create mode 100644 apps/perms/migrations/0004_assetpermission_actions.py diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index be1f594ec..b4387d1ff 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -61,13 +61,19 @@ class AssetSystemUserSerializer(serializers.ModelSerializer): """ 查看授权的资产系统用户的数据结构,这个和AssetSerializer不同,字段少 """ + actions = serializers.SerializerMethodField() + class Meta: model = SystemUser fields = ( 'id', 'name', 'username', 'priority', - 'protocol', 'comment', 'login_mode' + 'protocol', 'comment', 'login_mode', 'actions', ) + @staticmethod + def get_actions(obj): + return [action.name for action in obj.actions] + class SystemUserSimpleSerializer(serializers.ModelSerializer): """ diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index ddf7a8865876add6ab6f0e0c3c0adea06eedb89b..4bd12ed4a8ebc044210540bca16a4f21c73ee012 100644 GIT binary patch delta 21453 zcmZYH2Y3}#yY}%3fsjB53B5@YdXwIZ0)kXQiZtnv&>{&WfE1ZfrAqG|=~W0IRgfkK zg2+op1SAQds3@SK!uP-TJUPerd~;ok-+G=^X3d(Jy_0y}b96)cTU*n+x3Z<5=Wv|y zcbo#4P|$J0GC0oJ+R8dkXe-B=j$t?gSKt)P*V=IsaV=i-bDUyr9Oo!LZ|gYu(>cze zcI0Wl*1>VI;nt3h6NE=FCw_?mj^jGFsbnFMika~#=0d+tjuVb~Fg;es3RnlVwpgrz zTd*XiAZvF*-*ud_SPpw(S1gK0Q46?_CGc0DF01YAIJZbNKrNtf7sttt<&YIRZ(spz ziaD?kX2ek#g5y!+=3p?cFt?!Q*@v3vEAv073%-F_IluFON^blUH9=Tc$Kh@`1yL(5 zgjzru)Xp?PZE1H5#CXh)qflGB5VfFf*1jJL5g$X%lY-h&|8DHRt|X93D2Af0FcNtL zoT``!YnzQQjJP#s#opKx6Hr%l1&d)a>Xrm|=Qd$gjK_7zrgwg6?N}+U~#;T>X(ii-2eko_x>%^LPw!?;v)>l#i(1dAGP3f zyMEfRK6IhUlr6u^{xGFEJEBFwR2-o{iorpxCC`e zPou_Pbg2}favOCEGDLazG8gJzR>XYR7`5fSP!q7(hH1)xN~?n=u3NZqyDP zFi&Fu@fAEm5tDC%B?qgGrAGh%hr+fo;GYdWA78jG5EIBEwHQP03M z%!|uWJG>Y3;A!;d`E%}4(F7^xFXnSIeP6FV1ogV*K}{5i`T(kkI&lnU#CX(&jKn~k zit0ZXb)jpq5FWw2oZtDLiWcw>mc@+y`0&B1s4I^~ZRG&eRt`eny+fUFnmHFW@lt#f z52JP;t^^NYn{SqIROX*<=9wuL(Ml&<9UX)QN|oCY*_x@nclK?dGSb2@hL*4z-}m zm>IvrO85in)`Z7+{fnVKDXXA%rd164uPf_CLJw6O2H_;s&df&Lvkj;#JBGTl>!=0X z$6)*&wczycd9Ph=)CCnnov0dWVNFo;zGLwKmx?ADhI&dTnk!Kg?ne#$3N_I+%!EIn zcH|jqOJAUFRn~#t0>e=k@+#^Xs)kx&oJFa(>SR@@17@4DeZ zj7CjdILis`R^=TNWlWz@5A6HDs-e@sOK^T&H9 zEQz7SV$30E?9y%8nfaI%!VtFPjhD{mccv;-seg~)D9=09_DfAYDJ5w z=!#aNPOt@a!riD_a0oTBR&9n;}1^HbDk{C@LWY)0(7@9ji$)Pg?1NZg9;@H{rb zV#B=)ibLH3cRUp>U@Gb!FGSsn^|%j{a0I@`Kd<0@)XD=!dJE2qy26|m=R++Z0=4CD zqxyG1&EErcfpMO$GlGh?Zaivh=c5K}G z2isfwXw*U{qu#EMaEspm6;uY2X#RosqtXG?M2As3a|X5YtEgL)ih3;{p|bp?2&k z>X`^+GR;#O)xRd{LK>kK+y?ctU{@@PJ<<36PqGdlp$05MZQWYSpG9r?71Wj7vG}32 zKS7=FrNzOC-a>O@Zt|s37xo5fflW~lXU9bLUn?3(LRaKsUR;8DCU#>U`~r32+gK7G zpmrkr7;mA)Q9IEBb!9zJC;AX|ujiu{u-@E;y3pNYSh4Qu=OlDbzQ9-T3hKm9&5*I) zXLfPaJ*|y;m|CMw+#a={{#XDiF*ASVS0QAGhipw6?RAU?}J)k9F{{Dv*`WbPDS_b zka+>M^~tDb;2+e^-M&fvLB&y#y%z$&v#i$EdiCXY3)U)yx>dLR6Zs~2*!}}L%XTv733$Ift zLZvwN#v(WkJK+x0fZ)mA4-$p2B5_O96^%u0`3?-kJ?3H5EjfjH%`Rg`yo=hohp7I~ zC$s;m_)qax9E`fhxlkP=Q75c`+PdDT6Anc^WFxT|&O|NzChB$bo9f;BNYpb@!mNVY zx%wD{ji<8z0aRL((AITCt#mNz{T+eY@K3%qXmA5r)IH_VLb zrg=LVf_nIhpz>{9D(cuBOJFQ&;)SSNu)^A(n?cjP1>`|pK|$0`lt8^+bx`vSLVXz} zVi{b3`d~Y5`Mao{cmJf4g-W`Qye$t!J=GDY0o73pYKOY=E*AGf-HQH}k43$vL#%x` z1`{Wu7CId>;R4i^uSOn1*V#rzTlYDt<3-e!e2XP9Y=)Pwg<5Dsi`$r8EZ+xpMMJPC zj<$FWW+mQ=A$SONL1!^S@Bc+ATEG*`iUBjdd!7sRWmFt>CG}9RSqs!eJy8o9gt{`< z;wczPybyJz8&TtTV<|j}8vh8hbAIPivvC4Rlcpn}*u*rKpE>6*j>A zsLzd;s9TqRwzsfKsE4y2>V!?q4yf0<54xK811frmrdx;As4Lxp+R_uKiO!jqEq@F3 zOr)Z&^br=vKT%s8ImcUQ2@EB!huW#usP-;%?EUX;iTBI|>o^j%z=@~@%tvkca*Ma1 zu51_T?Ky_pp&wEG9%D9qX=a=2y~agRJ5_fs`>!qTKtc~+6l$fh7>0?cmCnZOxCpg% zTTySvG1QgcLG^!xP4T70P3L(F=#0OSk44SjYrc2h1eb~?7=zmC>8ORww|J$+8&Lz3 zP+NE$wea((E4_)j6%SDh`~`L80Sml&^O}WFJ6akw&aFj76E{JfxD#q%Z`2hH!w8&= z+S*O1d%WH9SIj>!7x}yky?b6BeLtw6P8e+twfsb^!ug$#spv%4P+y-vU=9pfe~859gzverK`Qzag$7?1b;*->8r9Axrq10nWw!xED2l{8Ii*tM`8rl>`#W zsC(LJnfE%qkL8Ky;wC(S#V~HU_qnhD%M$NEJ$$##pcURiD`GYBov;SZw)iaSVNSo2 z;!+8wqI(*KW3T`|#pSpM=W_)CIn>f&JGNR@va4s5xrF4yXwSqF$59sEHSt>rfN##!`3` zwV+3+r~MDq!UH#Y3(0}X7sLEm6(g{zW4t!6CWThN%7>krsYxiTA-v3Kfw9-@z#J^CtApJJ) zVJUzah-;yCsvd@5dsP2u)PiGC{an;TIsvtl(=Y_*qR#U%YTTz7toQ#26e<+D`Lp;IC%A#yxsrSNqQZ?h97Fc;O9@WG z6ZFfxpFiW^g#-Lfz$eqJLu@|ZfED>M9C76i&up!oTk9u2@hy_SYG1uc)#K$ohXCL!i zf!gXVm<^LGf70?7ExvB1nomr>X#W9%Syo8fwP?EPcwGxFRa9jm7Z|bAaXN zU`_IiQC~WjFgrd$T|nSzV$Sd6R0T_77OaDsxS847?1&oJ-F(j+f!f*+EnZ;p8Vn-8 z3rpZ%l|t)VI^u46W}e0$W1yIOt_MiP&I*VPyYyWCI%C)!yrtu_z-IRm#7{6 z*0sblO9b)KYXOB&1B;pE&6;KdvjyskJ6PNeD--v#{Bp~$MctC^mOqTa#O_HdTG2(+ zPbMiAr#t5@ASdee3CF6~0CmEV=2+B1C!;2uWBHZldMrkM8xF)vsD2I3`_Ah+t*9u` z8@0kgsI44hEM6*xHxi z_j>CPYx$w51x>W}rKl_3h}wxHi}zdmam!yYubSUsE&8WeT<8J|)caqOimtG#*+dP* zoh|N%I^ihvEd;d_ORaq`s{a}Ds+nxbC>6bDyCWcn&k-Rm_f)f^>wfGulA%7dS@SiTS|LXX{I%fEfw~%aRxLF3(uZCF{7ZEqI z_zvm7>3ay59*_}6zT-ct-T|L5_d=4!$FoGiRw4j z;u%<&c)sQLqxye=E%5^CJ16q0?g|X|%|FGZq7DmC_i8y##U0od>)i2v9+-`~1@p~SsHc4kYW!!Y2~U{c zpyvAy^Wv|T55DW=^P>8@WvJ+$)<8|v+TxDZVX)<0)I&27b>%b7m8e_riFp!r;+v>> ze>MZZ_wtdb6Ib?hoqAMs;ue?>-?2E(>);GC$D=+tW?8%+wV*FhKZ0Gb{B<+cd}8|D z^FF{r%y9I*|K+J@#dXm4x}iVuNYp2uYtBTSa209?Hlw~w4q#P0jq2|td;b9<3>8;H zExa-6$_HU~9F4yBf3_+l7Fmat7H>6op%!?+JZIiPE#!%rKE<0S2daO5)U7Ftx)lw~ zF6K~l)i8sKR<;2Ha5w5o51J>!=-ij2ib8i(tA`FD{N+KxNbd8==nA-i*Tl z;>oG@{Xdh0CR%0F#?!WM)ORS4E9~1GRv57DrqAaC72) z*ZW;>4hgO3qBUGc?LexfzTNI% zBsxEOUmk@}3+;<)e; z;)bY+T3EgV>cmkNk4KH0g_>s}w#Lm^0$*SuEdG->Pg4vgZYw#z(}Rj87-$`aSnQ(i z-9(F5piaCFb)pj%Uq$W6@2Gh){_GioI#EuG3t3#!tct$>{`Urz3Uq9PS#g3n54C`` zsEI$rad-kXag$#>+o9Tfp?0c2X2k^5C*ycj{{yInpTbgj;}`Z{CkS}#J)PmGiK?Im z)Ha(~dpk1*)o-jh*_>%EFqfNaQS)sv_n9Xjv;S(iXo=gX6+SYZU%mG1sPFj*EQ#-6 zCUngy<~(yHY6mu>cH{u+Aw6O759U)%s17etR}}P{_cLH9YGGyPY^+q+%|>mAWhSAf zo`)K-*794-edbB?B5IFsTl^<#zJMouBVbS$#-JAJTI^1wq5+Fg6RorOuyr_#?a6;}G`d zs#y~?zLCW(%y&^A`h9)v-2W+5^kF&+^zv<8Uokmn@U>v@Kn@}fk{^5sm$bz~BYfuaN+sygGYcGpB z;R&pYm+@81^3r>TYGE$oR#+PQVm!`8H$Rm?$IrLrMNlWGjJmS7EZ-e9K^!*553wPB zjru;#<>%*nx?e+Wd1LhL5XKUB#uIoJ7vk)6e!iV7?eFLM{yTVMe?Q+ZQNyu59S)#& z;sw^hw(0$RJ24yE5|_;2=lfrWjmD0|cd!%I3h?v&9c(`80=~fEwo@p z*IQ{(5?XOZ^L6Xc9QAq72K7F7w0sxT=R=ZX^C#AEs8OR zVqM}y)I;?-w!u@V6Bfwq<;$5>Q75Q_YHy0#$u1a(@1f=&in^tV7^)xACQ=zqVgXji zT*2N%EzQoTiTattu_p0k)QOIxCca|rcg)AAt@q2~jn9qhSKQ(XlJh(DsObG~g}Q>N z=0enaycV^9i>Obo@2x#Uh?mcXI#D5utDB8cC+vV)Xit0vhoH_g2YuiFE2-!NNvN$p zh8p-K>Z$*a#aFHUE^2}Y<}=ItXZ6NsGQ-R~s0D=MaIA>B@GV(+|8;^g{I_GBusmww zn&|uN#`451uqe8yPq4Mv0WTmwnL4Gy{Cs~E%A3vmfEtFi$sa+@{|}Z$zwF*yP#(3j z&9n3V>l1JU39WEGY5^O~J($an9l_VgU(exv0Oif;ec%j4E&McU{6*CG6x1i(Z`MAI zNeUCs$LzQtwd3bpD$M^shdw+))BR8AOrmi;VGS;$5k;S7ItGx}M{nA3m@(hlxc6`w z?d2$M(qRVWZ*tvn1Fo`ugpON{NwuZhC|@(6Bk@G*(2H0{X>%rR_pCmKK0i>efio$( z9sdxIq|Y>pj=RKpsK?oS%B>|gh!RikCp`uUR9>arr*WGNJWs55y)^L-I!{M^erK}w zlem~Zr6^-)PdhRb>-!>~^;5nd^+oiVX8q35=M@T%4hN56KiEm9>m<_-zE7MIltm=! zQYz6_l(>RTe2}=S)!X9lzRCFd#v{bP(B76ZjImL~Ka+1k+zs`r>sYOS^cn=yVI2*< zsPCmTqvIUZ@sRpYK3@C6~pY<4Wex`>o@&<+I}q@@a=2((#m!$yGp|-*@OI zmtX$X@fdMAwJ^aCHkm>l*;#Ni3a`EIwOfJ)c&tj{Luzoj*r)tPwWzsB%M?mNyfhM%^a53Eh!1AkCT z5Kp1(_P6UFL^217L6j)!{G8zAv=b8gjx5B7sr9ApPs_bDLuh-I{5eWz%gv{Lllmpf z8rm~5_XLWL4{1xLJm&Mq_unqsqmEP93Ugch(3j%JKd3=Y`)J|qo__%5F?j-t-K5jYwIY>~F&f1I$*5Lv93Djqie}eDOR*&+UwNrK0 z*a=$Gx2xp~QPY=C*c1 zXBHD(rhTyjJL0h?`5NR$Ti-7*nVgOj)E8R4IDSv=W;!l;8I@|3TNM6!;B26{*3l31 zQl>MhKCdfMKgvWY#QJ|l!PkPblDI0RKe3K8)b&H^SCkYb?3hZvI(-fh7pJaUzuu+t ze~)iy=tsx%n8n(Iuoq>8#cgPBMf-kS&IFZhlGWszlbb|50gn)Ors$}Pm&yHyQqfB} z?kFZ)Y?E5f$9ooxqVszUTuz^yl%v$k;CuQeHY(w%LYtSzjIzFJ(rR^AH zB}K<7`uqn6_#$or9whfRWf%2w_&c7WbYkv~ly9l8XM6$NhBL7_{q)K^XDPJ^7T{1@ zX(;hi;&3|epxmMyrCcF5gmRB^fVjW)&rkdoV@?xKq^={ohtryT5BhG$jO5Z&Po&?C zbez8f$>S6qjc6!ILlj;o&gskk??1O`)6a!E?o*#f=}6zVD3z(}7)(9w7)<3S;un@E zZ2pLY$Or2E|COKt!DF08*-9BnT*(%7hk9k|y(v36$s^j!;ss1QX4}c@<6axLg8F^N zeq?bsjAX!kJcPAr|2XY~W*&)GX=sXva56=QADwi(ZmRwzldi+&mODn@dX)DluhDjg zw))hIQRnAR-;qT81#u@zAL=^p(B>W@q2qmmKpGBE^hXCB*5rKbg-%1-UZ-9L`(Qmf zBw!QDtCY8xV}zaXH`>0TG^TGs+)LR?J?+@!)m>*R140NI+Q7Ng{pqZuGwRpq$re9P zn;f4}A}B?ztuVe#d1>)l-wb@F;Ja4WA2H_8Uf=rcPy7Dg&O|3jeo2QWEMNn%esgFC zE6D$Ycd-olX;_sw?a(h4ODVbNKa8@GdIUwkFX))Xm|(B!tfl=H@tYLiq4%#6!Rt)= z1$HH#MOkAVhEpGB^>NgjQZIrpEZ3NE`>3a4E%I3?Y2PV%$loQ_(TDbSlzA*b$9fy@ z4ySU6Qje024$+jfBNO$x7T0B9Dt*cjkE0%EogQF2n^dVi^qFmK`jzc7+Wuh72>gq- zNb1q3ql(`D@dVi@|FNXk#A`;!L(?kaz~Rpe>%9j%&nUQ)-hdjc=3toZ`ll=uf2)r4OC^(%6lnBLcfnKBJzQeygbu z#G%ChP~S>vO3_i2d=BcLF~?y_B=J&fQ+poDXyVKGI&C^O>-u}rxh4&HNo1v-)drm* zKB7+KJ|kC$L072nrkl;}(m2#9uP55cT8KWAG&E*l4Prk+IW>GucV6S{r{x@cl1| zYP%#p{nvoDG@R2&JN9vsJhWe;jHYc4@l@@POM`N^a#P8@fg30tm`_JKz5hBkk!XR}cfbuSVULihV?X7VPV}{_Ll;3IB-=BTQL=xXntBTneq~jwG-@h%%>*&Rx@z(yB z`a&*uiZNC2}EoEs8qZ}tO+8T>fZ)g+m!z4;c zi-R%L`n^HFg4Ex)MJfG&+*$ghTA!MC*+>^CVmSYj8#CSIOO`to~Pi z2k|=Ma7?6(pv5v{u!0LOk2kp;TRBwvE^*yNH|CW>3!hos1Vfv2? zytm|jru4PkB_`O+pdsXR^d`PY`H1oz<$dzeUX$;?{;GPFd?e)zCGA*1zy5aeSYO`m z{|7YOrfebk6#sqfu^`SS`j82BldDMyqHjU+9j(tQ>(j_o`XnV6@i6*UpnPR*ZE%;% zKkZ3AqthuSevOh~9R}D+S7;W?y>0%N+OqoF3mG~Nc60s-oBkConr?a`7 zOcDQeBqhE*B+@^+Urf@&L34siH%W--7a!3hW^h!G-oqpM4U9W!PV1<6Uz|1|x<|j5sNQA&zkK|VUcI8? z7V z@^9U`!P^mWgJTn7d&R~?^p1^+j~E!sVu!@V#SYe@<9ZBcy%D`)2PO=TjfszljvX8^ zBrYblM{ivWLGRdM1JgowA}T(~e|4#VOujA=N+gxu@Mf04iWMU&RH#|0eA4jUOTshE z|M1>{JxRfr`UgcOADWXqd3y4vJ5vwLNLjc&dD61v^;46l%}<@OCna&r&j-gO4Y_qM zw0g>z)yZq7KRB@P{9rGmb`88 z{dHshYjE1xl4maX`QVDAA74cHSJsv!PoHsr+qQdqr>D%_o-%Dr^7MJBQ@5miv_5&# ro_l+z+&j23Wzzh6d#Bz%bSTAlEuW@K=XX9sX0OT1oX_YNAN>CSn59f2 delta 20791 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 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 776273e3f..561e31db9 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-04-15 15:34+0800\n" +"POT-Creation-Date: 2019-04-22 11:30+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -36,7 +36,7 @@ msgstr "测试节点下资产是否可连接: {}" #: assets/forms/asset.py:27 assets/models/asset.py:80 assets/models/user.py:133 #: assets/templates/assets/asset_detail.html:194 #: assets/templates/assets/asset_detail.html:202 -#: assets/templates/assets/system_user_asset.html:95 perms/models.py:31 +#: assets/templates/assets/system_user_asset.html:95 perms/models.py:51 #: xpack/plugins/change_auth_plan/models.py:69 msgid "Nodes" msgstr "节点管理" @@ -73,7 +73,7 @@ msgstr "网域" #: assets/forms/asset.py:112 assets/models/node.py:31 #: assets/templates/assets/asset_create.html:30 #: assets/templates/assets/asset_update.html:35 perms/forms.py:45 -#: perms/forms.py:52 perms/models.py:84 +#: perms/forms.py:55 perms/models.py:105 #: perms/templates/perms/asset_permission_list.html:57 #: perms/templates/perms/asset_permission_list.html:78 #: perms/templates/perms/asset_permission_list.html:128 @@ -120,7 +120,7 @@ msgstr "选择资产" #: assets/templates/assets/system_user_list.html:33 audits/models.py:19 #: audits/templates/audits/ftp_log_list.html:41 #: audits/templates/audits/ftp_log_list.html:71 perms/forms.py:42 -#: perms/models.py:30 +#: perms/models.py:50 #: perms/templates/perms/asset_permission_create_update.html:45 #: perms/templates/perms/asset_permission_list.html:56 #: perms/templates/perms/asset_permission_list.html:125 @@ -164,7 +164,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: assets/templates/assets/system_user_detail.html:58 #: assets/templates/assets/system_user_list.html:29 ops/models/adhoc.py:37 #: ops/templates/ops/task_detail.html:60 ops/templates/ops/task_list.html:27 -#: orgs/models.py:12 perms/models.py:27 +#: orgs/models.py:12 perms/models.py:17 perms/models.py:47 #: perms/templates/perms/asset_permission_detail.html:62 #: perms/templates/perms/asset_permission_list.html:53 #: perms/templates/perms/asset_permission_list.html:72 @@ -439,8 +439,8 @@ msgstr "标签管理" #: assets/templates/assets/cmd_filter_detail.html:77 #: assets/templates/assets/domain_detail.html:72 #: assets/templates/assets/system_user_detail.html:100 -#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:36 -#: perms/models.py:89 perms/templates/perms/asset_permission_detail.html:98 +#: ops/templates/ops/adhoc_detail.html:86 orgs/models.py:15 perms/models.py:57 +#: perms/models.py:110 perms/templates/perms/asset_permission_detail.html:98 #: users/models/user.py:102 users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:103 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 @@ -455,7 +455,7 @@ msgstr "创建者" #: assets/templates/assets/domain_detail.html:68 #: assets/templates/assets/system_user_detail.html:96 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:16 perms/models.py:37 perms/models.py:90 +#: orgs/models.py:16 perms/models.py:58 perms/models.py:111 #: perms/templates/perms/asset_permission_detail.html:94 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 #: users/templates/users/user_group_detail.html:63 @@ -483,7 +483,7 @@ msgstr "创建日期" #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:37 #: assets/templates/assets/user_asset_list.html:171 ops/models/adhoc.py:43 -#: orgs/models.py:17 perms/models.py:38 perms/models.py:91 +#: orgs/models.py:17 perms/models.py:59 perms/models.py:112 #: perms/templates/perms/asset_permission_detail.html:102 settings/models.py:34 #: terminal/models.py:32 terminal/templates/terminal/terminal_detail.html:63 #: users/models/group.py:15 users/models/user.py:94 @@ -679,6 +679,8 @@ msgstr "每行一个命令" #: audits/templates/audits/operate_log_list.html:67 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:34 +#: perms/forms.py:51 perms/models.py:21 perms/models.py:53 +#: perms/templates/perms/asset_permission_create_update.html:50 #: perms/templates/perms/asset_permission_list.html:60 #: settings/templates/settings/terminal_setting.html:82 #: settings/templates/settings/terminal_setting.html:104 @@ -723,7 +725,7 @@ msgstr "默认资产组" #: audits/templates/audits/password_change_log_list.html:50 #: ops/templates/ops/command_execution_list.html:35 #: ops/templates/ops/command_execution_list.html:60 perms/forms.py:36 -#: perms/models.py:28 +#: perms/models.py:48 #: perms/templates/perms/asset_permission_create_update.html:41 #: perms/templates/perms/asset_permission_list.html:54 #: perms/templates/perms/asset_permission_list.html:119 templates/index.html:87 @@ -811,7 +813,7 @@ msgstr "登录模式" #: assets/models/user.py:247 assets/templates/assets/user_asset_list.html:168 #: audits/models.py:20 audits/templates/audits/ftp_log_list.html:49 #: audits/templates/audits/ftp_log_list.html:72 perms/forms.py:48 -#: perms/models.py:32 perms/models.py:86 +#: perms/models.py:52 perms/models.py:107 #: perms/templates/perms/asset_permission_detail.html:140 #: perms/templates/perms/asset_permission_list.html:58 #: perms/templates/perms/asset_permission_list.html:79 @@ -1009,7 +1011,7 @@ msgstr "自动生成密钥" #: assets/templates/assets/asset_create.html:60 #: assets/templates/assets/asset_update.html:64 #: assets/templates/assets/gateway_create_update.html:53 -#: perms/templates/perms/asset_permission_create_update.html:50 +#: perms/templates/perms/asset_permission_create_update.html:53 #: terminal/templates/terminal/terminal_update.html:40 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:67 msgid "Other" @@ -1025,7 +1027,7 @@ msgstr "其它" #: assets/templates/assets/domain_create_update.html:16 #: assets/templates/assets/gateway_create_update.html:58 #: assets/templates/assets/label_create_update.html:18 -#: perms/templates/perms/asset_permission_create_update.html:80 +#: perms/templates/perms/asset_permission_create_update.html:83 #: settings/templates/settings/basic_setting.html:61 #: settings/templates/settings/command_storage_create.html:79 #: settings/templates/settings/email_setting.html:62 @@ -1061,7 +1063,7 @@ msgstr "重置" #: assets/templates/assets/gateway_create_update.html:59 #: assets/templates/assets/label_create_update.html:19 #: audits/templates/audits/login_log_list.html:89 -#: perms/templates/perms/asset_permission_create_update.html:81 +#: perms/templates/perms/asset_permission_create_update.html:84 #: settings/templates/settings/basic_setting.html:62 #: settings/templates/settings/command_storage_create.html:80 #: settings/templates/settings/email_setting.html:63 @@ -1080,7 +1082,7 @@ msgstr "重置" #: users/templates/users/user_profile_update.html:64 #: users/templates/users/user_pubkey_update.html:77 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:72 -#: xpack/plugins/interface/templates/interface/interface.html:73 +#: xpack/plugins/interface/templates/interface/interface.html:74 msgid "Submit" msgstr "提交" @@ -1165,7 +1167,7 @@ msgstr "更新认证" #: assets/templates/assets/system_user_asset.html:350 #: users/templates/users/user_detail.html:307 #: users/templates/users/user_detail.html:334 -#: xpack/plugins/interface/views.py:31 +#: xpack/plugins/interface/views.py:34 msgid "Update successfully!" msgstr "更新成功" @@ -1275,6 +1277,7 @@ msgstr "选择节点" #: users/templates/users/user_profile.html:238 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 +#: xpack/plugins/interface/templates/interface/interface.html:103 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 msgid "Confirm" msgstr "确认" @@ -1362,9 +1365,9 @@ msgid "Date joined" msgstr "创建日期" #: assets/templates/assets/asset_detail.html:154 -#: assets/templates/assets/user_asset_list.html:46 perms/models.py:33 -#: perms/models.py:87 -#: perms/templates/perms/asset_permission_create_update.html:52 +#: assets/templates/assets/user_asset_list.html:46 perms/models.py:54 +#: perms/models.py:108 +#: perms/templates/perms/asset_permission_create_update.html:55 #: perms/templates/perms/asset_permission_detail.html:120 #: terminal/templates/terminal/terminal_list.html:34 #: users/templates/users/_select_user_modal.html:18 @@ -1504,6 +1507,7 @@ msgstr "重命名失败,不能更改root节点的名称" #: users/templates/users/user_detail.html:476 #: users/templates/users/user_group_list.html:82 #: users/templates/users/user_list.html:202 +#: xpack/plugins/interface/templates/interface/interface.html:97 msgid "Are you sure?" msgstr "你确认吗?" @@ -1520,6 +1524,7 @@ msgstr "删除选择资产" #: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_list.html:86 #: users/templates/users/user_list.html:206 +#: xpack/plugins/interface/templates/interface/interface.html:101 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:32 msgid "Cancel" msgstr "取消" @@ -1738,7 +1743,7 @@ msgstr "删除系统用户" msgid "System Users Deleting failed." msgstr "系统用户删除失败" -#: assets/templates/assets/user_asset_list.html:100 +#: assets/templates/assets/user_asset_list.html:100 perms/const.py:19 msgid "Connect" msgstr "连接" @@ -1970,7 +1975,7 @@ msgstr "登录日期" #: ops/templates/ops/adhoc_history.html:52 #: ops/templates/ops/adhoc_history_detail.html:61 #: ops/templates/ops/command_execution_list.html:66 -#: ops/templates/ops/task_history.html:58 perms/models.py:34 +#: ops/templates/ops/task_history.html:58 perms/models.py:55 #: perms/templates/perms/asset_permission_detail.html:86 terminal/models.py:165 #: terminal/templates/terminal/session_list.html:78 #: xpack/plugins/change_auth_plan/models.py:246 @@ -2697,7 +2702,19 @@ msgstr "命令执行" msgid "Organization" msgstr "组织管理" -#: perms/forms.py:39 perms/models.py:29 perms/models.py:85 +#: perms/const.py:18 settings/forms.py:136 +msgid "All" +msgstr "全部" + +#: perms/const.py:20 +msgid "Upload file" +msgstr "上传文件" + +#: perms/const.py:21 +msgid "Download file" +msgstr "下载文件" + +#: perms/forms.py:39 perms/models.py:49 perms/models.py:106 #: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:75 #: perms/templates/perms/asset_permission_list.html:122 templates/_nav.html:14 @@ -2709,22 +2726,28 @@ msgstr "组织管理" msgid "User group" msgstr "用户组" -#: perms/forms.py:61 +#: perms/forms.py:58 +msgid "" +"Tips: The RDP protocol does not support separate controls for uploading or " +"downloading files" +msgstr "提示:RDP 协议不支持单独控制上传或下载文件" + +#: perms/forms.py:68 msgid "User or group at least one required" msgstr "用户和用户组至少选一个" -#: perms/forms.py:70 +#: perms/forms.py:77 msgid "Asset or group at least one required" msgstr "资产和节点至少选一个" -#: perms/models.py:35 perms/models.py:88 +#: perms/models.py:56 perms/models.py:109 #: perms/templates/perms/asset_permission_detail.html:90 #: users/models/user.py:99 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:116 msgid "Date expired" msgstr "失效日期" -#: perms/models.py:44 perms/models.py:97 templates/_nav.html:34 +#: perms/models.py:65 perms/models.py:118 templates/_nav.html:34 msgid "Asset permission" msgstr "资产授权" @@ -2770,7 +2793,7 @@ msgstr "添加节点" msgid "Join" msgstr "加入" -#: perms/templates/perms/asset_permission_create_update.html:58 +#: perms/templates/perms/asset_permission_create_update.html:61 msgid "Validity period" msgstr "有效期" @@ -2828,29 +2851,29 @@ msgstr "添加用户组" msgid "Select user groups" msgstr "选择用户组" -#: perms/views.py:23 perms/views.py:53 perms/views.py:68 perms/views.py:83 -#: perms/views.py:118 perms/views.py:150 templates/_nav.html:31 +#: perms/views.py:24 perms/views.py:56 perms/views.py:71 perms/views.py:86 +#: perms/views.py:121 perms/views.py:153 templates/_nav.html:31 #: xpack/plugins/orgs/templates/orgs/org_list.html:21 msgid "Perms" msgstr "权限管理" -#: perms/views.py:24 +#: perms/views.py:25 msgid "Asset permission list" msgstr "资产授权列表" -#: perms/views.py:54 +#: perms/views.py:57 msgid "Create asset permission" msgstr "创建权限规则" -#: perms/views.py:69 perms/views.py:84 +#: perms/views.py:72 perms/views.py:87 msgid "Update asset permission" msgstr "更新资产授权" -#: perms/views.py:119 +#: perms/views.py:122 msgid "Asset permission user list" msgstr "资产授权用户列表" -#: perms/views.py:151 +#: perms/views.py:154 msgid "Asset permission asset list" msgstr "资产授权资产列表" @@ -2982,10 +3005,6 @@ msgstr "" msgid "Enable LDAP auth" msgstr "启用LDAP认证" -#: settings/forms.py:136 -msgid "All" -msgstr "全部" - #: settings/forms.py:137 msgid "Auto" msgstr "自动" @@ -5231,14 +5250,37 @@ msgid "Interface settings" msgstr "界面设置" #: xpack/plugins/interface/templates/interface/interface.html:15 -#: xpack/plugins/interface/views.py:21 +#: xpack/plugins/interface/views.py:24 msgid "Interface setting" msgstr "界面设置" -#: xpack/plugins/interface/views.py:20 +#: xpack/plugins/interface/templates/interface/interface.html:73 +#: xpack/plugins/interface/templates/interface/interface.html:108 +#: xpack/plugins/interface/templates/interface/interface.html:115 +msgid "Restore Default" +msgstr "恢复默认" + +#: xpack/plugins/interface/templates/interface/interface.html:98 +msgid "This will restore default Settings of the interface !!!" +msgstr "您确定要恢复默认初始化吗?" + +#: xpack/plugins/interface/templates/interface/interface.html:107 +#: xpack/plugins/interface/views.py:53 +msgid "Restore default successfully." +msgstr "恢复默认成功!" + +#: xpack/plugins/interface/templates/interface/interface.html:114 +msgid "Restore default failed." +msgstr "恢复默认失败!" + +#: xpack/plugins/interface/views.py:23 msgid "Interface" msgstr "界面" +#: xpack/plugins/interface/views.py:49 +msgid "It is already in the default setting state!" +msgstr "当前已经是初始化状态!" + #: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:94 #: xpack/plugins/license/templates/license/license_detail.html:50 #: xpack/plugins/license/templates/license/license_detail.html:55 @@ -5268,6 +5310,7 @@ msgid "Please Import License" msgstr "请导入许可证" #: xpack/plugins/license/templates/license/license_detail.html:17 +#: xpack/plugins/license/templates/license/license_detail.html:56 msgid "License has expired" msgstr "许可证已经过期" @@ -5375,24 +5418,6 @@ msgstr "创建组织" msgid "Update org" msgstr "更新组织" -#~ msgid "Restore Default" -#~ msgstr "恢复默认" - -#~ msgid "This will restore default Settings of the interface !!!" -#~ msgstr "您确定要恢复默认初始化吗?" - -#~ msgid "Restore default successfully." -#~ msgstr "恢复默认成功!" - -#~ msgid "Restore default failed." -#~ msgstr "恢复默认失败!" - -#~ msgid "It is already in the default setting state!" -#~ msgstr "当前已经是初始化状态!" - -#~ msgid "Restore default successfully!" -#~ msgstr "恢复默认成功!" - #~ msgid "Sync User" #~ msgstr "同步用户" diff --git a/apps/perms/api/permission.py b/apps/perms/api/permission.py index c425c90ad..a0381e698 100644 --- a/apps/perms/api/permission.py +++ b/apps/perms/api/permission.py @@ -10,7 +10,7 @@ from rest_framework.pagination import LimitOffsetPagination from common.permissions import IsOrgAdmin from common.utils import get_object_or_none -from ..models import AssetPermission +from ..models import AssetPermission, Action from ..hands import ( User, UserGroup, Asset, Node, SystemUser, ) @@ -20,10 +20,16 @@ from .. import serializers __all__ = [ 'AssetPermissionViewSet', 'AssetPermissionRemoveUserApi', 'AssetPermissionAddUserApi', 'AssetPermissionRemoveAssetApi', - 'AssetPermissionAddAssetApi', + 'AssetPermissionAddAssetApi', 'ActionViewSet', ] +class ActionViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Action.objects.all() + serializer_class = serializers.ActionSerializer + permission_classes = (IsOrgAdmin,) + + class AssetPermissionViewSet(viewsets.ModelViewSet): """ 资产授权列表的增删改查api diff --git a/apps/perms/api/user_permission.py b/apps/perms/api/user_permission.py index ecb3b4c3d..8d1a42c26 100644 --- a/apps/perms/api/user_permission.py +++ b/apps/perms/api/user_permission.py @@ -16,7 +16,8 @@ from common.tree import TreeNodeSerializer from common.utils import get_logger from orgs.utils import set_to_root_org from ..utils import ( - AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node + AssetPermissionUtil, parse_asset_to_tree_node, parse_node_to_tree_node, + check_system_user_action ) from ..hands import ( AssetGrantedSerializer, User, Asset, Node, @@ -24,6 +25,7 @@ from ..hands import ( ) from .. import serializers from ..mixins import AssetsFilterMixin +from ..models import Action logger = get_logger(__name__) @@ -31,7 +33,7 @@ __all__ = [ 'UserGrantedAssetsApi', 'UserGrantedNodesApi', 'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi', 'ValidateUserAssetPermissionApi', 'UserGrantedNodeChildrenApi', - 'UserGrantedNodesWithAssetsAsTreeApi', + 'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi', ] @@ -403,16 +405,45 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView): user_id = request.query_params.get('user_id', '') asset_id = request.query_params.get('asset_id', '') system_id = request.query_params.get('system_user_id', '') + action_name = request.query_params.get('action_name', '') user = get_object_or_404(User, id=user_id) asset = get_object_or_404(Asset, id=asset_id) - system_user = get_object_or_404(SystemUser, id=system_id) + su = get_object_or_404(SystemUser, id=system_id) + action = get_object_or_404(Action, name=action_name) util = AssetPermissionUtil(user, cache_policy=self.cache_policy) - assets_granted = util.get_assets() - if system_user in assets_granted.get(asset, []): - return Response({'msg': True}, status=200) - else: + granted_assets = util.get_assets() + granted_system_users = granted_assets.get(asset, []) + + if su not in granted_system_users: return Response({'msg': False}, status=403) + _su = next((s for s in granted_system_users if s.id == su.id), None) + if not check_system_user_action(_su, action): + return Response({'msg': False}, status=403) + return Response({'msg': True}, status=200) + + +class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, APIView): + permission_classes = (IsOrgAdminOrAppUser,) + + def get(self, request, *args, **kwargs): + user_id = request.query_params.get('user_id', '') + asset_id = request.query_params.get('asset_id', '') + system_id = request.query_params.get('system_user_id', '') + + user = get_object_or_404(User, id=user_id) + asset = get_object_or_404(Asset, id=asset_id) + su = get_object_or_404(SystemUser, id=system_id) + + util = AssetPermissionUtil(user, cache_policy=self.cache_policy) + granted_assets = util.get_assets() + granted_system_users = granted_assets.get(asset, []) + _su = next((s for s in granted_system_users if s.id == su.id), None) + if not _su: + return Response({'actions': []}, status=403) + + actions = [action.name for action in getattr(_su, 'actions', [])] + return Response({'actions': actions}, status=200) diff --git a/apps/perms/const.py b/apps/perms/const.py new file mode 100644 index 000000000..4dc315c06 --- /dev/null +++ b/apps/perms/const.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# + +from django.utils.translation import ugettext_lazy as _ + +__all__ = [ + 'PERMS_ACTION_NAME_ALL', 'PERMS_ACTION_NAME_CONNECT', + 'PERMS_ACTION_NAME_DOWNLOAD_FILE', 'PERMS_ACTION_NAME_UPLOAD_FILE', + 'PERMS_ACTION_NAME_CHOICES' +] + +PERMS_ACTION_NAME_ALL = 'all' +PERMS_ACTION_NAME_CONNECT = 'connect' +PERMS_ACTION_NAME_UPLOAD_FILE = 'upload_file' +PERMS_ACTION_NAME_DOWNLOAD_FILE = 'download_file' + +PERMS_ACTION_NAME_CHOICES = ( + (PERMS_ACTION_NAME_ALL, _('All')), + (PERMS_ACTION_NAME_CONNECT, _('Connect')), + (PERMS_ACTION_NAME_UPLOAD_FILE, _('Upload file')), + (PERMS_ACTION_NAME_DOWNLOAD_FILE, _('Download file')), +) diff --git a/apps/perms/forms.py b/apps/perms/forms.py index a07cdb2ef..35d8a2528 100644 --- a/apps/perms/forms.py +++ b/apps/perms/forms.py @@ -47,10 +47,17 @@ class AssetPermissionForm(OrgModelForm): 'system_users': forms.SelectMultiple( attrs={'class': 'select2', 'data-placeholder': _('System user')} ), + 'actions': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Action')} + ) } labels = { 'nodes': _("Node"), } + help_texts = { + 'actions': _('Tips: The RDP protocol does not support separate ' + 'controls for uploading or downloading files') + } def clean_user_groups(self): users = self.cleaned_data.get('users') diff --git a/apps/perms/migrations/0003_action.py b/apps/perms/migrations/0003_action.py new file mode 100644 index 000000000..41c0f9f39 --- /dev/null +++ b/apps/perms/migrations/0003_action.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1.7 on 2019-04-12 07:00 + +from django.db import migrations, models +import uuid + + +def add_default_actions(apps, schema_editor): + from ..const import PERMS_ACTION_NAME_CHOICES + action_model = apps.get_model('perms', 'Action') + db_alias = schema_editor.connection.alias + for action, _ in PERMS_ACTION_NAME_CHOICES: + action_model.objects.using(db_alias).update_or_create(name=action) + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0002_auto_20171228_0025_squashed_0009_auto_20180903_1132'), + ] + + operations = [ + migrations.CreateModel( + name='Action', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('name', models.CharField(choices=[('all', 'All'), ('connect', 'Connect'), ('upload_file', 'Upload file'), ('download_file', 'Download file')], max_length=128, unique=True, verbose_name='Name')), + ], + options={ + 'verbose_name': 'Action', + }, + ), + migrations.RunPython(add_default_actions) + ] diff --git a/apps/perms/migrations/0004_assetpermission_actions.py b/apps/perms/migrations/0004_assetpermission_actions.py new file mode 100644 index 000000000..be468e7f0 --- /dev/null +++ b/apps/perms/migrations/0004_assetpermission_actions.py @@ -0,0 +1,31 @@ +# Generated by Django 2.1.7 on 2019-04-12 09:17 + +from django.db import migrations, models + + +def set_default_action_to_existing_perms(apps, schema_editor): + from orgs.utils import set_to_root_org + from ..models import Action + set_to_root_org() + perm_model = apps.get_model('perms', 'AssetPermission') + db_alias = schema_editor.connection.alias + perms = perm_model.objects.using(db_alias).all() + default_action = Action.get_action_all() + for perm in perms: + perm.actions.add(default_action.id) + + +class Migration(migrations.Migration): + + dependencies = [ + ('perms', '0003_action'), + ] + + operations = [ + migrations.AddField( + model_name='assetpermission', + name='actions', + field=models.ManyToManyField(blank=True, related_name='permissions', to='perms.Action', verbose_name='Action'), + ), + migrations.RunPython(set_default_action_to_existing_perms) + ] diff --git a/apps/perms/models.py b/apps/perms/models.py index 1ae4f5e69..c524c4f58 100644 --- a/apps/perms/models.py +++ b/apps/perms/models.py @@ -7,6 +7,26 @@ from django.utils import timezone from common.utils import date_expired_default, set_or_append_attr_bulk from orgs.mixins import OrgModelMixin, OrgManager +from .const import PERMS_ACTION_NAME_CHOICES, PERMS_ACTION_NAME_ALL + + +class Action(models.Model): + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField( + max_length=128, unique=True, choices=PERMS_ACTION_NAME_CHOICES, + verbose_name=_('Name') + ) + + class Meta: + verbose_name = _('Action') + + def __str__(self): + return self.get_name_display() + + @classmethod + def get_action_all(cls): + return cls.objects.get(name=PERMS_ACTION_NAME_ALL) + class AssetPermissionQuerySet(models.QuerySet): def active(self): @@ -30,6 +50,7 @@ class AssetPermission(OrgModelMixin): assets = models.ManyToManyField('assets.Asset', related_name='granted_by_permissions', blank=True, verbose_name=_("Asset")) nodes = models.ManyToManyField('assets.Node', related_name='granted_by_permissions', blank=True, verbose_name=_("Nodes")) system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_permissions', verbose_name=_("System user")) + actions = models.ManyToManyField('Action', related_name='permissions', blank=True, verbose_name=_('Action')) is_active = models.BooleanField(default=True, verbose_name=_('Active')) date_start = models.DateTimeField(default=timezone.now, db_index=True, verbose_name=_("Date start")) date_expired = models.DateTimeField(default=date_expired_default, db_index=True, verbose_name=_('Date expired')) diff --git a/apps/perms/serializers.py b/apps/perms/serializers.py index cd874ce1f..01c5a077a 100644 --- a/apps/perms/serializers.py +++ b/apps/perms/serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers from common.fields import StringManyToManyField -from .models import AssetPermission +from .models import AssetPermission, Action from assets.models import Node, Asset, SystemUser from assets.serializers import AssetGrantedSerializer @@ -13,9 +13,16 @@ __all__ = [ 'AssetPermissionUpdateUserSerializer', 'AssetPermissionUpdateAssetSerializer', 'AssetPermissionNodeSerializer', 'GrantedNodeSerializer', 'GrantedAssetSerializer', 'GrantedSystemUserSerializer', + 'ActionSerializer', ] +class ActionSerializer(serializers.ModelSerializer): + class Meta: + model = Action + fields = '__all__' + + class AssetPermissionCreateUpdateSerializer(serializers.ModelSerializer): class Meta: model = AssetPermission @@ -28,6 +35,7 @@ class AssetPermissionListSerializer(serializers.ModelSerializer): assets = StringManyToManyField(many=True, read_only=True) nodes = StringManyToManyField(many=True, read_only=True) system_users = StringManyToManyField(many=True, read_only=True) + actions = StringManyToManyField(many=True, read_only=True) is_valid = serializers.BooleanField() is_expired = serializers.BooleanField() diff --git a/apps/perms/signals_handler.py b/apps/perms/signals_handler.py index 7876d7642..97884db1b 100644 --- a/apps/perms/signals_handler.py +++ b/apps/perms/signals_handler.py @@ -2,15 +2,37 @@ # from django.db.models.signals import m2m_changed, post_save, post_delete from django.dispatch import receiver +from django.db import transaction from common.utils import get_logger from .utils import AssetPermissionUtil -from .models import AssetPermission +from .models import AssetPermission, Action logger = get_logger(__file__) +def on_transaction_commit(func): + """ + 如果不调用on_commit, 对象创建时添加多对多字段值失败 + """ + def inner(*args, **kwargs): + transaction.on_commit(lambda: func(*args, **kwargs)) + return inner + + +@receiver(post_save, sender=AssetPermission, dispatch_uid="my_unique_identifier") +@on_transaction_commit +def on_permission_created(sender, instance=None, created=False, **kwargs): + actions = instance.actions.all() + if created and not actions: + default_action = Action.get_action_all() + instance.actions.add(default_action) + logger.debug( + "Set default action to perms: {}".format(default_action, instance) + ) + + @receiver(post_save, sender=AssetPermission) def on_permission_update(sender, **kwargs): AssetPermissionUtil.expire_all_cache() diff --git a/apps/perms/templates/perms/asset_permission_create_update.html b/apps/perms/templates/perms/asset_permission_create_update.html index b6789abbf..cf7a46eff 100644 --- a/apps/perms/templates/perms/asset_permission_create_update.html +++ b/apps/perms/templates/perms/asset_permission_create_update.html @@ -47,6 +47,9 @@ {% bootstrap_field form.nodes layout="horizontal" %} {% bootstrap_field form.system_users layout="horizontal" %}
+

{% trans 'Action' %}

+ {% bootstrap_field form.actions layout="horizontal" %} +

{% trans 'Other' %}

diff --git a/apps/perms/urls/api_urls.py b/apps/perms/urls/api_urls.py index 00e842f52..5894d3835 100644 --- a/apps/perms/urls/api_urls.py +++ b/apps/perms/urls/api_urls.py @@ -7,6 +7,7 @@ from .. import api app_name = 'perms' router = routers.DefaultRouter() +router.register('actions', api.ActionViewSet, 'action') router.register('asset-permissions', api.AssetPermissionViewSet, 'asset-permission') urlpatterns = [ @@ -67,6 +68,8 @@ urlpatterns = [ # 验证用户是否有某个资产和系统用户的权限 path('asset-permission/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'), + path('asset-permission/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), + name='get-user-asset-permission-actions'), ] urlpatterns += router.urls diff --git a/apps/perms/utils.py b/apps/perms/utils.py index 4243b17bd..bd5094def 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -1,6 +1,5 @@ # coding: utf-8 -from __future__ import absolute_import, unicode_literals import uuid from collections import defaultdict import json @@ -13,7 +12,7 @@ from django.conf import settings from common.utils import get_logger from common.tree import TreeNode -from .models import AssetPermission +from .models import AssetPermission, Action from .hands import Node logger = get_logger(__file__) @@ -101,7 +100,7 @@ class AssetPermissionUtil: "UserGroup": get_user_group_permissions, "Asset": get_asset_permissions, "Node": get_node_permissions, - "SystemUser": get_node_permissions, + "SystemUser": get_system_user_permissions, } CACHE_KEY_PREFIX = '_ASSET_PERM_CACHE_' @@ -180,6 +179,24 @@ class AssetPermissionUtil: ) return assets + def _setattr_actions_to_system_user(self): + """ + 动态给system_use设置属性actions + """ + for asset, system_users in self._assets.items(): + # 获取资产和资产的祖先节点的所有授权规则 + perms = get_asset_permissions(asset, include_node=True) + # 过滤当前self.permission的授权规则 + perms = perms.filter(id__in=[perm.id for perm in self.permissions]) + + for system_user in system_users: + actions = set() + _perms = perms.filter(system_users=system_user).\ + prefetch_related('actions') + for _perm in _perms: + actions.update(_perm.actions.all()) + setattr(system_user, 'actions', actions) + def get_assets_without_cache(self): if self._assets: return self._assets @@ -192,6 +209,7 @@ class AssetPermissionUtil: [s for s in system_users if s.protocol == asset.protocol] ) self._assets = assets + self._setattr_actions_to_system_user() return self._assets def get_cache_key(self, resource): @@ -395,6 +413,7 @@ def parse_asset_to_tree_node(node, asset, system_users): 'protocol': system_user.protocol, 'priority': system_user.priority, 'login_mode': system_user.login_mode, + 'actions': [action.name for action in system_user.actions], 'comment': system_user.comment, }) data = { @@ -423,3 +442,21 @@ def parse_asset_to_tree_node(node, asset, system_users): } tree_node = TreeNode(**data) return tree_node + + +# +# actions +# + + +def check_system_user_action(system_user, action): + """ + :param system_user: SystemUser object (包含动态属性: actions) + :param action: Action object + :return: bool + """ + + check_actions = [Action.get_action_all(), action] + granted_actions = getattr(system_user, 'actions', []) + actions = list(set(granted_actions).intersection(set(check_actions))) + return bool(actions) diff --git a/apps/perms/views.py b/apps/perms/views.py index 6b0a0d5d3..0e02b38a7 100644 --- a/apps/perms/views.py +++ b/apps/perms/views.py @@ -11,8 +11,9 @@ from django.conf import settings from common.permissions import AdminUserRequiredMixin from orgs.utils import current_org from .hands import Node, Asset, SystemUser, User, UserGroup -from .models import AssetPermission +from .models import AssetPermission, Action from .forms import AssetPermissionForm +from .const import PERMS_ACTION_NAME_ALL class AssetPermissionListView(AdminUserRequiredMixin, TemplateView): @@ -46,6 +47,8 @@ class AssetPermissionCreateView(AdminUserRequiredMixin, CreateView): assets_id = assets_id.split(",") assets = Asset.objects.filter(id__in=assets_id) form['assets'].initial = assets + form['actions'].initial = Action.objects.get(name=PERMS_ACTION_NAME_ALL) + return form def get_context_data(self, **kwargs): From 33a00f043b1b026ebd27b8d86f0ad50542b25749 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 22 Apr 2019 11:55:14 +0800 Subject: [PATCH 06/17] =?UTF-8?q?[Bugfix]=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BAazure=E7=B1=BB=E5=9E=8B=E7=9A=84=E5=BD=95=E5=83=8F?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E6=97=B6=E5=89=8D=E7=AB=AF=E7=9A=84bug=20(#2?= =?UTF-8?q?617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/templates/settings/replay_storage_create.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/settings/templates/settings/replay_storage_create.html b/apps/settings/templates/settings/replay_storage_create.html index c986b40e8..6521a730a 100644 --- a/apps/settings/templates/settings/replay_storage_create.html +++ b/apps/settings/templates/settings/replay_storage_create.html @@ -252,7 +252,7 @@ $(document).ready(function() { var name = $(id_field).attr('name'); data[name] = $(id_field).val(); }); - if (data['ENDPOINT'] !== '' && data['ENDPOINT'].indexOf('http') === -1) { + if (data['ENDPOINT'] && data['ENDPOINT'].indexOf('http') === -1) { var msg = "{% trans 'Endpoint need contain protocol, ex: http' %}"; $("#endpoint_error").html(msg); submitBtn.removeClass('disabled'); From 8196537878494955b4436441ab95ed4ead7d9c4b Mon Sep 17 00:00:00 2001 From: ibuler Date: Mon, 22 Apr 2019 12:07:48 +0800 Subject: [PATCH 07/17] =?UTF-8?q?[Update]=20=E5=8E=BB=E6=8E=89logger.info?= =?UTF-8?q?=20openid=20msg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/authentication/backends/openid/middleware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py index 75a752708..57ac80afe 100644 --- a/apps/authentication/backends/openid/middleware.py +++ b/apps/authentication/backends/openid/middleware.py @@ -23,7 +23,6 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): # Don't need openid auth if AUTH_OPENID is False if not settings.AUTH_OPENID: - logger.info("Not settings.AUTH_OPENID") return # Don't need check single logout if user not authenticated if not request.user.is_authenticated: From aabcf7f31cb493ba7648b79afb6c823cbdfd0788 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Mon, 22 Apr 2019 12:31:40 +0800 Subject: [PATCH 08/17] =?UTF-8?q?[Update]=20=E6=8E=88=E6=9D=83=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=8B=E6=8B=89=E4=BF=A1=E6=81=AF=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?action=E5=AD=97=E6=AE=B5=20(#2618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/perms/templates/perms/asset_permission_list.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/perms/templates/perms/asset_permission_list.html b/apps/perms/templates/perms/asset_permission_list.html index 4ae51ad52..9af4577f1 100644 --- a/apps/perms/templates/perms/asset_permission_list.html +++ b/apps/perms/templates/perms/asset_permission_list.html @@ -130,6 +130,9 @@ function format(d) { if (d.system_users.length > 0) { data += makeLabel(["{% trans 'System user' %}", d.system_users.join(", ")]) } + if (d.actions.length > 0) { + data += makeLabel(["{% trans 'Action' %}", d.actions.join(", ")]) + } return data } From caa5060ecd5bf2466cdc272473c94e61d17b09f7 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 25 Apr 2019 10:11:50 +0800 Subject: [PATCH 09/17] =?UTF-8?q?[Update]=20=E6=8E=A7=E5=88=B6=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E7=AE=A1=E7=90=86=E5=91=98=E4=B8=8D=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=80=81=E5=88=A0=E9=99=A4=E8=B6=85=E7=BA=A7?= =?UTF-8?q?=E7=94=A8=E6=88=B7=EF=BC=9B=E4=BF=AE=E5=A4=8DViewSet=20API?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0=E7=9A=84bug=20(#2629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Update] 控制组织管理员不允许编辑(更新、删除)超级用户 - 待续(控制批量更新API) * [Update] 修改方法名称 * [Update] 控制组织管理员不允许批量更新包含超级用户的用户列表 * [Bugfix] 修复所有ViewSet API进行批量更新时rest_framework_bulk库内部的bug * [Update] 修改 OpenID Middleware 日志输出模式 info => debug --- apps/assets/serializers/admin_user.py | 3 + apps/assets/serializers/asset.py | 4 +- apps/assets/serializers/cmd_filter.py | 3 + apps/assets/serializers/domain.py | 4 ++ apps/assets/serializers/label.py | 5 +- apps/assets/serializers/system_user.py | 3 + .../backends/openid/middleware.py | 7 +- apps/common/mixins.py | 58 +++++++++++++++++ apps/common/serializers.py | 9 +++ apps/orgs/serializers.py | 8 +-- apps/terminal/serializers/v1.py | 6 +- apps/users/api/user.py | 64 +++++++++++++++++++ apps/users/serializers/v1.py | 6 +- apps/users/templates/users/user_detail.html | 4 +- apps/users/templates/users/user_list.html | 10 ++- apps/users/views/user.py | 9 +++ 16 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 apps/common/serializers.py diff --git a/apps/assets/serializers/admin_user.py b/apps/assets/serializers/admin_user.py index 009caa1ce..e44679995 100644 --- a/apps/assets/serializers/admin_user.py +++ b/apps/assets/serializers/admin_user.py @@ -3,6 +3,8 @@ from django.core.cache import cache from rest_framework import serializers +from common.serializers import AdaptedBulkListSerializer + from ..models import Node, AdminUser from ..const import ADMIN_USER_CONN_CACHE_KEY @@ -18,6 +20,7 @@ class AdminUserSerializer(serializers.ModelSerializer): reachable_amount = serializers.SerializerMethodField() class Meta: + list_serializer_class = AdaptedBulkListSerializer model = AdminUser fields = '__all__' diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index 9640aff7f..c0f435adc 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer from ..models import Asset from .system_user import AssetSystemUserSerializer @@ -19,7 +19,7 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): """ class Meta: model = Asset - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' validators = [] diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index e49f89bcf..26040f6aa 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -3,6 +3,7 @@ from rest_framework import serializers from common.fields import ChoiceDisplayField +from common.serializers import AdaptedBulkListSerializer from ..models import CommandFilter, CommandFilterRule, SystemUser @@ -12,6 +13,7 @@ class CommandFilterSerializer(serializers.ModelSerializer): class Meta: model = CommandFilter + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' @@ -21,3 +23,4 @@ class CommandFilterRuleSerializer(serializers.ModelSerializer): class Meta: model = CommandFilterRule fields = '__all__' + list_serializer_class = AdaptedBulkListSerializer diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 9cddf0c49..553911eb8 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -2,6 +2,8 @@ # from rest_framework import serializers +from common.serializers import AdaptedBulkListSerializer + from ..models import Domain, Gateway @@ -12,6 +14,7 @@ class DomainSerializer(serializers.ModelSerializer): class Meta: model = Domain fields = '__all__' + list_serializer_class = AdaptedBulkListSerializer @staticmethod def get_asset_count(obj): @@ -25,6 +28,7 @@ class DomainSerializer(serializers.ModelSerializer): class GatewaySerializer(serializers.ModelSerializer): class Meta: model = Gateway + list_serializer_class = AdaptedBulkListSerializer fields = [ 'id', 'name', 'ip', 'port', 'protocol', 'username', 'domain', 'is_active', 'date_created', 'date_updated', diff --git a/apps/assets/serializers/label.py b/apps/assets/serializers/label.py index b45e1e508..9fbc9e804 100644 --- a/apps/assets/serializers/label.py +++ b/apps/assets/serializers/label.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from rest_framework_bulk.serializers import BulkListSerializer + +from common.serializers import AdaptedBulkListSerializer from ..models import Label @@ -12,7 +13,7 @@ class LabelSerializer(serializers.ModelSerializer): class Meta: model = Label fields = '__all__' - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer @staticmethod def get_asset_count(obj): diff --git a/apps/assets/serializers/system_user.py b/apps/assets/serializers/system_user.py index b4387d1ff..c737f8cbe 100644 --- a/apps/assets/serializers/system_user.py +++ b/apps/assets/serializers/system_user.py @@ -1,5 +1,7 @@ from rest_framework import serializers +from common.serializers import AdaptedBulkListSerializer + from ..models import SystemUser, Asset from .base import AuthSerializer @@ -17,6 +19,7 @@ class SystemUserSerializer(serializers.ModelSerializer): class Meta: model = SystemUser exclude = ('_password', '_private_key', '_public_key') + list_serializer_class = AdaptedBulkListSerializer def get_field_names(self, declared_fields, info): fields = super(SystemUserSerializer, self).get_field_names(declared_fields, info) diff --git a/apps/authentication/backends/openid/middleware.py b/apps/authentication/backends/openid/middleware.py index 75a752708..b1556ff04 100644 --- a/apps/authentication/backends/openid/middleware.py +++ b/apps/authentication/backends/openid/middleware.py @@ -23,15 +23,15 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): # Don't need openid auth if AUTH_OPENID is False if not settings.AUTH_OPENID: - logger.info("Not settings.AUTH_OPENID") + logger.debug("Not settings.AUTH_OPENID") return # Don't need check single logout if user not authenticated if not request.user.is_authenticated: - logger.info("User is not authenticated") + logger.debug("User is not authenticated") return elif not request.session[BACKEND_SESSION_KEY].endswith( BACKEND_OPENID_AUTH_CODE): - logger.info("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE") + logger.debug("BACKEND_SESSION_KEY is not BACKEND_OPENID_AUTH_CODE") return # Check openid user single logout or not with access_token @@ -40,7 +40,6 @@ class OpenIDAuthenticationMiddleware(MiddlewareMixin): client.openid_connect_client.userinfo( token=request.session.get(OIDT_ACCESS_TOKEN) ) - except Exception as e: logout(request) logger.error(e) diff --git a/apps/common/mixins.py b/apps/common/mixins.py index 0a7d15fef..a5e9a58d3 100644 --- a/apps/common/mixins.py +++ b/apps/common/mixins.py @@ -4,6 +4,10 @@ from django.db import models from django.http import JsonResponse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from rest_framework.utils import html +from rest_framework.settings import api_settings +from rest_framework.exceptions import ValidationError +from rest_framework.fields import SkipField class NoDeleteQuerySet(models.query.QuerySet): @@ -89,6 +93,60 @@ class BulkSerializerMixin(object): return ret +class BulkListSerializerMixin(object): + """ + Become rest_framework_bulk doing bulk update raise Exception: + 'QuerySet' object has no attribute 'pk' when doing bulk update + so rewrite it . + https://github.com/miki725/django-rest-framework-bulk/issues/68 + """ + + def to_internal_value(self, data): + """ + List of dicts of native values <- List of dicts of primitive datatypes. + """ + if html.is_html_input(data): + data = html.parse_html_list(data) + + if not isinstance(data, list): + message = self.error_messages['not_a_list'].format( + input_type=type(data).__name__ + ) + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='not_a_list') + + if not self.allow_empty and len(data) == 0: + if self.parent and self.partial: + raise SkipField() + + message = self.error_messages['empty'] + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='empty') + + ret = [] + errors = [] + + for item in data: + try: + # prepare child serializer to only handle one instance + self.child.instance = self.instance.get(id=item['id']) if self.instance else None + self.child.initial_data = item + # raw + validated = self.child.run_validation(item) + except ValidationError as exc: + errors.append(exc.detail) + else: + ret.append(validated) + errors.append({}) + + if any(errors): + raise ValidationError(errors) + + return ret + + class DatetimeSearchMixin: date_format = '%Y-%m-%d' date_from = date_to = None diff --git a/apps/common/serializers.py b/apps/common/serializers.py new file mode 100644 index 000000000..7f2abce92 --- /dev/null +++ b/apps/common/serializers.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# + +from .mixins import BulkListSerializerMixin +from rest_framework_bulk.serializers import BulkListSerializer + + +class AdaptedBulkListSerializer(BulkListSerializerMixin, BulkListSerializer): + pass diff --git a/apps/orgs/serializers.py b/apps/orgs/serializers.py index db3b56171..1c93cbf91 100644 --- a/apps/orgs/serializers.py +++ b/apps/orgs/serializers.py @@ -1,11 +1,11 @@ from rest_framework.serializers import ModelSerializer from rest_framework import serializers -from rest_framework_bulk import BulkListSerializer from users.models import User, UserGroup from assets.models import Asset, Domain, AdminUser, SystemUser, Label from perms.models import AssetPermission +from common.serializers import AdaptedBulkListSerializer from .utils import set_current_org, get_current_org from .models import Organization from .mixins import OrgMembershipSerializerMixin @@ -14,7 +14,7 @@ from .mixins import OrgMembershipSerializerMixin class OrgSerializer(ModelSerializer): class Meta: model = Organization - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' read_only_fields = ['created_by', 'date_created'] @@ -70,12 +70,12 @@ class OrgReadSerializer(ModelSerializer): class OrgMembershipAdminSerializer(OrgMembershipSerializerMixin, ModelSerializer): class Meta: model = Organization.admins.through - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer): class Meta: model = Organization.users.through - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' diff --git a/apps/terminal/serializers/v1.py b/apps/terminal/serializers/v1.py index adf75c936..27b12b77a 100644 --- a/apps/terminal/serializers/v1.py +++ b/apps/terminal/serializers/v1.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # from rest_framework import serializers -from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer from ..models import Terminal, Status, Session, Task @@ -29,7 +29,7 @@ class SessionSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: model = Session - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' @@ -44,7 +44,7 @@ class TaskSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: fields = '__all__' model = Task - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer class ReplaySerializer(serializers.Serializer): diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 33874deb7..63c3dab12 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -5,6 +5,7 @@ from django.core.cache import cache from django.contrib.auth import logout from django.utils.translation import ugettext as _ +from rest_framework import status from rest_framework import generics from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated @@ -52,9 +53,72 @@ class UserViewSet(IDInFilterMixin, BulkModelViewSet): self.permission_classes = (IsOrgAdminOrAppUser,) return super().get_permissions() + def _deny_permission(self, instance): + """ + check current user has permission to handle instance + (update, destroy, bulk_update, bulk destroy) + """ + return not self.request.user.is_superuser and instance.is_superuser + + def destroy(self, request, *args, **kwargs): + """ + rewrite because limit org_admin destroy superuser + """ + instance = self.get_object() + if self._deny_permission(instance): + data = {'msg': _("You do not have permission.")} + return Response(data=data, status=status.HTTP_403_FORBIDDEN) + + return super().destroy(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + """ + rewrite because limit org_admin update superuser + """ + instance = self.get_object() + if self._deny_permission(instance): + data = {'msg': _("You do not have permission.")} + return Response(data=data, status=status.HTTP_403_FORBIDDEN) + + return super().update(request, *args, **kwargs) + + def _bulk_deny_permission(self, instances): + deny_instances = [i for i in instances if self._deny_permission(i)] + if len(deny_instances) > 0: + return True + else: + return False + def allow_bulk_destroy(self, qs, filtered): + if self._bulk_deny_permission(filtered): + return False return qs.count() != filtered.count() + def bulk_update(self, request, *args, **kwargs): + """ + rewrite because limit org_admin update superuser + """ + partial = kwargs.pop('partial', False) + + # restrict the update to the filtered queryset + queryset = self.filter_queryset(self.get_queryset()) + if self._bulk_deny_permission(queryset): + data = {'msg': _("You do not have permission.")} + return Response(data=data, status=status.HTTP_403_FORBIDDEN) + + serializer = self.get_serializer( + queryset, data=request.data, many=True, partial=partial, + ) + + try: + serializer.is_valid(raise_exception=True) + except Exception as e: + data = {'error': str(e)} + return Response(data=data, status=status.HTTP_400_BAD_REQUEST) + + self.perform_bulk_update(serializer) + return Response(serializer.data, status=status.HTTP_200_OK) + class UserChangePasswordApi(generics.RetrieveUpdateAPIView): permission_classes = (IsOrgAdmin,) diff --git a/apps/users/serializers/v1.py b/apps/users/serializers/v1.py index 160df7c62..b8c91417d 100644 --- a/apps/users/serializers/v1.py +++ b/apps/users/serializers/v1.py @@ -3,10 +3,10 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from rest_framework_bulk import BulkListSerializer from common.utils import get_signer, validate_ssh_public_key from common.mixins import BulkSerializerMixin +from common.serializers import AdaptedBulkListSerializer from ..models import User, UserGroup signer = get_signer() @@ -16,7 +16,7 @@ class UserSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: model = User - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = [ 'id', 'name', 'username', 'email', 'groups', 'groups_display', 'role', 'role_display', 'avatar_url', 'wechat', 'phone', @@ -52,7 +52,7 @@ class UserGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): class Meta: model = UserGroup - list_serializer_class = BulkListSerializer + list_serializer_class = AdaptedBulkListSerializer fields = '__all__' read_only_fields = ['created_by'] diff --git a/apps/users/templates/users/user_detail.html b/apps/users/templates/users/user_detail.html index deb96eb68..351823a9e 100644 --- a/apps/users/templates/users/user_detail.html +++ b/apps/users/templates/users/user_detail.html @@ -22,11 +22,11 @@ {% trans 'Asset granted' %}
  • - {% trans 'Update' %} + {% trans 'Update' %}
  • - + {% trans 'Delete' %}
  • diff --git a/apps/users/templates/users/user_list.html b/apps/users/templates/users/user_list.html index 2ebfa6664..f3e2b7894 100644 --- a/apps/users/templates/users/user_list.html +++ b/apps/users/templates/users/user_list.html @@ -77,10 +77,16 @@ function initTable() { } }}, {targets: 7, createdCell: function (td, cellData, rowData) { - var update_btn = '{% trans "Update" %}'.replace('00000000-0000-0000-0000-000000000000', cellData); + var update_btn = ""; + if (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin')) { + update_btn = '{% trans "Update" %}'; + } + else{ + update_btn = '{% trans "Update" %}'.replace('00000000-0000-0000-0000-000000000000', cellData); + } var del_btn = ""; - if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}") { + if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}" || (rowData.role === 'Admin' && ('{{ request.user.role }}' !== 'Admin'))) { del_btn = '{% trans "Delete" %}' .replace('{{ DEFAULT_PK }}', cellData) .replace('99991938', rowData.name); diff --git a/apps/users/views/user.py b/apps/users/views/user.py index 2ffbd4688..270a9a5c9 100644 --- a/apps/users/views/user.py +++ b/apps/users/views/user.py @@ -107,6 +107,15 @@ class UserUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView): success_url = reverse_lazy('users:user-list') success_message = update_success_msg + def _deny_permission(self): + obj = self.get_object() + return not self.request.user.is_superuser and obj.is_superuser + + def get(self, request, *args, **kwargs): + if self._deny_permission(): + return redirect(self.success_url) + return super().get(request, *args, **kwargs) + def get_context_data(self, **kwargs): check_rules = get_password_check_rules() context = { From 8b98c20d68ea9f83f8b8b7cd5cb44299ace111b3 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Apr 2019 14:31:34 +0800 Subject: [PATCH 10/17] [Update] xss --- apps/assets/templates/assets/admin_user_list.html | 10 +++++----- apps/static/js/jumpserver.js | 13 ++++++++++++- apps/templates/_base_list.html | 2 -- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 25ee56fba..c590beb33 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -44,9 +44,10 @@ $(document).ready(function(){ var options = { ele: $('#admin_user_list_table'), columnDefs: [ - {targets: 1, createdCell: function (td, cellData, rowData) { + {targets: 1, render: function (cellData, tp, rowData, meta) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; - $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); + return detail_btn.replace('{{ DEFAULT_PK }}', rowData.id); }}, {targets: 4, createdCell: function (td, cellData) { var innerHtml = ""; @@ -82,7 +83,6 @@ $(document).ready(function(){ innerHtml = "" + num.toFixed(1) + "% "; } $(td).html('' + innerHtml + ''); - }}, {targets: 8, createdCell: function (td, cellData, rowData) { var update_btn = '{% trans "Update" %}'.replace('{{ DEFAULT_PK }}', cellData); @@ -90,8 +90,8 @@ $(document).ready(function(){ $(td).html(update_btn + del_btn) }}], ajax_url: '{% url "api-assets:admin-user-list" %}', - columns: [{data: function(){return ""}}, {data: "name" }, {data: "username" }, {data: "assets_amount" }, - {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment" }, {data: "id" }] + columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, + {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment", render: $.fn.dataTable.render.text()}, {data: "id" }] }; jumpserver.initServerSideDataTable(options) }) diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 8740a6508..50f78187c 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -538,7 +538,11 @@ jumpserver.initServerSideDataTable = function (options) { $(td).html(''.replace('99991937', cellData)); } }, - {className: 'text-center', targets: '_all'} + { + targets: '_all', + className: 'text-center', + render: $.fn.dataTable.render.text() + } ]; columnDefs = options.columnDefs ? options.columnDefs.concat(columnDefs) : columnDefs; var select = { @@ -945,4 +949,11 @@ function rootNodeAddDom(ztree, callback) { ztree.destroy(); callback() }) +} + + +function htmlEscape ( d ) { + return typeof d === 'string' ? + d.replace(//g, '>').replace(/"/g, '"') : + d; } \ No newline at end of file diff --git a/apps/templates/_base_list.html b/apps/templates/_base_list.html index 191ba8151..c5314af4e 100644 --- a/apps/templates/_base_list.html +++ b/apps/templates/_base_list.html @@ -2,10 +2,8 @@ {% load static %} {% load i18n %} {% block custom_head_css_js %} - - {% endblock %} {% block content %}
    From 4463e7545d774cd486ea751d43bf072e0b1a26c2 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 25 Apr 2019 14:34:47 +0800 Subject: [PATCH 11/17] =?UTF-8?q?[Update]=20=E5=8E=BB=E6=8E=89=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/templates/assets/admin_user_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index c590beb33..9b14a3d9c 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -91,7 +91,7 @@ $(document).ready(function(){ }}], ajax_url: '{% url "api-assets:admin-user-list" %}', columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, - {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment", render: $.fn.dataTable.render.text()}, {data: "id" }] + {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}] }; jumpserver.initServerSideDataTable(options) }) From b7ad6cfe6238829434005629b76bc5c3452bb609 Mon Sep 17 00:00:00 2001 From: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Date: Thu, 25 Apr 2019 18:16:41 +0800 Subject: [PATCH 12/17] =?UTF-8?q?[Update]=20=E9=98=B2=E6=AD=A2=20XSS=20(#2?= =?UTF-8?q?633)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Bugfix] 修改管理用户列表显示bug * [Bugfix] 修复刷新批量命令页面的bug * [Update] 防止 XSS --- apps/assets/templates/assets/admin_user_assets.html | 1 + apps/assets/templates/assets/admin_user_list.html | 2 +- apps/assets/templates/assets/asset_list.html | 1 + apps/assets/templates/assets/cmd_filter_list.html | 1 + apps/assets/templates/assets/domain_list.html | 1 + apps/assets/templates/assets/label_list.html | 1 + apps/assets/templates/assets/system_user_asset.html | 1 + apps/assets/templates/assets/system_user_list.html | 1 + apps/common/const.py | 4 ++-- apps/ops/templates/ops/command_execution_create.html | 4 ++++ apps/perms/templates/perms/asset_permission_list.html | 1 + apps/templates/_message.html | 3 ++- apps/terminal/templates/terminal/terminal_list.html | 1 + apps/users/templates/users/user_granted_asset.html | 4 +++- apps/users/templates/users/user_group_granted_asset.html | 4 +++- apps/users/templates/users/user_group_list.html | 2 ++ apps/users/templates/users/user_list.html | 1 + 17 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/assets/templates/assets/admin_user_assets.html b/apps/assets/templates/assets/admin_user_assets.html index d22c5406f..c893ead80 100644 --- a/apps/assets/templates/assets/admin_user_assets.html +++ b/apps/assets/templates/assets/admin_user_assets.html @@ -98,6 +98,7 @@ function initTable() { order: [], columnDefs: [ {targets: 0, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/assets/templates/assets/admin_user_list.html b/apps/assets/templates/assets/admin_user_list.html index 9b14a3d9c..605e89060 100644 --- a/apps/assets/templates/assets/admin_user_list.html +++ b/apps/assets/templates/assets/admin_user_list.html @@ -91,7 +91,7 @@ $(document).ready(function(){ }}], ajax_url: '{% url "api-assets:admin-user-list" %}', columns: [{data: function(){return ""}}, {data: "name"}, {data: "username" }, {data: "assets_amount" }, - {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}] + {data: "reachable_amount"}, {data: "unreachable_amount"}, {data: "id"}, {data: "comment"}, {data: "id"}] }; jumpserver.initServerSideDataTable(options) }) diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index 8adc81de9..aa27de7a8 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -156,6 +156,7 @@ function initTable() { ele: $('#asset_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); {% url 'assets:asset-detail' pk=DEFAULT_PK as the_url %} var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); diff --git a/apps/assets/templates/assets/cmd_filter_list.html b/apps/assets/templates/assets/cmd_filter_list.html index 3a4feeae0..c7f8e7d3e 100644 --- a/apps/assets/templates/assets/cmd_filter_list.html +++ b/apps/assets/templates/assets/cmd_filter_list.html @@ -40,6 +40,7 @@ function initTable() { ele: $('#cmd_filter_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/assets/templates/assets/domain_list.html b/apps/assets/templates/assets/domain_list.html index a0c6e869e..5cd717535 100644 --- a/apps/assets/templates/assets/domain_list.html +++ b/apps/assets/templates/assets/domain_list.html @@ -41,6 +41,7 @@ function initTable() { ele: $('#domain_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/assets/templates/assets/label_list.html b/apps/assets/templates/assets/label_list.html index d2fa9958a..3cb90788a 100644 --- a/apps/assets/templates/assets/label_list.html +++ b/apps/assets/templates/assets/label_list.html @@ -30,6 +30,7 @@ function initTable() { columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { {# var detail_btn = '' + cellData + '';#} + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/assets/templates/assets/system_user_asset.html b/apps/assets/templates/assets/system_user_asset.html index 4ffdf2a91..082e13fd8 100644 --- a/apps/assets/templates/assets/system_user_asset.html +++ b/apps/assets/templates/assets/system_user_asset.html @@ -144,6 +144,7 @@ function initAssetsTable() { order: [], columnDefs: [ {targets: 0, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/assets/templates/assets/system_user_list.html b/apps/assets/templates/assets/system_user_list.html index 6ed0d0d26..b31039a46 100644 --- a/apps/assets/templates/assets/system_user_list.html +++ b/apps/assets/templates/assets/system_user_list.html @@ -49,6 +49,7 @@ function initTable() { ele: $('#system_user_list_table'), columnDefs: [ {targets: 1, createdCell: function (td, cellData, rowData) { + cellData = htmlEscape(cellData); var detail_btn = '' + cellData + ''; $(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); }}, diff --git a/apps/common/const.py b/apps/common/const.py index 6652593cb..018177d89 100644 --- a/apps/common/const.py +++ b/apps/common/const.py @@ -3,7 +3,7 @@ from django.utils.translation import ugettext_lazy as _ -create_success_msg = _("%(name)s was created successfully") -update_success_msg = _("%(name)s was updated successfully") +create_success_msg = _("%(name)s was created successfully") +update_success_msg = _("%(name)s was updated successfully") FILE_END_GUARD = ">>> Content End <<<" celery_task_pre_key = "CELERY_" diff --git a/apps/ops/templates/ops/command_execution_create.html b/apps/ops/templates/ops/command_execution_create.html index 8352d1607..4aaee0406 100644 --- a/apps/ops/templates/ops/command_execution_create.html +++ b/apps/ops/templates/ops/command_execution_create.html @@ -82,6 +82,7 @@