mirror of https://github.com/jumpserver/jumpserver
feat: 管理员和普通用户支持单独设置MFA和密码长度 (#6562)
* feat: 支持配置系统管理员强制MFA和独立密码长度限制 * feat: 支持配置系统管理员强制MFA和独立密码长度限制 * feat: 支持配置系统管理员强制MFA和独立密码长度限制, 翻译文件 * fix: 设置界面可设置管理员用户开启MFA,当在设置开启全局的时候,不改变用户的mfa字段状态 * fix: 修改管理员最小密码长度变量名称 * perf: 优化不同的配置 * perf: 修改check password rule * perf: 添加配置文件 * perf: 修改profile * perf: 优化代码 * fix: 修复bug Co-authored-by: fit2cloud-jiangweidong <weidong.jiang@fit2cloud.com> Co-authored-by: ibuler <ibuler@qq.com>pull/6569/head
parent
b56b897260
commit
67f6b1080e
|
@ -333,7 +333,7 @@ class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'data': {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
|
|
|
@ -608,7 +608,7 @@ class Node(OrgModelMixin, SomeNodesMixin, FamilyMixin, NodeAssetsMixin):
|
|||
'isParent': True,
|
||||
'open': self.is_org_root(),
|
||||
'meta': {
|
||||
'node': {
|
||||
'data': {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"value": self.value,
|
||||
|
|
|
@ -16,9 +16,7 @@ import json
|
|||
import yaml
|
||||
from importlib import import_module
|
||||
from django.urls import reverse_lazy
|
||||
from django.templatetags.static import static
|
||||
from urllib.parse import urljoin, urlparse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
|
@ -244,7 +242,7 @@ class Config(dict):
|
|||
'TERMINAL_TELNET_REGEX': '',
|
||||
'TERMINAL_COMMAND_STORAGE': {},
|
||||
|
||||
'SECURITY_MFA_AUTH': False,
|
||||
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
|
||||
'SECURITY_COMMAND_EXECUTION': True,
|
||||
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
|
||||
'SECURITY_VIEW_AUTH_NEED_MFA': True,
|
||||
|
@ -253,6 +251,7 @@ class Config(dict):
|
|||
'SECURITY_MAX_IDLE_TIME': 30,
|
||||
'SECURITY_PASSWORD_EXPIRATION_TIME': 9999,
|
||||
'SECURITY_PASSWORD_MIN_LENGTH': 6,
|
||||
'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': 6,
|
||||
'SECURITY_PASSWORD_UPPER_CASE': False,
|
||||
'SECURITY_PASSWORD_LOWER_CASE': False,
|
||||
'SECURITY_PASSWORD_NUMBER': False,
|
||||
|
|
|
@ -38,6 +38,7 @@ SECURITY_LOGIN_LIMIT_TIME = CONFIG.SECURITY_LOGIN_LIMIT_TIME # Unit: minute
|
|||
SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute
|
||||
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day
|
||||
SECURITY_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_PASSWORD_MIN_LENGTH # Unit: bit
|
||||
SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH = CONFIG.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH # Unit: bit
|
||||
OLD_PASSWORD_HISTORY_LIMIT_COUNT = CONFIG.OLD_PASSWORD_HISTORY_LIMIT_COUNT
|
||||
SECURITY_PASSWORD_UPPER_CASE = CONFIG.SECURITY_PASSWORD_UPPER_CASE
|
||||
SECURITY_PASSWORD_LOWER_CASE = CONFIG.SECURITY_PASSWORD_LOWER_CASE
|
||||
|
|
Binary file not shown.
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-07-27 15:57+0800\n"
|
||||
"POT-Creation-Date: 2021-07-28 18:36+0800\n"
|
||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
|
@ -25,7 +25,7 @@ msgstr ""
|
|||
#: orgs/models.py:24 perms/models/base.py:49 settings/models.py:29
|
||||
#: terminal/models/storage.py:23 terminal/models/task.py:16
|
||||
#: terminal/models/terminal.py:100 users/forms/profile.py:32
|
||||
#: users/models/group.py:15 users/models/user.py:551
|
||||
#: users/models/group.py:15 users/models/user.py:555
|
||||
#: 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:27 perms/models/base.py:57 settings/models.py:34
|
||||
#: terminal/models/storage.py:26 terminal/models/terminal.py:114
|
||||
#: tickets/models/ticket.py:73 users/models/group.py:16
|
||||
#: users/models/user.py:584 xpack/plugins/change_auth_plan/models.py:77
|
||||
#: users/models/user.py:588 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"
|
||||
|
@ -99,7 +99,7 @@ msgstr "动作"
|
|||
#: terminal/backends/command/models.py:18
|
||||
#: terminal/backends/command/serializers.py:12 terminal/models/session.py:38
|
||||
#: tickets/models/comment.py:17 users/models/user.py:176
|
||||
#: users/models/user.py:747 users/models/user.py:773
|
||||
#: users/models/user.py:751 users/models/user.py:777
|
||||
#: users/serializers/group.py:19
|
||||
#: users/templates/users/user_asset_permission.html:38
|
||||
#: users/templates/users/user_asset_permission.html:64
|
||||
|
@ -179,7 +179,7 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
|
|||
#: applications/serializers/attrs/application_type/vmware_client.py:26
|
||||
#: assets/models/base.py:176 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:549
|
||||
#: ops/models/adhoc.py:148 users/forms/profile.py:31 users/models/user.py:553
|
||||
#: users/templates/users/_select_user_modal.html:14
|
||||
#: xpack/plugins/change_auth_plan/models.py:47
|
||||
#: xpack/plugins/change_auth_plan/models.py:278
|
||||
|
@ -515,7 +515,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:25
|
||||
#: orgs/models.py:437 perms/models/base.py:55 users/models/user.py:592
|
||||
#: orgs/models.py:437 perms/models/base.py:55 users/models/user.py:596
|
||||
#: users/serializers/group.py:33 xpack/plugins/change_auth_plan/models.py:81
|
||||
#: xpack/plugins/cloud/models.py:104 xpack/plugins/gathered_user/models.py:30
|
||||
msgid "Created by"
|
||||
|
@ -529,7 +529,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:26
|
||||
#: orgs/models.py:435 perms/models/base.py:56 users/models/group.py:18
|
||||
#: users/models/user.py:774 xpack/plugins/cloud/models.py:107
|
||||
#: users/models/user.py:778 xpack/plugins/cloud/models.py:107
|
||||
msgid "Date created"
|
||||
msgstr "创建日期"
|
||||
|
||||
|
@ -587,7 +587,7 @@ msgstr "带宽"
|
|||
msgid "Contact"
|
||||
msgstr "联系人"
|
||||
|
||||
#: assets/models/cluster.py:22 users/models/user.py:570
|
||||
#: assets/models/cluster.py:22 users/models/user.py:574
|
||||
msgid "Phone"
|
||||
msgstr "手机"
|
||||
|
||||
|
@ -613,7 +613,7 @@ msgid "Default"
|
|||
msgstr "默认"
|
||||
|
||||
#: assets/models/cluster.py:36 assets/models/label.py:14
|
||||
#: users/models/user.py:759
|
||||
#: users/models/user.py:763
|
||||
msgid "System"
|
||||
msgstr "系统"
|
||||
|
||||
|
@ -1151,7 +1151,7 @@ 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:573
|
||||
#: users/forms/profile.py:64 users/models/user.py:577
|
||||
#: users/serializers/profile.py:102
|
||||
msgid "MFA"
|
||||
msgstr "多因子认证"
|
||||
|
@ -1641,7 +1641,8 @@ msgid "Show"
|
|||
msgstr "显示"
|
||||
|
||||
#: authentication/templates/authentication/_access_key_modal.html:66
|
||||
#: users/models/user.py:463 users/serializers/profile.py:99
|
||||
#: settings/serializers/settings.py:148 users/models/user.py:463
|
||||
#: users/serializers/profile.py:99
|
||||
#: users/templates/users/user_verify_mfa.html:32
|
||||
msgid "Disable"
|
||||
msgstr "禁用"
|
||||
|
@ -2020,7 +2021,7 @@ msgstr ""
|
|||
"div>"
|
||||
|
||||
#: notifications/backends/__init__.py:11 users/forms/profile.py:101
|
||||
#: users/models/user.py:553
|
||||
#: users/models/user.py:557
|
||||
msgid "Email"
|
||||
msgstr "邮件"
|
||||
|
||||
|
@ -2213,7 +2214,7 @@ msgstr "组织审计员"
|
|||
msgid "GLOBAL"
|
||||
msgstr "全局组织"
|
||||
|
||||
#: orgs/models.py:434 users/models/user.py:561
|
||||
#: orgs/models.py:434 users/models/user.py:565
|
||||
#: users/templates/users/_select_user_modal.html:15
|
||||
msgid "Role"
|
||||
msgstr "角色"
|
||||
|
@ -2276,7 +2277,7 @@ msgid "Favorite"
|
|||
msgstr "收藏夹"
|
||||
|
||||
#: perms/models/base.py:51 templates/_nav.html:21 users/models/group.py:31
|
||||
#: users/models/user.py:557 users/templates/users/_select_user_modal.html:16
|
||||
#: users/models/user.py:561 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
|
||||
|
@ -2289,7 +2290,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:589
|
||||
#: users/models/user.py:593
|
||||
msgid "Date expired"
|
||||
msgstr "失效日期"
|
||||
|
||||
|
@ -2585,60 +2586,72 @@ msgstr "RDP 地址"
|
|||
msgid "RDP visit address, eg: dev.jumpserver.org:3389"
|
||||
msgstr "RDP 访问地址, 如: dev.jumpserver.org:3389"
|
||||
|
||||
#: settings/serializers/settings.py:147
|
||||
#: settings/serializers/settings.py:149
|
||||
msgid "All users"
|
||||
msgstr "所有用户"
|
||||
|
||||
#: settings/serializers/settings.py:150
|
||||
msgid "Only admin users"
|
||||
msgstr "仅管理员"
|
||||
|
||||
#: settings/serializers/settings.py:152
|
||||
msgid "Global MFA auth"
|
||||
msgstr "全局启用 MFA 认证"
|
||||
|
||||
#: settings/serializers/settings.py:148
|
||||
msgid "All user enable MFA"
|
||||
msgstr "强制所有用户启用多因子认证"
|
||||
#: settings/serializers/settings.py:155
|
||||
msgid "Admin user MFA auth"
|
||||
msgstr "所有管理员启用 MFA"
|
||||
|
||||
#: settings/serializers/settings.py:151
|
||||
#: settings/serializers/settings.py:156
|
||||
msgid "Admin user enable MFA"
|
||||
msgstr "强制管理员启用 MFA"
|
||||
|
||||
#: settings/serializers/settings.py:159
|
||||
msgid "Batch command execution"
|
||||
msgstr "批量命令执行"
|
||||
|
||||
#: settings/serializers/settings.py:152
|
||||
#: settings/serializers/settings.py:160
|
||||
msgid "Allow user run batch command or not using ansible"
|
||||
msgstr "是否允许用户使用 ansible 执行批量命令"
|
||||
|
||||
#: settings/serializers/settings.py:155
|
||||
#: settings/serializers/settings.py:163
|
||||
msgid "Enable terminal register"
|
||||
msgstr "终端注册"
|
||||
|
||||
#: settings/serializers/settings.py:156
|
||||
#: settings/serializers/settings.py:164
|
||||
msgid ""
|
||||
"Allow terminal register, after all terminal setup, you should disable this "
|
||||
"for security"
|
||||
msgstr "是否允许终端注册,当所有终端启动后,为了安全应该关闭"
|
||||
|
||||
#: settings/serializers/settings.py:160
|
||||
#: settings/serializers/settings.py:168
|
||||
msgid "Limit the number of login failures"
|
||||
msgstr "限制登录失败次数"
|
||||
|
||||
#: settings/serializers/settings.py:164
|
||||
#: settings/serializers/settings.py:172
|
||||
msgid "Block logon interval"
|
||||
msgstr "禁止登录时间间隔"
|
||||
|
||||
#: settings/serializers/settings.py:165
|
||||
#: settings/serializers/settings.py:173
|
||||
msgid ""
|
||||
"Tip: (unit/minute) if the user has failed to log in for a limited number of "
|
||||
"times, no login is allowed during this time interval."
|
||||
msgstr ""
|
||||
"提示:(单位:分)当用户登录失败次数达到限制后,那么在此时间间隔内禁止登录"
|
||||
|
||||
#: settings/serializers/settings.py:169
|
||||
#: settings/serializers/settings.py:177
|
||||
msgid "Connection max idle time"
|
||||
msgstr "连接最大空闲时间"
|
||||
|
||||
#: settings/serializers/settings.py:170
|
||||
#: settings/serializers/settings.py:178
|
||||
msgid "If idle time more than it, disconnect connection Unit: minute"
|
||||
msgstr "提示:如果超过该配置没有操作,连接会被断开 (单位:分)"
|
||||
|
||||
#: settings/serializers/settings.py:174
|
||||
#: settings/serializers/settings.py:182
|
||||
msgid "User password expiration"
|
||||
msgstr "用户密码过期时间"
|
||||
|
||||
#: settings/serializers/settings.py:175
|
||||
#: settings/serializers/settings.py:183
|
||||
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 "
|
||||
|
@ -2648,53 +2661,57 @@ msgstr ""
|
|||
"提示:(单位:天)如果用户在此期间没有更新密码,用户密码将过期失效; 密码过期"
|
||||
"提醒邮件将在密码过期前5天内由系统(每天)自动发送给用户"
|
||||
|
||||
#: settings/serializers/settings.py:179
|
||||
#: settings/serializers/settings.py:187
|
||||
msgid "Number of repeated historical passwords"
|
||||
msgstr "不能设置近几次密码"
|
||||
|
||||
#: settings/serializers/settings.py:180
|
||||
#: settings/serializers/settings.py:188
|
||||
msgid ""
|
||||
"Tip: When the user resets the password, it cannot be the previous n "
|
||||
"historical passwords of the user"
|
||||
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
|
||||
|
||||
#: settings/serializers/settings.py:184
|
||||
#: settings/serializers/settings.py:192
|
||||
msgid "Password minimum length"
|
||||
msgstr "密码最小长度"
|
||||
|
||||
#: settings/serializers/settings.py:187
|
||||
#: settings/serializers/settings.py:196
|
||||
msgid "Admin user password minimum length"
|
||||
msgstr "管理员密码最小长度"
|
||||
|
||||
#: settings/serializers/settings.py:199
|
||||
msgid "Must contain capital"
|
||||
msgstr "必须包含大写字符"
|
||||
|
||||
#: settings/serializers/settings.py:189
|
||||
#: settings/serializers/settings.py:201
|
||||
msgid "Must contain lowercase"
|
||||
msgstr "必须包含小写字符"
|
||||
|
||||
#: settings/serializers/settings.py:190
|
||||
#: settings/serializers/settings.py:202
|
||||
msgid "Must contain numeric"
|
||||
msgstr "必须包含数字"
|
||||
|
||||
#: settings/serializers/settings.py:191
|
||||
#: settings/serializers/settings.py:203
|
||||
msgid "Must contain special"
|
||||
msgstr "必须包含特殊字符"
|
||||
|
||||
#: settings/serializers/settings.py:192
|
||||
#: settings/serializers/settings.py:204
|
||||
msgid "Insecure command alert"
|
||||
msgstr "危险命令告警"
|
||||
|
||||
#: settings/serializers/settings.py:194
|
||||
#: settings/serializers/settings.py:206
|
||||
msgid "Email recipient"
|
||||
msgstr "邮件收件人"
|
||||
|
||||
#: settings/serializers/settings.py:195
|
||||
#: settings/serializers/settings.py:207
|
||||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
#: settings/serializers/settings.py:203
|
||||
#: settings/serializers/settings.py:215
|
||||
msgid "Enable WeCom Auth"
|
||||
msgstr "启用企业微信认证"
|
||||
|
||||
#: settings/serializers/settings.py:210
|
||||
#: settings/serializers/settings.py:222
|
||||
msgid "Enable DingTalk Auth"
|
||||
msgstr "启用钉钉认证"
|
||||
|
||||
|
@ -4069,7 +4086,7 @@ msgstr "不能和原来的密钥相同"
|
|||
msgid "Not a valid ssh public key"
|
||||
msgstr "SSH密钥不合法"
|
||||
|
||||
#: users/forms/profile.py:160 users/models/user.py:581
|
||||
#: users/forms/profile.py:160 users/models/user.py:585
|
||||
#: users/templates/users/user_password_update.html:48
|
||||
msgid "Public key"
|
||||
msgstr "SSH公钥"
|
||||
|
@ -4086,39 +4103,39 @@ msgstr "系统审计员"
|
|||
msgid "Force enable"
|
||||
msgstr "强制启用"
|
||||
|
||||
#: users/models/user.py:530
|
||||
#: users/models/user.py:534
|
||||
msgid "Local"
|
||||
msgstr "数据库"
|
||||
|
||||
#: users/models/user.py:564
|
||||
#: users/models/user.py:568
|
||||
msgid "Avatar"
|
||||
msgstr "头像"
|
||||
|
||||
#: users/models/user.py:567
|
||||
#: users/models/user.py:571
|
||||
msgid "Wechat"
|
||||
msgstr "微信"
|
||||
|
||||
#: users/models/user.py:578
|
||||
#: users/models/user.py:582
|
||||
msgid "Private key"
|
||||
msgstr "ssh私钥"
|
||||
|
||||
#: users/models/user.py:597
|
||||
#: users/models/user.py:601
|
||||
msgid "Source"
|
||||
msgstr "来源"
|
||||
|
||||
#: users/models/user.py:601
|
||||
#: users/models/user.py:605
|
||||
msgid "Date password last updated"
|
||||
msgstr "最后更新密码日期"
|
||||
|
||||
#: users/models/user.py:604
|
||||
#: users/models/user.py:608
|
||||
msgid "Need update password"
|
||||
msgstr "需要更新密码"
|
||||
|
||||
#: users/models/user.py:755
|
||||
#: users/models/user.py:759
|
||||
msgid "Administrator"
|
||||
msgstr "管理员"
|
||||
|
||||
#: users/models/user.py:758
|
||||
#: users/models/user.py:762
|
||||
msgid "Administrator is the super user of system"
|
||||
msgstr "Administrator是初始的超级管理员"
|
||||
|
||||
|
@ -4709,24 +4726,24 @@ msgstr ""
|
|||
" <br>\n"
|
||||
" "
|
||||
|
||||
#: users/views/profile/otp.py:107 users/views/profile/otp.py:146
|
||||
#: users/views/profile/otp.py:166
|
||||
#: users/views/profile/otp.py:107 users/views/profile/otp.py:147
|
||||
#: users/views/profile/otp.py:167
|
||||
msgid "MFA code invalid, or ntp sync server time"
|
||||
msgstr "MFA验证码不正确,或者服务器端时间不对"
|
||||
|
||||
#: users/views/profile/otp.py:190
|
||||
#: users/views/profile/otp.py:191
|
||||
msgid "MFA enable success"
|
||||
msgstr "多因子认证启用成功"
|
||||
|
||||
#: users/views/profile/otp.py:191
|
||||
#: users/views/profile/otp.py:192
|
||||
msgid "MFA enable success, return login page"
|
||||
msgstr "多因子认证启用成功,返回到登录页面"
|
||||
|
||||
#: users/views/profile/otp.py:193
|
||||
#: users/views/profile/otp.py:194
|
||||
msgid "MFA disable success"
|
||||
msgstr "多因子认证禁用成功"
|
||||
|
||||
#: users/views/profile/otp.py:194
|
||||
#: users/views/profile/otp.py:195
|
||||
msgid "MFA disable success, return login page"
|
||||
msgstr "多因子认证禁用成功,返回登录页面"
|
||||
|
||||
|
@ -5277,6 +5294,9 @@ msgstr "旗舰版"
|
|||
msgid "Community edition"
|
||||
msgstr "社区版"
|
||||
|
||||
#~ msgid "All user enable MFA"
|
||||
#~ msgstr "强制所有用户启用 MFA"
|
||||
|
||||
#~ msgid "Application category"
|
||||
#~ msgstr "应用类别"
|
||||
|
||||
|
|
|
@ -122,6 +122,7 @@ class PublicSettingApi(generics.RetrieveAPIView):
|
|||
"TICKETS_ENABLED": settings.TICKETS_ENABLED,
|
||||
"PASSWORD_RULE": {
|
||||
'SECURITY_PASSWORD_MIN_LENGTH': settings.SECURITY_PASSWORD_MIN_LENGTH,
|
||||
'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH': settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH,
|
||||
'SECURITY_PASSWORD_UPPER_CASE': settings.SECURITY_PASSWORD_UPPER_CASE,
|
||||
'SECURITY_PASSWORD_LOWER_CASE': settings.SECURITY_PASSWORD_LOWER_CASE,
|
||||
'SECURITY_PASSWORD_NUMBER': settings.SECURITY_PASSWORD_NUMBER,
|
||||
|
@ -160,7 +161,8 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
|||
|
||||
def get_object(self):
|
||||
items = self.get_fields().keys()
|
||||
return {item: getattr(settings, item) for item in items}
|
||||
obj = {item: getattr(settings, item) for item in items}
|
||||
return obj
|
||||
|
||||
def parse_serializer_data(self, serializer):
|
||||
data = []
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 3.1 on 2021-07-29 07:46
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_security_mfa_auth(apps, schema_editor):
|
||||
setting_model = apps.get_model("settings", "Setting")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
mfa_setting = setting_model.objects.using(db_alias).filter(name='SECURITY_MFA_AUTH').first()
|
||||
if not mfa_setting:
|
||||
return
|
||||
|
||||
if mfa_setting.value:
|
||||
mfa_setting.value = 1
|
||||
else:
|
||||
mfa_setting.value = 0
|
||||
mfa_setting.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('settings', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_security_mfa_auth)
|
||||
]
|
|
@ -143,9 +143,13 @@ class TerminalSettingSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class SecuritySettingSerializer(serializers.Serializer):
|
||||
SECURITY_MFA_AUTH = serializers.BooleanField(
|
||||
required=False, label=_("Global MFA auth"),
|
||||
help_text=_('All user enable MFA')
|
||||
SECURITY_MFA_AUTH = serializers.ChoiceField(
|
||||
choices=(
|
||||
[0, _('Disable')],
|
||||
[1, _('All users')],
|
||||
[2, _('Only admin users')],
|
||||
),
|
||||
required=False, label=_("Global MFA auth")
|
||||
)
|
||||
SECURITY_COMMAND_EXECUTION = serializers.BooleanField(
|
||||
required=False, label=_('Batch command execution'),
|
||||
|
@ -183,6 +187,10 @@ class SecuritySettingSerializer(serializers.Serializer):
|
|||
min_value=6, max_value=30, required=True,
|
||||
label=_('Password minimum length')
|
||||
)
|
||||
SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH = serializers.IntegerField(
|
||||
min_value=6, max_value=30, required=True,
|
||||
label=_('Admin user password minimum length')
|
||||
)
|
||||
SECURITY_PASSWORD_UPPER_CASE = serializers.BooleanField(
|
||||
required=False, label=_('Must contain capital')
|
||||
)
|
||||
|
|
|
@ -464,14 +464,19 @@ class MFAMixin:
|
|||
(1, _('Enable')),
|
||||
(2, _("Force enable")),
|
||||
)
|
||||
is_org_admin: bool
|
||||
|
||||
@property
|
||||
def mfa_enabled(self):
|
||||
return self.mfa_force_enabled or self.mfa_level > 0
|
||||
if self.mfa_force_enabled:
|
||||
return True
|
||||
return self.mfa_level > 0
|
||||
|
||||
@property
|
||||
def mfa_force_enabled(self):
|
||||
if settings.SECURITY_MFA_AUTH:
|
||||
if settings.SECURITY_MFA_AUTH in [True, 1]:
|
||||
return True
|
||||
if settings.SECURITY_MFA_AUTH == 2 and self.is_org_admin:
|
||||
return True
|
||||
return self.mfa_level == 2
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer):
|
|||
|
||||
def validate_new_password(self, value):
|
||||
from ..utils import check_password_rules
|
||||
if not check_password_rules(value):
|
||||
if not check_password_rules(value, user=self.instance):
|
||||
msg = _('Password does not match security rules')
|
||||
raise serializers.ValidationError(msg)
|
||||
if self.instance.is_history_password(value):
|
||||
|
@ -106,7 +106,8 @@ class UserProfileSerializer(UserSerializer):
|
|||
fields = UserSerializer.Meta.fields + [
|
||||
'public_key_comment', 'public_key_hash_md5',
|
||||
'admin_or_audit_orgs', 'current_org_roles',
|
||||
'guide_url', 'user_all_orgs'
|
||||
'guide_url', 'user_all_orgs', 'is_org_admin',
|
||||
'is_superuser'
|
||||
]
|
||||
read_only_fields = [
|
||||
'date_joined', 'last_login', 'created_by', 'source'
|
||||
|
|
|
@ -122,7 +122,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
|
|||
if self.instance and not password:
|
||||
# 更新用户, 未设置密码
|
||||
return
|
||||
if not check_password_rules(password):
|
||||
if not check_password_rules(password, user=self.instance):
|
||||
msg = _('Password does not match security rules')
|
||||
raise serializers.ValidationError(msg)
|
||||
return password
|
||||
|
|
|
@ -295,10 +295,12 @@ def check_otp_code(otp_secret_key, otp_code):
|
|||
return totp.verify(otp=otp_code, valid_window=otp_valid_window)
|
||||
|
||||
|
||||
def get_password_check_rules():
|
||||
def get_password_check_rules(user):
|
||||
check_rules = []
|
||||
for rule in settings.SECURITY_PASSWORD_RULES:
|
||||
key = "id_{}".format(rule.lower())
|
||||
if user.is_org_admin and rule == 'SECURITY_PASSWORD_MIN_LENGTH':
|
||||
rule = 'SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH'
|
||||
value = getattr(settings, rule)
|
||||
if not value:
|
||||
continue
|
||||
|
@ -306,7 +308,7 @@ def get_password_check_rules():
|
|||
return check_rules
|
||||
|
||||
|
||||
def check_password_rules(password):
|
||||
def check_password_rules(password, user):
|
||||
pattern = r"^"
|
||||
if settings.SECURITY_PASSWORD_UPPER_CASE:
|
||||
pattern += '(?=.*[A-Z])'
|
||||
|
@ -317,7 +319,11 @@ def check_password_rules(password):
|
|||
if settings.SECURITY_PASSWORD_SPECIAL_CHAR:
|
||||
pattern += '(?=.*[`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?])'
|
||||
pattern += '[a-zA-Z\d`~!@#\$%\^&\*\(\)-=_\+\[\]\{\}\|;:\'\",\.<>\/\?]'
|
||||
pattern += '.{' + str(settings.SECURITY_PASSWORD_MIN_LENGTH-1) + ',}$'
|
||||
if user.is_org_admin:
|
||||
min_length = settings.SECURITY_ADMIN_USER_PASSWORD_MIN_LENGTH
|
||||
else:
|
||||
min_length = settings.SECURITY_PASSWORD_MIN_LEN
|
||||
pattern += '.{' + str(min_length-1) + ',}$'
|
||||
match_obj = re.match(pattern, password)
|
||||
return bool(match_obj)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import time
|
||||
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
@ -33,6 +34,16 @@ logger = get_logger(__name__)
|
|||
class UserOtpEnableStartView(UserVerifyPasswordView):
|
||||
template_name = 'users/user_otp_check_password.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
# 开启了 OTP IN RADIUS 就不用绑定了
|
||||
resp = super().form_valid(form)
|
||||
if settings.OTP_IN_RADIUS:
|
||||
user_id = self.request.session.get('user_id')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
user.enable_mfa()
|
||||
user.save()
|
||||
return resp
|
||||
|
||||
def get_success_url(self):
|
||||
if settings.OTP_IN_RADIUS:
|
||||
success_url = reverse_lazy('authentication:user-otp-settings-success')
|
||||
|
@ -85,7 +96,11 @@ class UserOtpEnableBindView(AuthMixin, TemplateView, FormView):
|
|||
session_user = get_object_or_none(User, pk=user_id)
|
||||
|
||||
if session_user:
|
||||
if all((is_auth_password_time_valid(self.request.session), session_user.mfa_enabled, not session_user.otp_secret_key)):
|
||||
if all((
|
||||
is_auth_password_time_valid(self.request.session),
|
||||
session_user.mfa_enabled,
|
||||
not session_user.otp_secret_key
|
||||
)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
@ -4,65 +4,20 @@ import time
|
|||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.edit import UpdateView, FormView
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import (
|
||||
IsValidUser,
|
||||
UserCanUpdatePassword
|
||||
)
|
||||
from common.mixins.views import PermissionsMixin
|
||||
from ... import forms
|
||||
from ...models import User
|
||||
from ...utils import (
|
||||
get_user_or_pre_auth_user,
|
||||
check_password_rules, get_password_check_rules,
|
||||
)
|
||||
|
||||
__all__ = ['UserPasswordUpdateView', 'UserVerifyPasswordView']
|
||||
__all__ = ['UserVerifyPasswordView']
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class UserPasswordUpdateView(PermissionsMixin, UpdateView):
|
||||
template_name = 'users/user_password_update.html'
|
||||
model = User
|
||||
form_class = forms.UserPasswordForm
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
permission_classes = [IsValidUser, UserCanUpdatePassword]
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
check_rules = get_password_check_rules()
|
||||
context = {
|
||||
'app': _('Users'),
|
||||
'action': _('Password update'),
|
||||
'password_check_rules': check_rules,
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
auth_logout(self.request)
|
||||
return super().get_success_url()
|
||||
|
||||
def form_valid(self, form):
|
||||
password = form.cleaned_data.get('new_password')
|
||||
is_ok = check_password_rules(password)
|
||||
if not is_ok:
|
||||
form.add_error(
|
||||
"new_password",
|
||||
_("* Your password does not meet the requirements")
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class UserVerifyPasswordView(FormView):
|
||||
template_name = 'users/user_password_verify.html'
|
||||
form_class = forms.UserCheckPasswordForm
|
||||
|
@ -74,9 +29,6 @@ class UserVerifyPasswordView(FormView):
|
|||
if not user:
|
||||
form.add_error("password", _("Password invalid"))
|
||||
return self.form_invalid(form)
|
||||
if not user.mfa_is_otp():
|
||||
user.enable_mfa()
|
||||
user.save()
|
||||
self.request.session['user_id'] = str(user.id)
|
||||
self.request.session['auth_password'] = 1
|
||||
self.request.session['auth_password_expired_at'] = time.time() + settings.AUTH_EXPIRED_SECONDS
|
||||
|
|
|
@ -82,8 +82,9 @@ class UserResetPasswordView(FormView):
|
|||
if not user:
|
||||
context['errors'] = _('Token invalid or expired')
|
||||
context['token_invalid'] = True
|
||||
check_rules = get_password_check_rules()
|
||||
context['password_check_rules'] = check_rules
|
||||
else:
|
||||
check_rules = get_password_check_rules(user)
|
||||
context['password_check_rules'] = check_rules
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -100,7 +101,7 @@ class UserResetPasswordView(FormView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
password = form.cleaned_data['new_password']
|
||||
is_ok = check_password_rules(password)
|
||||
is_ok = check_password_rules(password, user)
|
||||
if not is_ok:
|
||||
error = _('* Your password does not meet the requirements')
|
||||
form.add_error('new_password', error)
|
||||
|
|
Loading…
Reference in New Issue