diff --git a/apps/applications/serializers/attrs/application_type/clickhouse.py b/apps/applications/serializers/attrs/application_type/clickhouse.py index 5593f0fb1..a4a942710 100644 --- a/apps/applications/serializers/attrs/application_type/clickhouse.py +++ b/apps/applications/serializers/attrs/application_type/clickhouse.py @@ -7,4 +7,10 @@ __all__ = ['ClickHouseSerializer'] class ClickHouseSerializer(DBSerializer): - port = serializers.IntegerField(default=9000, label=_('Port'), allow_null=True) + port = serializers.IntegerField( + default=9000, label=_('Port'), allow_null=True, + help_text=_( + 'Typically, the port is 9000,' + 'the HTTP interface and the native interface use different ports' + ), + ) diff --git a/apps/jumpserver/conf.py b/apps/jumpserver/conf.py index dd8d132a4..fdc2746a8 100644 --- a/apps/jumpserver/conf.py +++ b/apps/jumpserver/conf.py @@ -520,6 +520,7 @@ class Config(dict): 'PERM_SINGLE_ASSET_TO_UNGROUP_NODE': False, 'TICKET_AUTHORIZE_DEFAULT_TIME': 7, + 'TICKET_AUTHORIZE_DEFAULT_TIME_UNIT': 'day', 'WINDOWS_SSH_DEFAULT_SHELL': 'cmd', 'PERIOD_TASK_ENABLED': True, diff --git a/apps/jumpserver/settings/custom.py b/apps/jumpserver/settings/custom.py index 5cf0bc95c..37bb073c6 100644 --- a/apps/jumpserver/settings/custom.py +++ b/apps/jumpserver/settings/custom.py @@ -89,6 +89,7 @@ BACKEND_ASSET_USER_AUTH_VAULT = False PERM_SINGLE_ASSET_TO_UNGROUP_NODE = CONFIG.PERM_SINGLE_ASSET_TO_UNGROUP_NODE TICKET_AUTHORIZE_DEFAULT_TIME = CONFIG.TICKET_AUTHORIZE_DEFAULT_TIME +TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = CONFIG.TICKET_AUTHORIZE_DEFAULT_TIME_UNIT PERM_EXPIRED_CHECK_PERIODIC = CONFIG.PERM_EXPIRED_CHECK_PERIODIC WINDOWS_SSH_DEFAULT_SHELL = CONFIG.WINDOWS_SSH_DEFAULT_SHELL FLOWER_URL = CONFIG.FLOWER_URL diff --git a/apps/locale/ja/LC_MESSAGES/django.mo b/apps/locale/ja/LC_MESSAGES/django.mo index 0b1bfab48..a922b4d2e 100644 --- a/apps/locale/ja/LC_MESSAGES/django.mo +++ b/apps/locale/ja/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b21e8af3ad29606b9ff36bbf5da8dd03a041b25e046c3cedabb8650390c0a4c7 -size 132358 +oid sha256:7db985efdf818137dafe489f339955f4d71a245ffd6becc8f6efac539a625682 +size 133463 diff --git a/apps/locale/ja/LC_MESSAGES/django.po b/apps/locale/ja/LC_MESSAGES/django.po index 2db11ff18..c7b3c58a2 100644 --- a/apps/locale/ja/LC_MESSAGES/django.po +++ b/apps/locale/ja/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-14 17:07+0800\n" +"POT-Creation-Date: 2022-11-16 20:24+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -398,7 +398,7 @@ msgid "Host" msgstr "ホスト" #: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/clickhouse.py:10 +#: applications/serializers/attrs/application_type/clickhouse.py:11 #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 #: applications/serializers/attrs/application_type/mysql_workbench.py:22 @@ -470,6 +470,14 @@ msgstr "Chromeユーザー名" msgid "Chrome password" msgstr "Chromeパスワード" +#: applications/serializers/attrs/application_type/clickhouse.py:13 +msgid "" +"Typically, the port is 9000,the HTTP interface and the native interface use " +"different ports" +msgstr "" +"デフォルトポートは9000で、HTTPインタフェースとネイティブインタフェースは異な" +"るポートを使用する" + #: applications/serializers/attrs/application_type/custom.py:12 msgid "Operating parameter" msgstr "操作パラメータ" @@ -1436,11 +1444,11 @@ msgstr "文章の内容が長すぎる。Elasticsearchで操作履歴を保存 msgid "Tips" msgstr "謎々" -#: audits/handler.py:126 +#: audits/handler.py:134 msgid "Yes" msgstr "是" -#: audits/handler.py:126 +#: audits/handler.py:134 msgid "No" msgstr "否" @@ -1683,7 +1691,7 @@ msgstr "現在のユーザーはmfaタイプをサポートしていません: { msgid "User does not exist: {}" msgstr "ユーザーが存在しない: {}" -#: authentication/api/password.py:31 users/views/profile/reset.py:122 +#: authentication/api/password.py:31 users/views/profile/reset.py:127 msgid "No user matched" msgstr "ユーザーにマッチしなかった" @@ -2020,7 +2028,7 @@ msgstr "メッセージ検証コードが無効" #: authentication/serializers/password_mfa.py:24 #: settings/serializers/auth/sms.py:27 users/forms/profile.py:103 #: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 -#: users/views/profile/reset.py:74 +#: users/views/profile/reset.py:79 msgid "SMS" msgstr "メッセージ" @@ -2159,7 +2167,7 @@ msgstr "アセットまたはアプリが必要" #: settings/serializers/email.py:50 users/forms/profile.py:102 #: users/forms/profile.py:106 users/models/user.py:675 #: users/templates/users/forgot_password.html:116 -#: users/views/profile/reset.py:68 +#: users/views/profile/reset.py:73 msgid "Email" msgstr "メール" @@ -4007,7 +4015,7 @@ msgstr "ログインログは日数を保持します" #: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 #: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 -#: settings/serializers/cleaning.py:28 settings/serializers/other.py:37 +#: settings/serializers/cleaning.py:28 msgid "Unit: day" msgstr "単位: 日" @@ -4184,19 +4192,31 @@ msgstr "" msgid "Ticket authorize default time" msgstr "デフォルト製造オーダ承認時間" +#: settings/serializers/other.py:40 +msgid "day" +msgstr "日" + +#: settings/serializers/other.py:40 +msgid "hour" +msgstr "時" + #: settings/serializers/other.py:41 +msgid "Ticket authorize default time unit" +msgstr "デフォルト製造オーダ承認時間単位" + +#: settings/serializers/other.py:44 msgid "Help Docs URL" msgstr "ドキュメントリンク" -#: settings/serializers/other.py:42 +#: settings/serializers/other.py:45 msgid "default: http://docs.jumpserver.org" msgstr "デフォルト: http://docs.jumpserver.org" -#: settings/serializers/other.py:46 +#: settings/serializers/other.py:49 msgid "Help Support URL" msgstr "サポートリンク" -#: settings/serializers/other.py:47 +#: settings/serializers/other.py:50 msgid "default: http://www.jumpserver.org/support/" msgstr "デフォルト: http://www.jumpserver.org/support/" @@ -4520,7 +4540,9 @@ msgstr "デフォルトのグラフィック解像度" #: settings/serializers/terminal.py:52 msgid "" "Tip: Default resolution to use when connecting graphical assets in Luna pages" -msgstr "ヒント: Luna ページでグラフィック アセットを接続するときに使用するデフォルトの解像度" +msgstr "" +"ヒント: Luna ページでグラフィック アセットを接続するときに使用するデフォルト" +"の解像度" #: settings/utils/ldap.py:467 msgid "ldap:// or ldaps:// protocol is used." @@ -5887,7 +5909,7 @@ msgstr "ユーザーパスワード履歴" msgid "Reset password" msgstr "パスワードのリセット" -#: users/notifications.py:85 users/views/profile/reset.py:189 +#: users/notifications.py:85 users/views/profile/reset.py:194 msgid "Reset password success" msgstr "パスワードのリセット成功" @@ -6081,16 +6103,16 @@ msgstr "ここをクリックしてパスワードを設定してください" #: users/templates/users/forgot_password.html:32 msgid "Input your email account, that will send a email to your" -msgstr "電子メールアカウントを入力し、メールをメールアドレスに送信します" +msgstr "あなたのメールを入力し、それはあなたにメールを送信します" #: users/templates/users/forgot_password.html:35 msgid "" "Enter your mobile number and a verification code will be sent to your phone" -msgstr "携帯電話番号を入力すると認証コードが送信されます" +msgstr "携帯電話番号を入力すると、認証コードが携帯電話に送信されます" #: users/templates/users/forgot_password.html:57 msgid "Email account" -msgstr "メールアドレスアカウント" +msgstr "メールアドレス" #: users/templates/users/forgot_password.html:61 msgid "Mobile number" @@ -6251,23 +6273,30 @@ msgstr "OTP無効化成功、ログインページを返す" msgid "Password invalid" msgstr "パスワード無効" -#: users/views/profile/reset.py:144 users/views/profile/reset.py:155 +#: users/views/profile/reset.py:47 +msgid "" +"Non-local users can log in only from third-party platforms and cannot change " +"their passwords: {}" +msgstr "ローカル以外のユーザーは、サードパーティ プラットフォームからのログインのみが許可され、" +"パスワードの変更はサポートされていません: {}" + +#: users/views/profile/reset.py:149 users/views/profile/reset.py:160 msgid "Token invalid or expired" msgstr "トークンが無効または期限切れ" -#: users/views/profile/reset.py:160 +#: users/views/profile/reset.py:165 msgid "User auth from {}, go there change password" msgstr "ユーザー認証ソース {}, 対応するシステムにパスワードを変更してください" -#: users/views/profile/reset.py:167 +#: users/views/profile/reset.py:172 msgid "* Your password does not meet the requirements" msgstr "* パスワードが要件を満たしていない" -#: users/views/profile/reset.py:173 +#: users/views/profile/reset.py:178 msgid "* The new password cannot be the last {} passwords" msgstr "* 新しいパスワードを最後の {} パスワードにすることはできません" -#: users/views/profile/reset.py:190 +#: users/views/profile/reset.py:195 msgid "Reset password success, return to login page" msgstr "パスワードの成功をリセットし、ログインページに戻る" diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index 40b94f6d7..a06c70b86 100644 --- a/apps/locale/zh/LC_MESSAGES/django.mo +++ b/apps/locale/zh/LC_MESSAGES/django.mo @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9dbc8ef62e3746cebd07177934c7d68c373ff0ecceff4a83aeeb9e96829359b -size 108613 +oid sha256:b143c62843946c3e18b623d05065f12e9d3c578efe5cd0d2016056d2b8448ae8 +size 109495 diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index f2255f996..ac9bd433c 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: JumpServer 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-14 17:07+0800\n" +"POT-Creation-Date: 2022-11-16 20:24+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n" "Last-Translator: ibuler \n" "Language-Team: JumpServer team\n" @@ -393,7 +393,7 @@ msgid "Host" msgstr "主机" #: applications/serializers/attrs/application_category/db.py:12 -#: applications/serializers/attrs/application_type/clickhouse.py:10 +#: applications/serializers/attrs/application_type/clickhouse.py:11 #: applications/serializers/attrs/application_type/mongodb.py:10 #: applications/serializers/attrs/application_type/mysql.py:10 #: applications/serializers/attrs/application_type/mysql_workbench.py:22 @@ -465,6 +465,12 @@ msgstr "Chrome 用户名" msgid "Chrome password" msgstr "Chrome 密码" +#: applications/serializers/attrs/application_type/clickhouse.py:13 +msgid "" +"Typically, the port is 9000,the HTTP interface and the native interface use " +"different ports" +msgstr "默认端口为9000, HTTP接口和本机接口使用不同的端口" + #: applications/serializers/attrs/application_type/custom.py:12 msgid "Operating parameter" msgstr "运行参数" @@ -1424,11 +1430,11 @@ msgstr "文字内容太长。请使用 Elasticsearch 存储操作日志" msgid "Tips" msgstr "提示" -#: audits/handler.py:126 +#: audits/handler.py:134 msgid "Yes" msgstr "是" -#: audits/handler.py:126 +#: audits/handler.py:134 msgid "No" msgstr "否" @@ -1671,7 +1677,7 @@ msgstr "当前用户不支持 MFA 类型: {}" msgid "User does not exist: {}" msgstr "用户不存在: {}" -#: authentication/api/password.py:31 users/views/profile/reset.py:122 +#: authentication/api/password.py:31 users/views/profile/reset.py:127 msgid "No user matched" msgstr "没有匹配到用户" @@ -1996,7 +2002,7 @@ msgstr "短信验证码校验失败" #: authentication/serializers/password_mfa.py:24 #: settings/serializers/auth/sms.py:27 users/forms/profile.py:103 #: users/forms/profile.py:106 users/templates/users/forgot_password.html:111 -#: users/views/profile/reset.py:74 +#: users/views/profile/reset.py:79 msgid "SMS" msgstr "短信" @@ -2131,7 +2137,7 @@ msgstr "资产或应用必填" #: settings/serializers/email.py:50 users/forms/profile.py:102 #: users/forms/profile.py:106 users/models/user.py:675 #: users/templates/users/forgot_password.html:116 -#: users/views/profile/reset.py:68 +#: users/views/profile/reset.py:73 msgid "Email" msgstr "邮箱" @@ -3959,7 +3965,7 @@ msgstr "登录日志" #: settings/serializers/cleaning.py:12 settings/serializers/cleaning.py:16 #: settings/serializers/cleaning.py:20 settings/serializers/cleaning.py:24 -#: settings/serializers/cleaning.py:28 settings/serializers/other.py:37 +#: settings/serializers/cleaning.py:28 msgid "Unit: day" msgstr "单位: 天" @@ -4128,19 +4134,31 @@ msgstr "" msgid "Ticket authorize default time" msgstr "默认工单授权时间" +#: settings/serializers/other.py:40 +msgid "day" +msgstr "天" + +#: settings/serializers/other.py:40 +msgid "hour" +msgstr "时" + #: settings/serializers/other.py:41 +msgid "Ticket authorize default time unit" +msgstr "默认工单授权时间单位" + +#: settings/serializers/other.py:44 msgid "Help Docs URL" msgstr "文档链接" -#: settings/serializers/other.py:42 +#: settings/serializers/other.py:45 msgid "default: http://docs.jumpserver.org" msgstr "默认: http://dev.jumpserver.org:8080" -#: settings/serializers/other.py:46 +#: settings/serializers/other.py:49 msgid "Help Support URL" msgstr "支持链接" -#: settings/serializers/other.py:47 +#: settings/serializers/other.py:50 msgid "default: http://www.jumpserver.org/support/" msgstr "默认: http://www.jumpserver.org/support/" @@ -5796,7 +5814,7 @@ msgstr "用户密码历史" msgid "Reset password" msgstr "重置密码" -#: users/notifications.py:85 users/views/profile/reset.py:189 +#: users/notifications.py:85 users/views/profile/reset.py:194 msgid "Reset password success" msgstr "重置密码成功" @@ -5988,7 +6006,7 @@ msgstr "点击这里设置密码" #: users/templates/users/forgot_password.html:32 msgid "Input your email account, that will send a email to your" -msgstr "输入您的邮箱账号, 将会发一封重置邮件到您的邮箱中" +msgstr "输入您的邮箱, 将会发一封重置邮件到您的邮箱中" #: users/templates/users/forgot_password.html:35 msgid "" @@ -6150,23 +6168,29 @@ msgstr "MFA(OTP) 禁用成功,返回登录页面" msgid "Password invalid" msgstr "用户名或密码无效" -#: users/views/profile/reset.py:144 users/views/profile/reset.py:155 +#: users/views/profile/reset.py:47 +msgid "" +"Non-local users can log in only from third-party platforms and cannot change " +"their passwords: {}" +msgstr "非本地用户仅允许从第三方平台登录,不支持修改密码: {}" + +#: users/views/profile/reset.py:149 users/views/profile/reset.py:160 msgid "Token invalid or expired" msgstr "Token错误或失效" -#: users/views/profile/reset.py:160 +#: users/views/profile/reset.py:165 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/views/profile/reset.py:167 +#: users/views/profile/reset.py:172 msgid "* Your password does not meet the requirements" msgstr "* 您的密码不符合要求" -#: users/views/profile/reset.py:173 +#: users/views/profile/reset.py:178 msgid "* The new password cannot be the last {} passwords" msgstr "* 新密码不能是最近 {} 次的密码" -#: users/views/profile/reset.py:190 +#: users/views/profile/reset.py:195 msgid "Reset password success, return to login page" msgstr "重置密码成功,返回到登录页面" diff --git a/apps/settings/serializers/other.py b/apps/settings/serializers/other.py index 1eb57a673..c3d2f6b50 100644 --- a/apps/settings/serializers/other.py +++ b/apps/settings/serializers/other.py @@ -33,10 +33,13 @@ class OtherSettingSerializer(serializers.Serializer): ) TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField( - min_value=7, max_value=9999, required=False, - label=_("Ticket authorize default time"), help_text=_("Unit: day") + min_value=1, max_value=999999, required=False, + label=_("Ticket authorize default time") + ) + TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField( + choices=[('day', _("day")), ('hour', _("hour"))], + label=_("Ticket authorize default time unit"), required=False, ) - HELP_DOCUMENT_URL = serializers.URLField( required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"), help_text=_('default: http://docs.jumpserver.org') @@ -51,4 +54,3 @@ class OtherSettingSerializer(serializers.Serializer): # PERIOD_TASK_ENABLED = serializers.BooleanField( # required=False, label=_("Enable period task") # ) - diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 7768375e4..d0eafd018 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -15,6 +15,7 @@ class PrivateSettingSerializer(PublicSettingSerializer): WINDOWS_SKIP_ALL_MANUAL_PASSWORD = serializers.BooleanField() OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField() TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField() + TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.CharField() SECURITY_MAX_IDLE_TIME = serializers.IntegerField() SECURITY_VIEW_AUTH_NEED_MFA = serializers.BooleanField() SECURITY_MFA_VERIFY_TTL = serializers.IntegerField() diff --git a/apps/terminal/utils/db_port_mapper.py b/apps/terminal/utils/db_port_mapper.py index 9a47150f7..973e25c2a 100644 --- a/apps/terminal/utils/db_port_mapper.py +++ b/apps/terminal/utils/db_port_mapper.py @@ -51,22 +51,23 @@ class DBPortManager(object): def pop(self, db: Application): mapper = self.get_mapper() - to_delete_port = self.get_port_by_db(db) + to_delete_port = self.get_port_by_db(db, raise_exception=False) mapper.pop(to_delete_port, None) self.set_mapper(mapper) - def get_port_by_db(self, db): + def get_port_by_db(self, db, raise_exception=True): mapper = self.get_mapper() for port, db_id in mapper.items(): if db_id == str(db.id): return port - error = _( - 'No available port is matched. ' - 'The number of databases may have exceeded the number of ports ' - 'open to the database agent service, ' - 'Contact the administrator to open more ports.' - ) - raise JMSException(error) + if raise_exception: + error = _( + 'No available port is matched. ' + 'The number of databases may have exceeded the number of ports ' + 'open to the database agent service, ' + 'Contact the administrator to open more ports.' + ) + raise JMSException(error) def get_db_by_port(self, port): try: diff --git a/apps/tickets/api/ticket.py b/apps/tickets/api/ticket.py index d6e623892..e4dc94852 100644 --- a/apps/tickets/api/ticket.py +++ b/apps/tickets/api/ticket.py @@ -63,8 +63,6 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet): def perform_create(self, serializer): instance = serializer.save() - instance.applicant = self.request.user - instance.save(update_fields=['applicant']) instance.open() @action(detail=False, methods=[POST], permission_classes=[RBACPermission, ]) diff --git a/apps/tickets/serializers/ticket/ticket.py b/apps/tickets/serializers/ticket/ticket.py index a0760e4d0..007be5aa8 100644 --- a/apps/tickets/serializers/ticket/ticket.py +++ b/apps/tickets/serializers/ticket/ticket.py @@ -3,6 +3,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from users.models import User from orgs.models import Organization from orgs.mixins.serializers import OrgResourceModelSerializerMixin from tickets.models import Ticket, TicketFlow @@ -70,6 +71,7 @@ class TicketApplySerializer(TicketSerializer): org_id = serializers.CharField( required=True, max_length=36, allow_blank=True, label=_("Organization") ) + applicant = serializers.CharField(required=False, allow_blank=True) class Meta: model = Ticket @@ -78,6 +80,15 @@ class TicketApplySerializer(TicketSerializer): 'type': {'required': True} } + def get_applicant(self, applicant_id): + current_user = self.context['request'].user + want_applicant = User.objects.filter(id=applicant_id).first() + if want_applicant and current_user.has_perm('tickets.add_superticket'): + applicant = want_applicant + else: + applicant = current_user + return applicant + @staticmethod def validate_org_id(org_id): org = Organization.get_instance(org_id) @@ -98,4 +109,6 @@ class TicketApplySerializer(TicketSerializer): else: error = _('The ticket flow `{}` does not exist'.format(ticket_type)) raise serializers.ValidationError(error) + + attrs['applicant'] = self.get_applicant(attrs.get('applicant')) return attrs diff --git a/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py b/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py index 399605915..d4bc961ef 100644 --- a/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py +++ b/apps/users/migrations/0002_auto_20171225_1157_squashed_0019_auto_20190304_1459.py @@ -49,7 +49,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='is_first_login', - field=models.BooleanField(default=True), + field=models.BooleanField(default=True, verbose_name='Is first login'), ), migrations.AlterField( model_name='usergroup', diff --git a/apps/users/views/profile/reset.py b/apps/users/views/profile/reset.py index fd68efb87..2972225ef 100644 --- a/apps/users/views/profile/reset.py +++ b/apps/users/views/profile/reset.py @@ -43,6 +43,11 @@ class UserForgotPasswordPreviewingView(FormView): if not user: form.add_error('username', _('User does not exist: {}').format(username)) return super().form_invalid(form) + if settings.ONLY_ALLOW_AUTH_FROM_SOURCE and not user.is_local: + error = _('Non-local users can log in only from third-party platforms ' + 'and cannot change their passwords: {}').format(username) + form.add_error('username', error) + return super().form_invalid(form) token = random_string(36) user_map = {'username': user.username, 'phone': user.phone, 'email': user.email}