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
fit2bot 2021-07-30 15:19:00 +08:00 committed by GitHub
parent b56b897260
commit 67f6b1080e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 168 additions and 129 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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.

View File

@ -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 "应用类别"

View File

@ -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 = []

View File

@ -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)
]

View File

@ -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')
)

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)