feat: 支持企业微信、钉钉直接审批工单 (#8115)

pull/8157/head^2
jiangweidong 2022-05-05 13:07:48 +08:00 committed by GitHub
parent d23953932f
commit c56179e9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 424 additions and 81 deletions

View File

@ -21,6 +21,7 @@ from authentication.mixins import AuthMixin
from common.sdk.im.dingtalk import DingTalk from common.sdk.im.dingtalk import DingTalk
from common.utils.common import get_request_ip from common.utils.common import get_request_ip
from authentication.notifications import OAuthBindMessage from authentication.notifications import OAuthBindMessage
from .mixins import METAMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -200,14 +201,17 @@ class DingTalkEnableStartView(UserVerifyPasswordView):
return success_url return success_url
class DingTalkQRLoginView(DingTalkQRMixin, View): 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')
redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True) redirect_uri = reverse('authentication:dingtalk-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) redirect_uri += '?' + urlencode({
'redirect_url': redirect_url,
'next': self.get_next_url_from_meta()
})
url = self.get_qr_url(redirect_uri) url = self.get_qr_url(redirect_uri)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -305,4 +309,4 @@ class DingTalkOAuthLoginCallbackView(AuthMixin, DingTalkOAuthMixin, View):
response = self.get_failed_response(login_url, title=msg, msg=msg) response = self.get_failed_response(login_url, title=msg, msg=msg)
return response return response
return self.redirect_to_guard_view() return self.redirect_to_guard_view()

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
#
class METAMixin:
def get_next_url_from_meta(self):
request_meta = self.request.META or {}
next_url = None
referer = request_meta.get('HTTP_REFERER', '')
next_url_item = referer.rsplit('next=', 1)
if len(next_url_item) > 1:
next_url = next_url_item[-1]
return next_url

View File

@ -21,6 +21,7 @@ from common.utils.common import get_request_ip
from authentication import errors from authentication import errors
from authentication.mixins import AuthMixin from authentication.mixins import AuthMixin
from authentication.notifications import OAuthBindMessage from authentication.notifications import OAuthBindMessage
from .mixins import METAMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -196,14 +197,17 @@ class WeComEnableStartView(UserVerifyPasswordView):
return success_url return success_url
class WeComQRLoginView(WeComQRMixin, View): 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')
redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True) redirect_uri = reverse('authentication:wecom-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) redirect_uri += '?' + urlencode({
'redirect_url': redirect_url,
'next': self.get_next_url_from_meta()
})
url = self.get_qr_url(redirect_uri) url = self.get_qr_url(redirect_uri)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -301,4 +305,4 @@ class WeComOAuthLoginCallbackView(AuthMixin, WeComOAuthMixin, View):
response = self.get_failed_response(login_url, title=msg, msg=msg) response = self.get_failed_response(login_url, title=msg, msg=msg)
return response return response
return self.redirect_to_guard_view() return self.redirect_to_guard_view()

View File

@ -31,6 +31,7 @@ api_v1 = [
app_view_patterns = [ app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'), path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('ops/', include('ops.urls.view_urls'), name='ops'), path('ops/', include('ops.urls.view_urls'), name='ops'),
path('tickets/', include('tickets.urls.view_urls'), name='tickets'),
path('common/', include('common.urls.view_urls'), name='common'), path('common/', include('common.urls.view_urls'), name='common'),
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'), re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
path('download/', views.ResourceDownload.as_view(), name='download'), path('download/', views.ResourceDownload.as_view(), name='download'),

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e70a491494af861945bde8a0b03c9b6e78dde7016446236ead362362b76b09a8
size 125713

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-04-29 12:49+0800\n" "POT-Creation-Date: 2022-05-05 13:03+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"
@ -1402,6 +1402,7 @@ msgid "Filename"
msgstr "ファイル名" msgstr "ファイル名"
#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 #: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90
#: tickets/views/approve.py:93
#: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/app.py:87
#: xpack/plugins/change_auth_plan/serializers/asset.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:197
msgid "Success" msgid "Success"
@ -1557,12 +1558,12 @@ msgid "Auth Token"
msgstr "認証トークン" msgstr "認証トークン"
#: audits/signal_handlers.py:71 authentication/notifications.py:73 #: audits/signal_handlers.py:71 authentication/notifications.py:73
#: authentication/views/login.py:164 authentication/views/wecom.py:181 #: authentication/views/login.py:164 authentication/views/wecom.py:182
#: notifications/backends/__init__.py:11 users/models/user.py:720 #: notifications/backends/__init__.py:11 users/models/user.py:720
msgid "WeCom" msgid "WeCom"
msgstr "企業微信" msgstr "企業微信"
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 #: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183
#: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:721 #: users/models/user.py:721
msgid "DingTalk" msgid "DingTalk"
@ -1740,7 +1741,7 @@ msgstr "{ApplicationPermission} 追加 {SystemUser}"
msgid "{ApplicationPermission} REMOVE {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 削除 {SystemUser}" msgstr "{ApplicationPermission} 削除 {SystemUser}"
#: authentication/api/connection_token.py:328 #: authentication/api/connection_token.py:326
msgid "Invalid token" msgid "Invalid token"
msgstr "無効なトークン" msgstr "無効なトークン"
@ -2184,6 +2185,7 @@ msgstr "コードエラー"
#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:23
#: users/templates/users/_msg_account_expire_reminder.html:4 #: users/templates/users/_msg_account_expire_reminder.html:4
#: users/templates/users/_msg_password_expire_reminder.html:4 #: users/templates/users/_msg_password_expire_reminder.html:4
#: users/templates/users/_msg_reset_mfa.html:4 #: users/templates/users/_msg_reset_mfa.html:4
@ -2323,54 +2325,54 @@ msgstr "返品"
msgid "Copy success" msgid "Copy success"
msgstr "コピー成功" msgstr "コピー成功"
#: authentication/views/dingtalk.py:39 #: authentication/views/dingtalk.py:40
msgid "DingTalk Error, Please contact your system administrator" msgid "DingTalk Error, Please contact your system administrator"
msgstr "DingTalkエラー、システム管理者に連絡してください" msgstr "DingTalkエラー、システム管理者に連絡してください"
#: authentication/views/dingtalk.py:42 #: authentication/views/dingtalk.py:43
msgid "DingTalk Error" msgid "DingTalk Error"
msgstr "DingTalkエラー" msgstr "DingTalkエラー"
#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50 #: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50
#: authentication/views/wecom.py:54 #: authentication/views/wecom.py:55
msgid "" msgid ""
"The system configuration is incorrect. Please contact your administrator" "The system configuration is incorrect. Please contact your administrator"
msgstr "システム設定が正しくありません。管理者に連絡してください" msgstr "システム設定が正しくありません。管理者に連絡してください"
#: authentication/views/dingtalk.py:78 #: authentication/views/dingtalk.py:79
msgid "DingTalk is already bound" msgid "DingTalk is already bound"
msgstr "DingTalkはすでにバインドされています" msgstr "DingTalkはすでにバインドされています"
#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99 #: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99
#: authentication/views/wecom.py:127 #: authentication/views/wecom.py:128
msgid "Please verify your password first" msgid "Please verify your password first"
msgstr "最初にパスワードを確認してください" msgstr "最初にパスワードを確認してください"
#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151 #: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152
msgid "Invalid user_id" msgid "Invalid user_id"
msgstr "無効なuser_id" msgstr "無効なuser_id"
#: authentication/views/dingtalk.py:167 #: authentication/views/dingtalk.py:168
msgid "DingTalk query user failed" msgid "DingTalk query user failed"
msgstr "DingTalkクエリユーザーが失敗しました" msgstr "DingTalkクエリユーザーが失敗しました"
#: authentication/views/dingtalk.py:176 #: authentication/views/dingtalk.py:177
msgid "The DingTalk is already bound to another user" msgid "The DingTalk is already bound to another user"
msgstr "DingTalkはすでに別のユーザーにバインドされています" msgstr "DingTalkはすでに別のユーザーにバインドされています"
#: authentication/views/dingtalk.py:183 #: authentication/views/dingtalk.py:184
msgid "Binding DingTalk successfully" msgid "Binding DingTalk successfully"
msgstr "DingTalkのバインドに成功" msgstr "DingTalkのバインドに成功"
#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289 #: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293
msgid "Failed to get user from DingTalk" msgid "Failed to get user from DingTalk"
msgstr "DingTalkからユーザーを取得できませんでした" msgstr "DingTalkからユーザーを取得できませんでした"
#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295 #: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299
msgid "DingTalk is not bound" msgid "DingTalk is not bound"
msgstr "DingTalkはバインドされていません" msgstr "DingTalkはバインドされていません"
#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 #: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300
msgid "Please login with a password and then bind the DingTalk" msgid "Please login with a password and then bind the DingTalk"
msgstr "パスワードでログインし、DingTalkをバインドしてください" msgstr "パスワードでログインし、DingTalkをバインドしてください"
@ -2443,39 +2445,39 @@ msgstr "ログアウト成功"
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "ログアウト成功、ログインページを返す" msgstr "ログアウト成功、ログインページを返す"
#: authentication/views/wecom.py:39 #: authentication/views/wecom.py:40
msgid "WeCom Error, Please contact your system administrator" msgid "WeCom Error, Please contact your system administrator"
msgstr "企業微信エラー、システム管理者に連絡してください" msgstr "企業微信エラー、システム管理者に連絡してください"
#: authentication/views/wecom.py:42 #: authentication/views/wecom.py:43
msgid "WeCom Error" msgid "WeCom Error"
msgstr "企業微信エラー" msgstr "企業微信エラー"
#: authentication/views/wecom.py:78 #: authentication/views/wecom.py:79
msgid "WeCom is already bound" msgid "WeCom is already bound"
msgstr "企業の微信はすでにバインドされています" msgstr "企業の微信はすでにバインドされています"
#: authentication/views/wecom.py:166 #: authentication/views/wecom.py:167
msgid "WeCom query user failed" msgid "WeCom query user failed"
msgstr "企業微信ユーザーの問合せに失敗しました" msgstr "企業微信ユーザーの問合せに失敗しました"
#: authentication/views/wecom.py:175 #: authentication/views/wecom.py:176
msgid "The WeCom is already bound to another user" msgid "The WeCom is already bound to another user"
msgstr "この企業の微信はすでに他のユーザーをバインドしている。" msgstr "この企業の微信はすでに他のユーザーをバインドしている。"
#: authentication/views/wecom.py:182 #: authentication/views/wecom.py:183
msgid "Binding WeCom successfully" msgid "Binding WeCom successfully"
msgstr "企業の微信のバインドに成功" msgstr "企業の微信のバインドに成功"
#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 #: authentication/views/wecom.py:235 authentication/views/wecom.py:289
msgid "Failed to get user from WeCom" msgid "Failed to get user from WeCom"
msgstr "企業の微信からユーザーを取得できませんでした" msgstr "企業の微信からユーザーを取得できませんでした"
#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 #: authentication/views/wecom.py:241 authentication/views/wecom.py:295
msgid "WeCom is not bound" msgid "WeCom is not bound"
msgstr "企業の微信をバインドしていません" msgstr "企業の微信をバインドしていません"
#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 #: authentication/views/wecom.py:242 authentication/views/wecom.py:296
msgid "Please login with a password and then bind the WeCom" msgid "Please login with a password and then bind the WeCom"
msgstr "パスワードでログインしてからWeComをバインドしてください" msgstr "パスワードでログインしてからWeComをバインドしてください"
@ -4564,7 +4566,7 @@ msgstr "フィルター"
#: terminal/api/endpoint.py:63 #: terminal/api/endpoint.py:63
msgid "Not found protocol query params" msgid "Not found protocol query params"
msgstr "" msgstr "プロトコルクエリパラメータが見つかりません"
#: terminal/api/session.py:210 #: terminal/api/session.py:210
msgid "Session does not exist: {}" msgid "Session does not exist: {}"
@ -5295,19 +5297,19 @@ msgstr "もう一度お試しください"
msgid "Super ticket" msgid "Super ticket"
msgstr "スーパーチケット" msgstr "スーパーチケット"
#: tickets/notifications.py:63 #: tickets/notifications.py:72
msgid "Your has a new ticket, applicant - {}" msgid "Your has a new ticket, applicant - {}"
msgstr "新しいチケットがあります- {}" msgstr "新しいチケットがあります- {}"
#: tickets/notifications.py:69 #: tickets/notifications.py:78
msgid "New Ticket - {} ({})" msgid "New Ticket - {} ({})"
msgstr "新しいチケット- {} ({})" msgstr "新しいチケット- {} ({})"
#: tickets/notifications.py:91 #: tickets/notifications.py:123
msgid "Your ticket has been processed, processor - {}" msgid "Your ticket has been processed, processor - {}"
msgstr "チケットが処理されました。プロセッサー- {}" msgstr "チケットが処理されました。プロセッサー- {}"
#: tickets/notifications.py:95 #: tickets/notifications.py:127
msgid "Ticket has processed - {} ({})" msgid "Ticket has processed - {} ({})"
msgstr "チケットが処理済み- {} ({})" msgstr "チケットが処理済み- {} ({})"
@ -5444,10 +5446,55 @@ msgid "The current organization type already exists"
msgstr "現在の組織タイプは既に存在します。" msgstr "現在の組織タイプは既に存在します。"
#: tickets/templates/tickets/_base_ticket_body.html:17 #: tickets/templates/tickets/_base_ticket_body.html:17
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "Click here to review" msgid "Click here to review"
msgstr "こちらをクリックしてご覧ください" msgstr "こちらをクリックしてご覧ください"
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "View details"
msgstr "詳細の表示"
#: tickets/templates/tickets/_msg_ticket.html:17
msgid "Direct approval"
msgstr "直接承認"
#: tickets/templates/tickets/approve_check_password.html:11
msgid "Ticket information"
msgstr "作業指示情報"
#: tickets/templates/tickets/approve_check_password.html:19
#, fuzzy
#| msgid "Ticket flow approval rule"
msgid "Ticket approval"
msgstr "作業指示の承認"
#: tickets/templates/tickets/approve_check_password.html:35
#: tickets/views/approve.py:25
msgid "Ticket direct approval"
msgstr "作業指示の直接承認"
#: tickets/templates/tickets/approve_check_password.html:40
msgid "Go Login"
msgstr "ログイン"
#: tickets/views/approve.py:26
msgid ""
"This ticket does not exist, the process has ended, or this link has expired"
msgstr ""
"このワークシートが存在しないか、ワークシートが終了したか、このリンクが無効に"
"なっています"
#: tickets/views/approve.py:55
msgid "Click the button to approve directly"
msgstr "ボタンをクリックすると、直接承認に成功します。"
#: tickets/views/approve.py:57
msgid "After successful authentication, this ticket can be approved directly"
msgstr "認証に成功した後、作業指示書は直接承認することができる。"
#: tickets/views/approve.py:84
msgid "This user is not authorized to approve this ticket"
msgstr "このユーザーはこの作業指示を承認する権限がありません"
#: users/api/user.py:183 #: users/api/user.py:183
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用" msgstr "自己otpをリセットできませんでした、代わりにプロファイルリセットを使用"

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:95e9f6addbdb6811647fd2bb5ae64bfc2572a80702c371eab0a1bb041a1e8476
size 104032

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-04-29 12:49+0800\n" "POT-Creation-Date: 2022-05-05 13:03+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"
@ -1390,6 +1390,7 @@ msgid "Filename"
msgstr "文件名" msgstr "文件名"
#: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90 #: audits/models.py:43 audits/models.py:115 terminal/models/sharing.py:90
#: tickets/views/approve.py:93
#: xpack/plugins/change_auth_plan/serializers/app.py:87 #: xpack/plugins/change_auth_plan/serializers/app.py:87
#: xpack/plugins/change_auth_plan/serializers/asset.py:197 #: xpack/plugins/change_auth_plan/serializers/asset.py:197
msgid "Success" msgid "Success"
@ -1545,12 +1546,12 @@ msgid "Auth Token"
msgstr "认证令牌" msgstr "认证令牌"
#: audits/signal_handlers.py:71 authentication/notifications.py:73 #: audits/signal_handlers.py:71 authentication/notifications.py:73
#: authentication/views/login.py:164 authentication/views/wecom.py:181 #: authentication/views/login.py:164 authentication/views/wecom.py:182
#: notifications/backends/__init__.py:11 users/models/user.py:720 #: notifications/backends/__init__.py:11 users/models/user.py:720
msgid "WeCom" msgid "WeCom"
msgstr "企业微信" msgstr "企业微信"
#: audits/signal_handlers.py:72 authentication/views/dingtalk.py:182 #: audits/signal_handlers.py:72 authentication/views/dingtalk.py:183
#: authentication/views/login.py:170 notifications/backends/__init__.py:12 #: authentication/views/login.py:170 notifications/backends/__init__.py:12
#: users/models/user.py:721 #: users/models/user.py:721
msgid "DingTalk" msgid "DingTalk"
@ -1728,7 +1729,7 @@ msgstr "{ApplicationPermission} 添加 {SystemUser}"
msgid "{ApplicationPermission} REMOVE {SystemUser}" msgid "{ApplicationPermission} REMOVE {SystemUser}"
msgstr "{ApplicationPermission} 移除 {SystemUser}" msgstr "{ApplicationPermission} 移除 {SystemUser}"
#: authentication/api/connection_token.py:328 #: authentication/api/connection_token.py:326
msgid "Invalid token" msgid "Invalid token"
msgstr "无效的令牌" msgstr "无效的令牌"
@ -2163,6 +2164,7 @@ msgstr "代码错误"
#: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148 #: jumpserver/conf.py:301 ops/tasks.py:145 ops/tasks.py:148
#: perms/templates/perms/_msg_item_permissions_expire.html:3 #: perms/templates/perms/_msg_item_permissions_expire.html:3
#: perms/templates/perms/_msg_permed_items_expire.html:3 #: perms/templates/perms/_msg_permed_items_expire.html:3
#: tickets/templates/tickets/approve_check_password.html:23
#: users/templates/users/_msg_account_expire_reminder.html:4 #: users/templates/users/_msg_account_expire_reminder.html:4
#: users/templates/users/_msg_password_expire_reminder.html:4 #: users/templates/users/_msg_password_expire_reminder.html:4
#: users/templates/users/_msg_reset_mfa.html:4 #: users/templates/users/_msg_reset_mfa.html:4
@ -2293,54 +2295,54 @@ msgstr "返回"
msgid "Copy success" msgid "Copy success"
msgstr "复制成功" msgstr "复制成功"
#: authentication/views/dingtalk.py:39 #: authentication/views/dingtalk.py:40
msgid "DingTalk Error, Please contact your system administrator" msgid "DingTalk Error, Please contact your system administrator"
msgstr "钉钉错误,请联系系统管理员" msgstr "钉钉错误,请联系系统管理员"
#: authentication/views/dingtalk.py:42 #: authentication/views/dingtalk.py:43
msgid "DingTalk Error" msgid "DingTalk Error"
msgstr "钉钉错误" msgstr "钉钉错误"
#: authentication/views/dingtalk.py:54 authentication/views/feishu.py:50 #: authentication/views/dingtalk.py:55 authentication/views/feishu.py:50
#: authentication/views/wecom.py:54 #: authentication/views/wecom.py:55
msgid "" msgid ""
"The system configuration is incorrect. Please contact your administrator" "The system configuration is incorrect. Please contact your administrator"
msgstr "企业配置错误,请联系系统管理员" msgstr "企业配置错误,请联系系统管理员"
#: authentication/views/dingtalk.py:78 #: authentication/views/dingtalk.py:79
msgid "DingTalk is already bound" msgid "DingTalk is already bound"
msgstr "钉钉已经绑定" msgstr "钉钉已经绑定"
#: authentication/views/dingtalk.py:127 authentication/views/feishu.py:99 #: authentication/views/dingtalk.py:128 authentication/views/feishu.py:99
#: authentication/views/wecom.py:127 #: authentication/views/wecom.py:128
msgid "Please verify your password first" msgid "Please verify your password first"
msgstr "请检查密码" msgstr "请检查密码"
#: authentication/views/dingtalk.py:151 authentication/views/wecom.py:151 #: authentication/views/dingtalk.py:152 authentication/views/wecom.py:152
msgid "Invalid user_id" msgid "Invalid user_id"
msgstr "无效的 user_id" msgstr "无效的 user_id"
#: authentication/views/dingtalk.py:167 #: authentication/views/dingtalk.py:168
msgid "DingTalk query user failed" msgid "DingTalk query user failed"
msgstr "钉钉查询用户失败" msgstr "钉钉查询用户失败"
#: authentication/views/dingtalk.py:176 #: authentication/views/dingtalk.py:177
msgid "The DingTalk is already bound to another user" msgid "The DingTalk is already bound to another user"
msgstr "该钉钉已经绑定其他用户" msgstr "该钉钉已经绑定其他用户"
#: authentication/views/dingtalk.py:183 #: authentication/views/dingtalk.py:184
msgid "Binding DingTalk successfully" msgid "Binding DingTalk successfully"
msgstr "绑定 钉钉 成功" msgstr "绑定 钉钉 成功"
#: authentication/views/dingtalk.py:235 authentication/views/dingtalk.py:289 #: authentication/views/dingtalk.py:239 authentication/views/dingtalk.py:293
msgid "Failed to get user from DingTalk" msgid "Failed to get user from DingTalk"
msgstr "从钉钉获取用户失败" msgstr "从钉钉获取用户失败"
#: authentication/views/dingtalk.py:241 authentication/views/dingtalk.py:295 #: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:299
msgid "DingTalk is not bound" msgid "DingTalk is not bound"
msgstr "钉钉没有绑定" msgstr "钉钉没有绑定"
#: authentication/views/dingtalk.py:242 authentication/views/dingtalk.py:296 #: authentication/views/dingtalk.py:246 authentication/views/dingtalk.py:300
msgid "Please login with a password and then bind the DingTalk" msgid "Please login with a password and then bind the DingTalk"
msgstr "请使用密码登录,然后绑定钉钉" msgstr "请使用密码登录,然后绑定钉钉"
@ -2413,39 +2415,39 @@ msgstr "退出登录成功"
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: authentication/views/wecom.py:39 #: authentication/views/wecom.py:40
msgid "WeCom Error, Please contact your system administrator" msgid "WeCom Error, Please contact your system administrator"
msgstr "企业微信错误,请联系系统管理员" msgstr "企业微信错误,请联系系统管理员"
#: authentication/views/wecom.py:42 #: authentication/views/wecom.py:43
msgid "WeCom Error" msgid "WeCom Error"
msgstr "企业微信错误" msgstr "企业微信错误"
#: authentication/views/wecom.py:78 #: authentication/views/wecom.py:79
msgid "WeCom is already bound" msgid "WeCom is already bound"
msgstr "企业微信已经绑定" msgstr "企业微信已经绑定"
#: authentication/views/wecom.py:166 #: authentication/views/wecom.py:167
msgid "WeCom query user failed" msgid "WeCom query user failed"
msgstr "企业微信查询用户失败" msgstr "企业微信查询用户失败"
#: authentication/views/wecom.py:175 #: authentication/views/wecom.py:176
msgid "The WeCom is already bound to another user" msgid "The WeCom is already bound to another user"
msgstr "该企业微信已经绑定其他用户" msgstr "该企业微信已经绑定其他用户"
#: authentication/views/wecom.py:182 #: authentication/views/wecom.py:183
msgid "Binding WeCom successfully" msgid "Binding WeCom successfully"
msgstr "绑定 企业微信 成功" msgstr "绑定 企业微信 成功"
#: authentication/views/wecom.py:231 authentication/views/wecom.py:285 #: authentication/views/wecom.py:235 authentication/views/wecom.py:289
msgid "Failed to get user from WeCom" msgid "Failed to get user from WeCom"
msgstr "从企业微信获取用户失败" msgstr "从企业微信获取用户失败"
#: authentication/views/wecom.py:237 authentication/views/wecom.py:291 #: authentication/views/wecom.py:241 authentication/views/wecom.py:295
msgid "WeCom is not bound" msgid "WeCom is not bound"
msgstr "没有绑定企业微信" msgstr "没有绑定企业微信"
#: authentication/views/wecom.py:238 authentication/views/wecom.py:292 #: authentication/views/wecom.py:242 authentication/views/wecom.py:296
msgid "Please login with a password and then bind the WeCom" msgid "Please login with a password and then bind the WeCom"
msgstr "请使用密码登录,然后绑定企业微信" msgstr "请使用密码登录,然后绑定企业微信"
@ -5221,19 +5223,19 @@ msgstr "请再次尝试"
msgid "Super ticket" msgid "Super ticket"
msgstr "超级工单" msgstr "超级工单"
#: tickets/notifications.py:63 #: tickets/notifications.py:72
msgid "Your has a new ticket, applicant - {}" msgid "Your has a new ticket, applicant - {}"
msgstr "你有一个新的工单, 申请人 - {}" msgstr "你有一个新的工单, 申请人 - {}"
#: tickets/notifications.py:69 #: tickets/notifications.py:78
msgid "New Ticket - {} ({})" msgid "New Ticket - {} ({})"
msgstr "新工单 - {} ({})" msgstr "新工单 - {} ({})"
#: tickets/notifications.py:91 #: tickets/notifications.py:123
msgid "Your ticket has been processed, processor - {}" msgid "Your ticket has been processed, processor - {}"
msgstr "你的工单已被处理, 处理人 - {}" msgstr "你的工单已被处理, 处理人 - {}"
#: tickets/notifications.py:95 #: tickets/notifications.py:127
msgid "Ticket has processed - {} ({})" msgid "Ticket has processed - {} ({})"
msgstr "你的工单已被处理, 处理人 - {} ({})" msgstr "你的工单已被处理, 处理人 - {} ({})"
@ -5368,10 +5370,51 @@ msgid "The current organization type already exists"
msgstr "当前组织已存在该类型" msgstr "当前组织已存在该类型"
#: tickets/templates/tickets/_base_ticket_body.html:17 #: tickets/templates/tickets/_base_ticket_body.html:17
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "Click here to review" msgid "Click here to review"
msgstr "点击查看" msgstr "点击查看"
#: tickets/templates/tickets/_msg_ticket.html:12
msgid "View details"
msgstr "查看详情"
#: tickets/templates/tickets/_msg_ticket.html:17
msgid "Direct approval"
msgstr "直接批准"
#: tickets/templates/tickets/approve_check_password.html:11
msgid "Ticket information"
msgstr "工单信息"
#: tickets/templates/tickets/approve_check_password.html:19
msgid "Ticket approval"
msgstr "工单审批"
#: tickets/templates/tickets/approve_check_password.html:35
#: tickets/views/approve.py:25
msgid "Ticket direct approval"
msgstr "工单直接审批"
#: tickets/templates/tickets/approve_check_password.html:40
msgid "Go Login"
msgstr "去登录"
#: tickets/views/approve.py:26
msgid ""
"This ticket does not exist, the process has ended, or this link has expired"
msgstr "工单不存在,或者工单流程已经结束,或者此链接已经过期"
#: tickets/views/approve.py:55
msgid "Click the button to approve directly"
msgstr "点击按钮,即可直接审批成功"
#: tickets/views/approve.py:57
msgid "After successful authentication, this ticket can be approved directly"
msgstr "认证成功后,工单可直接审批"
#: tickets/views/approve.py:84
msgid "This user is not authorized to approve this ticket"
msgstr "此用户无权审批此工单"
#: users/api/user.py:183 #: users/api/user.py:183
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置" msgstr "不能在该页面重置 MFA 多因子认证, 请去个人信息页面重置"

View File

@ -0,0 +1,42 @@
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
<title>{% block html_title %}{% endblock %}</title>
{% include '_head_css_js.html' %}
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
<script src="{% static "js/jumpserver.js" %}"></script>
<style>
.outerBox {
max-width: 80%;
margin: 0 auto;
padding: 100px 20px 20px 20px;
}
</style>
{% block custom_head_css_js %} {% endblock %}
</head>
<body class="gray-bg">
<div class="outerBox animated fadeInDown">
<div class="row">
<div class="col-md-12">
{% block content %} {% endblock %}
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-12">
{% include '_copyright.html' %}
</div>
</div>
</div>
</body>
{% include '_foot_js.html' %}
{% block custom_foot_js %} {% endblock %}
</html>

View File

@ -1,13 +1,15 @@
from urllib.parse import urljoin from urllib.parse import urljoin
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from django.shortcuts import reverse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from . import const
from notifications.notifications import UserMessage from notifications.notifications import UserMessage
from common.utils import get_logger from common.utils import get_logger, random_string
from .models import Ticket from .models import Ticket
from . import const
logger = get_logger(__file__) logger = get_logger(__file__)
@ -57,6 +59,13 @@ class TicketAppliedToAssignee(BaseTicketMessage):
def __init__(self, user, ticket): def __init__(self, user, ticket):
self.ticket = ticket self.ticket = ticket
super().__init__(user) super().__init__(user)
self._token = None
@property
def token(self):
if self._token is None:
self._token = random_string(32)
return self._token
@property @property
def content_title(self): def content_title(self):
@ -71,6 +80,29 @@ class TicketAppliedToAssignee(BaseTicketMessage):
) )
return title return title
def get_ticket_approval_url(self):
url = reverse('tickets:direct-approve', kwargs={'token': self.token})
return urljoin(settings.SITE_URL, url)
def get_html_msg(self) -> dict:
body = self.ticket.body.replace('\n', '<br/>')
context = dict(
title=self.content_title,
ticket_detail_url=self.ticket_detail_url,
body=body,
)
ticket_approval_url = self.get_ticket_approval_url()
context.update({'ticket_approval_url': ticket_approval_url})
message = render_to_string('tickets/_msg_ticket.html', context)
cache.set(self.token, {
'body': body, 'ticket_id': self.ticket.id
}, 3600)
return {
'subject': self.subject,
'message': message
}
@classmethod @classmethod
def gen_test_msg(cls): def gen_test_msg(cls):
from .models import Ticket from .models import Ticket

View File

@ -9,7 +9,13 @@
<br> <br>
<div> <div>
<a href="{{ ticket_detail_url }}" target="_blank"> <a href="{{ ticket_detail_url }}" target="_blank">
{% trans 'Click here to review' %} {% trans 'View details' %}
</a> </a>
<br>
{% if ticket_approval_url %}
<a href="{{ ticket_approval_url }}" target="_blank">
{% trans 'Direct approval' %}
</a>
{% endif %}
</div> </div>
</div> </div>

View File

@ -0,0 +1,52 @@
{% extends '_base_double_screen.html' %}
{% load bootstrap3 %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="col-lg-12">
<div class="col-lg-6">
<div class="ibox-content">
<h2 class="font-bold" style="display: inline">{% trans 'Ticket information' %}</h2>
<h1></h1>
<div style="word-break: break-all">{{ ticket_info | safe }}</div>
</div>
</div>
<div class="col-lg-6">
<div class="ibox-content">
<img src="{{ LOGO_URL }}" style="margin: auto" width="50" height="50">
<h2 class="font-bold" style="display: inline">{% trans 'Ticket approval' %}</h2>
<h1></h1>
<div class="ibox-content">
<p>
{% trans 'Hello' %} {{ user.name }},
</p>
<p style="text-indent: 3em">
{{ prompt_msg }}
</p>
<br>
<form id="approve-form" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %}
<div class="form-group" style="">
{% if user.is_authenticated %}
<button id='approve_button' class="btn btn-primary block full-width m-b"
type="submit">
{% trans 'Ticket direct approval' %}
</button>
{% else %}
<a id='login_button' class="btn btn-primary block full-width m-b"
href="{{ login_url }}">
{% trans 'Go Login' %}
</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
</div>
<style>
</style>
{% endblock %}

View File

@ -0,0 +1,12 @@
# coding:utf-8
#
from django.urls import path
from .. import views
app_name = 'tickets'
urlpatterns = [
path('direct-approve/<str:token>/', views.TicketDirectApproveView.as_view(), name='direct-approve'),
]

View File

@ -0,0 +1 @@
from .approve import *

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals
from django.views.generic.base import TemplateView
from django.shortcuts import redirect, reverse
from django.core.cache import cache
from django.utils.translation import ugettext as _
from tickets.models import Ticket
from tickets.errors import AlreadyClosed
from common.utils import get_logger, FlashMessageUtil
logger = get_logger(__name__)
__all__ = ['TicketDirectApproveView']
class TicketDirectApproveView(TemplateView):
template_name = 'tickets/approve_check_password.html'
redirect_field_name = 'next'
@property
def message_data(self):
return {
'title': _('Ticket direct approval'),
'error': _("This ticket does not exist, "
"the process has ended, or this link has expired"),
'redirect_url': self.login_url,
'auto_redirect': False
}
@property
def login_url(self):
return reverse('authentication:login') + '?admin=1'
def redirect_message_response(self, **kwargs):
message_data = self.message_data
for key, value in kwargs.items():
if isinstance(value, str):
message_data[key] = value
if message_data.get('message'):
message_data.pop('error')
redirect_url = FlashMessageUtil.gen_message_url(message_data)
return redirect(redirect_url)
@staticmethod
def clear(token):
cache.delete(token)
def get_context_data(self, **kwargs):
# 放入工单信息
token = kwargs.get('token')
ticket_info = cache.get(token, {}).get('body', '')
if self.request.user.is_authenticated:
prompt_msg = _('Click the button to approve directly')
else:
prompt_msg = _('After successful authentication, this ticket can be approved directly')
kwargs.update({
'ticket_info': ticket_info, 'prompt_msg': prompt_msg,
'login_url': '%s&next=%s' % (
self.login_url,
reverse('tickets:direct-approve', kwargs={'token': token})
),
})
return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs):
token = kwargs.get('token')
ticket_info = cache.get(token)
if not ticket_info:
return self.redirect_message_response(redirect_url=self.login_url)
return super().get(request, *args, **kwargs)
def post(self, request, **kwargs):
user = request.user
token = kwargs.get('token')
ticket_info = cache.get(token)
if not ticket_info:
return self.redirect_message_response(redirect_url=self.login_url)
try:
ticket_id = ticket_info.get('ticket_id')
ticket = Ticket.all().get(id=ticket_id)
if not ticket.has_current_assignee(user):
raise Exception(_("This user is not authorized to approve this ticket"))
ticket.approve(user)
except AlreadyClosed as e:
self.clear(token)
return self.redirect_message_response(error=str(e), redirect_url=self.login_url)
except Exception as e:
return self.redirect_message_response(error=str(e), redirect_url=self.login_url)
self.clear(token)
return self.redirect_message_response(message=_("Success"), redirect_url=self.login_url)