diff --git a/README.md b/README.md index 05d18ac6e..b8e86a2c7 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向 ## 致谢 -- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备,JumpServer 图形化连接依赖 +- [Apache Guacamole](https://guacamole.apache.org/) Web页面连接 RDP, SSH, VNC协议设备,JumpServer 图形化组件 Lion 依赖 - [OmniDB](https://omnidb.org/) Web页面连接使用数据库,JumpServer Web数据库依赖 diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 27baaa017..8ec151285 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -98,8 +98,8 @@ class SystemUserTaskApi(generics.CreateAPIView): return task @staticmethod - def do_test(system_user): - task = test_system_user_connectivity_manual.delay(system_user) + def do_test(system_user, asset_ids): + task = test_system_user_connectivity_manual.delay(system_user, asset_ids) return task def get_object(self): @@ -109,16 +109,20 @@ class SystemUserTaskApi(generics.CreateAPIView): def perform_create(self, serializer): action = serializer.validated_data["action"] asset = serializer.validated_data.get('asset') - assets = serializer.validated_data.get('assets') or [] + + if asset: + assets = [asset] + else: + assets = serializer.validated_data.get('assets') or [] + + asset_ids = [asset.id for asset in assets] + asset_ids = asset_ids if asset_ids else None system_user = self.get_object() if action == 'push': - assets = [asset] if asset else assets - asset_ids = [asset.id for asset in assets] - asset_ids = asset_ids if asset_ids else None task = self.do_push(system_user, asset_ids) else: - task = self.do_test(system_user) + task = self.do_test(system_user, asset_ids) data = getattr(serializer, '_data', {}) data["task"] = task.id setattr(serializer, '_data', data) diff --git a/apps/assets/serializers/cmd_filter.py b/apps/assets/serializers/cmd_filter.py index 052452825..5cf419979 100644 --- a/apps/assets/serializers/cmd_filter.py +++ b/apps/assets/serializers/cmd_filter.py @@ -4,7 +4,7 @@ import re from rest_framework import serializers from common.drf.serializers import AdaptedBulkListSerializer -from ..models import CommandFilter, CommandFilterRule, SystemUser +from ..models import CommandFilter, CommandFilterRule from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.utils import tmp_to_root_org from common.utils import get_object_or_none, lazyproperty @@ -50,6 +50,20 @@ class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer): fields = '__all__' list_serializer_class = AdaptedBulkListSerializer + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_action_choices() + + def set_action_choices(self): + from django.conf import settings + action = self.fields.get('action') + if not action: + return + choices = action._choices + if not settings.XPACK_ENABLED: + choices.pop(CommandFilterRule.ActionChoices.confirm, None) + action._choices = choices + # def validate_content(self, content): # tp = self.initial_data.get("type") # if tp == CommandFilterRule.TYPE_REGEX: diff --git a/apps/assets/serializers/domain.py b/apps/assets/serializers/domain.py index 5f0a44ec2..0d9c4a6f5 100644 --- a/apps/assets/serializers/domain.py +++ b/apps/assets/serializers/domain.py @@ -61,7 +61,9 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): fields_fk = ['domain'] fields = fields_small + fields_fk extra_kwargs = { - 'password': {'validators': [NoSpecialChars()]} + 'password': {'write_only': True, 'validators': [NoSpecialChars()]}, + 'private_key': {"write_only": True}, + 'public_key': {"write_only": True}, } def __init__(self, *args, **kwargs): diff --git a/apps/assets/tasks/system_user_connectivity.py b/apps/assets/tasks/system_user_connectivity.py index 42b6f2331..87152c8cc 100644 --- a/apps/assets/tasks/system_user_connectivity.py +++ b/apps/assets/tasks/system_user_connectivity.py @@ -5,6 +5,7 @@ from collections import defaultdict from celery import shared_task from django.utils.translation import ugettext as _ +from assets.models import Asset from common.utils import get_logger from orgs.utils import tmp_to_org, org_aware_func from ..models import SystemUser @@ -96,9 +97,12 @@ def test_system_user_connectivity_util(system_user, assets, task_name): @shared_task(queue="ansible") @org_aware_func("system_user") -def test_system_user_connectivity_manual(system_user): +def test_system_user_connectivity_manual(system_user, asset_ids=None): task_name = _("Test system user connectivity: {}").format(system_user) - assets = system_user.get_related_assets() + if asset_ids: + assets = Asset.objects.filter(id__in=asset_ids) + else: + assets = system_user.get_related_assets() test_system_user_connectivity_util(system_user, assets, task_name) diff --git a/apps/authentication/api/dingtalk.py b/apps/authentication/api/dingtalk.py index e4b2ea85b..ce1732118 100644 --- a/apps/authentication/api/dingtalk.py +++ b/apps/authentication/api/dingtalk.py @@ -21,7 +21,7 @@ class DingTalkQRUnBindBase(APIView): if not user.dingtalk_id: raise errors.DingTalkNotBound - user.dingtalk_id = '' + user.dingtalk_id = None user.save() return Response() diff --git a/apps/authentication/api/wecom.py b/apps/authentication/api/wecom.py index 1ab5ff725..c66da5f79 100644 --- a/apps/authentication/api/wecom.py +++ b/apps/authentication/api/wecom.py @@ -21,7 +21,7 @@ class WeComQRUnBindBase(APIView): if not user.wecom_id: raise errors.WeComNotBound - user.wecom_id = '' + user.wecom_id = None user.save() return Response() diff --git a/apps/authentication/backends/api.py b/apps/authentication/backends/api.py index 63356eff6..9da3bbbc3 100644 --- a/apps/authentication/backends/api.py +++ b/apps/authentication/backends/api.py @@ -8,7 +8,7 @@ from django.core.cache import cache from django.utils.translation import ugettext as _ from six import text_type from django.contrib.auth import get_user_model -from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.backends import ModelBackend as DJModelBackend from rest_framework import HTTP_HEADER_ENCODING from rest_framework import authentication, exceptions from common.auth import signature @@ -25,6 +25,11 @@ def get_request_date_header(request): return date +class ModelBackend(DJModelBackend): + def user_can_authenticate(self, user): + return user.is_valid + + class AccessKeyAuthentication(authentication.BaseAuthentication): """App使用Access key进行签名认证, 目前签名算法比较简单, app注册或者手动建立后,会生成 access_key_id 和 access_key_secret, diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 24861b979..6243b80b7 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -2,7 +2,7 @@ import urllib from django.http.response import HttpResponseRedirect from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django.views.decorators.cache import never_cache from django.views.generic import TemplateView from django.views import View diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 981c12508..b7b2a707b 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -2,7 +2,7 @@ import urllib from django.http.response import HttpResponseRedirect from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django.views.decorators.cache import never_cache from django.views.generic import TemplateView from django.views import View diff --git a/apps/jumpserver/api.py b/apps/jumpserver/api.py index 9e9ceeda6..6be2e4407 100644 --- a/apps/jumpserver/api.py +++ b/apps/jumpserver/api.py @@ -189,6 +189,7 @@ class DatesLoginMetricMixin: def get_dates_login_times_top10_users(self): users = self.sessions_queryset.values("user_id") \ .annotate(total=Count("user_id")) \ + .annotate(user=Max('user')) \ .annotate(last=Max("date_start")).order_by("-total")[:10] for user in users: user['last'] = str(user['last']) diff --git a/apps/jumpserver/settings/auth.py b/apps/jumpserver/settings/auth.py index 9fe5886c0..502146f13 100644 --- a/apps/jumpserver/settings/auth.py +++ b/apps/jumpserver/settings/auth.py @@ -120,7 +120,7 @@ LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS -AUTH_BACKEND_MODEL = 'django.contrib.auth.backends.ModelBackend' +AUTH_BACKEND_MODEL = 'authentication.backends.api.ModelBackend' AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend' AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend' AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend' diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 70c15aafc..231c4df8c 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index 0bd344d55..8cf941acb 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -3,19 +3,19 @@ # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-05-17 16:17+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"POT-Creation-Date: 2021-05-20 14:56+0800\n" +"PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.3\n" #: acls/models/base.py:25 acls/serializers/login_asset_acl.py:47 #: applications/models/application.py:11 assets/models/asset.py:142 @@ -25,7 +25,7 @@ msgstr "" #: orgs/models.py:23 perms/models/base.py:49 settings/models.py:29 #: terminal/models/storage.py:23 terminal/models/storage.py:90 #: terminal/models/task.py:16 terminal/models/terminal.py:100 -#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:558 +#: users/forms/profile.py:32 users/models/group.py:15 users/models/user.py:550 #: users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:154 @@ -61,7 +61,7 @@ msgstr "激活中" #: orgs/models.py:26 perms/models/base.py:57 settings/models.py:34 #: terminal/models/storage.py:29 terminal/models/storage.py:96 #: terminal/models/terminal.py:114 tickets/models/ticket.py:73 -#: users/models/group.py:16 users/models/user.py:591 +#: users/models/group.py:16 users/models/user.py:583 #: xpack/plugins/change_auth_plan/models.py:77 xpack/plugins/cloud/models.py:35 #: xpack/plugins/cloud/models.py:98 xpack/plugins/gathered_user/models.py:26 msgid "Comment" @@ -98,8 +98,8 @@ msgstr "动作" #: perms/models/base.py:50 templates/index.html:78 #: terminal/backends/command/models.py:18 #: terminal/backends/command/serializers.py:12 terminal/models/session.py:38 -#: tickets/models/comment.py:17 users/models/user.py:184 -#: users/models/user.py:746 users/models/user.py:772 +#: tickets/models/comment.py:17 users/models/user.py:176 +#: users/models/user.py:738 users/models/user.py:764 #: users/serializers/group.py:20 #: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:64 @@ -180,11 +180,11 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. " #: applications/serializers/attrs/application_type/vmware_client.py:26 #: assets/models/base.py:251 assets/models/gathered_user.py:15 #: audits/models.py:100 authentication/forms.py:15 authentication/forms.py:17 -#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:556 +#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:548 #: users/templates/users/_select_user_modal.html:14 #: xpack/plugins/change_auth_plan/models.py:47 #: xpack/plugins/change_auth_plan/models.py:278 -#: xpack/plugins/cloud/serializers.py:51 +#: xpack/plugins/cloud/serializers.py:71 msgid "Username" msgstr "用户名" @@ -285,7 +285,7 @@ msgid "Cluster" msgstr "集群" #: applications/serializers/attrs/application_category/db.py:11 -#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:49 +#: ops/models/adhoc.py:146 xpack/plugins/cloud/serializers.py:69 msgid "Host" msgstr "主机" @@ -295,7 +295,7 @@ msgstr "主机" #: applications/serializers/attrs/application_type/oracle.py:11 #: applications/serializers/attrs/application_type/pgsql.py:11 #: assets/models/asset.py:188 assets/models/domain.py:53 -#: xpack/plugins/cloud/serializers.py:50 +#: xpack/plugins/cloud/serializers.py:70 msgid "Port" msgstr "端口" @@ -325,7 +325,7 @@ msgstr "目标URL" #: xpack/plugins/change_auth_plan/models.py:68 #: xpack/plugins/change_auth_plan/models.py:190 #: xpack/plugins/change_auth_plan/models.py:285 -#: xpack/plugins/cloud/serializers.py:53 +#: xpack/plugins/cloud/serializers.py:73 msgid "Password" msgstr "密码" @@ -407,7 +407,7 @@ msgstr "激活" #: assets/models/asset.py:196 assets/models/cluster.py:19 #: assets/models/user.py:66 templates/_nav.html:44 -#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:146 +#: xpack/plugins/cloud/models.py:92 xpack/plugins/cloud/serializers.py:166 msgid "Admin user" msgstr "管理用户" @@ -483,7 +483,7 @@ msgstr "标签管理" #: assets/models/cluster.py:28 assets/models/cmd_filter.py:26 #: assets/models/cmd_filter.py:67 assets/models/group.py:21 #: common/db/models.py:70 common/mixins/models.py:49 orgs/models.py:24 -#: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:599 +#: orgs/models.py:422 perms/models/base.py:55 users/models/user.py:591 #: users/serializers/group.py:35 xpack/plugins/change_auth_plan/models.py:81 #: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30 msgid "Created by" @@ -497,7 +497,7 @@ msgstr "创建者" #: assets/models/label.py:25 common/db/models.py:72 common/mixins/models.py:50 #: ops/models/adhoc.py:38 ops/models/command.py:29 orgs/models.py:25 #: orgs/models.py:420 perms/models/base.py:56 users/models/group.py:18 -#: users/models/user.py:773 xpack/plugins/cloud/models.py:107 +#: users/models/user.py:765 xpack/plugins/cloud/models.py:107 msgid "Date created" msgstr "创建日期" @@ -543,7 +543,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:577 +#: assets/models/cluster.py:22 users/models/user.py:569 msgid "Phone" msgstr "手机" @@ -569,7 +569,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:758 +#: users/models/user.py:750 msgid "System" msgstr "系统" @@ -678,7 +678,7 @@ msgstr "ssh私钥" #: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:158 -#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:147 +#: xpack/plugins/cloud/models.py:89 xpack/plugins/cloud/serializers.py:167 msgid "Node" msgstr "节点" @@ -814,11 +814,11 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:80 users/forms/profile.py:160 -#: users/models/user.py:588 users/templates/users/user_password_update.html:48 +#: users/models/user.py:580 users/templates/users/user_password_update.html:48 msgid "Public key" msgstr "SSH公钥" -#: assets/serializers/asset_user.py:84 users/models/user.py:585 +#: assets/serializers/asset_user.py:84 users/models/user.py:577 msgid "Private key" msgstr "ssh私钥" @@ -949,7 +949,7 @@ msgid "" msgstr "自检程序已经在运行,不能重复启动" #: assets/tasks/push_system_user.py:193 -#: assets/tasks/system_user_connectivity.py:89 +#: assets/tasks/system_user_connectivity.py:90 msgid "System user is dynamic: {}" msgstr "系统用户是动态的: {}" @@ -958,7 +958,7 @@ msgid "Start push system user for platform: [{}]" msgstr "推送系统用户到平台: [{}]" #: assets/tasks/push_system_user.py:234 -#: assets/tasks/system_user_connectivity.py:81 +#: assets/tasks/system_user_connectivity.py:82 msgid "Hosts count: {}" msgstr "主机数量: {}" @@ -970,19 +970,19 @@ msgstr "推送系统用户到入资产: {}" msgid "Push system users to asset: {}({}) => {}" msgstr "推送系统用户到入资产: {}({}) => {}" -#: assets/tasks/system_user_connectivity.py:80 +#: assets/tasks/system_user_connectivity.py:81 msgid "Start test system user connectivity for platform: [{}]" msgstr "开始测试系统用户在该系统平台的可连接性: [{}]" -#: assets/tasks/system_user_connectivity.py:100 +#: assets/tasks/system_user_connectivity.py:101 msgid "Test system user connectivity: {}" msgstr "测试系统用户可连接性: {}" -#: assets/tasks/system_user_connectivity.py:108 +#: assets/tasks/system_user_connectivity.py:112 msgid "Test system user connectivity: {} => {}" msgstr "测试系统用户可连接性: {} => {}" -#: assets/tasks/system_user_connectivity.py:121 +#: assets/tasks/system_user_connectivity.py:125 msgid "Test system user connectivity period: {}" msgstr "定期测试系统用户可连接性: {}" @@ -1127,8 +1127,8 @@ msgstr "用户代理" #: audits/models.py:105 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/login_otp.html:6 -#: users/forms/profile.py:64 users/models/user.py:580 -#: users/serializers/profile.py:104 +#: users/forms/profile.py:64 users/models/user.py:572 +#: users/serializers/profile.py:102 msgid "MFA" msgstr "多因子认证" @@ -1218,54 +1218,54 @@ msgstr "钉钉" msgid "Code is invalid" msgstr "Code无效" -#: authentication/backends/api.py:52 +#: authentication/backends/api.py:57 msgid "Invalid signature header. No credentials provided." msgstr "" -#: authentication/backends/api.py:55 +#: authentication/backends/api.py:60 msgid "Invalid signature header. Signature string should not contain spaces." msgstr "" -#: authentication/backends/api.py:62 +#: authentication/backends/api.py:67 msgid "Invalid signature header. Format like AccessKeyId:Signature" msgstr "" -#: authentication/backends/api.py:66 +#: authentication/backends/api.py:71 msgid "" "Invalid signature header. Signature string should not contain invalid " "characters." msgstr "" -#: authentication/backends/api.py:86 authentication/backends/api.py:102 +#: authentication/backends/api.py:91 authentication/backends/api.py:107 msgid "Invalid signature." msgstr "" -#: authentication/backends/api.py:93 +#: authentication/backends/api.py:98 msgid "HTTP header: Date not provide or not %a, %d %b %Y %H:%M:%S GMT" msgstr "" -#: authentication/backends/api.py:98 +#: authentication/backends/api.py:103 msgid "Expired, more than 15 minutes" msgstr "" -#: authentication/backends/api.py:105 +#: authentication/backends/api.py:110 msgid "User disabled." msgstr "用户已禁用" -#: authentication/backends/api.py:123 +#: authentication/backends/api.py:128 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication/backends/api.py:126 +#: authentication/backends/api.py:131 msgid "Invalid token header. Sign string should not contain spaces." msgstr "" -#: authentication/backends/api.py:133 +#: authentication/backends/api.py:138 msgid "" "Invalid token header. Sign string should not contain invalid characters." msgstr "" -#: authentication/backends/api.py:144 +#: authentication/backends/api.py:149 msgid "Invalid token or cache refreshed." msgstr "" @@ -1437,13 +1437,13 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:470 users/serializers/profile.py:101 +#: users/models/user.py:462 users/serializers/profile.py:99 #: users/templates/users/user_verify_mfa.html:32 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:471 users/serializers/profile.py:102 +#: users/models/user.py:463 users/serializers/profile.py:100 msgid "Enable" msgstr "启用" @@ -1547,48 +1547,56 @@ msgstr "返回" msgid "Copy success" msgstr "复制成功" -#: authentication/views/dingtalk.py:41 authentication/views/wecom.py:41 +#: authentication/views/dingtalk.py:41 +msgid "DingTalk Error, Please contact your system administrator" +msgstr "钉钉错误,请联系系统管理员" + +#: authentication/views/dingtalk.py:44 +msgid "DingTalk Error" +msgstr "钉钉错误" + +#: authentication/views/dingtalk.py:56 authentication/views/wecom.py:56 msgid "You've been hacked" msgstr "你被攻击了" -#: authentication/views/dingtalk.py:77 +#: authentication/views/dingtalk.py:92 msgid "DingTalk is already bound" msgstr "钉钉已经绑定" -#: authentication/views/dingtalk.py:90 authentication/views/wecom.py:89 +#: authentication/views/dingtalk.py:105 authentication/views/wecom.py:104 msgid "Please verify your password first" msgstr "请检查密码" -#: authentication/views/dingtalk.py:114 authentication/views/wecom.py:113 +#: authentication/views/dingtalk.py:129 authentication/views/wecom.py:128 msgid "Invalid user_id" msgstr "无效的 user_id" -#: authentication/views/dingtalk.py:130 +#: authentication/views/dingtalk.py:145 msgid "DingTalk query user failed" msgstr "钉钉查询用户失败" -#: authentication/views/dingtalk.py:139 +#: authentication/views/dingtalk.py:154 msgid "The DingTalk is already bound to another user" msgstr "该钉钉已经绑定其他用户" -#: authentication/views/dingtalk.py:144 authentication/views/dingtalk.py:227 -#: authentication/views/dingtalk.py:228 +#: authentication/views/dingtalk.py:159 authentication/views/dingtalk.py:242 +#: authentication/views/dingtalk.py:243 msgid "Binding DingTalk successfully" msgstr "绑定 钉钉 成功" -#: authentication/views/dingtalk.py:196 +#: authentication/views/dingtalk.py:211 msgid "Failed to get user from DingTalk" msgstr "从钉钉获取用户失败" -#: authentication/views/dingtalk.py:202 +#: authentication/views/dingtalk.py:217 msgid "DingTalk is not bound" msgstr "钉钉没有绑定" -#: authentication/views/dingtalk.py:203 authentication/views/wecom.py:201 +#: authentication/views/dingtalk.py:218 authentication/views/wecom.py:216 msgid "Please login with a password and then bind the WeCom" msgstr "请使用密码登录,然后绑定企业微信" -#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:246 +#: authentication/views/dingtalk.py:260 authentication/views/dingtalk.py:261 msgid "Binding DingTalk failed" msgstr "绑定钉钉失败" @@ -1624,32 +1632,40 @@ msgstr "退出登录成功" msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" -#: authentication/views/wecom.py:76 +#: authentication/views/wecom.py:41 +msgid "WeCom Error, Please contact your system administrator" +msgstr "企业微信错误,请联系系统管理员" + +#: authentication/views/wecom.py:44 +msgid "WeCom Error" +msgstr "企业微信错误" + +#: authentication/views/wecom.py:91 msgid "WeCom is already bound" msgstr "企业微信已经绑定" -#: authentication/views/wecom.py:128 +#: authentication/views/wecom.py:143 msgid "WeCom query user failed" msgstr "企业微信查询用户失败" -#: authentication/views/wecom.py:137 +#: authentication/views/wecom.py:152 msgid "The WeCom is already bound to another user" msgstr "该企业微信已经绑定其他用户" -#: authentication/views/wecom.py:142 authentication/views/wecom.py:225 -#: authentication/views/wecom.py:226 +#: authentication/views/wecom.py:157 authentication/views/wecom.py:240 +#: authentication/views/wecom.py:241 msgid "Binding WeCom successfully" msgstr "绑定 企业微信 成功" -#: authentication/views/wecom.py:194 +#: authentication/views/wecom.py:209 msgid "Failed to get user from WeCom" msgstr "从企业微信获取用户失败" -#: authentication/views/wecom.py:200 +#: authentication/views/wecom.py:215 msgid "WeCom is not bound" msgstr "没有绑定企业微信" -#: authentication/views/wecom.py:243 authentication/views/wecom.py:244 +#: authentication/views/wecom.py:258 authentication/views/wecom.py:259 msgid "Binding WeCom failed" msgstr "绑定企业微信失败" @@ -1675,7 +1691,7 @@ msgstr "对象" msgid "The file content overflowed (The maximum length `{}` bytes)" msgstr "文件内容太大 (最大长度 `{}` 字节)" -#: common/drf/parsers/base.py:146 +#: common/drf/parsers/base.py:148 msgid "Parse file error: {}" msgstr "解析文件错误: {}" @@ -1811,36 +1827,36 @@ msgstr "没有该主机 {} 权限" msgid "Operations" msgstr "运维" -#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:160 +#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162 msgid "Cycle perform" msgstr "周期执行" -#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:148 +#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150 msgid "Regularly perform" msgstr "定期执行" -#: ops/mixin.py:106 ops/mixin.py:145 +#: ops/mixin.py:106 ops/mixin.py:147 #: xpack/plugins/change_auth_plan/serializers.py:53 msgid "Periodic perform" msgstr "定时执行" -#: ops/mixin.py:111 +#: ops/mixin.py:112 msgid "Interval" msgstr "间隔" -#: ops/mixin.py:120 +#: ops/mixin.py:122 msgid "* Please enter a valid crontab expression" msgstr "* 请输入有效的 crontab 表达式" -#: ops/mixin.py:127 +#: ops/mixin.py:129 msgid "Range {} to {}" msgstr "输入在 {} - {} 范围之间" -#: ops/mixin.py:138 +#: ops/mixin.py:140 msgid "Require periodic or regularly perform setting" msgstr "需要周期或定期设置" -#: ops/mixin.py:149 +#: ops/mixin.py:151 msgid "" "eg: Every Sunday 03:05 run <5 3 * * 0>
Tips: Using 5 digits linux " "crontab expressions (在线工" "具
注意: 如果同时设置了定期执行和周期执行,优先使用定期执行" -#: ops/mixin.py:160 +#: ops/mixin.py:162 msgid "Tips: (Units: hour)" msgstr "提示:(单位: 时)" @@ -1962,12 +1978,11 @@ msgstr "更新任务内容: {}" msgid "Disk used more than 80%: {} => {}" msgstr "磁盘使用率超过 80%: {} => {}" -#: orgs/api.py:76 -#, python-brace-format -msgid "Have `{model._meta.verbose_name}` exists, Please delete" -msgstr "`{model._meta.verbose_name}` 存在数据, 请先删除" +#: orgs/api.py:79 +msgid "Have {} exists, Please delete" +msgstr "{} 存在数据, 请先删除" -#: orgs/api.py:80 +#: orgs/api.py:83 msgid "The current organization cannot be deleted" msgstr "当前组织不能被删除" @@ -1989,7 +2004,7 @@ msgstr "组织审计员" msgid "GLOBAL" msgstr "全局组织" -#: orgs/models.py:419 users/models/user.py:568 +#: orgs/models.py:419 users/models/user.py:560 #: users/templates/users/_select_user_modal.html:15 msgid "Role" msgstr "角色" @@ -2002,7 +2017,7 @@ msgstr "管理员正在修改授权,请稍等" msgid "The authorization cannot be revoked for the time being" msgstr "该授权暂时不能撤销" -#: perms/models/application_permission.py:27 users/models/user.py:185 +#: perms/models/application_permission.py:27 users/models/user.py:177 msgid "Application" msgstr "应用程序" @@ -2061,7 +2076,7 @@ msgid "Favorite" msgstr "收藏夹" #: perms/models/base.py:51 templates/_nav.html:21 users/models/group.py:31 -#: users/models/user.py:564 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:556 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_database_app_permission.html:38 @@ -2074,7 +2089,7 @@ msgstr "用户组" #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:77 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:43 #: tickets/serializers/ticket/meta/ticket_type/apply_asset.py:81 -#: users/models/user.py:596 +#: users/models/user.py:588 msgid "Date expired" msgstr "失效日期" @@ -2096,15 +2111,15 @@ msgstr "用户名" #: perms/serializers/asset/permission.py:47 msgid "User groups name" -msgstr "用户组数量" +msgstr "用户组名称" #: perms/serializers/asset/permission.py:48 msgid "Assets name" -msgstr "资产名字" +msgstr "资产名称" #: perms/serializers/asset/permission.py:50 msgid "System users name" -msgstr "系统用户名字" +msgstr "系统用户名称" #: perms/serializers/asset/permission.py:70 users/serializers/user.py:81 msgid "Is valid" @@ -2131,13 +2146,9 @@ msgstr "邮件已经发送{}, 请检查" msgid "Welcome to the JumpServer open source Bastion Host" msgstr "欢迎使用JumpServer开源堡垒机" -#: settings/api/dingtalk.py:29 -msgid "AppSecret is required" -msgstr "AppSecret 是必须的" - -#: settings/api/dingtalk.py:35 settings/api/wecom.py:35 -msgid "OK" -msgstr "" +#: settings/api/dingtalk.py:36 settings/api/wecom.py:36 +msgid "Test success" +msgstr "测试成功" #: settings/api/ldap.py:189 msgid "Get ldap users is None" @@ -2147,10 +2158,6 @@ msgstr "获取 LDAP 用户为 None" msgid "Imported {} users successfully" msgstr "导入 {} 个用户成功" -#: settings/api/wecom.py:29 -msgid "Secret is required" -msgstr "Secret 是必须的" - #: settings/models.py:123 users/templates/users/reset_password.html:29 msgid "Setting" msgstr "设置" @@ -2435,16 +2442,13 @@ msgstr "" #: settings/serializers/settings.py:172 msgid "Number of repeated historical passwords" -msgstr "历史密码可重复次数" +msgstr "不能设置近几次密码" #: settings/serializers/settings.py:173 msgid "" "Tip: When the user resets the password, it cannot be the previous n " -"historical passwords of the user (the value of n here is the value filled in " -"the input box)" -msgstr "" -"提示:用户重置密码时,不能为该用户前n次历史密码 (此处的n值即为输入框中填写的" -"值)" +"historical passwords of the user" +msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码" #: settings/serializers/settings.py:177 msgid "Password minimum length" @@ -3040,19 +3044,19 @@ msgstr "登录了" msgid "Filters" msgstr "过滤" -#: terminal/api/session.py:189 +#: terminal/api/session.py:185 msgid "Session does not exist: {}" msgstr "会话不存在: {}" -#: terminal/api/session.py:192 +#: terminal/api/session.py:188 msgid "Session is finished or the protocol not supported" msgstr "会话已经完成或协议不支持" -#: terminal/api/session.py:197 +#: terminal/api/session.py:193 msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: terminal/api/session.py:201 +#: terminal/api/session.py:197 msgid "User does not have permission" msgstr "用户没有权限" @@ -3084,6 +3088,10 @@ msgstr "测试成功" msgid "Test failure: Account invalid" msgstr "测试失败: 账户无效" +#: terminal/api/terminal.py:39 +msgid "Have online sessions" +msgstr "有在线会话" + #: terminal/backends/command/es.py:27 msgid "Invalid elasticsearch config" msgstr "无效的 Elasticsearch 配置" @@ -3820,7 +3828,7 @@ msgstr "确认密码" msgid "Password does not match" msgstr "密码不一致" -#: users/forms/profile.py:101 users/models/user.py:560 +#: users/forms/profile.py:101 users/models/user.py:552 msgid "Email" msgstr "邮件" @@ -3852,48 +3860,48 @@ msgstr "复制你的公钥到这里" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms/profile.py:149 users/serializers/profile.py:76 -#: users/serializers/profile.py:150 users/serializers/profile.py:163 +#: users/forms/profile.py:149 users/serializers/profile.py:74 +#: users/serializers/profile.py:148 users/serializers/profile.py:161 msgid "Not a valid ssh public key" msgstr "SSH密钥不合法" -#: users/models/user.py:182 +#: users/models/user.py:174 msgid "System administrator" msgstr "系统管理员" -#: users/models/user.py:183 +#: users/models/user.py:175 msgid "System auditor" msgstr "系统审计员" -#: users/models/user.py:472 +#: users/models/user.py:464 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:537 +#: users/models/user.py:529 msgid "Local" msgstr "数据库" -#: users/models/user.py:571 +#: users/models/user.py:563 msgid "Avatar" msgstr "头像" -#: users/models/user.py:574 +#: users/models/user.py:566 msgid "Wechat" msgstr "微信" -#: users/models/user.py:604 +#: users/models/user.py:596 msgid "Source" msgstr "用户来源" -#: users/models/user.py:608 +#: users/models/user.py:600 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:754 +#: users/models/user.py:746 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:757 +#: users/models/user.py:749 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" @@ -3909,11 +3917,11 @@ msgstr "密码不满足安全规则" msgid "The new password cannot be the last {} passwords" msgstr "新密码不能是最近 {} 次的密码" -#: users/serializers/profile.py:48 +#: users/serializers/profile.py:46 msgid "The newly set password is inconsistent" msgstr "两次密码不一致" -#: users/serializers/profile.py:121 users/serializers/user.py:80 +#: users/serializers/profile.py:119 users/serializers/user.py:80 msgid "Is first login" msgstr "首次登录" @@ -3991,7 +3999,7 @@ msgid "Security token validation" msgstr "安全令牌验证" #: users/templates/users/_base_otp.html:14 xpack/plugins/cloud/models.py:78 -#: xpack/plugins/cloud/serializers.py:145 +#: xpack/plugins/cloud/serializers.py:165 msgid "Account" msgstr "账户" @@ -4281,7 +4289,7 @@ msgstr "" "
\n" " " -#: users/utils.py:116 users/views/profile/reset.py:126 +#: users/utils.py:116 users/views/profile/reset.py:124 msgid "Reset password success" msgstr "重置密码成功" @@ -4553,7 +4561,7 @@ msgstr "用户认证源来自 {}, 请去相应系统修改密码" msgid "* The new password cannot be the last {} passwords" msgstr "* 新密码不能是最近 {} 次的密码" -#: users/views/profile/reset.py:127 +#: users/views/profile/reset.py:125 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" @@ -4732,7 +4740,7 @@ msgstr "云服务商" msgid "Cloud account" msgstr "云账号" -#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:126 +#: xpack/plugins/cloud/models.py:81 xpack/plugins/cloud/serializers.py:146 msgid "Regions" msgstr "地域" @@ -4740,7 +4748,7 @@ msgstr "地域" msgid "Hostname strategy" msgstr "主机名策略" -#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:149 +#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers.py:169 msgid "Always update" msgstr "总是更新" @@ -4932,20 +4940,24 @@ msgstr "" msgid "Subscription ID" msgstr "" -#: xpack/plugins/cloud/serializers.py:124 +#: xpack/plugins/cloud/serializers.py:55 +msgid "{} is required" +msgstr "{} 字段是必填项" + +#: xpack/plugins/cloud/serializers.py:144 msgid "History count" msgstr "执行次数" -#: xpack/plugins/cloud/serializers.py:125 +#: xpack/plugins/cloud/serializers.py:145 msgid "Instance count" msgstr "实例个数" -#: xpack/plugins/cloud/serializers.py:148 +#: xpack/plugins/cloud/serializers.py:168 #: xpack/plugins/gathered_user/serializers.py:20 msgid "Periodic display" msgstr "定时执行" -#: xpack/plugins/cloud/utils.py:64 +#: xpack/plugins/cloud/utils.py:65 msgid "Account unavailable" msgstr "账户无效" @@ -5033,6 +5045,9 @@ msgstr "旗舰版" msgid "Community edition" msgstr "社区版" +#~ msgid "AppSecret is required" +#~ msgstr "AppSecret 是必须的" + #~ msgid "Corporation ID(corpid)" #~ msgstr "企业 ID(CorpId)" diff --git a/apps/orgs/api.py b/apps/orgs/api.py index b241301a8..ace14112b 100644 --- a/apps/orgs/api.py +++ b/apps/orgs/api.py @@ -60,7 +60,10 @@ class OrgViewSet(BulkModelViewSet): @tmp_to_root_org() def get_data_from_model(self, model): if model == User: - data = model.objects.filter(orgs__id=self.org.id, m2m_org_members__role=ROLE.USER) + data = model.objects.filter( + orgs__id=self.org.id, + m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR] + ) elif model == Node: # 跟节点不能手动删除,所以排除检查 data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$') @@ -73,7 +76,7 @@ class OrgViewSet(BulkModelViewSet): for model in org_related_models: data = self.get_data_from_model(model) if data: - msg = _(f'Have `{model._meta.verbose_name}` exists, Please delete') + msg = _('Have {} exists, Please delete').format(model._meta.verbose_name) return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN) else: if str(current_org) == str(self.org): diff --git a/apps/orgs/caches.py b/apps/orgs/caches.py index 8f73dc06e..8b5a61b51 100644 --- a/apps/orgs/caches.py +++ b/apps/orgs/caches.py @@ -79,7 +79,7 @@ class OrgResourceStatisticsCache(OrgRelatedCache): def compute_users_amount(self): if self.org.is_root(): - users_amount = User.objects.all().count() + users_amount = User.objects.exclude(role='App').count() else: users_amount = OrganizationMember.objects.values( 'user_id' diff --git a/apps/settings/api/dingtalk.py b/apps/settings/api/dingtalk.py index 4e4a73bf7..bbbfc28a8 100644 --- a/apps/settings/api/dingtalk.py +++ b/apps/settings/api/dingtalk.py @@ -25,13 +25,18 @@ class DingTalkTestingAPI(GenericAPIView): if not dingtalk_appsecret: secret = Setting.objects.filter(name='DINGTALK_APPSECRET').first() - if not secret: - return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('AppSecret is required')}) - dingtalk_appsecret = secret.cleaned_value + if secret: + dingtalk_appsecret = secret.cleaned_value + + dingtalk_appsecret = dingtalk_appsecret or '' try: dingtalk = DingTalk(appid=dingtalk_appkey, appsecret=dingtalk_appsecret, agentid=dingtalk_agentid) dingtalk.send_text(['test'], 'test') - return Response(status=status.HTTP_200_OK, data={'msg': _('OK')}) + return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')}) except APIException as e: - return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail}) + try: + error = e.detail['errmsg'] + except: + error = e.detail + return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error}) diff --git a/apps/settings/api/wecom.py b/apps/settings/api/wecom.py index 5059b7647..39d1576b5 100644 --- a/apps/settings/api/wecom.py +++ b/apps/settings/api/wecom.py @@ -25,13 +25,18 @@ class WeComTestingAPI(GenericAPIView): if not wecom_corpsecret: secret = Setting.objects.filter(name='WECOM_SECRET').first() - if not secret: - return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('Secret is required')}) - wecom_corpsecret = secret.cleaned_value + if secret: + wecom_corpsecret = secret.cleaned_value + + wecom_corpsecret = wecom_corpsecret or '' try: wecom = WeCom(corpid=wecom_corpid, corpsecret=wecom_corpsecret, agentid=wecom_agentid) wecom.send_text(['test'], 'test') - return Response(status=status.HTTP_200_OK, data={'msg': _('OK')}) + return Response(status=status.HTTP_200_OK, data={'msg': _('Test success')}) except APIException as e: - return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail}) + try: + error = e.detail['errmsg'] + except: + error = e.detail + return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': error}) diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index 28ddf1cd7..f26679c29 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -170,7 +170,7 @@ class SecuritySettingSerializer(serializers.Serializer): OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( min_value=0, max_value=99999, required=True, label=_('Number of repeated historical passwords'), - help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user (the value of n here is the value filled in the input box)') + help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user') ) SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField( min_value=6, max_value=30, required=True, diff --git a/apps/terminal/api/terminal.py b/apps/terminal/api/terminal.py index 5ee19b3e2..a5ccf8001 100644 --- a/apps/terminal/api/terminal.py +++ b/apps/terminal/api/terminal.py @@ -8,8 +8,9 @@ from rest_framework import generics from rest_framework.views import APIView, Response from rest_framework import status from django.conf import settings +from django.utils.translation import gettext_lazy as _ - +from common.exceptions import JMSException from common.drf.api import JMSBulkModelViewSet from common.utils import get_object_or_none from common.permissions import IsAppUser, IsSuperUser, WithBootstrapToken @@ -30,6 +31,17 @@ class TerminalViewSet(JMSBulkModelViewSet): permission_classes = (IsSuperUser,) filterset_fields = ['name', 'remote_addr', 'type'] + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.get_online_session_count() > 0: + raise JMSException( + code='have_online_session', + detail=_('Have online sessions') + ) + + self.perform_destroy(instance) + return Response(status=status.HTTP_204_NO_CONTENT) + def create(self, request, *args, **kwargs): if isinstance(request.data, list): raise exceptions.BulkCreateNotSupport() diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 715f02b9d..97a9e3d6d 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -7,8 +7,6 @@ import string import random import datetime -from functools import partial - from django.conf import settings from django.contrib.auth.models import AbstractUser from django.contrib.auth.hashers import check_password, make_password @@ -30,7 +28,7 @@ from users.exceptions import MFANotEnabled from ..signals import post_user_change_password -__all__ = ['User'] +__all__ = ['User', 'UserPasswordHistory'] logger = get_logger(__file__) @@ -83,12 +81,6 @@ class AuthMixin: else: return False - def save_history_password(self, password): - UserPasswordHistory.objects.create( - user=self, password=make_password(password), - date_created=self.date_password_last_updated - ) - def is_public_key_valid(self): """ Check if the user's ssh public key is valid. @@ -771,3 +763,9 @@ class UserPasswordHistory(models.Model): user = models.ForeignKey("users.User", related_name='history_passwords', on_delete=models.CASCADE, verbose_name=_('User')) date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created")) + + def __str__(self): + return f'{self.user} set at {self.date_created}' + + def __repr__(self): + return self.__str__() diff --git a/apps/users/serializers/profile.py b/apps/users/serializers/profile.py index 261dd6f01..c1caf1f99 100644 --- a/apps/users/serializers/profile.py +++ b/apps/users/serializers/profile.py @@ -39,8 +39,6 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer): limit_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT msg = _('The new password cannot be the last {} passwords').format(limit_count) raise serializers.ValidationError(msg) - else: - self.instance.save_history_password(value) return value def validate_new_password_again(self, value): diff --git a/apps/users/signals_handler.py b/apps/users/signals_handler.py index 887531f45..9831367fc 100644 --- a/apps/users/signals_handler.py +++ b/apps/users/signals_handler.py @@ -6,17 +6,33 @@ from django_auth_ldap.backend import populate_user from django.conf import settings from django.core.exceptions import PermissionDenied from django_cas_ng.signals import cas_user_authenticated +from django.db.models.signals import post_save from jms_oidc_rp.signals import openid_create_or_update_user from common.utils import get_logger from .signals import post_user_create -from .models import User +from .models import User, UserPasswordHistory logger = get_logger(__file__) +@receiver(post_save, sender=User) +def save_passwd_change(sender, instance: User, **kwargs): + passwds = UserPasswordHistory.objects.filter(user=instance).order_by('-date_created')\ + .values_list('password', flat=True)[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)] + + for p in passwds: + if instance.password == p: + break + else: + UserPasswordHistory.objects.create( + user=instance, password=instance.password, + date_created=instance.date_password_last_updated + ) + + @receiver(post_user_create) def on_user_create(sender, user=None, **kwargs): logger.debug("Receive user `{}` create signal".format(user.name)) diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index 356694020..ba9cfd9b7 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -111,8 +111,6 @@ class UserResetPasswordView(FormView): error = _('* The new password cannot be the last {} passwords').format(limit_count) form.add_error('new_password', error) return self.form_invalid(form) - else: - user.save_history_password(password) user.reset_password(password) User.expired_reset_password_token(token)