[Feature] 添加功能,密码过期间隔时间配置,检测用户密码过期 (#2043)

* [Update] user model 添加date_password_last_updated字段, 并在用户详情/个人信息页进行展示、重置密码/修改密码时进行更新;安全设置添加密码过期时间配置项;

* [Update] 修改依赖,deb_requirements 删除 gcc automake

* [Update] 添加定时任务: 检测用户密码是否过期

* [Update] 登录页面添加检测用户密码是否过期,并给出过期提示,拒绝登录

* [update] 用户密码过期时间5天以内,每天发送重置密码邮件

* [Update] api 登录认证,添加密码过期检测

* [Update] 添加提示用户密码即将过期信息

* [Update] 修改小细节

* [Update] User model 添加密码即将过期/已过期 property属性

* [Update] 修改用户api auth检测用户密码过期逻辑

* [Update] 添加翻译,用户密码过期

* [Update] 用户密码即将过期,发送密码过期提醒邮件

* [Update] 修改检测用户密码过期任务,修改interval为crontab

* [Update] 修改翻译小细节

* [Update] 修改翻译小细节

* [Bugfix] 修复在用户更新页面修改密码时, 不更新最后密码修改时间的bug

* [Update] 修复小细节

* [Update] 修改系统设置成功提示翻译信息
pull/2081/head
BaiJiangJie 2018-11-22 18:02:12 +08:00 committed by 老广
parent 16cc4a0f4e
commit 363985ee7a
20 changed files with 467 additions and 172 deletions

View File

@ -171,10 +171,11 @@ class SecuritySettingForm(BaseForm):
initial=30, min_value=5, initial=30, min_value=5,
label=_("No logon interval"), label=_("No logon interval"),
help_text=_( help_text=_(
"Tip :(unit/minute) if the user has failed to log in for a limited " "Tip: (unit/minute) if the user has failed to log in for a limited "
"number of times, no login is allowed during this time interval." "number of times, no login is allowed during this time interval."
) )
) )
# ssh max idle time
SECURITY_MAX_IDLE_TIME = forms.IntegerField( SECURITY_MAX_IDLE_TIME = forms.IntegerField(
initial=30, required=False, initial=30, required=False,
label=_("Connection max idle time"), label=_("Connection max idle time"),
@ -183,6 +184,18 @@ class SecuritySettingForm(BaseForm):
'Unit: minute' 'Unit: minute'
), ),
) )
# password expiration time
SECURITY_PASSWORD_EXPIRATION_TIME = forms.IntegerField(
initial=9999, label=_("Password expiration time"),
min_value=1,
help_text=_(
"Tip: (unit/day) "
"If the user does not update the password during the time, "
"the user password will expire failure;"
"The password expiration reminder mail will be automatic sent to the user "
"by system within 5 days (daily) before the password expires"
)
)
# min length # min length
SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField( SECURITY_PASSWORD_MIN_LENGTH = forms.IntegerField(
initial=6, label=_("Password minimum length"), initial=6, label=_("Password minimum length"),

View File

@ -81,7 +81,9 @@ class Setting(models.Model):
@classmethod @classmethod
def delete_storage(cls, name, storage_name): def delete_storage(cls, name, storage_name):
obj = cls.objects.get(name=name) obj = cls.objects.filter(name=name).first()
if not obj:
return False
value = obj.cleaned_value value = obj.cleaned_value
value.pop(storage_name, '') value.pop(storage_name, '')
obj.cleaned_value = value obj.cleaned_value = value

View File

@ -151,7 +151,7 @@ function deleteStorage($this, the_url){
toastr.success("{% trans 'Delete succeed' %}"); toastr.success("{% trans 'Delete succeed' %}");
}; };
var error = function(){ var error = function(){
toastr.error("{% trans 'Delete failed' %}}"); toastr.error("{% trans 'Delete failed' %}");
}; };
ajaxAPI(the_url, JSON.stringify(data), success, error, method); ajaxAPI(the_url, JSON.stringify(data), success, error, method);
} }

View File

@ -26,7 +26,7 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
msg = _("Update setting successfully, please restart program") msg = _("Update setting successfully")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:basic-setting') return redirect('settings:basic-setting')
else: else:
@ -78,7 +78,7 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
msg = _("Update setting successfully, please restart program") msg = _("Update setting successfully")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:ldap-setting') return redirect('settings:ldap-setting')
else: else:
@ -109,7 +109,7 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
msg = _("Update setting successfully, please restart program") msg = _("Update setting successfully")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:terminal-setting') return redirect('settings:terminal-setting')
else: else:
@ -159,7 +159,7 @@ class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() form.save()
msg = _("Update setting successfully, please restart program") msg = _("Update setting successfully")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:security-setting') return redirect('settings:security-setting')
else: else:

View File

@ -468,7 +468,8 @@ SECURITY_MFA_AUTH = False
SECURITY_LOGIN_LIMIT_COUNT = 7 SECURITY_LOGIN_LIMIT_COUNT = 7
SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute SECURITY_LOGIN_LIMIT_TIME = 30 # Unit: minute
SECURITY_MAX_IDLE_TIME = 30 # Unit: minute SECURITY_MAX_IDLE_TIME = 30 # Unit: minute
SECURITY_PASSWORD_MIN_LENGTH = 6 SECURITY_PASSWORD_EXPIRATION_TIME = 9999 # Unit: day
SECURITY_PASSWORD_MIN_LENGTH = 6 # Unit: bit
SECURITY_PASSWORD_UPPER_CASE = False SECURITY_PASSWORD_UPPER_CASE = False
SECURITY_PASSWORD_LOWER_CASE = False SECURITY_PASSWORD_LOWER_CASE = False
SECURITY_PASSWORD_NUMBER = False SECURITY_PASSWORD_NUMBER = False

Binary file not shown.

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n" "Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-11-21 19:06+0800\n" "POT-Creation-Date: 2018-11-22 15:16+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n" "Language-Team: Jumpserver team<ibuler@qq.com>\n"
@ -191,9 +191,9 @@ msgstr "名称"
#: assets/templates/assets/system_user_list.html:30 #: assets/templates/assets/system_user_list.html:30
#: audits/templates/audits/login_log_list.html:49 #: audits/templates/audits/login_log_list.html:49
#: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15 #: perms/templates/perms/asset_permission_user.html:55 users/forms.py:15
#: users/forms.py:33 users/models/authentication.py:72 users/models/user.py:50 #: users/forms.py:33 users/models/authentication.py:74 users/models/user.py:50
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: users/templates/users/login.html:62 #: users/templates/users/login.html:64
#: users/templates/users/user_detail.html:67 #: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:24 #: users/templates/users/user_list.html:24
#: users/templates/users/user_profile.html:47 #: users/templates/users/user_profile.html:47
@ -206,7 +206,7 @@ msgstr "密码或密钥密码"
#: assets/forms/user.py:26 assets/models/base.py:24 common/forms.py:105 #: assets/forms/user.py:26 assets/models/base.py:24 common/forms.py:105
#: users/forms.py:17 users/forms.py:35 users/forms.py:47 #: users/forms.py:17 users/forms.py:35 users/forms.py:47
#: users/templates/users/login.html:65 #: users/templates/users/login.html:67
#: users/templates/users/reset_password.html:53 #: users/templates/users/reset_password.html:53
#: users/templates/users/user_create.html:10 #: users/templates/users/user_create.html:10
#: users/templates/users/user_password_authentication.html:18 #: users/templates/users/user_password_authentication.html:18
@ -431,10 +431,10 @@ msgstr "创建日期"
#: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102 #: perms/models.py:86 perms/templates/perms/asset_permission_detail.html:102
#: terminal/models.py:28 terminal/templates/terminal/terminal_detail.html:63 #: terminal/models.py:28 terminal/templates/terminal/terminal_detail.html:63
#: users/models/group.py:15 users/models/user.py:85 #: users/models/group.py:15 users/models/user.py:85
#: users/templates/users/user_detail.html:123 #: users/templates/users/user_detail.html:127
#: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_detail.html:67
#: users/templates/users/user_group_list.html:14 #: users/templates/users/user_group_list.html:14
#: users/templates/users/user_profile.html:130 xpack/plugins/cloud/models.py:45 #: users/templates/users/user_profile.html:134 xpack/plugins/cloud/models.py:45
#: xpack/plugins/cloud/models.py:138 #: xpack/plugins/cloud/models.py:138
#: xpack/plugins/cloud/templates/cloud/account_detail.html:72 #: xpack/plugins/cloud/templates/cloud/account_detail.html:72
#: xpack/plugins/cloud/templates/cloud/account_list.html:15 #: xpack/plugins/cloud/templates/cloud/account_list.html:15
@ -487,7 +487,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:364 #: users/models/user.py:397
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
@ -620,8 +620,8 @@ msgstr "默认资产组"
#: terminal/templates/terminal/command_list.html:32 #: terminal/templates/terminal/command_list.html:32
#: terminal/templates/terminal/command_list.html:72 #: terminal/templates/terminal/command_list.html:72
#: terminal/templates/terminal/session_list.html:33 #: terminal/templates/terminal/session_list.html:33
#: terminal/templates/terminal/session_list.html:71 users/forms.py:312 #: terminal/templates/terminal/session_list.html:71 users/forms.py:310
#: users/models/user.py:32 users/models/user.py:352 #: users/models/user.py:32 users/models/user.py:385
#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_detail.html:78
#: users/templates/users/user_group_list.html:13 users/views/user.py:384 #: users/templates/users/user_group_list.html:13 users/views/user.py:384
#: xpack/plugins/orgs/forms.py:26 #: xpack/plugins/orgs/forms.py:26
@ -862,9 +862,9 @@ msgstr "其它"
#: terminal/templates/terminal/terminal_update.html:47 #: terminal/templates/terminal/terminal_update.html:47
#: users/templates/users/_user.html:46 #: users/templates/users/_user.html:46
#: users/templates/users/user_bulk_update.html:23 #: users/templates/users/user_bulk_update.html:23
#: users/templates/users/user_detail.html:172 #: users/templates/users/user_detail.html:176
#: users/templates/users/user_password_update.html:71 #: users/templates/users/user_password_update.html:71
#: users/templates/users/user_profile.html:198 #: users/templates/users/user_profile.html:202
#: users/templates/users/user_profile_update.html:63 #: users/templates/users/user_profile_update.html:63
#: users/templates/users/user_pubkey_update.html:70 #: users/templates/users/user_pubkey_update.html:70
#: users/templates/users/user_pubkey_update.html:76 #: users/templates/users/user_pubkey_update.html:76
@ -992,9 +992,9 @@ msgstr "测试"
#: users/templates/users/user_group_detail.html:28 #: users/templates/users/user_group_detail.html:28
#: users/templates/users/user_group_list.html:43 #: users/templates/users/user_group_list.html:43
#: users/templates/users/user_list.html:77 #: users/templates/users/user_list.html:77
#: users/templates/users/user_profile.html:151 #: users/templates/users/user_profile.html:155
#: users/templates/users/user_profile.html:181 #: users/templates/users/user_profile.html:185
#: users/templates/users/user_profile.html:190 #: users/templates/users/user_profile.html:194
#: xpack/plugins/cloud/templates/cloud/account_detail.html:25 #: xpack/plugins/cloud/templates/cloud/account_detail.html:25
#: xpack/plugins/cloud/templates/cloud/account_list.html:38 #: xpack/plugins/cloud/templates/cloud/account_list.html:38
#: xpack/plugins/orgs/templates/orgs/org_detail.html:25 #: xpack/plugins/orgs/templates/orgs/org_detail.html:25
@ -1054,14 +1054,14 @@ msgstr "选择节点"
#: assets/templates/assets/system_user_list.html:143 #: assets/templates/assets/system_user_list.html:143
#: common/templates/common/terminal_setting.html:165 templates/_modal.html:22 #: common/templates/common/terminal_setting.html:165 templates/_modal.html:22
#: terminal/templates/terminal/session_detail.html:108 #: terminal/templates/terminal/session_detail.html:108
#: users/templates/users/user_detail.html:382 #: users/templates/users/user_detail.html:386
#: users/templates/users/user_detail.html:408 #: users/templates/users/user_detail.html:412
#: users/templates/users/user_detail.html:431 #: users/templates/users/user_detail.html:435
#: users/templates/users/user_detail.html:476 #: users/templates/users/user_detail.html:480
#: users/templates/users/user_group_create_update.html:32 #: users/templates/users/user_group_create_update.html:32
#: users/templates/users/user_group_list.html:88 #: users/templates/users/user_group_list.html:88
#: users/templates/users/user_list.html:205 #: users/templates/users/user_list.html:205
#: users/templates/users/user_profile.html:232 #: users/templates/users/user_profile.html:236
#: xpack/plugins/cloud/templates/cloud/account_create_update.html:34 #: xpack/plugins/cloud/templates/cloud/account_create_update.html:34
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_create.html:36
#: xpack/plugins/orgs/templates/orgs/org_create_update.html:33 #: xpack/plugins/orgs/templates/orgs/org_create_update.html:33
@ -1128,8 +1128,8 @@ msgstr "创建日期"
#: assets/templates/assets/asset_detail.html:141 #: assets/templates/assets/asset_detail.html:141
#: terminal/templates/terminal/session_detail.html:81 #: terminal/templates/terminal/session_detail.html:81
#: users/templates/users/user_detail.html:134 #: users/templates/users/user_detail.html:138
#: users/templates/users/user_profile.html:142 #: users/templates/users/user_profile.html:146
msgid "Quick modify" msgid "Quick modify"
msgstr "快速修改" msgstr "快速修改"
@ -1142,7 +1142,7 @@ msgstr "快速修改"
#: perms/templates/perms/asset_permission_list.html:59 #: perms/templates/perms/asset_permission_list.html:59
#: terminal/templates/terminal/terminal_list.html:34 #: terminal/templates/terminal/terminal_list.html:34
#: users/templates/users/_select_user_modal.html:18 #: users/templates/users/_select_user_modal.html:18
#: users/templates/users/user_detail.html:140 #: users/templates/users/user_detail.html:144
#: users/templates/users/user_granted_asset.html:46 #: users/templates/users/user_granted_asset.html:46
#: users/templates/users/user_group_granted_asset.html:46 #: users/templates/users/user_group_granted_asset.html:46
#: users/templates/users/user_list.html:28 #: users/templates/users/user_list.html:28
@ -1159,8 +1159,8 @@ msgid "Refresh"
msgstr "刷新" msgstr "刷新"
#: assets/templates/assets/asset_detail.html:304 #: assets/templates/assets/asset_detail.html:304
#: users/templates/users/user_detail.html:301 #: users/templates/users/user_detail.html:305
#: users/templates/users/user_detail.html:328 #: users/templates/users/user_detail.html:332
msgid "Update successfully!" msgid "Update successfully!"
msgstr "更新成功" msgstr "更新成功"
@ -1273,9 +1273,9 @@ msgstr "重命名失败不能更改root节点的名称"
#: assets/templates/assets/asset_list.html:627 #: assets/templates/assets/asset_list.html:627
#: assets/templates/assets/system_user_list.html:137 #: assets/templates/assets/system_user_list.html:137
#: users/templates/users/user_detail.html:376 #: users/templates/users/user_detail.html:380
#: users/templates/users/user_detail.html:402 #: users/templates/users/user_detail.html:406
#: users/templates/users/user_detail.html:470 #: users/templates/users/user_detail.html:474
#: users/templates/users/user_group_list.html:82 #: users/templates/users/user_group_list.html:82
#: users/templates/users/user_list.html:199 #: users/templates/users/user_list.html:199
msgid "Are you sure?" msgid "Are you sure?"
@ -1288,9 +1288,9 @@ msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/asset_list.html:631
#: assets/templates/assets/system_user_list.html:141 #: assets/templates/assets/system_user_list.html:141
#: common/templates/common/terminal_setting.html:163 #: common/templates/common/terminal_setting.html:163
#: users/templates/users/user_detail.html:380 #: users/templates/users/user_detail.html:384
#: users/templates/users/user_detail.html:406 #: users/templates/users/user_detail.html:410
#: users/templates/users/user_detail.html:474 #: users/templates/users/user_detail.html:478
#: users/templates/users/user_group_create_update.html:31 #: users/templates/users/user_group_create_update.html:31
#: users/templates/users/user_group_list.html:86 #: users/templates/users/user_group_list.html:86
#: users/templates/users/user_list.html:203 #: users/templates/users/user_list.html:203
@ -1639,8 +1639,8 @@ msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76 #: audits/models.py:22 audits/templates/audits/ftp_log_list.html:76
#: ops/templates/ops/task_list.html:39 users/models/authentication.py:68 #: ops/templates/ops/task_list.html:39 users/models/authentication.py:70
#: users/templates/users/user_detail.html:452 xpack/plugins/cloud/api.py:61 #: users/templates/users/user_detail.html:456 xpack/plugins/cloud/api.py:61
msgid "Success" msgid "Success"
msgstr "成功" msgstr "成功"
@ -1706,20 +1706,20 @@ msgstr "Agent"
msgid "City" msgid "City"
msgstr "城市" msgstr "城市"
#: audits/templates/audits/login_log_list.html:54 users/forms.py:169 #: audits/templates/audits/login_log_list.html:54 users/forms.py:168
#: users/models/authentication.py:77 users/models/user.py:74 #: users/models/authentication.py:79 users/models/user.py:74
#: users/templates/users/first_login.html:45 #: users/templates/users/first_login.html:45
msgid "MFA" msgid "MFA"
msgstr "MFA" msgstr "MFA"
#: audits/templates/audits/login_log_list.html:55 #: audits/templates/audits/login_log_list.html:55
#: users/models/authentication.py:78 xpack/plugins/cloud/models.py:192 #: users/models/authentication.py:80 xpack/plugins/cloud/models.py:192
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:69
msgid "Reason" msgid "Reason"
msgstr "原因" msgstr "原因"
#: audits/templates/audits/login_log_list.html:56 #: audits/templates/audits/login_log_list.html:56
#: users/models/authentication.py:79 xpack/plugins/cloud/models.py:191 #: users/models/authentication.py:81 xpack/plugins/cloud/models.py:191
#: xpack/plugins/cloud/models.py:208 #: xpack/plugins/cloud/models.py:208
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70
#: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:67
@ -1758,7 +1758,7 @@ msgstr "改密日志"
#: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28 #: audits/views.py:187 templates/_nav.html:10 users/views/group.py:28
#: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76 #: users/views/group.py:44 users/views/group.py:60 users/views/group.py:76
#: users/views/group.py:92 users/views/login.py:332 users/views/user.py:68 #: users/views/group.py:92 users/views/login.py:344 users/views/user.py:68
#: users/views/user.py:83 users/views/user.py:111 users/views/user.py:192 #: users/views/user.py:83 users/views/user.py:111 users/views/user.py:192
#: users/views/user.py:353 users/views/user.py:403 users/views/user.py:437 #: users/views/user.py:353 users/views/user.py:403 users/views/user.py:437
msgid "Users" msgid "Users"
@ -1952,59 +1952,71 @@ msgstr "禁止登录时间间隔"
#: common/forms.py:174 #: common/forms.py:174
msgid "" msgid ""
"Tip :(unit/minute) if the user has failed to log in for a limited number of " "Tip: (unit/minute) if the user has failed to log in for a limited number of "
"times, no login is allowed during this time interval." "times, no login is allowed during this time interval."
msgstr "" msgstr ""
"提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录." "提示: (单位: 分钟) 当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录."
#: common/forms.py:180 #: common/forms.py:181
msgid "Connection max idle time" msgid "Connection max idle time"
msgstr "SSH最大空闲时间" msgstr "SSH最大空闲时间"
#: common/forms.py:182 #: common/forms.py:183
msgid "" msgid ""
"If idle time more than it, disconnect connection(only ssh now) Unit: minute" "If idle time more than it, disconnect connection(only ssh now) Unit: minute"
msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) " msgstr "提示: (单位: 分钟) 如果超过该配置没有操作,连接会被断开(仅ssh) "
#: common/forms.py:188 #: common/forms.py:189
msgid "Password expiration time"
msgstr "密码过期时间"
#: common/forms.py:192
msgid ""
"Tip: (unit/day) If the user does not update the password during the time, "
"the user password will expire failure;The password expiration reminder mail "
"will be automatic sent to the user by system within 5 days (daily) before "
"the password expires"
msgstr ""
#: common/forms.py:201
msgid "Password minimum length" msgid "Password minimum length"
msgstr "密码最小长度 " msgstr "密码最小长度 "
#: common/forms.py:194 #: common/forms.py:207
msgid "Must contain capital letters" msgid "Must contain capital letters"
msgstr "必须包含大写字母" msgstr "必须包含大写字母"
#: common/forms.py:196 #: common/forms.py:209
msgid "" msgid ""
"After opening, the user password changes and resets must contain uppercase " "After opening, the user password changes and resets must contain uppercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含大写字母" msgstr "开启后,用户密码修改、重置必须包含大写字母"
#: common/forms.py:202 #: common/forms.py:215
msgid "Must contain lowercase letters" msgid "Must contain lowercase letters"
msgstr "必须包含小写字母" msgstr "必须包含小写字母"
#: common/forms.py:203 #: common/forms.py:216
msgid "" msgid ""
"After opening, the user password changes and resets must contain lowercase " "After opening, the user password changes and resets must contain lowercase "
"letters" "letters"
msgstr "开启后,用户密码修改、重置必须包含小写字母" msgstr "开启后,用户密码修改、重置必须包含小写字母"
#: common/forms.py:209 #: common/forms.py:222
msgid "Must contain numeric characters" msgid "Must contain numeric characters"
msgstr "必须包含数字字符" msgstr "必须包含数字字符"
#: common/forms.py:210 #: common/forms.py:223
msgid "" msgid ""
"After opening, the user password changes and resets must contain numeric " "After opening, the user password changes and resets must contain numeric "
"characters" "characters"
msgstr "开启后,用户密码修改、重置必须包含数字字符" msgstr "开启后,用户密码修改、重置必须包含数字字符"
#: common/forms.py:216 #: common/forms.py:229
msgid "Must contain special characters" msgid "Must contain special characters"
msgstr "必须包含特殊字符" msgstr "必须包含特殊字符"
#: common/forms.py:217 #: common/forms.py:230
msgid "" msgid ""
"After opening, the user password changes and resets must contain special " "After opening, the user password changes and resets must contain special "
"characters" "characters"
@ -2176,8 +2188,8 @@ msgstr "系统设置"
#: common/views.py:29 common/views.py:55 common/views.py:81 common/views.py:112 #: common/views.py:29 common/views.py:55 common/views.py:81 common/views.py:112
#: common/views.py:162 #: common/views.py:162
msgid "Update setting successfully, please restart program" msgid "Update setting successfully"
msgstr "更新设置成功, 请手动重启程序" msgstr "更新设置成功"
#: common/views.py:127 #: common/views.py:127
msgid "Create replay storage" msgid "Create replay storage"
@ -2445,9 +2457,9 @@ msgstr "组织管理"
#: perms/forms.py:31 perms/models.py:30 perms/models.py:80 #: perms/forms.py:31 perms/models.py:30 perms/models.py:80
#: perms/templates/perms/asset_permission_list.html:55 #: perms/templates/perms/asset_permission_list.html:55
#: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14 #: perms/templates/perms/asset_permission_list.html:145 templates/_nav.html:14
#: users/forms.py:282 users/models/group.py:26 users/models/user.py:58 #: users/forms.py:280 users/models/group.py:26 users/models/user.py:58
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_detail.html:207 #: users/templates/users/user_detail.html:211
#: users/templates/users/user_list.html:26 #: users/templates/users/user_list.html:26
#: xpack/plugins/orgs/templates/orgs/org_list.html:15 #: xpack/plugins/orgs/templates/orgs/org_list.html:15
msgid "User group" msgid "User group"
@ -2464,7 +2476,7 @@ msgstr "资产和节点至少选一个"
#: perms/models.py:36 perms/models.py:83 #: perms/models.py:36 perms/models.py:83
#: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/asset_permission_detail.html:90
#: users/models/user.py:90 users/templates/users/user_detail.html:107 #: users/models/user.py:90 users/templates/users/user_detail.html:107
#: users/templates/users/user_profile.html:112 #: users/templates/users/user_profile.html:116
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
@ -2493,7 +2505,7 @@ msgid "Add node to this permission"
msgstr "添加节点" msgstr "添加节点"
#: perms/templates/perms/asset_permission_asset.html:125 #: perms/templates/perms/asset_permission_asset.html:125
#: users/templates/users/user_detail.html:224 #: users/templates/users/user_detail.html:228
msgid "Join" msgid "Join"
msgstr "加入" msgstr "加入"
@ -2587,7 +2599,7 @@ msgstr "文档"
msgid "Commercial support" msgid "Commercial support"
msgstr "商业支持" msgstr "商业支持"
#: templates/_header_bar.html:89 templates/_nav_user.html:9 users/forms.py:148 #: templates/_header_bar.html:89 templates/_nav_user.html:9 users/forms.py:147
#: users/templates/users/_user.html:39 #: users/templates/users/_user.html:39
#: users/templates/users/first_login.html:39 #: users/templates/users/first_login.html:39
#: users/templates/users/user_password_update.html:40 #: users/templates/users/user_password_update.html:40
@ -2611,7 +2623,7 @@ msgid "Logout"
msgstr "注销登录" msgstr "注销登录"
#: templates/_header_bar.html:101 users/templates/users/login.html:46 #: templates/_header_bar.html:101 users/templates/users/login.html:46
#: users/templates/users/login.html:70 #: users/templates/users/login.html:72
msgid "Login" msgid "Login"
msgstr "登录" msgstr "登录"
@ -2619,7 +2631,41 @@ msgstr "登录"
msgid "Dashboard" msgid "Dashboard"
msgstr "仪表盘" msgstr "仪表盘"
#: templates/_message.html:6 #: templates/_message.html:7
#, python-format
msgid ""
"\n"
" Your password has expired, please click <a href="
"\"%(user_password_update_url)s\"> this link </a> update password.\n"
" "
msgstr ""
"\n"
" 您的密码已经过期,请点击 <a href="
"\"%(user_password_update_url)s\"> 链接 </a> 更新密码\n"
" "
#: templates/_message.html:14
msgid "Your password will at"
msgstr "您的密码将于"
#: templates/_message.html:14
msgid "expired. "
msgstr "过期。"
#: templates/_message.html:15
#, python-format
msgid ""
"\n"
" please click <a href=\"%(user_password_update_url)s\"> this "
"link </a> to update your password.\n"
" "
msgstr ""
"\n"
" 请点击 <a href=\"%(user_password_update_url)s\"> 链接 </a> 更"
"新密码\n"
" "
#: templates/_message.html:27
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -2632,7 +2678,7 @@ msgstr ""
"</a> 补充完整\n" "</a> 补充完整\n"
" " " "
#: templates/_message.html:20 #: templates/_message.html:40
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -3088,15 +3134,19 @@ msgstr "你可以使用ssh客户端工具连接终端"
msgid "Log in frequently and try again later" msgid "Log in frequently and try again later"
msgstr "登录频繁, 稍后重试" msgstr "登录频繁, 稍后重试"
#: users/api/auth.py:82 #: users/api/auth.py:67
msgid "The user {} password has expired, please update."
msgstr "用户 {} 密码已经过期,请更新。"
#: users/api/auth.py:92
msgid "Please carry seed value and conduct MFA secondary certification" msgid "Please carry seed value and conduct MFA secondary certification"
msgstr "请携带seed值, 进行MFA二次认证" msgstr "请携带seed值, 进行MFA二次认证"
#: users/api/auth.py:195 #: users/api/auth.py:205
msgid "Please verify the user name and password first" msgid "Please verify the user name and password first"
msgstr "请先进行用户名和密码验证" msgstr "请先进行用户名和密码验证"
#: users/api/auth.py:207 #: users/api/auth.py:217
msgid "MFA certification failed" msgid "MFA certification failed"
msgstr "MFA认证失败" msgstr "MFA认证失败"
@ -3167,11 +3217,11 @@ msgstr "MFA 验证码"
msgid "Role" msgid "Role"
msgstr "角色" msgstr "角色"
#: users/forms.py:55 users/forms.py:228 #: users/forms.py:55 users/forms.py:226
msgid "ssh public key" msgid "ssh public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:56 users/forms.py:229 #: users/forms.py:56 users/forms.py:227
msgid "ssh-rsa AAAA..." msgid "ssh-rsa AAAA..."
msgstr "" msgstr ""
@ -3179,19 +3229,19 @@ msgstr ""
msgid "Paste user id_rsa.pub here." msgid "Paste user id_rsa.pub here."
msgstr "复制用户公钥到这里" msgstr "复制用户公钥到这里"
#: users/forms.py:76 users/templates/users/user_detail.html:215 #: users/forms.py:76 users/templates/users/user_detail.html:219
msgid "Join user groups" msgid "Join user groups"
msgstr "添加到用户组" msgstr "添加到用户组"
#: users/forms.py:110 users/forms.py:243 #: users/forms.py:110 users/forms.py:241
msgid "Public key should not be the same as your old one." msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同" msgstr "不能和原来的密钥相同"
#: users/forms.py:114 users/forms.py:247 users/serializers.py:49 #: users/forms.py:114 users/forms.py:245 users/serializers.py:49
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "ssh密钥不合法" msgstr "ssh密钥不合法"
#: users/forms.py:154 #: users/forms.py:153
msgid "" msgid ""
"Tip: when enabled, you will enter the MFA binding process the next time you " "Tip: when enabled, you will enter the MFA binding process the next time you "
"log in. you can also directly bind in \"personal information -> quick " "log in. you can also directly bind in \"personal information -> quick "
@ -3200,11 +3250,11 @@ msgstr ""
"提示启用之后您将会在下次登录时进入MFA绑定流程您也可以在个人信息->快速修" "提示启用之后您将会在下次登录时进入MFA绑定流程您也可以在个人信息->快速修"
"改->更改MFA设置中直接绑定!" "改->更改MFA设置中直接绑定!"
#: users/forms.py:164 #: users/forms.py:163
msgid "* Enable MFA authentication to make the account more secure." msgid "* Enable MFA authentication to make the account more secure."
msgstr "* 启用MFA认证使账号更加安全." msgstr "* 启用MFA认证使账号更加安全."
#: users/forms.py:174 #: users/forms.py:173
msgid "" msgid ""
"In order to protect you and your company, please keep your account, password " "In order to protect you and your company, please keep your account, password "
"and key sensitive information properly. (for example: setting complex " "and key sensitive information properly. (for example: setting complex "
@ -3213,41 +3263,41 @@ msgstr ""
"为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:" "为了保护您和公司的安全,请妥善保管您的账户、密码和密钥等重要敏感信息;(如:"
"设置复杂密码启用MFA认证" "设置复杂密码启用MFA认证"
#: users/forms.py:181 users/templates/users/first_login.html:48 #: users/forms.py:180 users/templates/users/first_login.html:48
#: users/templates/users/first_login.html:107 #: users/templates/users/first_login.html:107
#: users/templates/users/first_login.html:130 #: users/templates/users/first_login.html:130
msgid "Finish" msgid "Finish"
msgstr "完成" msgstr "完成"
#: users/forms.py:187 #: users/forms.py:186
msgid "Old password" msgid "Old password"
msgstr "原来密码" msgstr "原来密码"
#: users/forms.py:192 #: users/forms.py:191
msgid "New password" msgid "New password"
msgstr "新密码" msgstr "新密码"
#: users/forms.py:197 #: users/forms.py:196
msgid "Confirm password" msgid "Confirm password"
msgstr "确认密码" msgstr "确认密码"
#: users/forms.py:207 #: users/forms.py:206
msgid "Old password error" msgid "Old password error"
msgstr "原来密码错误" msgstr "原来密码错误"
#: users/forms.py:215 #: users/forms.py:214
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms.py:226 #: users/forms.py:224
msgid "Automatically configure and download the SSH key" msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥" msgstr "自动配置并下载SSH密钥"
#: users/forms.py:230 #: users/forms.py:228
msgid "Paste your id_rsa.pub here." msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里" msgstr "复制你的公钥到这里"
#: users/forms.py:258 users/models/user.py:82 #: users/forms.py:256 users/models/user.py:82
#: users/templates/users/first_login.html:42 #: users/templates/users/first_login.html:42
#: users/templates/users/user_password_update.html:46 #: users/templates/users/user_password_update.html:46
#: users/templates/users/user_profile.html:68 #: users/templates/users/user_profile.html:68
@ -3256,7 +3306,7 @@ msgstr "复制你的公钥到这里"
msgid "Public key" msgid "Public key"
msgstr "ssh公钥" msgstr "ssh公钥"
#: users/forms.py:265 users/forms.py:270 users/forms.py:316 #: users/forms.py:263 users/forms.py:268 users/forms.py:314
#: xpack/plugins/orgs/forms.py:30 #: xpack/plugins/orgs/forms.py:30
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
@ -3269,48 +3319,52 @@ msgstr "ssh密钥"
msgid "Disabled" msgid "Disabled"
msgstr "禁用" msgstr "禁用"
#: users/models/authentication.py:52 users/models/authentication.py:61 #: users/models/authentication.py:52 users/models/authentication.py:62
msgid "-" msgid "-"
msgstr "" msgstr ""
#: users/models/authentication.py:62 #: users/models/authentication.py:63
msgid "Username/password check failed" msgid "Username/password check failed"
msgstr "用户名/密码 校验失败" msgstr "用户名/密码 校验失败"
#: users/models/authentication.py:63 #: users/models/authentication.py:64
msgid "MFA authentication failed" msgid "MFA authentication failed"
msgstr "MFA 认证失败" msgstr "MFA 认证失败"
#: users/models/authentication.py:64 #: users/models/authentication.py:65
msgid "Username does not exist" msgid "Username does not exist"
msgstr "用户名不存在" msgstr "用户名不存在"
#: users/models/authentication.py:69 xpack/plugins/cloud/models.py:184 #: users/models/authentication.py:66
msgid "Password expired"
msgstr "密码过期"
#: users/models/authentication.py:71 xpack/plugins/cloud/models.py:184
#: xpack/plugins/cloud/models.py:198 #: xpack/plugins/cloud/models.py:198
msgid "Failed" msgid "Failed"
msgstr "失败" msgstr "失败"
#: users/models/authentication.py:73 #: users/models/authentication.py:75
msgid "Login type" msgid "Login type"
msgstr "登录方式" msgstr "登录方式"
#: users/models/authentication.py:74 #: users/models/authentication.py:76
msgid "Login ip" msgid "Login ip"
msgstr "登录IP" msgstr "登录IP"
#: users/models/authentication.py:75 #: users/models/authentication.py:77
msgid "Login city" msgid "Login city"
msgstr "登录城市" msgstr "登录城市"
#: users/models/authentication.py:76 #: users/models/authentication.py:78
msgid "User agent" msgid "User agent"
msgstr "Agent" msgstr "Agent"
#: users/models/authentication.py:80 #: users/models/authentication.py:82
msgid "Date login" msgid "Date login"
msgstr "登录日期" msgstr "登录日期"
#: users/models/user.py:31 users/models/user.py:360 #: users/models/user.py:31 users/models/user.py:393
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
@ -3319,13 +3373,13 @@ msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:36 users/templates/users/user_profile.html:92 #: users/models/user.py:36 users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:167
#: users/templates/users/user_profile.html:166 #: users/templates/users/user_profile.html:170
msgid "Disable" msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: users/models/user.py:37 users/templates/users/user_profile.html:90 #: users/models/user.py:37 users/templates/users/user_profile.html:90
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:174
msgid "Enable" msgid "Enable"
msgstr "启用" msgstr "启用"
@ -3352,7 +3406,11 @@ msgstr "微信"
msgid "Source" msgid "Source"
msgstr "用户来源" msgstr "用户来源"
#: users/models/user.py:363 #: users/models/user.py:101
msgid "Date password last updated"
msgstr "最后更新密码日期"
#: users/models/user.py:396
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
@ -3448,7 +3506,7 @@ msgstr "获取更多信息"
#: users/templates/users/forgot_password.html:11 #: users/templates/users/forgot_password.html:11
#: users/templates/users/forgot_password.html:27 #: users/templates/users/forgot_password.html:27
#: users/templates/users/login.html:81 #: users/templates/users/login.html:83
msgid "Forgot password" msgid "Forgot password"
msgstr "忘记密码" msgstr "忘记密码"
@ -3493,15 +3551,19 @@ msgstr ""
msgid "Changes the world, starting with a little bit." msgid "Changes the world, starting with a little bit."
msgstr "改变世界,从一点点开始。" msgstr "改变世界,从一点点开始。"
#: users/templates/users/login.html:55 #: users/templates/users/login.html:54
msgid "The user password has expired"
msgstr "用户密码已过期"
#: users/templates/users/login.html:57
msgid "Captcha invalid" msgid "Captcha invalid"
msgstr "验证码错误" msgstr "验证码错误"
#: users/templates/users/login.html:87 #: users/templates/users/login.html:89
msgid "More login options" msgid "More login options"
msgstr "更多登录方式" msgstr "更多登录方式"
#: users/templates/users/login.html:91 #: users/templates/users/login.html:93
msgid "Keycloak" msgid "Keycloak"
msgstr "" msgstr ""
@ -3565,7 +3627,7 @@ msgid "Always young, always with tears in my eyes. Stay foolish Stay hungry"
msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry" msgstr "永远年轻,永远热泪盈眶 stay foolish stay hungry"
#: users/templates/users/reset_password.html:46 #: users/templates/users/reset_password.html:46
#: users/templates/users/user_detail.html:367 users/utils.py:80 #: users/templates/users/user_detail.html:371 users/utils.py:81
msgid "Reset password" msgid "Reset password"
msgstr "重置密码" msgstr "重置密码"
@ -3656,79 +3718,84 @@ msgstr "强制启用"
msgid "Last login" msgid "Last login"
msgstr "最后登录" msgstr "最后登录"
#: users/templates/users/user_detail.html:154 #: users/templates/users/user_detail.html:123
#: users/templates/users/user_profile.html:112
msgid "Last password updated"
msgstr "最后更新密码"
#: users/templates/users/user_detail.html:158
msgid "Force enabled MFA" msgid "Force enabled MFA"
msgstr "强制启用MFA" msgstr "强制启用MFA"
#: users/templates/users/user_detail.html:169 #: users/templates/users/user_detail.html:173
msgid "Reset MFA" msgid "Reset MFA"
msgstr "重置MFA" msgstr "重置MFA"
#: users/templates/users/user_detail.html:177 #: users/templates/users/user_detail.html:181
msgid "Send reset password mail" msgid "Send reset password mail"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/templates/users/user_detail.html:180 #: users/templates/users/user_detail.html:184
#: users/templates/users/user_detail.html:188 #: users/templates/users/user_detail.html:192
msgid "Send" msgid "Send"
msgstr "发送" msgstr "发送"
#: users/templates/users/user_detail.html:185 #: users/templates/users/user_detail.html:189
msgid "Send reset ssh key mail" msgid "Send reset ssh key mail"
msgstr "发送重置密钥邮件" msgstr "发送重置密钥邮件"
#: users/templates/users/user_detail.html:193 #: users/templates/users/user_detail.html:197
#: users/templates/users/user_detail.html:455 #: users/templates/users/user_detail.html:459
msgid "Unblock user" msgid "Unblock user"
msgstr "解除登录限制" msgstr "解除登录限制"
#: users/templates/users/user_detail.html:196 #: users/templates/users/user_detail.html:200
msgid "Unblock" msgid "Unblock"
msgstr "解除" msgstr "解除"
#: users/templates/users/user_detail.html:310 #: users/templates/users/user_detail.html:314
msgid "Goto profile page enable MFA" msgid "Goto profile page enable MFA"
msgstr "请去个人信息页面启用自己的MFA" msgstr "请去个人信息页面启用自己的MFA"
#: users/templates/users/user_detail.html:366 #: users/templates/users/user_detail.html:370
msgid "An e-mail has been sent to the user`s mailbox." msgid "An e-mail has been sent to the user`s mailbox."
msgstr "已发送邮件到用户邮箱" msgstr "已发送邮件到用户邮箱"
#: users/templates/users/user_detail.html:377 #: users/templates/users/user_detail.html:381
msgid "This will reset the user password and send a reset mail" msgid "This will reset the user password and send a reset mail"
msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱" msgstr "将失效用户当前密码,并发送重设密码邮件到用户邮箱"
#: users/templates/users/user_detail.html:392 #: users/templates/users/user_detail.html:396
msgid "" msgid ""
"The reset-ssh-public-key E-mail has been sent successfully. Please inform " "The reset-ssh-public-key E-mail has been sent successfully. Please inform "
"the user to update his new ssh public key." "the user to update his new ssh public key."
msgstr "重设密钥邮件将会发送到用户邮箱" msgstr "重设密钥邮件将会发送到用户邮箱"
#: users/templates/users/user_detail.html:393 #: users/templates/users/user_detail.html:397
msgid "Reset SSH public key" msgid "Reset SSH public key"
msgstr "重置SSH密钥" msgstr "重置SSH密钥"
#: users/templates/users/user_detail.html:403 #: users/templates/users/user_detail.html:407
msgid "This will reset the user public key and send a reset mail" msgid "This will reset the user public key and send a reset mail"
msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱" msgstr "将会失效用户当前密钥,并发送重置邮件到用户邮箱"
#: users/templates/users/user_detail.html:421 #: users/templates/users/user_detail.html:425
#: users/templates/users/user_profile.html:221 #: users/templates/users/user_profile.html:225
msgid "Successfully updated the SSH public key." msgid "Successfully updated the SSH public key."
msgstr "更新ssh密钥成功" msgstr "更新ssh密钥成功"
#: users/templates/users/user_detail.html:422
#: users/templates/users/user_detail.html:426 #: users/templates/users/user_detail.html:426
#: users/templates/users/user_profile.html:222 #: users/templates/users/user_detail.html:430
#: users/templates/users/user_profile.html:227 #: users/templates/users/user_profile.html:226
#: users/templates/users/user_profile.html:231
msgid "User SSH public key update" msgid "User SSH public key update"
msgstr "ssh密钥" msgstr "ssh密钥"
#: users/templates/users/user_detail.html:471 #: users/templates/users/user_detail.html:475
msgid "After unlocking the user, the user can log in normally." msgid "After unlocking the user, the user can log in normally."
msgstr "解除用户登录限制后,此用户即可正常登录" msgstr "解除用户登录限制后,此用户即可正常登录"
#: users/templates/users/user_detail.html:485 #: users/templates/users/user_detail.html:489
msgid "Reset user MFA success" msgid "Reset user MFA success"
msgstr "重置用户MFA成功" msgstr "重置用户MFA成功"
@ -3826,32 +3893,32 @@ msgstr "安装完成后点击下一步进入绑定页面(如已安装,直接
msgid "Administrator Settings force MFA login" msgid "Administrator Settings force MFA login"
msgstr "管理员设置强制使用MFA登录" msgstr "管理员设置强制使用MFA登录"
#: users/templates/users/user_profile.html:116 users/views/user.py:229 #: users/templates/users/user_profile.html:120 users/views/user.py:229
#: users/views/user.py:283 #: users/views/user.py:283
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
#: users/templates/users/user_profile.html:148 #: users/templates/users/user_profile.html:152
msgid "Update password" msgid "Update password"
msgstr "更改密码" msgstr "更改密码"
#: users/templates/users/user_profile.html:156 #: users/templates/users/user_profile.html:160
msgid "Set MFA" msgid "Set MFA"
msgstr "设置MFA" msgstr "设置MFA"
#: users/templates/users/user_profile.html:178 #: users/templates/users/user_profile.html:182
msgid "Update MFA" msgid "Update MFA"
msgstr "更改MFA" msgstr "更改MFA"
#: users/templates/users/user_profile.html:187 #: users/templates/users/user_profile.html:191
msgid "Update SSH public key" msgid "Update SSH public key"
msgstr "更改SSH密钥" msgstr "更改SSH密钥"
#: users/templates/users/user_profile.html:195 #: users/templates/users/user_profile.html:199
msgid "Reset public key and download" msgid "Reset public key and download"
msgstr "重置并下载SSH密钥" msgstr "重置并下载SSH密钥"
#: users/templates/users/user_profile.html:225 #: users/templates/users/user_profile.html:229
msgid "Failed to update SSH public key." msgid "Failed to update SSH public key."
msgstr "更新密钥失败" msgstr "更新密钥失败"
@ -3881,11 +3948,11 @@ msgstr "新的公钥已设置成功,请下载对应的私钥"
msgid "Update user" msgid "Update user"
msgstr "更新用户" msgstr "更新用户"
#: users/utils.py:41 #: users/utils.py:42
msgid "Create account successfully" msgid "Create account successfully"
msgstr "创建账户成功" msgstr "创建账户成功"
#: users/utils.py:43 #: users/utils.py:44
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -3930,7 +3997,7 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:82 #: users/utils.py:83
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -3974,11 +4041,64 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:113 #: users/utils.py:114
msgid "Security notice"
msgstr "安全通知"
#: users/utils.py:116
#, python-format
msgid ""
"\n"
" Hello %(name)s:\n"
" </br>\n"
" Your password will expire in %(date_password_expired)s,\n"
" </br>\n"
" For your account security, please click on the link below to update your "
"password in time\n"
" </br>\n"
" <a href=\"%(update_password_url)s\">Click here update password</a>\n"
" </br>\n"
" If your password has expired, please click \n"
" <a href=\"%(forget_password_url)s?email=%(email)s\">Password expired</"
"a> \n"
" to apply for a password reset email.\n"
"\n"
" </br>\n"
" ---\n"
"\n"
" </br>\n"
" <a href=\"%(login_url)s\">Login direct</a>\n"
"\n"
" </br>\n"
" "
msgstr ""
"\n"
" 您好 %(name)s:\n"
" </br>\n"
" 您的密码会在 %(date_password_expired)s 过期,\n"
" </br>\n"
" 为了您的账号安全,请点击下面的链接及时更新密码\n"
" </br>\n"
" <a href=\"%(update_password_url)s\">请点击这里更新密码</a>\n"
" </br>\n"
" 如果您的密码已经过期,请点击 \n"
" <a href=\"%(forget_password_url)s?email=%(email)s\">密码过期</a> \n"
" 申请一份重置密码邮件。\n"
"\n"
" </br>\n"
" ---\n"
"\n"
" </br>\n"
" <a href=\"%(login_url)s\">直接登录</a>\n"
"\n"
" </br>\n"
" "
#: users/utils.py:152
msgid "SSH Key Reset" msgid "SSH Key Reset"
msgstr "重置ssh密钥" msgstr "重置ssh密钥"
#: users/utils.py:115 #: users/utils.py:154
#, python-format #, python-format
msgid "" msgid ""
"\n" "\n"
@ -4003,15 +4123,15 @@ msgstr ""
" </br>\n" " </br>\n"
" " " "
#: users/utils.py:148 #: users/utils.py:187
msgid "User not exist" msgid "User not exist"
msgstr "用户不存在" msgstr "用户不存在"
#: users/utils.py:150 #: users/utils.py:189
msgid "Disabled or expired" msgid "Disabled or expired"
msgstr "禁用或失效" msgstr "禁用或失效"
#: users/utils.py:163 #: users/utils.py:202
msgid "Password or SSH public key invalid" msgid "Password or SSH public key invalid"
msgstr "密码或密钥不合法" msgstr "密码或密钥不合法"
@ -4031,52 +4151,52 @@ msgstr "用户组授权资产"
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: users/views/login.py:179 users/views/user.py:524 users/views/user.py:549 #: users/views/login.py:191 users/views/user.py:524 users/views/user.py:549
msgid "MFA code invalid, or ntp sync server time" msgid "MFA code invalid, or ntp sync server time"
msgstr "MFA验证码不正确或者服务器端时间不对" msgstr "MFA验证码不正确或者服务器端时间不对"
#: users/views/login.py:211 #: users/views/login.py:223
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: users/views/login.py:212 #: users/views/login.py:224
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: users/views/login.py:228 #: users/views/login.py:240
msgid "Email address invalid, please input again" msgid "Email address invalid, please input again"
msgstr "邮箱地址错误,重新输入" msgstr "邮箱地址错误,重新输入"
#: users/views/login.py:241 #: users/views/login.py:253
msgid "Send reset password message" msgid "Send reset password message"
msgstr "发送重置密码邮件" msgstr "发送重置密码邮件"
#: users/views/login.py:242 #: users/views/login.py:254
msgid "Send reset password mail success, login your mail box and follow it " msgid "Send reset password mail success, login your mail box and follow it "
msgstr "" msgstr ""
"发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)" "发送重置邮件成功, 请登录邮箱查看, 按照提示操作 (如果没收到,请等待3-5分钟)"
#: users/views/login.py:255 #: users/views/login.py:267
msgid "Reset password success" msgid "Reset password success"
msgstr "重置密码成功" msgstr "重置密码成功"
#: users/views/login.py:256 #: users/views/login.py:268
msgid "Reset password success, return to login page" msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面" msgstr "重置密码成功,返回到登录页面"
#: users/views/login.py:272 users/views/login.py:288 #: users/views/login.py:284 users/views/login.py:300
msgid "Token invalid or expired" msgid "Token invalid or expired"
msgstr "Token错误或失效" msgstr "Token错误或失效"
#: users/views/login.py:284 #: users/views/login.py:296
msgid "Password not same" msgid "Password not same"
msgstr "密码不一致" msgstr "密码不一致"
#: users/views/login.py:294 users/views/user.py:126 users/views/user.py:420 #: users/views/login.py:306 users/views/user.py:126 users/views/user.py:420
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/views/login.py:332 #: users/views/login.py:344
msgid "First login" msgid "First login"
msgstr "首次登陆" msgstr "首次登陆"

View File

@ -1,4 +1,25 @@
{% load i18n %} {% load i18n %}
{% block password_expired_message %}
{% url 'users:user-password-update' as user_password_update_url %}
{% if request.user.password_has_expired %}
<div class="alert alert-danger help-message alert-dismissable">
{% blocktrans %}
Your password has expired, please click <a href="{{ user_password_update_url }}"> this link </a> update password.
{% endblocktrans %}
<button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button>
</div>
{% elif request.user.password_will_expired %}
<div class="alert alert-danger help-message alert-dismissable">
{% trans 'Your password will at' %} {{ request.user.date_password_expired }} {% trans 'expired. ' %}
{% blocktrans %}
please click <a href="{{ user_password_update_url }}"> this link </a> to update your password.
{% endblocktrans %}
<button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button>
</div>
{% endif %}
{% endblock %}
{% block first_login_message %} {% block first_login_message %}
{% if request.user.is_authenticated and request.user.is_first_login %} {% if request.user.is_authenticated and request.user.is_first_login %}
<div class="alert alert-danger help-message alert-dismissable"> <div class="alert alert-danger help-message alert-dismissable">
@ -6,7 +27,6 @@
{% blocktrans %} {% blocktrans %}
Your information was incomplete. Please click <a href="{{ first_login_url }}"> this link </a>to complete your information. Your information was incomplete. Please click <a href="{{ first_login_url }}"> this link </a>to complete your information.
{% endblocktrans %} {% endblocktrans %}
<button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button> <button aria-hidden="true" data-dismiss="alert" class="close" type="button" style="outline: none;">×</button>
</div> </div>
{% endif %} {% endif %}

View File

@ -56,6 +56,19 @@ class UserAuthApi(RootOrgViewMixin, APIView):
increase_login_failed_count(username, ip) increase_login_failed_count(username, ip)
return Response({'msg': msg}, status=401) return Response({'msg': msg}, status=401)
if user.password_has_expired:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
'status': False
}
self.write_login_log(request, data)
msg = _("The user {} password has expired, please update.".format(
user.username))
logger.info(msg)
return Response({'msg': msg}, status=401)
if not user.otp_enabled: if not user.otp_enabled:
data = { data = {
'username': user.username, 'username': user.username,
@ -68,10 +81,7 @@ class UserAuthApi(RootOrgViewMixin, APIView):
clean_failed_count(username, ip) clean_failed_count(username, ip)
token = generate_token(request, user) token = generate_token(request, user)
return Response( return Response(
{ {'token': token, 'user': self.serializer_class(user).data}
'token': token,
'user': self.serializer_class(user).data
}
) )
seed = uuid.uuid4().hex seed = uuid.uuid4().hex

View File

@ -120,8 +120,7 @@ class UserCreateUpdateForm(OrgModelForm):
public_key = self.cleaned_data.get('public_key') public_key = self.cleaned_data.get('public_key')
user = super().save(commit=commit) user = super().save(commit=commit)
if password: if password:
user.set_password(password) user.reset_password(password)
user.save()
if otp_level: if otp_level:
user.otp_level = otp_level user.otp_level = otp_level
user.save() user.save()
@ -217,8 +216,7 @@ class UserPasswordForm(forms.Form):
def save(self): def save(self):
password = self.cleaned_data['new_password'] password = self.cleaned_data['new_password']
self.instance.set_password(password) self.instance.reset_password(new_password=password)
self.instance.save()
return self.instance return self.instance

View File

@ -56,12 +56,14 @@ class LoginLog(models.Model):
REASON_PASSWORD = 1 REASON_PASSWORD = 1
REASON_MFA = 2 REASON_MFA = 2
REASON_NOT_EXIST = 3 REASON_NOT_EXIST = 3
REASON_PASSWORD_EXPIRED = 4
REASON_CHOICE = ( REASON_CHOICE = (
(REASON_NOTHING, _('-')), (REASON_NOTHING, _('-')),
(REASON_PASSWORD, _('Username/password check failed')), (REASON_PASSWORD, _('Username/password check failed')),
(REASON_MFA, _('MFA authentication failed')), (REASON_MFA, _('MFA authentication failed')),
(REASON_NOT_EXIST, _("Username does not exist")), (REASON_NOT_EXIST, _("Username does not exist")),
(REASON_PASSWORD_EXPIRED, _("Password expired")),
) )
STATUS_CHOICE = ( STATUS_CHOICE = (

View File

@ -96,6 +96,10 @@ class User(AbstractUser):
max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES, max_length=30, default=SOURCE_LOCAL, choices=SOURCE_CHOICES,
verbose_name=_('Source') verbose_name=_('Source')
) )
date_password_last_updated = models.DateTimeField(
auto_now_add=True, blank=True, null=True,
verbose_name=_('Date password last updated')
)
def __str__(self): def __str__(self):
return '{0.name}({0.username})'.format(self) return '{0.name}({0.username})'.format(self)
@ -220,6 +224,34 @@ class User(AbstractUser):
def is_staff(self, value): def is_staff(self, value):
pass pass
@property
def is_local(self):
return self.source == self.SOURCE_LOCAL
@property
def date_password_expired(self):
interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
date_expired = self.date_password_last_updated + timezone.timedelta(
days=int(interval))
return date_expired
@property
def password_expired_remain_days(self):
date_remain = self.date_password_expired - timezone.now()
return date_remain.days
@property
def password_has_expired(self):
if self.is_local and self.password_expired_remain_days < 0:
return True
return False
@property
def password_will_expired(self):
if self.is_local and self.password_expired_remain_days < 5:
return True
return False
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.name: if not self.name:
self.name = self.username self.name = self.username
@ -258,7 +290,7 @@ class User(AbstractUser):
return False return False
def check_public_key(self, public_key): def check_public_key(self, public_key):
if self.ssH_public_key == public_key: if self.ssh_public_key == public_key:
return True return True
return False return False
@ -340,6 +372,7 @@ class User(AbstractUser):
def reset_password(self, new_password): def reset_password(self, new_password):
self.set_password(new_password) self.set_password(new_password)
self.date_password_last_updated = timezone.now()
self.save() self.save()
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):

View File

@ -36,4 +36,3 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user: if user:
user.source = user.SOURCE_LDAP user.source = user.SOURCE_LDAP
user.save() user.save()

View File

@ -2,10 +2,46 @@
# #
from celery import shared_task from celery import shared_task
from .utils import write_login_log
from ops.celery.utils import (
create_or_update_celery_periodic_tasks,
after_app_ready_start
)
from .models import User
from common.utils import get_logger
from .utils import write_login_log, send_password_expiration_reminder_mail
logger = get_logger(__file__)
@shared_task @shared_task
def write_login_log_async(*args, **kwargs): def write_login_log_async(*args, **kwargs):
write_login_log(*args, **kwargs) write_login_log(*args, **kwargs)
@shared_task
def check_password_expired():
users = User.objects.exclude(role=User.ROLE_APP)
for user in users:
if not user.password_will_expired:
continue
send_password_expiration_reminder_mail(user)
logger.info("The user {} password expires in {} days".format(
user, user.password_expired_remain_days)
)
@shared_task
@after_app_ready_start
def check_password_expired_periodic():
tasks = {
'check_password_expired_periodic': {
'task': check_password_expired.name,
'interval': None,
'crontab': '0 10 * * *',
'enabled': True,
}
}
create_or_update_celery_periodic_tasks(tasks)

View File

@ -50,6 +50,8 @@
{% if block_login %} {% if block_login %}
<p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p> <p class="red-fonts">{% trans 'Log in frequently and try again later' %}</p>
{% elif password_expired %}
<p class="red-fonts">{% trans 'The user password has expired' %}</p>
{% elif form.errors %} {% elif form.errors %}
{% if 'captcha' in form.errors %} {% if 'captcha' in form.errors %}
<p class="red-fonts">{% trans 'Captcha invalid' %}</p> <p class="red-fonts">{% trans 'Captcha invalid' %}</p>

View File

@ -119,6 +119,10 @@
<td>{% trans 'Last login' %}:</td> <td>{% trans 'Last login' %}:</td>
<td><b>{{ user_object.last_login|date:"Y-m-j H:i:s" }}</b></td> <td><b>{{ user_object.last_login|date:"Y-m-j H:i:s" }}</b></td>
</tr> </tr>
<tr>
<td>{% trans 'Last password updated' %}:</td>
<td><b>{{ user_object.date_password_last_updated|date:"Y-m-j H:i:s" }}</b></td>
</tr>
<tr> <tr>
<td>{% trans 'Comment' %}:</td> <td>{% trans 'Comment' %}:</td>
<td><b>{{ user_object.comment }}</b></td> <td><b>{{ user_object.comment }}</b></td>

View File

@ -108,6 +108,10 @@
<td class="text-navy">{% trans 'Last login' %}</td> <td class="text-navy">{% trans 'Last login' %}</td>
<td>{{ user.last_login|date:"Y-m-d H:i:s" }}</td> <td>{{ user.last_login|date:"Y-m-d H:i:s" }}</td>
</tr> </tr>
<tr>
<td class="text-navy">{% trans 'Last password updated' %}</td>
<td>{{ user.date_password_last_updated|date:"Y-m-d H:i:s" }}</td>
</tr>
<tr> <tr>
<td class="text-navy">{% trans 'Date expired' %}</td> <td class="text-navy">{% trans 'Date expired' %}</td>
<td>{{ user.date_expired|date:"Y-m-d H:i:s" }}</td> <td>{{ user.date_expired|date:"Y-m-d H:i:s" }}</td>

View File

@ -16,6 +16,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.cache import cache from django.core.cache import cache
from datetime import datetime
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none
@ -109,6 +110,44 @@ def send_reset_password_mail(user):
send_mail_async.delay(subject, message, recipient_list, html_message=message) send_mail_async.delay(subject, message, recipient_list, html_message=message)
def send_password_expiration_reminder_mail(user):
subject = _('Security notice')
recipient_list = [user.email]
message = _("""
Hello %(name)s:
</br>
Your password will expire in %(date_password_expired)s,
</br>
For your account security, please click on the link below to update your password in time
</br>
<a href="%(update_password_url)s">Click here update password</a>
</br>
If your password has expired, please click
<a href="%(forget_password_url)s?email=%(email)s">Password expired</a>
to apply for a password reset email.
</br>
---
</br>
<a href="%(login_url)s">Login direct</a>
</br>
""") % {
'name': user.name,
'date_password_expired': datetime.fromtimestamp(datetime.timestamp(
user.date_password_expired)).strftime('%Y-%m-%d %H:%M'),
'update_password_url': reverse('users:user-password-update', external=True),
'forget_password_url': reverse('users:forgot-password', external=True),
'email': user.email,
'login_url': reverse('users:login', external=True),
}
if settings.DEBUG:
logger.debug(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
def send_reset_ssh_key_mail(user): def send_reset_ssh_key_mail(user):
subject = _('SSH Key Reset') subject = _('SSH Key Reset')
recipient_list = [user.email] recipient_list = [user.email]

View File

@ -68,7 +68,20 @@ class UserLoginView(FormView):
if not self.request.session.test_cookie_worked(): if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again.")) return HttpResponse(_("Please enable cookies and try again."))
set_tmp_user_to_cache(self.request, form.get_user()) user = form.get_user()
# user password expired
if user.password_has_expired:
data = {
'username': user.username,
'mfa': int(user.otp_enabled),
'reason': LoginLog.REASON_PASSWORD_EXPIRED,
'status': False
}
self.write_login_log(data)
return self.render_to_response(self.get_context_data(password_expired=True))
set_tmp_user_to_cache(self.request, user)
username = form.cleaned_data.get('username') username = form.cleaned_data.get('username')
ip = get_request_ip(self.request) ip = get_request_ip(self.request)
# 登陆成功,清除缓存计数 # 登陆成功,清除缓存计数
@ -86,7 +99,6 @@ class UserLoginView(FormView):
'reason': reason, 'reason': reason,
'status': False 'status': False
} }
self.write_login_log(data) self.write_login_log(data)
# limit user login failed count # limit user login failed count

View File

@ -1 +1 @@
libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite gcc automake libkrb5-dev sshpass libtiff5-dev libjpeg8-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk python-dev openssl libssl-dev libldap2-dev libsasl2-dev sqlite libkrb5-dev sshpass