Merge pull request #9052 from jumpserver/dev

v2.28.0-rc2
pull/9099/head
Jiangjie.Bai 2022-11-14 09:54:50 +08:00 committed by GitHub
commit 52830db500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 322 additions and 234 deletions

View File

@ -23,7 +23,7 @@ class ChromeSerializer(RemoteAppSerializer):
)
chrome_password = EncryptedField(
max_length=128, allow_blank=True, required=False,
label=_('Chrome password'), allow_null=True
label=_('Chrome password'), allow_null=True, encrypted_key='chrome_password'
)

View File

@ -16,6 +16,8 @@ MODELS_NEED_RECORD = (
'CommandFilter', 'Platform', 'Label',
# applications
'Application',
# account
'AuthBook',
# orgs
'Organization',
# settings

View File

@ -59,14 +59,15 @@ def get_resource_display(resource):
def model_to_dict_for_operate_log(
instance, include_model_fields=True, include_related_fields=True
):
need_continue_fields = ['date_updated']
model_need_continue_fields = ['date_updated']
m2m_need_continue_fields = ['history_passwords']
opts = instance._meta
data = {}
for f in chain(opts.concrete_fields, opts.private_fields):
if isinstance(f, (models.FileField, models.ImageField)):
continue
if getattr(f, 'attname', None) in need_continue_fields:
if getattr(f, 'attname', None) in model_need_continue_fields:
continue
value = getattr(instance, f.name) or getattr(instance, f.attname)
@ -75,11 +76,6 @@ def model_to_dict_for_operate_log(
if getattr(f, 'primary_key', False):
f.verbose_name = 'id'
elif isinstance(f, (
fields.EncryptCharField, fields.EncryptTextField,
fields.EncryptJsonDictCharField, fields.EncryptJsonDictTextField
)) or getattr(f, 'attname', '') == 'password':
value = 'encrypt|%s' % value
elif isinstance(value, list):
value = [str(v) for v in value]
@ -91,11 +87,12 @@ def model_to_dict_for_operate_log(
value = []
if instance.pk is not None:
related_name = getattr(f, 'attname', '') or getattr(f, 'related_name', '')
if related_name:
try:
value = [str(i) for i in getattr(instance, related_name).all()]
except:
pass
if not related_name or related_name in m2m_need_continue_fields:
continue
try:
value = [str(i) for i in getattr(instance, related_name).all()]
except:
pass
if not value:
continue
try:

View File

@ -3,6 +3,7 @@ from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from django.utils.translation import ugettext as _
from django.template.loader import render_to_string
from django.core.cache import cache
from common.utils.verify_code import SendAndVerifyCodeUtil
from common.permissions import IsValidUser
@ -23,7 +24,7 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
serializer_class = ResetPasswordCodeSerializer
@staticmethod
def is_valid_user( **kwargs):
def is_valid_user(**kwargs):
user = get_object_or_none(User, **kwargs)
if not user:
err_msg = _('User does not exist: {}').format(_("No user matched"))
@ -38,33 +39,25 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
token = request.GET.get('token')
username = cache.get(token)
form_type = serializer.validated_data['form_type']
username = serializer.validated_data['username']
code = random_string(6, lower=False, upper=False)
other_args = {}
if form_type == 'phone':
backend = 'sms'
target = serializer.validated_data['phone']
user, err = self.is_valid_user(username=username, phone=target)
if not user:
return Response({'error': err}, status=400)
else:
backend = 'email'
target = serializer.validated_data['email']
user, err = self.is_valid_user(username=username, email=target)
if not user:
return Response({'error': err}, status=400)
target = serializer.validated_data[form_type]
query_key = 'phone' if form_type == 'sms' else form_type
user, err = self.is_valid_user(username=username, **{query_key: target})
if not user:
return Response({'error': err}, status=400)
subject = '%s: %s' % (get_login_title(), _('Forgot password'))
context = {
'user': user, 'title': subject, 'code': code,
}
message = render_to_string('authentication/_msg_reset_password_code.html', context)
other_args['subject'] = subject
other_args['message'] = message
SendAndVerifyCodeUtil(target, code, backend=backend, **other_args).gen_and_send_async()
subject = '%s: %s' % (get_login_title(), _('Forgot password'))
context = {
'user': user, 'title': subject, 'code': code,
}
message = render_to_string('authentication/_msg_reset_password_code.html', context)
other_args['subject'], other_args['message'] = subject, message
SendAndVerifyCodeUtil(target, code, backend=form_type, **other_args).gen_and_send_async()
return Response({'data': 'ok'}, status=200)

View File

@ -12,26 +12,23 @@ __all__ = [
class ResetPasswordCodeSerializer(serializers.Serializer):
form_type = serializers.CharField(default='email')
username = serializers.CharField()
form_type = serializers.ChoiceField(
choices=[('sms', _('SMS')), ('email', _('Email'))], default='email'
)
email = serializers.CharField(allow_blank=True)
phone = serializers.CharField(allow_blank=True)
sms = serializers.CharField(allow_blank=True)
def create(self, attrs):
error = []
validate_backends = {
'email': _('Email'), 'sms': _('SMS')
}
form_type = attrs.get('form_type', 'email')
username = attrs.get('username')
if not username:
error.append(_('The {} cannot be empty').format(_('Username')))
if form_type == 'phone':
phone = attrs.get('phone')
if not phone:
error.append(_('The {} cannot be empty').format(_('Phone')))
else:
email = attrs.get('email')
if not email:
error.append(_('The {} cannot be empty').format(_('Email')))
validate_backend_input = attrs.get(form_type)
if not validate_backend_input:
error.append(_('The {} cannot be empty').format(
validate_backends.get(validate_backend_input))
)
if error:
raise serializers.ValidationError(error)

View File

@ -18,6 +18,7 @@ urlpatterns = [
path('logout/', views.UserLogoutView.as_view(), name='logout'),
# 原来在users中的
path('password/forget/previewing/', users_view.UserForgotPasswordPreviewingView.as_view(), name='forgot-previewing'),
path('password/forgot/', users_view.UserForgotPasswordView.as_view(), name='forgot-password'),
path('password/reset/', users_view.UserResetPasswordView.as_view(), name='reset-password'),
path('password/verify/', users_view.UserVerifyPasswordView.as_view(), name='user-verify-password'),

View File

@ -115,7 +115,7 @@ class UserLoginContextMixin:
@staticmethod
def get_forgot_password_url():
forgot_password_url = reverse('authentication:forgot-password')
forgot_password_url = reverse('authentication:forgot-previewing')
forgot_password_url = settings.FORGOT_PASSWORD_URL or forgot_password_url
return forgot_password_url

View File

@ -32,8 +32,9 @@ class EncryptedField(serializers.CharField):
if write_only is None:
write_only = True
kwargs['write_only'] = write_only
encrypted_key = kwargs.pop('encrypted_key', None)
super().__init__(**kwargs)
add_encrypted_field_set(self.label)
add_encrypted_field_set(encrypted_key or self.label)
def to_internal_value(self, value):
value = super().to_internal_value(value)

View File

@ -199,6 +199,13 @@ class Config(dict):
'REDIS_PORT': 6379,
'REDIS_PASSWORD': '',
'REDIS_USE_SSL': False,
'REDIS_SSL_KEY': None,
'REDIS_SSL_CERT': None,
'REDIS_SSL_CA': None,
# Redis Sentinel
'REDIS_SENTINEL_HOSTS': '',
'REDIS_SENTINEL_PASSWORD': '',
'REDIS_SENTINEL_SOCKET_TIMEOUT': None,
# Default value
'REDIS_DB_CELERY': 3,
'REDIS_DB_CACHE': 4,
@ -491,6 +498,8 @@ class Config(dict):
'SERVER_REPLAY_STORAGE': {},
'SECURITY_DATA_CRYPTO_ALGO': None,
'GMSSL_ENABLED': False,
# 操作日志变更字段的存储ES配置
'OPERATE_LOG_ELASTICSEARCH_CONFIG': {},
# Magnus 组件需要监听的端口范围
'MAGNUS_PORTS': '30000-30100',

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-11-10 10:55+0800\n"
"POT-Creation-Date: 2022-11-10 16:37+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -158,13 +158,11 @@ msgstr "コンマ区切り文字列の形式。* はすべて一致すること
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 authentication/serializers/password_mfa.py:25
#: authentication/models.py:260
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:102
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:109
#: users/models/user.py:671 users/templates/users/_msg_user_created.html:12
#: users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:98
#: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195
#: xpack/plugins/cloud/serializers/account_attrs.py:26
@ -226,7 +224,7 @@ msgid "None of the reviewers belong to Organization `{}`"
msgstr "いずれのレビューアも組織 '{}' に属していません"
#: acls/serializers/rules/rules.py:20
#: xpack/plugins/cloud/serializers/task.py:24
#: xpack/plugins/cloud/serializers/task.py:23
msgid "IP address invalid: `{}`"
msgstr "IPアドレスが無効: '{}'"
@ -326,7 +324,7 @@ msgstr "タイプ"
msgid "Domain"
msgstr "ドメイン"
#: applications/models/application.py:230 xpack/plugins/cloud/models.py:32
#: applications/models/application.py:230 xpack/plugins/cloud/models.py:33
#: xpack/plugins/cloud/serializers/account.py:64
msgid "Attrs"
msgstr "ツールバーの"
@ -365,7 +363,7 @@ msgstr "タイプ表示"
#: orgs/models.py:72 orgs/models.py:223 perms/models/base.py:92
#: users/models/group.py:18 users/models/user.py:937
#: xpack/plugins/change_auth_plan/models/base.py:45
#: xpack/plugins/cloud/models.py:127
#: xpack/plugins/cloud/models.py:125
msgid "Date created"
msgstr "作成された日付"
@ -610,7 +608,7 @@ msgstr "ホスト名生"
#: assets/models/asset.py:215 assets/serializers/account.py:16
#: assets/serializers/asset.py:65 perms/serializers/asset/user_permission.py:41
#: xpack/plugins/cloud/models.py:106 xpack/plugins/cloud/serializers/task.py:44
#: xpack/plugins/cloud/models.py:107 xpack/plugins/cloud/serializers/task.py:43
msgid "Protocols"
msgstr "プロトコル"
@ -632,7 +630,7 @@ msgstr "アクティブです。"
msgid "Admin user"
msgstr "管理ユーザー"
#: assets/models/asset.py:225 xpack/plugins/cloud/const.py:32
#: assets/models/asset.py:225
msgid "Public IP"
msgstr "パブリックIP"
@ -651,7 +649,7 @@ msgstr "ラベル"
#: orgs/models.py:225 perms/models/base.py:91 users/models/user.py:720
#: users/serializers/group.py:33
#: xpack/plugins/change_auth_plan/models/base.py:48
#: xpack/plugins/cloud/models.py:124 xpack/plugins/gathered_user/models.py:30
#: xpack/plugins/cloud/models.py:122 xpack/plugins/gathered_user/models.py:30
msgid "Created by"
msgstr "によって作成された"
@ -763,7 +761,7 @@ msgstr "トリガーモード"
#: xpack/plugins/change_auth_plan/models/base.py:201
#: xpack/plugins/change_auth_plan/serializers/app.py:66
#: xpack/plugins/change_auth_plan/serializers/asset.py:180
#: xpack/plugins/cloud/models.py:181
#: xpack/plugins/cloud/models.py:179
msgid "Reason"
msgstr "理由"
@ -793,7 +791,7 @@ msgstr "OK"
#: assets/models/base.py:32 audits/models.py:136
#: xpack/plugins/change_auth_plan/serializers/app.py:88
#: xpack/plugins/change_auth_plan/serializers/asset.py:199
#: xpack/plugins/cloud/const.py:41
#: xpack/plugins/cloud/const.py:36
msgid "Failed"
msgstr "失敗しました"
@ -844,10 +842,10 @@ msgstr "帯域幅"
msgid "Contact"
msgstr "連絡先"
#: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:29
#: users/forms/profile.py:104 users/models/user.py:693
#: users/templates/users/forgot_password.html:59
#: users/templates/users/forgot_password.html:103
#: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:25
#: users/forms/profile.py:103 users/models/user.py:693
#: users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:121
msgid "Phone"
msgstr "電話"
@ -1013,7 +1011,7 @@ msgid "Parent key"
msgstr "親キー"
#: assets/models/node.py:566 assets/serializers/system_user.py:267
#: xpack/plugins/cloud/models.py:95 xpack/plugins/cloud/serializers/task.py:72
#: xpack/plugins/cloud/models.py:96 xpack/plugins/cloud/serializers/task.py:70
msgid "Node"
msgstr "ノード"
@ -1170,8 +1168,8 @@ msgstr "CPU情報"
msgid "Actions"
msgstr "アクション"
#: assets/serializers/backup.py:31 ops/mixin.py:26 ops/mixin.py:106
#: ops/mixin.py:147 settings/serializers/auth/ldap.py:66
#: assets/serializers/backup.py:31 ops/mixin.py:106 ops/mixin.py:147
#: settings/serializers/auth/ldap.py:66
#: xpack/plugins/change_auth_plan/serializers/base.py:43
msgid "Periodic perform"
msgstr "定期的なパフォーマンス"
@ -1581,8 +1579,8 @@ msgid "MFA"
msgstr "MFA"
#: audits/models.py:146 terminal/models/status.py:33
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:177
#: xpack/plugins/cloud/models.py:229
#: tickets/models/ticket/general.py:281 xpack/plugins/cloud/models.py:175
#: xpack/plugins/cloud/models.py:227
msgid "Status"
msgstr "ステータス"
@ -1619,7 +1617,7 @@ msgid "Hosts display"
msgstr "ホスト表示"
#: audits/serializers.py:102 ops/models/command.py:27
#: xpack/plugins/cloud/models.py:175
#: xpack/plugins/cloud/models.py:173
msgid "Result"
msgstr "結果"
@ -1683,16 +1681,16 @@ msgstr "この操作には、MFAを検証する必要があります"
msgid "Current user not support mfa type: {}"
msgstr "現在のユーザーはmfaタイプをサポートしていません: {}"
#: authentication/api/password.py:29 terminal/api/session.py:224
#: users/views/profile/reset.py:74
#: authentication/api/password.py:30 terminal/api/session.py:224
#: users/views/profile/reset.py:45 users/views/profile/reset.py:107
msgid "User does not exist: {}"
msgstr "ユーザーが存在しない: {}"
#: authentication/api/password.py:29
#: authentication/api/password.py:30
msgid "No user matched"
msgstr "ユーザーにマッチしなかった"
#: authentication/api/password.py:33
#: authentication/api/password.py:34
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
@ -1700,10 +1698,12 @@ msgstr ""
"ユーザーは {}からです。対応するシステムにアクセスしてパスワードを変更してくだ"
"さい。"
#: authentication/api/password.py:59
#: authentication/api/password.py:61
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:33
#: users/templates/users/forgot_password.html:34
#: users/templates/users/forgot_password_previewing.html:13
#: users/templates/users/forgot_password_previewing.html:14
msgid "Forgot password"
msgstr "パスワードを忘れた"
@ -2015,6 +2015,7 @@ msgid "SMS verify code invalid"
msgstr "SMS検証コードが無効"
#: authentication/mfa/sms.py:12 settings/serializers/auth/sms.py:27
#: users/templates/users/forgot_password.html:74
msgid "SMS"
msgstr "SMS"
@ -2135,7 +2136,7 @@ msgid "binding reminder"
msgstr "バインディングリマインダー"
#: authentication/serializers/connection_token.py:23
#: xpack/plugins/cloud/models.py:33
#: xpack/plugins/cloud/models.py:34
msgid "Validity"
msgstr "有効性"
@ -2149,16 +2150,16 @@ msgstr "アセットまたはアプリが必要"
#: authentication/serializers/password_mfa.py:25
#: authentication/serializers/password_mfa.py:29
#: authentication/serializers/password_mfa.py:33
#: users/templates/users/forgot_password.html:95
#: users/templates/users/forgot_password.html:117
msgid "The {} cannot be empty"
msgstr "{} 空にしてはならない"
#: authentication/serializers/password_mfa.py:33
#: authentication/serializers/password_mfa.py:29
#: notifications/backends/__init__.py:10 settings/serializers/email.py:19
#: settings/serializers/email.py:50 users/forms/profile.py:103
#: users/models/user.py:675 users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:108
#: settings/serializers/email.py:50 users/forms/profile.py:102
#: users/models/user.py:675 users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:70
#: users/templates/users/forgot_password.html:126
msgid "Email"
msgstr "メール"
@ -2312,7 +2313,7 @@ msgstr "新しいものを要求する"
#: authentication/templates/authentication/_msg_reset_password_code.html:12
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
#: users/forms/profile.py:105 users/templates/users/forgot_password.html:63
#: users/forms/profile.py:104 users/templates/users/forgot_password.html:59
msgid "Verify code"
msgstr "コードの確認"
@ -4760,17 +4761,17 @@ msgstr ""
" "
#: templates/_mfa_login_field.html:28
#: users/templates/users/forgot_password.html:65
#: users/templates/users/forgot_password.html:61
msgid "Send verification code"
msgstr "確認コードを送信"
#: templates/_mfa_login_field.html:106
#: users/templates/users/forgot_password.html:122
#: users/templates/users/forgot_password.html:139
msgid "Wait: "
msgstr "待つ:"
#: templates/_mfa_login_field.html:116
#: users/templates/users/forgot_password.html:138
#: users/templates/users/forgot_password.html:155
msgid "The verification code has been sent"
msgstr "確認コードが送信されました"
@ -5247,7 +5248,7 @@ msgstr "アクセスキー"
msgid "Access key secret"
msgstr "アクセスキーシークレット"
#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:222
#: terminal/serializers/storage.py:65 xpack/plugins/cloud/models.py:220
msgid "Region"
msgstr "リージョン"
@ -5773,40 +5774,40 @@ msgstr "パスワードの確認"
msgid "Password does not match"
msgstr "パスワードが一致しない"
#: users/forms/profile.py:112
#: users/forms/profile.py:115
msgid "Old password"
msgstr "古いパスワード"
#: users/forms/profile.py:122
#: users/forms/profile.py:125
msgid "Old password error"
msgstr "古いパスワードエラー"
#: users/forms/profile.py:132
#: users/forms/profile.py:135
msgid "Automatically configure and download the SSH key"
msgstr "SSHキーの自動設定とダウンロード"
#: users/forms/profile.py:134
#: users/forms/profile.py:137
msgid "ssh public key"
msgstr "ssh公開キー"
#: users/forms/profile.py:135
#: users/forms/profile.py:138
msgid "ssh-rsa AAAA..."
msgstr "ssh-rsa AAAA.."
#: users/forms/profile.py:136
#: users/forms/profile.py:139
msgid "Paste your id_rsa.pub here."
msgstr "ここにid_rsa.pubを貼り付けます。"
#: users/forms/profile.py:149
#: users/forms/profile.py:152
msgid "Public key should not be the same as your old one."
msgstr "公開鍵は古いものと同じであってはなりません。"
#: users/forms/profile.py:153 users/serializers/profile.py:100
#: users/forms/profile.py:156 users/serializers/profile.py:100
#: users/serializers/profile.py:183 users/serializers/profile.py:210
msgid "Not a valid ssh public key"
msgstr "有効なssh公開鍵ではありません"
#: users/forms/profile.py:164 users/models/user.py:706
#: users/forms/profile.py:167 users/models/user.py:706
msgid "Public key"
msgstr "公開キー"
@ -5886,7 +5887,7 @@ msgstr "ユーザーパスワード履歴"
msgid "Reset password"
msgstr "パスワードのリセット"
#: users/notifications.py:85 users/views/profile/reset.py:139
#: users/notifications.py:85 users/views/profile/reset.py:172
msgid "Reset password success"
msgstr "パスワードのリセット成功"
@ -6082,17 +6083,14 @@ msgstr "ここをクリックしてパスワードを設定してください"
msgid "Input your email, that will send a mail to your"
msgstr "あなたのメールを入力し、それはあなたにメールを送信します"
#: users/templates/users/forgot_password.html:68
#: users/templates/users/forgot_password.html:64
#: users/templates/users/forgot_password_previewing.html:30
msgid "Submit"
msgstr "送信"
#: users/templates/users/forgot_password.html:71
msgid "Use the phone number to retrieve the password"
msgstr "携帯電話番号を使ってパスワードを探す"
#: users/templates/users/forgot_password.html:72
msgid "Use email to retrieve the password"
msgstr "メールアドレスを使ってパスワードを取り戻す"
#: users/templates/users/forgot_password_previewing.html:21
msgid "Please enter the username for which you want to retrieve the password"
msgstr "パスワードを取り戻す必要があるユーザー名を入力してください"
#: users/templates/users/mfa_setting.html:24
msgid "Enable MFA"
@ -6240,19 +6238,19 @@ msgstr "パスワード無効"
msgid "Token invalid or expired"
msgstr "トークンが無効または期限切れ"
#: users/views/profile/reset.py:112
#: users/views/profile/reset.py:145
msgid "User auth from {}, go there change password"
msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください"
#: users/views/profile/reset.py:119
#: users/views/profile/reset.py:152
msgid "* Your password does not meet the requirements"
msgstr "* パスワードが要件を満たしていない"
#: users/views/profile/reset.py:125
#: users/views/profile/reset.py:158
msgid "* The new password cannot be the last {} passwords"
msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません"
#: users/views/profile/reset.py:140
#: users/views/profile/reset.py:173
msgid "Reset password success, return to login page"
msgstr "パスワードの成功をリセットし、ログインページに戻る"
@ -6523,27 +6521,27 @@ msgstr "プライベートIP"
msgid "Instance name"
msgstr "インスタンス名"
#: xpack/plugins/cloud/const.py:37
#: xpack/plugins/cloud/const.py:32
msgid "Instance name and Partial IP"
msgstr "インスタンス名と部分IP"
#: xpack/plugins/cloud/const.py:42
#: xpack/plugins/cloud/const.py:37
msgid "Succeed"
msgstr "成功"
#: xpack/plugins/cloud/const.py:46
#: xpack/plugins/cloud/const.py:41
msgid "Unsync"
msgstr "同期していません"
#: xpack/plugins/cloud/const.py:47
#: xpack/plugins/cloud/const.py:42
msgid "New Sync"
msgstr "新しい同期"
#: xpack/plugins/cloud/const.py:48
#: xpack/plugins/cloud/const.py:43
msgid "Synced"
msgstr "同期済み"
#: xpack/plugins/cloud/const.py:49
#: xpack/plugins/cloud/const.py:44
msgid "Released"
msgstr "リリース済み"
@ -6551,19 +6549,19 @@ msgstr "リリース済み"
msgid "Cloud center"
msgstr "クラウドセンター"
#: xpack/plugins/cloud/models.py:29
#: xpack/plugins/cloud/models.py:30
msgid "Provider"
msgstr "プロバイダー"
#: xpack/plugins/cloud/models.py:38
#: xpack/plugins/cloud/models.py:39
msgid "Cloud account"
msgstr "クラウドアカウント"
#: xpack/plugins/cloud/models.py:40
#: xpack/plugins/cloud/models.py:41
msgid "Test cloud account"
msgstr "クラウドアカウントのテスト"
#: xpack/plugins/cloud/models.py:84 xpack/plugins/cloud/serializers/task.py:69
#: xpack/plugins/cloud/models.py:85 xpack/plugins/cloud/serializers/task.py:67
msgid "Account"
msgstr "アカウント"

View File

@ -157,13 +157,11 @@ msgstr "格式为逗号分隔的字符串, * 表示匹配所有. "
#: acls/serializers/login_asset_acl.py:51 assets/models/base.py:176
#: assets/models/gathered_user.py:15 audits/models.py:139
#: authentication/forms.py:25 authentication/forms.py:27
#: authentication/models.py:260 authentication/serializers/password_mfa.py:25
#: authentication/models.py:260
#: authentication/templates/authentication/_msg_different_city.html:9
#: authentication/templates/authentication/_msg_oauth_bind.html:9
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:102
#: ops/models/adhoc.py:159 users/forms/profile.py:32 users/forms/profile.py:109
#: users/models/user.py:671 users/templates/users/_msg_user_created.html:12
#: users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:98
#: xpack/plugins/change_auth_plan/models/asset.py:34
#: xpack/plugins/change_auth_plan/models/asset.py:195
#: xpack/plugins/cloud/serializers/account_attrs.py:26
@ -839,10 +837,10 @@ msgstr "带宽"
msgid "Contact"
msgstr "联系人"
#: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:29
#: users/forms/profile.py:104 users/models/user.py:693
#: users/templates/users/forgot_password.html:59
#: users/templates/users/forgot_password.html:103
#: assets/models/cluster.py:22 authentication/serializers/password_mfa.py:25
#: users/forms/profile.py:103 users/models/user.py:693
#: users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:121
msgid "Phone"
msgstr "手机"
@ -1671,25 +1669,27 @@ msgstr "此操作需要验证您的 MFA"
msgid "Current user not support mfa type: {}"
msgstr "当前用户不支持 MFA 类型: {}"
#: authentication/api/password.py:29 terminal/api/session.py:224
#: users/views/profile/reset.py:74
#: authentication/api/password.py:30 terminal/api/session.py:224
#: users/views/profile/reset.py:45 users/views/profile/reset.py:107
msgid "User does not exist: {}"
msgstr "用户不存在: {}"
#: authentication/api/password.py:29
#: authentication/api/password.py:30
msgid "No user matched"
msgstr "没有匹配到用户"
#: authentication/api/password.py:33
#: authentication/api/password.py:34
msgid ""
"The user is from {}, please go to the corresponding system to change the "
"password"
msgstr "用户来自 {} 请去相应系统修改密码"
#: authentication/api/password.py:59
#: authentication/api/password.py:61
#: authentication/templates/authentication/login.html:256
#: users/templates/users/forgot_password.html:33
#: users/templates/users/forgot_password.html:34
#: users/templates/users/forgot_password_previewing.html:13
#: users/templates/users/forgot_password_previewing.html:14
msgid "Forgot password"
msgstr "忘记密码"
@ -1991,6 +1991,7 @@ msgid "SMS verify code invalid"
msgstr "短信验证码校验失败"
#: authentication/mfa/sms.py:12 settings/serializers/auth/sms.py:27
#: users/templates/users/forgot_password.html:74
msgid "SMS"
msgstr "短信"
@ -2121,16 +2122,16 @@ msgstr "资产或应用必填"
#: authentication/serializers/password_mfa.py:25
#: authentication/serializers/password_mfa.py:29
#: authentication/serializers/password_mfa.py:33
#: users/templates/users/forgot_password.html:95
#: users/templates/users/forgot_password.html:117
msgid "The {} cannot be empty"
msgstr "{} 不能为空"
#: authentication/serializers/password_mfa.py:33
#: authentication/serializers/password_mfa.py:29
#: notifications/backends/__init__.py:10 settings/serializers/email.py:19
#: settings/serializers/email.py:50 users/forms/profile.py:103
#: users/models/user.py:675 users/templates/users/forgot_password.html:55
#: users/templates/users/forgot_password.html:108
#: settings/serializers/email.py:50 users/forms/profile.py:102
#: users/models/user.py:675 users/templates/users/forgot_password.html:51
#: users/templates/users/forgot_password.html:70
#: users/templates/users/forgot_password.html:126
msgid "Email"
msgstr "邮件"
@ -2280,7 +2281,7 @@ msgstr "重新申请"
#: authentication/templates/authentication/_msg_reset_password_code.html:12
#: terminal/models/sharing.py:26 terminal/models/sharing.py:80
#: users/forms/profile.py:105 users/templates/users/forgot_password.html:63
#: users/forms/profile.py:104 users/templates/users/forgot_password.html:59
msgid "Verify code"
msgstr "验证码"
@ -4685,17 +4686,17 @@ msgstr ""
" "
#: templates/_mfa_login_field.html:28
#: users/templates/users/forgot_password.html:65
#: users/templates/users/forgot_password.html:61
msgid "Send verification code"
msgstr "发送验证码"
#: templates/_mfa_login_field.html:106
#: users/templates/users/forgot_password.html:122
#: users/templates/users/forgot_password.html:139
msgid "Wait: "
msgstr "等待:"
#: templates/_mfa_login_field.html:116
#: users/templates/users/forgot_password.html:138
#: users/templates/users/forgot_password.html:155
msgid "The verification code has been sent"
msgstr "验证码已发送"
@ -5682,40 +5683,40 @@ msgstr "确认密码"
msgid "Password does not match"
msgstr "密码不一致"
#: users/forms/profile.py:112
#: users/forms/profile.py:115
msgid "Old password"
msgstr "原来密码"
#: users/forms/profile.py:122
#: users/forms/profile.py:125
msgid "Old password error"
msgstr "原来密码错误"
#: users/forms/profile.py:132
#: users/forms/profile.py:135
msgid "Automatically configure and download the SSH key"
msgstr "自动配置并下载SSH密钥"
#: users/forms/profile.py:134
#: users/forms/profile.py:137
msgid "ssh public key"
msgstr "SSH公钥"
#: users/forms/profile.py:135
#: users/forms/profile.py:138
msgid "ssh-rsa AAAA..."
msgstr "ssh-rsa AAAA..."
#: users/forms/profile.py:136
#: users/forms/profile.py:139
msgid "Paste your id_rsa.pub here."
msgstr "复制你的公钥到这里"
#: users/forms/profile.py:149
#: users/forms/profile.py:152
msgid "Public key should not be the same as your old one."
msgstr "不能和原来的密钥相同"
#: users/forms/profile.py:153 users/serializers/profile.py:100
#: users/forms/profile.py:156 users/serializers/profile.py:100
#: users/serializers/profile.py:183 users/serializers/profile.py:210
msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法"
#: users/forms/profile.py:164 users/models/user.py:706
#: users/forms/profile.py:167 users/models/user.py:706
msgid "Public key"
msgstr "SSH公钥"
@ -5795,7 +5796,7 @@ msgstr "用户密码历史"
msgid "Reset password"
msgstr "重置密码"
#: users/notifications.py:85 users/views/profile/reset.py:139
#: users/notifications.py:85 users/views/profile/reset.py:172
msgid "Reset password success"
msgstr "重置密码成功"
@ -5989,17 +5990,14 @@ msgstr "点击这里设置密码"
msgid "Input your email, that will send a mail to your"
msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中"
#: users/templates/users/forgot_password.html:68
#: users/templates/users/forgot_password.html:64
#: users/templates/users/forgot_password_previewing.html:30
msgid "Submit"
msgstr "提交"
#: users/templates/users/forgot_password.html:71
msgid "Use the phone number to retrieve the password"
msgstr "使用手机号找回密码"
#: users/templates/users/forgot_password.html:72
msgid "Use email to retrieve the password"
msgstr "使用邮箱找回密码"
#: users/templates/users/forgot_password_previewing.html:21
msgid "Please enter the username for which you want to retrieve the password"
msgstr "请输入您需要找回密码的用户名"
#: users/templates/users/mfa_setting.html:24
msgid "Enable MFA"
@ -6135,23 +6133,23 @@ msgstr "MFA(OTP) 禁用成功,返回登录页面"
msgid "Password invalid"
msgstr "用户名或密码无效"
#: users/views/profile/reset.py:96 users/views/profile/reset.py:107
#: users/views/profile/reset.py:129 users/views/profile/reset.py:140
msgid "Token invalid or expired"
msgstr "Token错误或失效"
#: users/views/profile/reset.py:112
#: users/views/profile/reset.py:145
msgid "User auth from {}, go there change password"
msgstr "用户认证源来自 {}, 请去相应系统修改密码"
#: users/views/profile/reset.py:119
#: users/views/profile/reset.py:152
msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求"
#: users/views/profile/reset.py:125
#: users/views/profile/reset.py:158
msgid "* The new password cannot be the last {} passwords"
msgstr "* 新密码不能是最近 {} 次的密码"
#: users/views/profile/reset.py:140
#: users/views/profile/reset.py:173
msgid "Reset password success, return to login page"
msgstr "重置密码成功,返回到登录页面"

View File

@ -2,10 +2,9 @@
#
from django import forms
from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField
from common.utils import validate_ssh_public_key
from authentication.forms import EncryptedField
from authentication.forms import EncryptedField, CaptchaMixin
from ..models import User
@ -13,7 +12,8 @@ __all__ = [
'UserProfileForm', 'UserMFAForm', 'UserFirstLoginFinishForm',
'UserPasswordForm', 'UserPublicKeyForm', 'FileForm',
'UserTokenResetPasswordForm', 'UserForgotPasswordForm',
'UserCheckPasswordForm', 'UserCheckOtpCodeForm'
'UserCheckPasswordForm', 'UserCheckOtpCodeForm',
'UserForgotPasswordPreviewingForm'
]
@ -99,11 +99,17 @@ class UserTokenResetPasswordForm(forms.Form):
class UserForgotPasswordForm(forms.Form):
username = forms.CharField(label=_("Username"))
email = forms.CharField(label=_("Email"), required=False)
phone = forms.CharField(label=_('Phone'), required=False, max_length=11)
sms = forms.CharField(label=_('SMS'), required=False, max_length=11)
code = forms.CharField(label=_('Verify code'), max_length=6, required=False)
form_type = forms.CharField(widget=forms.HiddenInput({'value': 'email'}))
form_type = forms.ChoiceField(
choices=[('sms', _('SMS')), ('email', _('Email'))],
widget=forms.HiddenInput({'value': 'email'})
)
class UserForgotPasswordPreviewingForm(CaptchaMixin):
username = forms.CharField(label=_("Username"))
class UserPasswordForm(UserTokenResetPasswordForm):

View File

@ -22,85 +22,93 @@
height: 100%;
vertical-align: top;
}
.display-fade {
display: none;
}
.display-show {
display: block;
}
</style>
{% endblock %}
{% block html_title %}{% trans 'Forgot password' %}{% endblock %}
{% block title %} {% trans 'Forgot password' %}?{% endblock %}
{% block content %}
<p class="{% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %}">
<p id="validate-email-tip" class="validate-field">
{% trans 'Input your email, that will send a mail to your' %}
</p>
<p class="{% if form_type == 'email' %} display-fade {% else %} display-show {% endif %}">
<p id="validate-sms-tip" class="validate-field">
Enter your phone number and a verification code will be sent to your phone
</p>
{% if 'code' in form.errors %}
<p class="red-fonts">{{ form.code.errors.as_text }}</p>
{% endif %}
<div class="row">
<div class="col-sm-12">
<select id="validate-backend-select" name="validate-backend_type"
class="form-control select-con margin-bottom" onchange="selectChange(this.value)">
{% for backend in validate_backends %}
<option value="{{ backend.value }}" {% if not backend.is_active %} disabled {% endif %}>
{{ backend.name }}
</option>
{% endfor %}
</select>
<form role="form" class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_field form.form_type layout="horizontal" %}
<div class="margin-bottom {% if 'username' in form.errors %} has-error {% endif %}">
<label class="control-label" for="username">{{ form.username.errors.as_text }}</label>
<input type="text" id="username" name="username" class="form-control input-style" placeholder="{% trans 'Username' %}" value="{{ username }}">
<div id="validate-email" class="validate-field margin-bottom">
<input type="email" id="email" name="email" class="form-control input-style"
placeholder="{% trans 'Email' %}" value="{{ email }}">
</div>
<div class="margin-bottom {% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %} {% if 'email' in form.errors %} has-error {% endif %}">
<label class="control-label" for="email">{{ form.email.errors.as_text }}</label>
<input type="email" id="email" name="email" class="form-control input-style" placeholder="{% trans 'Email' %}" value="{{ email }}">
<div id="validate-sms" class="validate-field margin-bottom">
<input type="tel" id="sms" name="sms" class="form-control input-style"
placeholder="{% trans 'SMS' %}" value="{{ sms }}">
</div>
<div class="margin-bottom {% if form_type == 'email' %} display-fade {% else %} display-show {% endif %} {% if 'phone' in form.errors %} has-error {% endif %}">
<label class="control-label" for="phone">{{ form.phone.errors.as_text }}</label>
<input type="tel" id="phone" name="phone" class="form-control input-style" placeholder="{% trans 'Phone' %}" value="{{ phone }}">
</div>
<div class="margin-bottom challenge-required {% if 'code' in form.errors %} has-error {% endif %}" style="margin-top: 30px">
<label class="control-label" for="code">{{ form.code.errors.as_text }}</label>
<input type="text" id="code" name="code" class="form-control input-style" placeholder="{% trans 'Verify code' %}">
<div class="margin-bottom challenge-required">
<input type="text" id="code" name="code" class="form-control input-style"
placeholder="{% trans 'Verify code' %}">
<button class="btn btn-primary full-width btn-challenge"
type='button' onclick="sendChallengeCode(this)">{% trans 'Send verification code' %}</button>
type='button' onclick="sendChallengeCode(this)">
{% trans 'Send verification code' %}
</button>
</div>
<div class="margin-bottom">
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
</div>
{% if XPACK_ENABLED %}
<a id="switch-email" class="{% if form_type == 'phone' %} display-fade {% else %} display-show {% endif %}" onclick="changeType()">{% trans 'Use the phone number to retrieve the password' %}</a>
<a id="switch-phone" class="{% if form_type == 'email' %} display-fade {% else %} display-show {% endif %}" onclick="changeType()">{% trans 'Use email to retrieve the password' %}</a>
{% endif %}
</form>
</div>
</div>
<script>
function changeType() {
// 把所有 display-fade display-show 互换
const showElements = $('.display-show')
const fadeElements = $('.display-fade')
const formTypeElement = $('input[name="form_type"]')
showElements.addClass('display-fade').removeClass('display-show')
fadeElements.addClass('display-show').removeClass('display-fade')
formTypeElement.attr('value', formTypeElement.val() === 'email' ? 'phone': 'email')
$(function (){
const validateSelectRef = $('#validate-backend-select')
const formType = $('input[name="form_type"]').val()
validateSelectRef.val(formType)
if (formType !== null) {
selectChange(formType);
}
})
function getQueryString(name) {
const reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
const r = window.location.search.substr(1).match(reg);
if(r !== null)
return unescape(r[2])
return null
}
function selectChange(name) {
$('.validate-field').hide()
$('#validate-' + name).show()
$('#validate-' + name + '-tip').show()
$('input[name="form_type"]').attr('value', name)
}
function sendChallengeCode(currentBtn) {
let time = 60;
const url = "{% url 'api-auth:reset-password-code' %}";
const token = getQueryString('token')
const url = "{% url 'api-auth:reset-password-code' %}" + "?token=" + token;
const formType = $('input[name="form_type"]').val()
const username = $('#username').val()
const email = $('#email').val()
const phone = $('#phone').val()
const sms = $('#sms').val()
const errMsg = "{% trans 'The {} cannot be empty' %}"
if (username === "") {
toastr.error(errMsg.replace('{}', "{% trans 'Username' %}"))
return
}
if (formType === 'phone') {
if (phone === "") {
toastr.error(errMsg.replace('{}', "{% trans 'Phone' %}"))
if (formType === 'sms') {
if (sms === "") {
toastr.error(errMsg.replace('{}', "{% trans 'SMS' %}"))
return
}
} else {
@ -111,8 +119,7 @@
}
const data = {
username: username, form_type: formType,
email: email, phone: phone,
form_type: formType, email: email, sms: sms,
}
function onSuccess() {
const originBtnText = currentBtn.innerHTML;

View File

@ -0,0 +1,34 @@
{% extends '_base_only_content.html' %}
{% load static %}
{% load i18n %}
{% load bootstrap3 %}
{% block custom_head_css_js %}
<style>
.captcha {
float: right;
width: 50%;
}
</style>
{% endblock %}
{% block html_title %}{% trans 'Forgot password' %}{% endblock %}
{% block title %} {% trans 'Forgot password' %}?{% endblock %}
{% block content %}
{% if errors %}
<p class="red-fonts">{{ errors }}</p>
{% endif %}
<p>
{% trans 'Please enter the username for which you want to retrieve the password' %}
</p>
<div class="row">
<div class="col-sm-12">
<form role="form" class="form-horizontal" action="" method="post">
{% csrf_token %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.captcha layout="horizontal" %}
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Submit' %}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -7,9 +7,10 @@ from django.utils.translation import ugettext as _
from django.conf import settings
from django.urls import reverse_lazy
from django.views.generic import FormView
from django.core.cache import cache
from users.notifications import ResetPasswordSuccessMsg
from common.utils import get_object_or_none, FlashMessageUtil
from common.utils import get_object_or_none, FlashMessageUtil, random_string
from common.utils.verify_code import SendAndVerifyCodeUtil
from ...models import User
from ...utils import (
@ -20,6 +21,7 @@ from ... import forms
__all__ = [
'UserLoginView', 'UserResetPasswordView', 'UserForgotPasswordView',
'UserForgotPasswordPreviewingView'
]
@ -28,10 +30,52 @@ class UserLoginView(RedirectView):
query_string = True
class UserForgotPasswordPreviewingView(FormView):
template_name = 'users/forgot_password_previewing.html'
form_class = forms.UserForgotPasswordPreviewingForm
@staticmethod
def get_redirect_url(token):
return reverse('authentication:forgot-password') + '?token=%s' % token
def form_valid(self, form):
username = form.cleaned_data['username']
user = get_object_or_none(User, username=username)
if not user:
form.add_error('username', _('User does not exist: {}').format(username))
return super().form_invalid(form)
token = random_string(36)
cache.set(token, username, 5 * 60)
return redirect(self.get_redirect_url(token))
class UserForgotPasswordView(FormView):
template_name = 'users/forgot_password.html'
form_class = forms.UserForgotPasswordForm
def get(self, request, *args, **kwargs):
token = self.request.GET.get('token')
username = cache.get(token)
if not username:
return redirect(self.get_redirect_url(return_previewing=True))
else:
return super().get(request, *args, **kwargs)
@staticmethod
def get_validate_backends_context():
validate_backends = [
{'name': _('Email'), 'is_active': True, 'value': 'email'}
]
if settings.XPACK_ENABLED:
if settings.SMS_ENABLED:
is_active = True
else:
is_active = False
sms_backend = {'name': _('SMS'), 'is_active': is_active, 'value': 'sms'}
validate_backends.append(sms_backend)
return {'validate_backends': validate_backends}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
@ -44,32 +88,33 @@ class UserForgotPasswordView(FormView):
for k, v in cleaned_data.items():
if v:
context[k] = v
validate_backends = self.get_validate_backends_context()
context.update(validate_backends)
return context
@staticmethod
def get_redirect_url(user):
def get_redirect_url(user=None, return_previewing=False):
if not user and return_previewing:
return reverse('authentication:forgot-previewing')
query_params = '?token=%s' % user.generate_reset_token()
reset_password_url = reverse('authentication:reset-password')
return reset_password_url + query_params
def form_valid(self, form):
token = self.request.GET.get('token')
username = cache.get(token)
form_type = form.cleaned_data['form_type']
code = form.cleaned_data['code']
username = form.cleaned_data['username']
if settings.XPACK_ENABLED and form_type == 'phone':
backend = 'sms'
target = form.cleaned_data['phone']
else:
backend = 'email'
target = form.cleaned_data['email']
target = form.cleaned_data[form_type]
try:
sender_util = SendAndVerifyCodeUtil(target, backend=backend)
sender_util = SendAndVerifyCodeUtil(target, backend=form_type)
sender_util.verify(code)
except Exception as e:
form.add_error('code', str(e))
return super().form_invalid(form)
user = get_object_or_none(User, **{'username': username, form_type: target})
query_key = 'phone' if form_type == 'sms' else form_type
user = get_object_or_none(User, **{'username': username, query_key: target})
if not user:
form.add_error('username', _('User does not exist: {}').format(username))
return super().form_invalid(form)