Merge pull request #8260 from jumpserver/dev

v2.22.0-rc3
pull/8289/head
Jiangjie.Bai 2022-05-17 21:14:05 +08:00 committed by GitHub
commit d672122c79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 157 additions and 93 deletions

View File

@ -123,6 +123,8 @@ class LoginACL(BaseACL):
'org_id': Organization.ROOT_ID, 'org_id': Organization.ROOT_ID,
} }
ticket = Ticket.objects.create(**data) ticket = Ticket.objects.create(**data)
ticket.create_process_map_and_node(self.reviewers.all()) applicant = self.user
ticket.open(self.user) assignees = self.reviewers.all()
ticket.create_process_map_and_node(assignees, applicant)
ticket.open(applicant)
return ticket return ticket

View File

@ -97,7 +97,7 @@ class LoginAssetACL(BaseACL, OrgModelMixin):
'org_id': org_id, 'org_id': org_id,
} }
ticket = Ticket.objects.create(**data) ticket = Ticket.objects.create(**data)
ticket.create_process_map_and_node(assignees) ticket.create_process_map_and_node(assignees, user)
ticket.open(applicant=user) ticket.open(applicant=user)
return ticket return ticket

View File

@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from assets.serializers.base import AuthSerializerMixin from assets.serializers.base import AuthSerializerMixin
from common.drf.serializers import MethodSerializer from common.drf.serializers import MethodSerializer, SecretReadableMixin
from .attrs import ( from .attrs import (
category_serializer_classes_mapping, category_serializer_classes_mapping,
type_serializer_classes_mapping, type_serializer_classes_mapping,
@ -152,7 +152,7 @@ class AppAccountSerializer(AppSerializerMixin, AuthSerializerMixin, BulkOrgResou
return super().to_representation(instance) return super().to_representation(instance)
class AppAccountSecretSerializer(AppAccountSerializer): class AppAccountSecretSerializer(SecretReadableMixin, AppAccountSerializer):
class Meta(AppAccountSerializer.Meta): class Meta(AppAccountSerializer.Meta):
fields_backup = [ fields_backup = [
'id', 'app_display', 'attrs', 'username', 'password', 'private_key', 'id', 'app_display', 'attrs', 'username', 'password', 'private_key',

View File

@ -181,8 +181,10 @@ class CommandFilterRule(OrgModelMixin):
'org_id': org_id, 'org_id': org_id,
} }
ticket = Ticket.objects.create(**data) ticket = Ticket.objects.create(**data)
ticket.create_process_map_and_node(self.reviewers.all()) applicant = session.user_obj
ticket.open(applicant=session.user_obj) assignees = self.reviewers.all()
ticket.create_process_map_and_node(assignees, applicant)
ticket.open(applicant)
return ticket return ticket
@classmethod @classmethod

View File

@ -7,6 +7,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .base import AuthSerializerMixin from .base import AuthSerializerMixin
from .utils import validate_password_contains_left_double_curly_bracket from .utils import validate_password_contains_left_double_curly_bracket
from common.utils.encode import ssh_pubkey_gen from common.utils.encode import ssh_pubkey_gen
from common.drf.serializers import SecretReadableMixin
class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer): class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
@ -70,7 +71,7 @@ class AccountSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
return super().to_representation(instance) return super().to_representation(instance)
class AccountSecretSerializer(AccountSerializer): class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
class Meta(AccountSerializer.Meta): class Meta(AccountSerializer.Meta):
fields_backup = [ fields_backup = [
'hostname', 'ip', 'platform', 'protocols', 'username', 'password', 'hostname', 'ip', 'platform', 'protocols', 'username', 'password',

View File

@ -4,6 +4,7 @@ from django.db.models import Count
from common.mixins.serializers import BulkSerializerMixin from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen from common.utils import ssh_pubkey_gen
from common.drf.fields import EncryptedField
from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re from common.validators import alphanumeric_re, alphanumeric_cn_re, alphanumeric_win_re
from orgs.mixins.serializers import BulkOrgResourceModelSerializer from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser, Asset from ..models import SystemUser, Asset
@ -26,6 +27,9 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True) auto_generate_key = serializers.BooleanField(initial=True, required=False, write_only=True)
type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display')) type_display = serializers.ReadOnlyField(source='get_type_display', label=_('Type display'))
ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint')) ssh_key_fingerprint = serializers.ReadOnlyField(label=_('SSH key fingerprint'))
token = EncryptedField(
label=_('Token'), required=False, write_only=True, style={'base_template': 'textarea.html'}
)
applications_amount = serializers.IntegerField( applications_amount = serializers.IntegerField(
source='apps_amount', read_only=True, label=_('Apps amount') source='apps_amount', read_only=True, label=_('Apps amount')
) )

View File

@ -205,12 +205,13 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') redirect_url = request.GET.get('redirect_url') or reverse('index')
next_url = self.get_next_url_from_meta() or reverse('index')
redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True) redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({ redirect_uri += '?' + urlencode({
'redirect_url': redirect_url, 'redirect_url': redirect_url,
'next': self.get_next_url_from_meta() 'next': next_url,
}) })
url = self.get_qr_url(redirect_uri) url = self.get_qr_url(redirect_uri)

View File

@ -170,10 +170,11 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') redirect_url = request.GET.get('redirect_url') or reverse('index')
redirect_uri = reverse('authentication:feishu-qr-login-callback', external=True) redirect_uri = reverse('authentication:feishu-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) redirect_uri += '?' + urlencode({
'redirect_url': redirect_url,
})
url = self.get_qr_url(redirect_uri) url = self.get_qr_url(redirect_uri)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -201,12 +201,12 @@ class WeComQRLoginView(WeComQRMixin, METAMixin, View):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') redirect_url = request.GET.get('redirect_url') or reverse('index')
next_url = self.get_next_url_from_meta() or reverse('index')
redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True) redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({ redirect_uri += '?' + urlencode({
'redirect_url': redirect_url, 'redirect_url': redirect_url,
'next': self.get_next_url_from_meta() 'next': next_url,
}) })
url = self.get_qr_url(redirect_uri) url = self.get_qr_url(redirect_uri)

View File

@ -27,8 +27,10 @@ class ReadableHiddenField(serializers.HiddenField):
class EncryptedField(serializers.CharField): class EncryptedField(serializers.CharField):
def __init__(self, **kwargs): def __init__(self, write_only=None, **kwargs):
kwargs['write_only'] = True if write_only is None:
write_only = True
kwargs['write_only'] = write_only
super().__init__(**kwargs) super().__init__(**kwargs)
def to_internal_value(self, value): def to_internal_value(self, value):

View File

@ -8,10 +8,12 @@ from common.mixins import BulkListSerializerMixin
from django.utils.functional import cached_property from django.utils.functional import cached_property
from rest_framework.utils.serializer_helpers import BindingDict from rest_framework.utils.serializer_helpers import BindingDict
from common.mixins.serializers import BulkSerializerMixin from common.mixins.serializers import BulkSerializerMixin
from common.drf.fields import EncryptedField
__all__ = [ __all__ = [
'MethodSerializer', 'MethodSerializer',
'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer' 'EmptySerializer', 'BulkModelSerializer', 'AdaptedBulkListSerializer', 'CeleryTaskSerializer',
'SecretReadableMixin'
] ]
@ -83,3 +85,20 @@ class CeleryTaskSerializer(serializers.Serializer):
task = serializers.CharField(read_only=True) task = serializers.CharField(read_only=True)
class SecretReadableMixin(serializers.Serializer):
""" 加密字段 (EncryptedField) 可读性 """
def __init__(self, *args, **kwargs):
super(SecretReadableMixin, self).__init__(*args, **kwargs)
if not hasattr(self, 'Meta') or not hasattr(self.Meta, 'extra_kwargs'):
return
extra_kwargs = self.Meta.extra_kwargs
for field_name, serializer_field in self.fields.items():
if not isinstance(serializer_field, EncryptedField):
continue
if field_name not in extra_kwargs:
continue
field_extra_kwargs = extra_kwargs[field_name]
if 'write_only' not in field_extra_kwargs:
continue
serializer_field.write_only = field_extra_kwargs['write_only']

View File

@ -389,6 +389,8 @@ class Config(dict):
'FTP_LOG_KEEP_DAYS': 200, 'FTP_LOG_KEEP_DAYS': 200,
'CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS': 30, 'CLOUD_SYNC_TASK_EXECUTION_KEEP_DAYS': 30,
'TICKETS_ENABLED': True,
# 废弃的 # 废弃的
'DEFAULT_ORG_SHOW_ALL_USERS': True, 'DEFAULT_ORG_SHOW_ALL_USERS': True,
'ORG_CHANGE_TO_URL': '', 'ORG_CHANGE_TO_URL': '',

View File

@ -119,6 +119,7 @@ CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABL
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S' DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
TICKETS_ENABLED = CONFIG.TICKETS_ENABLED
REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED REFERER_CHECK_ENABLED = CONFIG.REFERER_CHECK_ENABLED
CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED CONNECTION_TOKEN_ENABLED = CONFIG.CONNECTION_TOKEN_ENABLED

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:01a52223f421d736b00a600f623d28ac4a43e97a30f5e9cbebc3e6d18ed4527e oid sha256:843b6dffe6af09073053e21f65be4c8264e6dee05509b375c8191dde8c9079b6
size 127324 size 127386

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-16 17:43+0800\n" "POT-Creation-Date: 2022-05-17 18:02+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -758,10 +758,12 @@ msgstr "接続性"
msgid "Date verified" msgid "Date verified"
msgstr "確認済みの日付" msgstr "確認済みの日付"
#: assets/models/base.py:177 audits/signal_handlers.py:48 #: assets/models/base.py:177 assets/serializers/base.py:14
#: assets/serializers/base.py:36 audits/signal_handlers.py:48
#: authentication/forms.py:32 #: authentication/forms.py:32
#: authentication/templates/authentication/login.html:182 #: authentication/templates/authentication/login.html:182
#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46
#: users/forms/profile.py:22 users/serializers/user.py:92
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:42
@ -771,7 +773,8 @@ msgstr "確認済みの日付"
msgid "Password" msgid "Password"
msgstr "パスワード" msgstr "パスワード"
#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 #: assets/models/base.py:178 assets/serializers/base.py:39
#: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
@ -1120,11 +1123,15 @@ msgstr "定期的なパフォーマンス"
msgid "Currently only mail sending is supported" msgid "Currently only mail sending is supported"
msgstr "現在、メール送信のみがサポートされています" msgstr "現在、メール送信のみがサポートされています"
#: assets/serializers/base.py:39 #: assets/serializers/base.py:15 users/models/user.py:689
msgid "Private key"
msgstr "ssh秘密鍵"
#: assets/serializers/base.py:43
msgid "Key password" msgid "Key password"
msgstr "キーパスワード" msgstr "キーパスワード"
#: assets/serializers/base.py:52 #: assets/serializers/base.py:56
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "秘密鍵が無効またはpassphraseエラー" msgstr "秘密鍵が無効またはpassphraseエラー"
@ -2102,7 +2109,7 @@ msgstr "バインディングリマインダー"
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
#: perms/serializers/asset/permission.py:19 #: perms/serializers/asset/permission.py:19
#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 #: perms/serializers/asset/permission.py:45 users/serializers/user.py:145
msgid "Is valid" msgid "Is valid"
msgstr "有効です" msgstr "有効です"
@ -3063,7 +3070,7 @@ msgstr "Organization {} のアプリケーション権限"
#: perms/serializers/application/permission.py:40 #: perms/serializers/application/permission.py:40
#: perms/serializers/asset/permission.py:20 #: perms/serializers/asset/permission.py:20
#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 #: perms/serializers/asset/permission.py:44 users/serializers/user.py:87
#: users/serializers/user.py:145 #: users/serializers/user.py:147
msgid "Is expired" msgid "Is expired"
msgstr "期限切れです" msgstr "期限切れです"
@ -3773,6 +3780,10 @@ msgstr "アナウンスの有効化"
msgid "Announcement" msgid "Announcement"
msgstr "発表" msgstr "発表"
#: settings/serializers/basic.py:46
msgid "Enable tickets"
msgstr "チケットを有効にする"
#: settings/serializers/cleaning.py:10 #: settings/serializers/cleaning.py:10
msgid "Login log keep days" msgid "Login log keep days"
msgstr "ログインログは日数を保持します" msgstr "ログインログは日数を保持します"
@ -5640,7 +5651,7 @@ msgstr "強制有効"
msgid "Local" msgid "Local"
msgstr "ローカル" msgstr "ローカル"
#: users/models/user.py:673 users/serializers/user.py:144 #: users/models/user.py:673 users/serializers/user.py:146
msgid "Is service account" msgid "Is service account"
msgstr "サービスアカウントです" msgstr "サービスアカウントです"
@ -5652,10 +5663,6 @@ msgstr "アバター"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:689
msgid "Private key"
msgstr "ssh秘密鍵"
#: users/models/user.py:711 #: users/models/user.py:711
msgid "Source" msgid "Source"
msgstr "ソース" msgstr "ソース"
@ -5739,7 +5746,7 @@ msgstr "新しいパスワードを最後の {} 個のパスワードにする
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "新しく設定されたパスワードが一致しない" msgstr "新しく設定されたパスワードが一致しない"
#: users/serializers/profile.py:147 users/serializers/user.py:142 #: users/serializers/profile.py:147 users/serializers/user.py:144
msgid "Is first login" msgid "Is first login"
msgstr "最初のログインです" msgstr "最初のログインです"
@ -5777,63 +5784,63 @@ msgstr "ログインブロック"
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "公開鍵認証が可能" msgstr "公開鍵認証が可能"
#: users/serializers/user.py:146 #: users/serializers/user.py:148
msgid "Avatar url" msgid "Avatar url"
msgstr "アバターURL" msgstr "アバターURL"
#: users/serializers/user.py:148 #: users/serializers/user.py:150
msgid "Groups name" msgid "Groups name"
msgstr "グループ名" msgstr "グループ名"
#: users/serializers/user.py:149 #: users/serializers/user.py:151
msgid "Source name" msgid "Source name"
msgstr "ソース名" msgstr "ソース名"
#: users/serializers/user.py:150 #: users/serializers/user.py:152
msgid "Organization role name" msgid "Organization role name"
msgstr "組織の役割名" msgstr "組織の役割名"
#: users/serializers/user.py:151 #: users/serializers/user.py:153
msgid "Super role name" msgid "Super role name"
msgstr "スーパーロール名" msgstr "スーパーロール名"
#: users/serializers/user.py:152 #: users/serializers/user.py:154
msgid "Total role name" msgid "Total role name"
msgstr "合計ロール名" msgstr "合計ロール名"
#: users/serializers/user.py:154 #: users/serializers/user.py:156
msgid "Is wecom bound" msgid "Is wecom bound"
msgstr "企業の微信をバインドしているかどうか" msgstr "企業の微信をバインドしているかどうか"
#: users/serializers/user.py:155 #: users/serializers/user.py:157
msgid "Is dingtalk bound" msgid "Is dingtalk bound"
msgstr "ピンをバインドしているかどうか" msgstr "ピンをバインドしているかどうか"
#: users/serializers/user.py:156 #: users/serializers/user.py:158
msgid "Is feishu bound" msgid "Is feishu bound"
msgstr "飛本を縛ったかどうか" msgstr "飛本を縛ったかどうか"
#: users/serializers/user.py:157 #: users/serializers/user.py:159
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "仮想MFAがバインドされているか" msgstr "仮想MFAがバインドされているか"
#: users/serializers/user.py:159 #: users/serializers/user.py:161
msgid "System role name" msgid "System role name"
msgstr "システムロール名" msgstr "システムロール名"
#: users/serializers/user.py:199 #: users/serializers/user.py:201
msgid "User cannot self-update fields: {}" msgid "User cannot self-update fields: {}"
msgstr "ユーザーは自分のフィールドを更新できません: {}" msgstr "ユーザーは自分のフィールドを更新できません: {}"
#: users/serializers/user.py:256 #: users/serializers/user.py:258
msgid "Select users" msgid "Select users"
msgstr "ユーザーの選択" msgstr "ユーザーの選択"
#: users/serializers/user.py:257 #: users/serializers/user.py:259
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "セキュリティのために、複数のユーザーのみをリストします" msgstr "セキュリティのために、複数のユーザーのみをリストします"
#: users/serializers/user.py:292 #: users/serializers/user.py:294
msgid "name not unique" msgid "name not unique"
msgstr "名前が一意ではない" msgstr "名前が一意ではない"

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e4a00b4e1a3bc944c968987fd3c65798fb39fa552e91457693ec8fcb597820f0 oid sha256:a78975a5a6669bfcc0f99bc4d47811be82a0620e873e51e4a17d06548e3b1e7f
size 105225 size 105269

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: JumpServer 0.3.3\n" "Project-Id-Version: JumpServer 0.3.3\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-16 17:43+0800\n" "POT-Creation-Date: 2022-05-17 18:02+0800\n"
"PO-Revision-Date: 2021-05-20 10:54+0800\n" "PO-Revision-Date: 2021-05-20 10:54+0800\n"
"Last-Translator: ibuler <ibuler@qq.com>\n" "Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: JumpServer team<ibuler@qq.com>\n" "Language-Team: JumpServer team<ibuler@qq.com>\n"
@ -753,10 +753,12 @@ msgstr "可连接性"
msgid "Date verified" msgid "Date verified"
msgstr "校验日期" msgstr "校验日期"
#: assets/models/base.py:177 audits/signal_handlers.py:48 #: assets/models/base.py:177 assets/serializers/base.py:14
#: assets/serializers/base.py:36 audits/signal_handlers.py:48
#: authentication/forms.py:32 #: authentication/forms.py:32
#: authentication/templates/authentication/login.html:182 #: authentication/templates/authentication/login.html:182
#: settings/serializers/auth/ldap.py:46 users/forms/profile.py:22 #: settings/serializers/auth/ldap.py:25 settings/serializers/auth/ldap.py:46
#: users/forms/profile.py:22 users/serializers/user.py:92
#: users/templates/users/_msg_user_created.html:13 #: users/templates/users/_msg_user_created.html:13
#: users/templates/users/user_password_verify.html:18 #: users/templates/users/user_password_verify.html:18
#: xpack/plugins/change_auth_plan/models/base.py:42 #: xpack/plugins/change_auth_plan/models/base.py:42
@ -766,7 +768,8 @@ msgstr "校验日期"
msgid "Password" msgid "Password"
msgstr "密码" msgstr "密码"
#: assets/models/base.py:178 xpack/plugins/change_auth_plan/models/asset.py:53 #: assets/models/base.py:178 assets/serializers/base.py:39
#: xpack/plugins/change_auth_plan/models/asset.py:53
#: xpack/plugins/change_auth_plan/models/asset.py:130 #: xpack/plugins/change_auth_plan/models/asset.py:130
#: xpack/plugins/change_auth_plan/models/asset.py:206 #: xpack/plugins/change_auth_plan/models/asset.py:206
msgid "SSH private key" msgid "SSH private key"
@ -1112,11 +1115,15 @@ msgstr "定时执行"
msgid "Currently only mail sending is supported" msgid "Currently only mail sending is supported"
msgstr "当前只支持邮件发送" msgstr "当前只支持邮件发送"
#: assets/serializers/base.py:39 #: assets/serializers/base.py:15 users/models/user.py:689
msgid "Private key"
msgstr "ssh私钥"
#: assets/serializers/base.py:43
msgid "Key password" msgid "Key password"
msgstr "密钥密码" msgstr "密钥密码"
#: assets/serializers/base.py:52 #: assets/serializers/base.py:56
msgid "private key invalid or passphrase error" msgid "private key invalid or passphrase error"
msgstr "密钥不合法或密钥密码错误" msgstr "密钥不合法或密钥密码错误"
@ -2081,7 +2088,7 @@ msgstr "绑定提醒"
#: perms/serializers/application/permission.py:20 #: perms/serializers/application/permission.py:20
#: perms/serializers/application/permission.py:41 #: perms/serializers/application/permission.py:41
#: perms/serializers/asset/permission.py:19 #: perms/serializers/asset/permission.py:19
#: perms/serializers/asset/permission.py:45 users/serializers/user.py:143 #: perms/serializers/asset/permission.py:45 users/serializers/user.py:145
msgid "Is valid" msgid "Is valid"
msgstr "账号是否有效" msgstr "账号是否有效"
@ -3027,7 +3034,7 @@ msgstr "组织 ({}) 的应用授权"
#: perms/serializers/application/permission.py:40 #: perms/serializers/application/permission.py:40
#: perms/serializers/asset/permission.py:20 #: perms/serializers/asset/permission.py:20
#: perms/serializers/asset/permission.py:44 users/serializers/user.py:87 #: perms/serializers/asset/permission.py:44 users/serializers/user.py:87
#: users/serializers/user.py:145 #: users/serializers/user.py:147
msgid "Is expired" msgid "Is expired"
msgstr "已过期" msgstr "已过期"
@ -3733,6 +3740,10 @@ msgstr "启用公告"
msgid "Announcement" msgid "Announcement"
msgstr "公告" msgstr "公告"
#: settings/serializers/basic.py:46
msgid "Enable tickets"
msgstr "启用工单"
#: settings/serializers/cleaning.py:10 #: settings/serializers/cleaning.py:10
msgid "Login log keep days" msgid "Login log keep days"
msgstr "登录日志" msgstr "登录日志"
@ -5562,7 +5573,7 @@ msgstr "强制启用"
msgid "Local" msgid "Local"
msgstr "数据库" msgstr "数据库"
#: users/models/user.py:673 users/serializers/user.py:144 #: users/models/user.py:673 users/serializers/user.py:146
msgid "Is service account" msgid "Is service account"
msgstr "服务账号" msgstr "服务账号"
@ -5574,10 +5585,6 @@ msgstr "头像"
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:689
msgid "Private key"
msgstr "ssh私钥"
#: users/models/user.py:711 #: users/models/user.py:711
msgid "Source" msgid "Source"
msgstr "来源" msgstr "来源"
@ -5661,7 +5668,7 @@ msgstr "新密码不能是最近 {} 次的密码"
msgid "The newly set password is inconsistent" msgid "The newly set password is inconsistent"
msgstr "两次密码不一致" msgstr "两次密码不一致"
#: users/serializers/profile.py:147 users/serializers/user.py:142 #: users/serializers/profile.py:147 users/serializers/user.py:144
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
@ -5699,63 +5706,63 @@ msgstr "登录被阻塞"
msgid "Can public key authentication" msgid "Can public key authentication"
msgstr "能否公钥认证" msgstr "能否公钥认证"
#: users/serializers/user.py:146 #: users/serializers/user.py:148
msgid "Avatar url" msgid "Avatar url"
msgstr "头像路径" msgstr "头像路径"
#: users/serializers/user.py:148 #: users/serializers/user.py:150
msgid "Groups name" msgid "Groups name"
msgstr "用户组名" msgstr "用户组名"
#: users/serializers/user.py:149 #: users/serializers/user.py:151
msgid "Source name" msgid "Source name"
msgstr "用户来源名" msgstr "用户来源名"
#: users/serializers/user.py:150 #: users/serializers/user.py:152
msgid "Organization role name" msgid "Organization role name"
msgstr "组织角色名称" msgstr "组织角色名称"
#: users/serializers/user.py:151 #: users/serializers/user.py:153
msgid "Super role name" msgid "Super role name"
msgstr "超级角色名称" msgstr "超级角色名称"
#: users/serializers/user.py:152 #: users/serializers/user.py:154
msgid "Total role name" msgid "Total role name"
msgstr "汇总角色名称" msgstr "汇总角色名称"
#: users/serializers/user.py:154 #: users/serializers/user.py:156
msgid "Is wecom bound" msgid "Is wecom bound"
msgstr "是否绑定了企业微信" msgstr "是否绑定了企业微信"
#: users/serializers/user.py:155 #: users/serializers/user.py:157
msgid "Is dingtalk bound" msgid "Is dingtalk bound"
msgstr "是否绑定了钉钉" msgstr "是否绑定了钉钉"
#: users/serializers/user.py:156 #: users/serializers/user.py:158
msgid "Is feishu bound" msgid "Is feishu bound"
msgstr "是否绑定了飞书" msgstr "是否绑定了飞书"
#: users/serializers/user.py:157 #: users/serializers/user.py:159
msgid "Is OTP bound" msgid "Is OTP bound"
msgstr "是否绑定了虚拟 MFA" msgstr "是否绑定了虚拟 MFA"
#: users/serializers/user.py:159 #: users/serializers/user.py:161
msgid "System role name" msgid "System role name"
msgstr "系统角色名称" msgstr "系统角色名称"
#: users/serializers/user.py:199 #: users/serializers/user.py:201
msgid "User cannot self-update fields: {}" msgid "User cannot self-update fields: {}"
msgstr "用户不能更新自己的字段: {}" msgstr "用户不能更新自己的字段: {}"
#: users/serializers/user.py:256 #: users/serializers/user.py:258
msgid "Select users" msgid "Select users"
msgstr "选择用户" msgstr "选择用户"
#: users/serializers/user.py:257 #: users/serializers/user.py:259
msgid "For security, only list several users" msgid "For security, only list several users"
msgstr "为了安全,仅列出几个用户" msgstr "为了安全,仅列出几个用户"
#: users/serializers/user.py:292 #: users/serializers/user.py:294
msgid "name not unique" msgid "name not unique"
msgstr "名称重复" msgstr "名称重复"

View File

@ -43,6 +43,7 @@ class BasicSettingSerializer(serializers.Serializer):
) )
ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True)
ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement"))
TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets"))
@staticmethod @staticmethod
def validate_SITE_URL(s): def validate_SITE_URL(s):

View File

@ -40,4 +40,6 @@ class PrivateSettingSerializer(PublicSettingSerializer):
TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField() TERMINAL_KOKO_SSH_ENABLED = serializers.BooleanField()
ANNOUNCEMENT_ENABLED = serializers.BooleanField() ANNOUNCEMENT_ENABLED = serializers.BooleanField()
ANNOUNCEMENT = serializers.DictField() ANNOUNCEMENT = serializers.CharField()
TICKETS_ENABLED = serializers.BooleanField()

View File

@ -53,9 +53,10 @@ class TicketViewSet(CommonApiMixin, viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
instance = serializer.save() instance = serializer.save()
instance.create_related_node() applicant = self.request.user
instance.process_map = instance.create_process_map() instance.create_related_node(applicant)
instance.open(applicant=self.request.user) instance.process_map = instance.create_process_map(applicant)
instance.open(applicant)
@action(detail=False, methods=[POST], permission_classes=[RBACPermission, ]) @action(detail=False, methods=[POST], permission_classes=[RBACPermission, ])
def open(self, request, *args, **kwargs): def open(self, request, *args, **kwargs):

View File

@ -188,22 +188,30 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin):
.exclude(state=ProcessStatus.notified).first() .exclude(state=ProcessStatus.notified).first()
return processor.assignee if processor else None return processor.assignee if processor else None
def create_related_node(self): def ignore_applicant(self, assignees, applicant=None):
applicant = applicant if applicant else self.applicant
if len(assignees) != 1:
assignees = set(assignees) - {applicant, }
return list(assignees)
def create_related_node(self, applicant=None):
org_id = self.flow.org_id org_id = self.flow.org_id
approval_rule = self.get_current_ticket_flow_approve() approval_rule = self.get_current_ticket_flow_approve()
ticket_step = TicketStep.objects.create(ticket=self, level=self.approval_step) ticket_step = TicketStep.objects.create(ticket=self, level=self.approval_step)
ticket_assignees = [] ticket_assignees = []
assignees = approval_rule.get_assignees(org_id=org_id) assignees = approval_rule.get_assignees(org_id=org_id)
assignees = self.ignore_applicant(assignees, applicant)
for assignee in assignees: for assignee in assignees:
ticket_assignees.append(TicketAssignee(step=ticket_step, assignee=assignee)) ticket_assignees.append(TicketAssignee(step=ticket_step, assignee=assignee))
TicketAssignee.objects.bulk_create(ticket_assignees) TicketAssignee.objects.bulk_create(ticket_assignees)
def create_process_map(self): def create_process_map(self, applicant=None):
org_id = self.flow.org_id org_id = self.flow.org_id
approval_rules = self.flow.rules.order_by('level') approval_rules = self.flow.rules.order_by('level')
nodes = list() nodes = list()
for node in approval_rules: for node in approval_rules:
assignees = node.get_assignees(org_id=org_id) assignees = node.get_assignees(org_id=org_id)
assignees = self.ignore_applicant(assignees, applicant)
assignee_ids = [assignee.id for assignee in assignees] assignee_ids = [assignee.id for assignee in assignees]
assignees_display = [str(assignee) for assignee in assignees] assignees_display = [str(assignee) for assignee in assignees]
nodes.append( nodes.append(
@ -217,7 +225,8 @@ class Ticket(CommonModelMixin, StatusMixin, OrgModelMixin):
return nodes return nodes
# TODO 兼容不存在流的工单 # TODO 兼容不存在流的工单
def create_process_map_and_node(self, assignees): def create_process_map_and_node(self, assignees, applicant):
assignees = self.ignore_applicant(assignees, applicant)
self.process_map = [{ self.process_map = [{
'approval_level': 1, 'approval_level': 1,
'state': 'notified', 'state': 'notified',

View File

@ -46,11 +46,13 @@ def get_user_or_pre_auth_user(request):
def redirect_user_first_login_or_index(request, redirect_field_name): def redirect_user_first_login_or_index(request, redirect_field_name):
url_in_post = request.POST.get(redirect_field_name) url = request.POST.get(redirect_field_name)
if url_in_post: if not url:
return url_in_post url = request.GET.get(redirect_field_name)
url_in_get = request.GET.get(redirect_field_name, reverse('index')) # 防止 next 地址为 None
return url_in_get if not url or url.lower() in ['none']:
url = reverse('index')
return url
def generate_otp_uri(username, otp_secret_key=None, issuer="JumpServer"): def generate_otp_uri(username, otp_secret_key=None, issuer="JumpServer"):