diff --git a/apps/assets/views/domain.py b/apps/assets/views/domain.py index 7b4dcfcce..67626b094 100644 --- a/apps/assets/views/domain.py +++ b/apps/assets/views/domain.py @@ -7,7 +7,7 @@ from django.views.generic.detail import SingleObjectMixin from django.utils.translation import ugettext_lazy as _ from django.urls import reverse_lazy, reverse -from common.permissions import PermissionsMixin ,IsOrgAdmin +from common.permissions import PermissionsMixin, IsOrgAdmin from common.const import create_success_msg, update_success_msg from common.utils import get_object_or_none from ..models import Domain, Gateway diff --git a/apps/authentication/api/auth.py b/apps/authentication/api/auth.py index 101d6436e..8b1ab69c0 100644 --- a/apps/authentication/api/auth.py +++ b/apps/authentication/api/auth.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # - import uuid import time @@ -8,19 +7,17 @@ from django.core.cache import cache from django.urls import reverse from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext as _ - from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.generics import CreateAPIView from rest_framework.views import APIView -from common.utils import get_logger, get_request_ip +from common.utils import get_logger, get_request_ip, get_object_or_none from common.permissions import IsOrgAdminOrAppUser, IsValidUser from orgs.mixins.api import RootOrgViewMixin from users.serializers import UserSerializer from users.models import User from assets.models import Asset, SystemUser -from audits.models import UserLoginLog as LoginLog from users.utils import ( check_otp_code, increase_login_failed_count, is_block_login, clean_failed_count @@ -33,7 +30,7 @@ from ..signals import post_auth_success, post_auth_failed logger = get_logger(__name__) __all__ = [ 'UserAuthApi', 'UserConnectionTokenApi', 'UserOtpAuthApi', - 'UserOtpVerifyApi', + 'UserOtpVerifyApi', 'UserOrderAcceptAuthApi', ] @@ -209,3 +206,26 @@ class UserOtpVerifyApi(CreateAPIView): else: return Response({"error": "Code not valid"}, status=400) + +class UserOrderAcceptAuthApi(APIView): + permission_classes = () + + def get(self, request, *args, **kwargs): + from orders.models import LoginConfirmOrder + order_id = self.request.session.get("auth_order_id") + logger.debug('Login confirm order id: {}'.format(order_id)) + if not order_id: + order = None + else: + order = get_object_or_none(LoginConfirmOrder, pk=order_id) + if not order: + error = _("No order found or order expired") + return Response({"error": error, "status": "not found"}, status=404) + if order.status == order.STATUS_ACCEPTED: + self.request.session["auth_confirm"] = "1" + return Response({"msg": "ok"}) + elif order.status == order.STATUS_REJECTED: + error = _("Order was rejected by {}").format(order.assignee_display) + else: + error = "Order status: {}".format(order.status) + return Response({"error": error, "status": order.status}, status=400) diff --git a/apps/authentication/api/token.py b/apps/authentication/api/token.py index f44e93609..8855ac1c9 100644 --- a/apps/authentication/api/token.py +++ b/apps/authentication/api/token.py @@ -71,7 +71,8 @@ class TokenCreateApi(CreateAPIView): raise MFARequiredError() self.send_auth_signal(success=True, user=user) clean_failed_count(username, ip) - return super().create(request, *args, **kwargs) + resp = super().create(request, *args, **kwargs) + return resp except AuthFailedError as e: increase_login_failed_count(username, ip) self.send_auth_signal(success=False, user=user, username=username, reason=str(e)) @@ -80,8 +81,8 @@ class TokenCreateApi(CreateAPIView): msg = _("MFA required") seed = uuid.uuid4().hex cache.set(seed, user.username, 300) - resp = {'msg': msg, "choices": ["otp"], "req": seed} - return Response(resp, status=300) + data = {'msg': msg, "choices": ["otp"], "req": seed} + return Response(data, status=300) def send_auth_signal(self, success=True, user=None, username='', reason=''): if success: diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 21fb2aafd..bc92eb8b5 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -49,8 +49,8 @@ class LoginConfirmSetting(CommonModelMixin): return get_object_or_none(cls, user=user) def create_confirm_order(self, request=None): - from orders.models import Order - title = _('User login request confirm: {}'.format(self.user)) + from orders.models import LoginConfirmOrder + title = _('User login request: {}'.format(self.user)) if request: remote_addr = get_request_ip(request) city = get_ip_city(remote_addr) @@ -58,14 +58,17 @@ class LoginConfirmSetting(CommonModelMixin): self.user, remote_addr, city, timezone.now() ) else: + city = '' + remote_addr = '' body = '' reviewer = self.reviewers.all() reviewer_names = ','.join([u.name for u in reviewer]) - order = Order.objects.create( + order = LoginConfirmOrder.objects.create( user=self.user, user_display=str(self.user), title=title, body=body, + city=city, ip=remote_addr, assignees_display=reviewer_names, - type=Order.TYPE_LOGIN_REQUEST, + type=LoginConfirmOrder.TYPE_LOGIN_CONFIRM, ) order.assignees.set(reviewer) return order diff --git a/apps/authentication/templates/authentication/login_wait_confirm.html b/apps/authentication/templates/authentication/login_wait_confirm.html index 54526427e..0a14e8515 100644 --- a/apps/authentication/templates/authentication/login_wait_confirm.html +++ b/apps/authentication/templates/authentication/login_wait_confirm.html @@ -6,13 +6,11 @@ - + {{ title }} - {% include '_head_css_js.html' %} - + @@ -29,23 +27,29 @@

-
- Wait for Guanghongwei confirm, You also can copy link to her/his
- Don't close .... +
+ {{ msg|safe }}
-
-
+{% include '_foot_js.html' %} + diff --git a/apps/authentication/urls/api_urls.py b/apps/authentication/urls/api_urls.py index 68dd8eeaa..a90b328cc 100644 --- a/apps/authentication/urls/api_urls.py +++ b/apps/authentication/urls/api_urls.py @@ -1,20 +1,15 @@ # coding:utf-8 # - -from __future__ import absolute_import - from django.urls import path from rest_framework.routers import DefaultRouter from .. import api +app_name = 'authentication' router = DefaultRouter() router.register('access-keys', api.AccessKeyViewSet, 'access-key') -app_name = 'authentication' - - urlpatterns = [ # path('token/', api.UserToken.as_view(), name='user-token'), path('auth/', api.UserAuthApi.as_view(), name='user-auth'), @@ -24,6 +19,7 @@ urlpatterns = [ api.UserConnectionTokenApi.as_view(), name='connection-token'), path('otp/auth/', api.UserOtpAuthApi.as_view(), name='user-otp-auth'), path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'), + path('order/auth/', api.UserOrderAcceptAuthApi.as_view(), name='user-order-auth') ] urlpatterns += router.urls diff --git a/apps/authentication/urls/view_urls.py b/apps/authentication/urls/view_urls.py index 0981f1b09..64d01ae34 100644 --- a/apps/authentication/urls/view_urls.py +++ b/apps/authentication/urls/view_urls.py @@ -16,7 +16,7 @@ urlpatterns = [ # login path('login/', views.UserLoginView.as_view(), name='login'), path('login/otp/', views.UserLoginOtpView.as_view(), name='login-otp'), - path('login/continue/', views.UserLoginContinueView.as_view(), name='login-continue'), - path('login/wait/', views.UserLoginWaitConfirmView.as_view(), name='login-wait'), + path('login/wait-confirm/', views.UserLoginWaitConfirmView.as_view(), name='login-wait-confirm'), + path('login/guard/', views.UserLoginGuardView.as_view(), name='login-guard'), path('logout/', views.UserLogoutView.as_view(), name='logout'), ] diff --git a/apps/authentication/utils.py b/apps/authentication/utils.py index 70c7e52fa..85b486bf3 100644 --- a/apps/authentication/utils.py +++ b/apps/authentication/utils.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- # -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ugettext_lazy as __ from django.contrib.auth import authenticate +from django.utils import timezone -from common.utils import get_ip_city, get_object_or_none, validate_ip +from common.utils import ( + get_ip_city, get_object_or_none, validate_ip, get_request_ip +) from users.models import User from . import const diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index afff9dd45..646268eea 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import os +import datetime from django.core.cache import cache from django.contrib.auth import login as auth_login, logout as auth_logout from django.http import HttpResponse @@ -12,17 +13,18 @@ from django.utils.translation import ugettext as _ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters -from django.views.generic.base import TemplateView, View, RedirectView +from django.views.generic.base import TemplateView, RedirectView from django.views.generic.edit import FormView from django.conf import settings -from common.utils import get_request_ip +from common.utils import get_request_ip, get_object_or_none from users.models import User from users.utils import ( check_otp_code, is_block_login, clean_failed_count, get_user_or_tmp_user, set_tmp_user_to_cache, increase_login_failed_count, - redirect_user_first_login_or_index, + redirect_user_first_login_or_index ) +from ..models import LoginConfirmSetting from ..signals import post_auth_success, post_auth_failed from .. import forms from .. import const @@ -30,7 +32,7 @@ from .. import const __all__ = [ 'UserLoginView', 'UserLoginOtpView', 'UserLogoutView', - 'UserLoginContinueView', 'UserLoginWaitConfirmView', + 'UserLoginGuardView', 'UserLoginWaitConfirmView', ] @@ -91,7 +93,7 @@ class UserLoginView(FormView): # 登陆成功,清除缓存计数 clean_failed_count(username, ip) self.request.session['auth_password'] = '1' - return self.redirect_to_continue_view() + return self.redirect_to_guard_view() def form_invalid(self, form): # write login failed log @@ -112,8 +114,8 @@ class UserLoginView(FormView): return super().form_invalid(form) @staticmethod - def redirect_to_continue_view(): - continue_url = reverse('authentication:login-continue') + def redirect_to_guard_view(): + continue_url = reverse('authentication:login-guard') return redirect(continue_url) def get_form_class(self): @@ -144,7 +146,7 @@ class UserLoginOtpView(FormView): if check_otp_code(otp_secret_key, otp_code): self.request.session['auth_otp'] = '1' - return UserLoginView.redirect_to_continue_view() + return UserLoginView.redirect_to_guard_view() else: self.send_auth_signal( success=False, username=user.username, @@ -165,7 +167,7 @@ class UserLoginOtpView(FormView): ) -class UserLoginContinueView(RedirectView): +class UserLoginGuardView(RedirectView): redirect_field_name = 'next' def get_redirect_url(self, *args, **kwargs): @@ -173,11 +175,18 @@ class UserLoginContinueView(RedirectView): return reverse('authentication:login') user = get_user_or_tmp_user(self.request) + # 启用并设置了otp if user.otp_enabled and user.otp_secret_key and \ not self.request.session.get('auth_otp'): return reverse('authentication:login-otp') - + confirm_setting = LoginConfirmSetting.get_user_confirm_setting(user) + if confirm_setting and not self.request.session.get('auth_confirm'): + order = confirm_setting.create_confirm_order(self.request) + self.request.session['auth_order_id'] = str(order.id) + url = reverse('authentication:login-wait-confirm') + return url self.login_success(user) + # 启用但是没有设置otp if user.otp_enabled and not user.otp_secret_key: # 1,2,mfa_setting & F return reverse('users:user-otp-enable-authentication') @@ -204,7 +213,28 @@ class UserLoginWaitConfirmView(TemplateView): template_name = 'authentication/login_wait_confirm.html' def get_context_data(self, **kwargs): - return super().get_context_data(**kwargs) + from orders.models import LoginConfirmOrder + order_id = self.request.session.get("auth_order_id") + if not order_id: + order = None + else: + order = get_object_or_none(LoginConfirmOrder, pk=order_id) + context = super().get_context_data(**kwargs) + if order: + order_detail_url = reverse('orders:login-confirm-order-detail', kwargs={'pk': order_id}) + timestamp_created = datetime.datetime.timestamp(order.date_created) + msg = _("""Wait for {} confirm, You also can copy link to her/him
+ Don't close this page""").format(order.assignees_display) + else: + timestamp_created = 0 + order_detail_url = '' + msg = _("No order found") + context.update({ + "msg": msg, + "timestamp": timestamp_created, + "order_detail_url": order_detail_url + }) + return context @method_decorator(never_cache, name='dispatch') diff --git a/apps/jumpserver/urls.py b/apps/jumpserver/urls.py index 129368c47..b9bdb697f 100644 --- a/apps/jumpserver/urls.py +++ b/apps/jumpserver/urls.py @@ -13,22 +13,23 @@ from .celery_flower import celery_flower_view from .swagger import get_swagger_view api_v1 = [ - path('users/', include('users.urls.api_urls', namespace='api-users')), - path('assets/', include('assets.urls.api_urls', namespace='api-assets')), - path('perms/', include('perms.urls.api_urls', namespace='api-perms')), - path('terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), - path('ops/', include('ops.urls.api_urls', namespace='api-ops')), - path('audits/', include('audits.urls.api_urls', namespace='api-audits')), - path('orgs/', include('orgs.urls.api_urls', namespace='api-orgs')), - path('settings/', include('settings.urls.api_urls', namespace='api-settings')), - path('authentication/', include('authentication.urls.api_urls', namespace='api-auth')), - path('common/', include('common.urls.api_urls', namespace='api-common')), - path('applications/', include('applications.urls.api_urls', namespace='api-applications')), + path('users/', include('users.urls.api_urls', namespace='api-users')), + path('assets/', include('assets.urls.api_urls', namespace='api-assets')), + path('perms/', include('perms.urls.api_urls', namespace='api-perms')), + path('terminal/', include('terminal.urls.api_urls', namespace='api-terminal')), + path('ops/', include('ops.urls.api_urls', namespace='api-ops')), + path('audits/', include('audits.urls.api_urls', namespace='api-audits')), + path('orgs/', include('orgs.urls.api_urls', namespace='api-orgs')), + path('settings/', include('settings.urls.api_urls', namespace='api-settings')), + path('authentication/', include('authentication.urls.api_urls', namespace='api-auth')), + path('common/', include('common.urls.api_urls', namespace='api-common')), + path('applications/', include('applications.urls.api_urls', namespace='api-applications')), + path('orders/', include('orders.urls.api_urls', namespace='api-orders')), ] api_v2 = [ - path('terminal/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')), - path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')), + path('terminal/', include('terminal.urls.api_urls_v2', namespace='api-terminal-v2')), + path('users/', include('users.urls.api_urls_v2', namespace='api-users-v2')), ] @@ -42,6 +43,7 @@ app_view_patterns = [ path('orgs/', include('orgs.urls.views_urls', namespace='orgs')), path('auth/', include('authentication.urls.view_urls'), name='auth'), path('applications/', include('applications.urls.views_urls', namespace='applications')), + path('orders/', include('orders.urls.views_urls', namespace='orders')), re_path(r'flower/(?P.*)', celery_flower_view, name='flower-view'), ] diff --git a/apps/locale/zh/LC_MESSAGES/django.mo b/apps/locale/zh/LC_MESSAGES/django.mo index a6510ca5a..6f724f936 100644 Binary files a/apps/locale/zh/LC_MESSAGES/django.mo and b/apps/locale/zh/LC_MESSAGES/django.mo differ diff --git a/apps/locale/zh/LC_MESSAGES/django.po b/apps/locale/zh/LC_MESSAGES/django.po index fdc37654f..0f18de91d 100644 --- a/apps/locale/zh/LC_MESSAGES/django.po +++ b/apps/locale/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-10-25 10:52+0800\n" +"POT-Creation-Date: 2019-10-30 11:52+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -144,7 +144,7 @@ msgstr "资产" #: settings/templates/settings/terminal_setting.html:105 terminal/models.py:23 #: terminal/models.py:260 terminal/templates/terminal/terminal_detail.html:43 #: terminal/templates/terminal/terminal_list.html:29 users/models/group.py:14 -#: users/models/user.py:373 users/templates/users/_select_user_modal.html:13 +#: users/models/user.py:375 users/templates/users/_select_user_modal.html:13 #: users/templates/users/user_detail.html:63 #: users/templates/users/user_group_detail.html:55 #: users/templates/users/user_group_list.html:35 @@ -197,7 +197,7 @@ msgstr "参数" #: orgs/models.py:16 perms/models/base.py:54 #: perms/templates/perms/asset_permission_detail.html:98 #: perms/templates/perms/remote_app_permission_detail.html:90 -#: users/models/user.py:414 users/serializers/v1.py:143 +#: users/models/user.py:416 users/serializers/group.py:32 #: users/templates/users/user_detail.html:111 #: xpack/plugins/change_auth_plan/models.py:108 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:113 @@ -219,7 +219,8 @@ msgstr "创建者" #: assets/templates/assets/system_user_detail.html:96 #: common/mixins/models.py:51 ops/models/adhoc.py:45 #: ops/templates/ops/adhoc_detail.html:90 ops/templates/ops/task_detail.html:64 -#: orgs/models.py:17 perms/models/base.py:55 +#: orders/templates/orders/login_confirm_order_detail.html:60 orgs/models.py:17 +#: perms/models/base.py:55 #: perms/templates/perms/asset_permission_detail.html:94 #: perms/templates/perms/remote_app_permission_detail.html:86 #: terminal/templates/terminal/terminal_detail.html:59 users/models/group.py:17 @@ -253,12 +254,14 @@ msgstr "创建日期" #: assets/templates/assets/domain_list.html:28 #: assets/templates/assets/system_user_detail.html:104 #: assets/templates/assets/system_user_list.html:55 ops/models/adhoc.py:43 -#: orgs/models.py:18 perms/models/base.py:56 +#: orders/serializers.py:23 +#: orders/templates/orders/login_confirm_order_detail.html:96 orgs/models.py:18 +#: perms/models/base.py:56 #: perms/templates/perms/asset_permission_detail.html:102 #: perms/templates/perms/remote_app_permission_detail.html:94 #: settings/models.py:34 terminal/models.py:33 #: terminal/templates/terminal/terminal_detail.html:63 users/models/group.py:15 -#: users/models/user.py:406 users/templates/users/user_detail.html:129 +#: users/models/user.py:408 users/templates/users/user_detail.html:129 #: users/templates/users/user_group_detail.html:67 #: users/templates/users/user_group_list.html:37 #: users/templates/users/user_profile.html:138 @@ -516,6 +519,7 @@ msgstr "创建远程应用" #: authentication/templates/authentication/_access_key_modal.html:34 #: ops/templates/ops/adhoc_history.html:59 ops/templates/ops/task_adhoc.html:64 #: ops/templates/ops/task_history.html:65 ops/templates/ops/task_list.html:18 +#: orders/templates/orders/login_confirm_order_list.html:19 #: perms/forms/asset_permission.py:21 #: perms/templates/perms/asset_permission_create_update.html:50 #: perms/templates/perms/asset_permission_list.html:56 @@ -699,12 +703,12 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC" #: assets/templates/assets/system_user_list.html:48 audits/models.py:80 #: audits/templates/audits/login_log_list.html:57 authentication/forms.py:13 #: authentication/templates/authentication/login.html:65 -#: authentication/templates/authentication/new_login.html:92 +#: authentication/templates/authentication/xpack_login.html:92 #: ops/models/adhoc.py:189 perms/templates/perms/asset_permission_list.html:70 #: perms/templates/perms/asset_permission_user.html:55 #: perms/templates/perms/remote_app_permission_user.html:54 #: settings/templates/settings/_ldap_list_users_modal.html:30 users/forms.py:13 -#: users/models/user.py:371 users/templates/users/_select_user_modal.html:14 +#: users/models/user.py:373 users/templates/users/_select_user_modal.html:14 #: users/templates/users/user_detail.html:67 #: users/templates/users/user_list.html:36 #: users/templates/users/user_profile.html:47 @@ -729,7 +733,7 @@ msgstr "密码或密钥密码" #: assets/templates/assets/_asset_user_auth_view_modal.html:27 #: authentication/forms.py:15 #: authentication/templates/authentication/login.html:68 -#: authentication/templates/authentication/new_login.html:95 +#: authentication/templates/authentication/xpack_login.html:95 #: settings/forms.py:114 users/forms.py:15 users/forms.py:27 #: users/templates/users/reset_password.html:53 #: users/templates/users/user_password_authentication.html:18 @@ -744,7 +748,7 @@ msgstr "密码" #: assets/forms/user.py:30 assets/serializers/asset_user.py:71 #: assets/templates/assets/_asset_user_auth_update_modal.html:27 -#: users/models/user.py:400 +#: users/models/user.py:402 msgid "Private key" msgstr "ssh私钥" @@ -793,6 +797,8 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig" #: assets/templates/assets/domain_gateway_list.html:68 #: assets/templates/assets/user_asset_list.html:76 #: audits/templates/audits/login_log_list.html:60 +#: orders/templates/orders/login_confirm_order_detail.html:33 +#: orders/templates/orders/login_confirm_order_list.html:15 #: perms/templates/perms/asset_permission_asset.html:58 settings/forms.py:144 #: users/templates/users/_granted_assets.html:31 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:54 @@ -842,6 +848,7 @@ msgstr "系统平台" #: assets/models/asset.py:146 assets/models/authbook.py:27 #: assets/models/cmd_filter.py:22 assets/models/domain.py:54 #: assets/models/label.py:22 assets/templates/assets/asset_detail.html:110 +#: authentication/models.py:45 msgid "Is active" msgstr "激活" @@ -957,7 +964,7 @@ msgstr "带宽" msgid "Contact" msgstr "联系人" -#: assets/models/cluster.py:22 users/models/user.py:392 +#: assets/models/cluster.py:22 users/models/user.py:394 #: users/templates/users/user_detail.html:76 msgid "Phone" msgstr "手机" @@ -983,7 +990,7 @@ msgid "Default" msgstr "默认" #: assets/models/cluster.py:36 assets/models/label.py:14 -#: users/models/user.py:512 +#: users/models/user.py:514 msgid "System" msgstr "系统" @@ -1011,7 +1018,7 @@ msgstr "BGP全网通" msgid "Regex" msgstr "正则表达式" -#: assets/models/cmd_filter.py:40 ops/models/command.py:21 +#: assets/models/cmd_filter.py:40 ops/models/command.py:22 #: ops/templates/ops/command_execution_list.html:64 terminal/models.py:163 #: terminal/templates/terminal/command_list.html:28 #: terminal/templates/terminal/command_list.html:68 @@ -1034,7 +1041,7 @@ msgstr "过滤器" #: assets/models/cmd_filter.py:51 #: assets/templates/assets/cmd_filter_rule_list.html:58 -#: audits/templates/audits/login_log_list.html:58 +#: audits/templates/audits/login_log_list.html:58 orders/models.py:41 #: perms/templates/perms/remote_app_permission_remote_app.html:54 #: settings/templates/settings/command_storage_create.html:31 #: settings/templates/settings/replay_storage_create.html:31 @@ -1097,8 +1104,11 @@ msgstr "默认资产组" #: audits/templates/audits/operate_log_list.html:72 #: audits/templates/audits/password_change_log_list.html:39 #: audits/templates/audits/password_change_log_list.html:56 -#: ops/templates/ops/command_execution_list.html:38 -#: ops/templates/ops/command_execution_list.html:63 +#: authentication/models.py:43 ops/templates/ops/command_execution_list.html:38 +#: ops/templates/ops/command_execution_list.html:63 orders/models.py:11 +#: orders/models.py:32 +#: orders/templates/orders/login_confirm_order_detail.html:32 +#: orders/templates/orders/login_confirm_order_list.html:14 #: perms/forms/asset_permission.py:78 perms/forms/remote_app_permission.py:34 #: perms/models/base.py:49 #: perms/templates/perms/asset_permission_create_update.html:41 @@ -1111,8 +1121,9 @@ msgstr "默认资产组" #: terminal/templates/terminal/command_list.html:65 #: terminal/templates/terminal/session_list.html:27 #: terminal/templates/terminal/session_list.html:71 users/forms.py:319 -#: users/models/user.py:127 users/models/user.py:143 users/models/user.py:500 -#: users/serializers/v1.py:132 users/templates/users/user_group_detail.html:78 +#: users/models/user.py:129 users/models/user.py:145 users/models/user.py:502 +#: users/serializers/group.py:21 +#: users/templates/users/user_group_detail.html:78 #: users/templates/users/user_group_list.html:36 users/views/user.py:250 #: xpack/plugins/orgs/forms.py:28 #: xpack/plugins/orgs/templates/orgs/org_detail.html:113 @@ -1235,7 +1246,7 @@ msgid "Reachable" msgstr "可连接" #: assets/models/utils.py:45 assets/tasks/const.py:86 -#: authentication/utils.py:13 xpack/plugins/license/models.py:78 +#: authentication/utils.py:16 xpack/plugins/license/models.py:78 msgid "Unknown" msgstr "未知" @@ -1266,7 +1277,7 @@ msgid "Backend" msgstr "后端" #: assets/serializers/asset_user.py:67 users/forms.py:262 -#: users/models/user.py:403 users/templates/users/first_login.html:42 +#: users/models/user.py:405 users/templates/users/first_login.html:42 #: users/templates/users/user_password_update.html:49 #: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile_update.html:46 @@ -1470,6 +1481,7 @@ msgid "Asset user auth" msgstr "资产用户信息" #: assets/templates/assets/_asset_user_auth_view_modal.html:54 +#: authentication/templates/authentication/login_wait_confirm.html:117 msgid "Copy success" msgstr "复制成功" @@ -1490,6 +1502,7 @@ msgstr "关闭" #: audits/templates/audits/operate_log_list.html:77 #: audits/templates/audits/password_change_log_list.html:59 #: ops/templates/ops/task_adhoc.html:63 +#: orders/templates/orders/login_confirm_order_list.html:18 #: terminal/templates/terminal/command_list.html:33 #: terminal/templates/terminal/session_detail.html:50 msgid "Datetime" @@ -1768,7 +1781,7 @@ msgstr "硬盘" msgid "Date joined" msgstr "创建日期" -#: assets/templates/assets/asset_detail.html:148 authentication/models.py:15 +#: assets/templates/assets/asset_detail.html:148 authentication/models.py:19 #: authentication/templates/authentication/_access_key_modal.html:32 #: perms/models/base.py:51 #: perms/templates/perms/asset_permission_create_update.html:55 @@ -1787,6 +1800,7 @@ msgid "Refresh hardware" msgstr "更新硬件信息" #: assets/templates/assets/asset_detail.html:168 +#: authentication/templates/authentication/login_wait_confirm.html:42 msgid "Refresh" msgstr "刷新" @@ -2264,7 +2278,7 @@ msgstr "Agent" #: audits/models.py:85 audits/templates/audits/login_log_list.html:62 #: authentication/templates/authentication/_mfa_confirm_modal.html:14 -#: users/forms.py:174 users/models/user.py:395 +#: users/forms.py:174 users/models/user.py:397 #: users/templates/users/first_login.html:45 msgid "MFA" msgstr "MFA" @@ -2278,6 +2292,8 @@ msgid "Reason" msgstr "原因" #: audits/models.py:87 audits/templates/audits/login_log_list.html:64 +#: orders/templates/orders/login_confirm_order_detail.html:35 +#: orders/templates/orders/login_confirm_order_list.html:17 #: xpack/plugins/cloud/models.py:275 xpack/plugins/cloud/models.py:310 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_history.html:70 #: xpack/plugins/cloud/templates/cloud/sync_instance_task_instance.html:65 @@ -2338,6 +2354,8 @@ msgid "UA" msgstr "Agent" #: audits/templates/audits/login_log_list.html:61 +#: orders/templates/orders/login_confirm_order_detail.html:58 +#: orders/templates/orders/login_confirm_order_list.html:16 msgid "City" msgstr "城市" @@ -2348,23 +2366,23 @@ msgid "Date" msgstr "日期" #: audits/views.py:86 audits/views.py:130 audits/views.py:167 -#: audits/views.py:212 audits/views.py:244 templates/_nav.html:129 +#: audits/views.py:212 audits/views.py:244 templates/_nav.html:139 msgid "Audits" msgstr "日志审计" -#: audits/views.py:87 templates/_nav.html:133 +#: audits/views.py:87 templates/_nav.html:143 msgid "FTP log" msgstr "FTP日志" -#: audits/views.py:131 templates/_nav.html:134 +#: audits/views.py:131 templates/_nav.html:144 msgid "Operate log" msgstr "操作日志" -#: audits/views.py:168 templates/_nav.html:135 +#: audits/views.py:168 templates/_nav.html:145 msgid "Password change log" msgstr "改密日志" -#: audits/views.py:213 templates/_nav.html:132 +#: audits/views.py:213 templates/_nav.html:142 msgid "Login log" msgstr "登录日志" @@ -2372,25 +2390,33 @@ msgstr "登录日志" msgid "Command execution log" msgstr "命令执行" -#: authentication/api/auth.py:61 authentication/api/token.py:45 +#: authentication/api/auth.py:58 authentication/api/token.py:45 #: authentication/templates/authentication/login.html:52 -#: authentication/templates/authentication/new_login.html:77 +#: authentication/templates/authentication/xpack_login.html:77 msgid "Log in frequently and try again later" msgstr "登录频繁, 稍后重试" -#: authentication/api/auth.py:86 +#: authentication/api/auth.py:83 msgid "Please carry seed value and conduct MFA secondary certification" msgstr "请携带seed值, 进行MFA二次认证" -#: authentication/api/auth.py:176 +#: authentication/api/auth.py:173 msgid "Please verify the user name and password first" msgstr "请先进行用户名和密码验证" -#: authentication/api/auth.py:181 +#: authentication/api/auth.py:178 msgid "MFA certification failed" msgstr "MFA认证失败" -#: authentication/api/token.py:80 +#: authentication/api/auth.py:222 +msgid "No order found or order expired" +msgstr "没有找到工单,或者已过期" + +#: authentication/api/auth.py:228 +msgid "Order was rejected by {}" +msgstr "工单被拒绝 {}" + +#: authentication/api/token.py:81 msgid "MFA required" msgstr "" @@ -2491,10 +2517,38 @@ msgstr "账号已被锁定(请联系管理员解锁 或 {}分钟后重试)" msgid "MFA code" msgstr "MFA 验证码" -#: authentication/models.py:35 +#: authentication/models.py:39 msgid "Private Token" msgstr "ssh密钥" +#: authentication/models.py:43 +msgid "login_confirmation_setting" +msgstr "" + +#: authentication/models.py:44 +msgid "Reviewers" +msgstr "" + +#: authentication/models.py:44 +msgid "review_login_confirmation_settings" +msgstr "" + +#: authentication/models.py:53 +msgid "User login request: {}" +msgstr "用户登录请求: {}" + +#: authentication/models.py:57 +msgid "" +"User: {}\n" +"IP: {}\n" +"City: {}\n" +"Date: {}\n" +msgstr "" +"用户: {}\n" +"IP: {}\n" +"城市: {}\n" +"日期: {}\n" + #: authentication/templates/authentication/_access_key_modal.html:6 msgid "API key list" msgstr "API Key列表" @@ -2517,14 +2571,14 @@ msgid "Show" msgstr "显示" #: authentication/templates/authentication/_access_key_modal.html:66 -#: users/models/user.py:330 users/templates/users/user_profile.html:94 +#: users/models/user.py:332 users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:166 msgid "Disable" msgstr "禁用" #: authentication/templates/authentication/_access_key_modal.html:67 -#: users/models/user.py:331 users/templates/users/user_profile.html:92 +#: users/models/user.py:333 users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:170 msgid "Enable" msgstr "启用" @@ -2586,23 +2640,23 @@ msgstr "改变世界,从一点点开始。" #: authentication/templates/authentication/login.html:46 #: authentication/templates/authentication/login.html:73 -#: authentication/templates/authentication/new_login.html:101 +#: authentication/templates/authentication/xpack_login.html:101 #: templates/_header_bar.html:83 msgid "Login" msgstr "登录" #: authentication/templates/authentication/login.html:54 -#: authentication/templates/authentication/new_login.html:80 +#: authentication/templates/authentication/xpack_login.html:80 msgid "The user password has expired" msgstr "用户密码已过期" #: authentication/templates/authentication/login.html:57 -#: authentication/templates/authentication/new_login.html:83 +#: authentication/templates/authentication/xpack_login.html:83 msgid "Captcha invalid" msgstr "验证码错误" #: authentication/templates/authentication/login.html:84 -#: authentication/templates/authentication/new_login.html:105 +#: authentication/templates/authentication/xpack_login.html:105 #: users/templates/users/forgot_password.html:10 #: users/templates/users/forgot_password.html:25 msgid "Forgot password" @@ -2653,24 +2707,45 @@ msgstr "下一步" msgid "Can't provide security? Please contact the administrator!" msgstr "如果不能提供MFA验证码,请联系管理员!" -#: authentication/templates/authentication/new_login.html:67 +#: authentication/templates/authentication/login_wait_confirm.html:47 +msgid "Copy link" +msgstr "复制链接" + +#: authentication/templates/authentication/login_wait_confirm.html:52 +#: templates/flash_message_standalone.html:47 +msgid "Return" +msgstr "返回" + +#: authentication/templates/authentication/xpack_login.html:67 msgid "Welcome back, please enter username and password to login" msgstr "欢迎回来,请输入用户名和密码登录" -#: authentication/views/login.py:81 +#: authentication/views/login.py:82 msgid "Please enable cookies and try again." msgstr "设置你的浏览器支持cookie" -#: authentication/views/login.py:174 users/views/user.py:393 +#: authentication/views/login.py:156 users/views/user.py:393 #: users/views/user.py:418 msgid "MFA code invalid, or ntp sync server time" msgstr "MFA验证码不正确,或者服务器端时间不对" -#: authentication/views/login.py:205 +#: authentication/views/login.py:226 +msgid "" +"Wait for {} confirm, You also can copy link to her/him
\n" +" Don't close this page" +msgstr "" +"等待 {} 确认, 你也可以复制链接发给他/她
\n" +" 不要关闭本页面" + +#: authentication/views/login.py:231 +msgid "No order found" +msgstr "没有发现工单" + +#: authentication/views/login.py:254 msgid "Logout success" msgstr "退出登录成功" -#: authentication/views/login.py:206 +#: authentication/views/login.py:255 msgid "Logout success, return login page" msgstr "退出登录成功,返回到登录页面" @@ -2757,6 +2832,21 @@ msgstr "" msgid "Websocket server run on port: {}, you should proxy it on nginx" msgstr "" +#: jumpserver/views.py:241 +#, fuzzy +#| msgid "" +#| "
Luna is a separately deployed program, you need to deploy Luna, " +#| "koko, configure nginx for url distribution,
If you see this " +#| "page, prove that you are not accessing the nginx listening port. Good " +#| "luck." +msgid "" +"
Koko is a separately deployed program, you need to deploy Koko, " +"configure nginx for url distribution,
If you see this page, " +"prove that you are not accessing the nginx listening port. Good luck." +msgstr "" +"
Luna是单独部署的一个程序,你需要部署luna,koko,
如果你看到了" +"这个页面,证明你访问的不是nginx监听的端口,祝你好运
" + #: ops/api/celery.py:54 msgid "Waiting task start" msgstr "等待任务开始" @@ -2872,21 +2962,21 @@ msgstr "结果" msgid "Adhoc result summary" msgstr "汇总" -#: ops/models/command.py:22 +#: ops/models/command.py:23 #: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:56 #: xpack/plugins/cloud/models.py:273 msgid "Result" msgstr "结果" -#: ops/models/command.py:57 +#: ops/models/command.py:58 msgid "Task start" msgstr "任务开始" -#: ops/models/command.py:71 +#: ops/models/command.py:75 msgid "Command `{}` is forbidden ........" msgstr "命令 `{}` 不允许被执行 ......." -#: ops/models/command.py:77 +#: ops/models/command.py:81 msgid "Task end" msgstr "任务结束" @@ -3028,7 +3118,7 @@ msgstr "没有输入命令" msgid "No system user was selected" msgstr "没有选择系统用户" -#: ops/templates/ops/command_execution_create.html:296 +#: ops/templates/ops/command_execution_create.html:296 orders/models.py:26 msgid "Pending" msgstr "等待" @@ -3112,7 +3202,93 @@ msgstr "命令执行列表" msgid "Command execution" msgstr "命令执行" -#: orgs/mixins/models.py:58 orgs/mixins/serializers.py:26 orgs/models.py:31 +#: orders/models.py:12 orders/models.py:33 +#, fuzzy +#| msgid "User is inactive" +msgid "User display name" +msgstr "用户已禁用" + +#: orders/models.py:13 orders/models.py:36 +msgid "Body" +msgstr "" + +#: orders/models.py:24 +#, fuzzy +#| msgid "Accept" +msgid "Accepted" +msgstr "接受" + +#: orders/models.py:25 +msgid "Rejected" +msgstr "拒绝" + +#: orders/models.py:35 orders/templates/orders/login_confirm_order_list.html:13 +msgid "Title" +msgstr "标题" + +#: orders/models.py:37 +#: orders/templates/orders/login_confirm_order_detail.html:59 +msgid "Assignee" +msgstr "处理人" + +#: orders/models.py:38 +msgid "Assignee display name" +msgstr "处理人名称" + +#: orders/models.py:39 +#: orders/templates/orders/login_confirm_order_detail.html:34 +msgid "Assignees" +msgstr "待处理人" + +#: orders/models.py:40 +msgid "Assignees display name" +msgstr "待处理人名称" + +#: orders/serializers.py:21 +#: orders/templates/orders/login_confirm_order_detail.html:94 +#: orders/templates/orders/login_confirm_order_list.html:53 +#: terminal/templates/terminal/terminal_list.html:78 +msgid "Accept" +msgstr "接受" + +#: orders/serializers.py:22 +#: orders/templates/orders/login_confirm_order_detail.html:95 +#: orders/templates/orders/login_confirm_order_list.html:54 +#: terminal/templates/terminal/terminal_list.html:80 +msgid "Reject" +msgstr "拒绝" + +#: orders/serializers.py:43 +msgid "this order" +msgstr "这个工单" + +#: orders/templates/orders/login_confirm_order_detail.html:75 +msgid "ago" +msgstr "前" + +#: orders/templates/orders/login_confirm_order_list.html:83 +#: users/templates/users/user_list.html:327 +msgid "User is expired" +msgstr "用户已失效" + +#: orders/templates/orders/login_confirm_order_list.html:86 +#: users/templates/users/user_list.html:330 +msgid "User is inactive" +msgstr "用户已禁用" + +#: orders/views.py:15 orders/views.py:31 templates/_nav.html:127 +msgid "Orders" +msgstr "工单管理" + +#: orders/views.py:16 +msgid "Login confirm order list" +msgstr "登录复核工单列表" + +#: orders/views.py:32 +msgid "Login confirm order detail" +msgstr "登录复核工单详情" + +#: orgs/mixins/models.py:44 orgs/mixins/serializers.py:26 orgs/models.py:31 msgid "Organization" msgstr "组织" @@ -3136,7 +3312,7 @@ msgstr "提示:RDP 协议不支持单独控制上传或下载文件" #: perms/templates/perms/asset_permission_list.html:118 #: perms/templates/perms/remote_app_permission_list.html:16 #: templates/_nav.html:21 users/forms.py:293 users/models/group.py:26 -#: users/models/user.py:379 users/templates/users/_select_user_modal.html:16 +#: users/models/user.py:381 users/templates/users/_select_user_modal.html:16 #: users/templates/users/user_detail.html:218 #: users/templates/users/user_list.html:38 #: xpack/plugins/orgs/templates/orgs/org_list.html:16 @@ -3178,7 +3354,7 @@ msgstr "资产授权" #: perms/models/base.py:53 #: perms/templates/perms/asset_permission_detail.html:90 #: perms/templates/perms/remote_app_permission_detail.html:82 -#: users/models/user.py:411 users/templates/users/user_detail.html:107 +#: users/models/user.py:413 users/templates/users/user_detail.html:107 #: users/templates/users/user_profile.html:120 msgid "Date expired" msgstr "失效日期" @@ -3742,7 +3918,7 @@ msgid "Please submit the LDAP configuration before import" msgstr "请先提交LDAP配置再进行导入" #: settings/templates/settings/_ldap_list_users_modal.html:32 -#: users/models/user.py:375 users/templates/users/user_detail.html:71 +#: users/models/user.py:377 users/templates/users/user_detail.html:71 #: users/templates/users/user_profile.html:59 msgid "Email" msgstr "邮件" @@ -3946,7 +4122,7 @@ msgstr "用户来源不是LDAP" #: settings/views.py:19 settings/views.py:46 settings/views.py:73 #: settings/views.py:103 settings/views.py:131 settings/views.py:144 -#: settings/views.py:158 settings/views.py:185 templates/_nav.html:170 +#: settings/views.py:158 settings/views.py:185 templates/_nav.html:180 msgid "Settings" msgstr "系统设置" @@ -4139,7 +4315,7 @@ msgstr "终端管理" msgid "Job Center" msgstr "作业中心" -#: templates/_nav.html:116 templates/_nav.html:136 +#: templates/_nav.html:116 templates/_nav.html:146 msgid "Batch command" msgstr "批量命令" @@ -4147,15 +4323,19 @@ msgstr "批量命令" msgid "Task monitor" msgstr "任务监控" -#: templates/_nav.html:146 +#: templates/_nav.html:130 +msgid "Login confirm" +msgstr "登录复核" + +#: templates/_nav.html:156 msgid "XPack" msgstr "" -#: templates/_nav.html:154 xpack/plugins/cloud/views.py:28 +#: templates/_nav.html:164 xpack/plugins/cloud/views.py:28 msgid "Account list" msgstr "账户列表" -#: templates/_nav.html:155 +#: templates/_nav.html:165 msgid "Sync instance" msgstr "同步实例" @@ -4184,10 +4364,6 @@ msgstr "语言播放验证码" msgid "Captcha" msgstr "验证码" -#: templates/flash_message_standalone.html:47 -msgid "Return" -msgstr "返回" - #: templates/index.html:11 msgid "Total users" msgstr "用户总数" @@ -4496,14 +4672,6 @@ msgstr "地址" msgid "Alive" msgstr "在线" -#: terminal/templates/terminal/terminal_list.html:78 -msgid "Accept" -msgstr "接受" - -#: terminal/templates/terminal/terminal_list.html:80 -msgid "Reject" -msgstr "拒绝" - #: terminal/templates/terminal/terminal_modal_accept.html:5 msgid "Accept terminal registration" msgstr "接受终端注册" @@ -4541,7 +4709,7 @@ msgstr "你可以使用ssh客户端工具连接终端" msgid "Could not reset self otp, use profile reset instead" msgstr "不能再该页面重置MFA, 请去个人信息页面重置" -#: users/forms.py:32 users/models/user.py:383 +#: users/forms.py:32 users/models/user.py:385 #: users/templates/users/_select_user_modal.html:15 #: users/templates/users/user_detail.html:87 #: users/templates/users/user_list.html:37 @@ -4570,7 +4738,7 @@ msgstr "添加到用户组" msgid "Public key should not be the same as your old one." msgstr "不能和原来的密钥相同" -#: users/forms.py:90 users/forms.py:251 users/serializers/v1.py:116 +#: users/forms.py:90 users/forms.py:251 users/serializers/user.py:110 msgid "Not a valid ssh public key" msgstr "ssh密钥不合法" @@ -4655,98 +4823,98 @@ msgstr "复制你的公钥到这里" msgid "Select users" msgstr "选择用户" -#: users/models/user.py:50 users/templates/users/user_update.html:22 +#: users/models/user.py:52 users/templates/users/user_update.html:22 #: users/views/login.py:46 users/views/login.py:107 msgid "User auth from {}, go there change password" msgstr "用户认证源来自 {}, 请去相应系统修改密码" -#: users/models/user.py:126 users/models/user.py:508 +#: users/models/user.py:128 users/models/user.py:510 msgid "Administrator" msgstr "管理员" -#: users/models/user.py:128 +#: users/models/user.py:130 msgid "Application" msgstr "应用程序" -#: users/models/user.py:129 xpack/plugins/orgs/forms.py:30 +#: users/models/user.py:131 xpack/plugins/orgs/forms.py:30 #: xpack/plugins/orgs/templates/orgs/org_list.html:14 msgid "Auditor" msgstr "审计员" -#: users/models/user.py:139 +#: users/models/user.py:141 msgid "Org admin" msgstr "组织管理员" -#: users/models/user.py:141 +#: users/models/user.py:143 msgid "Org auditor" msgstr "组织审计员" -#: users/models/user.py:332 users/templates/users/user_profile.html:90 +#: users/models/user.py:334 users/templates/users/user_profile.html:90 msgid "Force enable" msgstr "强制启用" -#: users/models/user.py:386 +#: users/models/user.py:388 msgid "Avatar" msgstr "头像" -#: users/models/user.py:389 users/templates/users/user_detail.html:82 +#: users/models/user.py:391 users/templates/users/user_detail.html:82 msgid "Wechat" msgstr "微信" -#: users/models/user.py:418 users/templates/users/user_detail.html:103 +#: users/models/user.py:420 users/templates/users/user_detail.html:103 #: users/templates/users/user_list.html:39 #: users/templates/users/user_profile.html:102 msgid "Source" msgstr "用户来源" -#: users/models/user.py:422 +#: users/models/user.py:424 msgid "Date password last updated" msgstr "最后更新密码日期" -#: users/models/user.py:511 +#: users/models/user.py:513 msgid "Administrator is the super user of system" msgstr "Administrator是初始的超级管理员" -#: users/serializers/v1.py:45 +#: users/serializers/group.py:46 +msgid "Auditors cannot be join in the user group" +msgstr "审计员不能被加入到用户组" + +#: users/serializers/user.py:39 msgid "Groups name" msgstr "用户组名" -#: users/serializers/v1.py:46 +#: users/serializers/user.py:40 msgid "Source name" msgstr "用户来源名" -#: users/serializers/v1.py:47 +#: users/serializers/user.py:41 msgid "Is first login" msgstr "首次登录" -#: users/serializers/v1.py:48 +#: users/serializers/user.py:42 msgid "Role name" msgstr "角色名" -#: users/serializers/v1.py:49 +#: users/serializers/user.py:43 msgid "Is valid" msgstr "账户是否有效" -#: users/serializers/v1.py:50 +#: users/serializers/user.py:44 msgid "Is expired" msgstr " 是否过期" -#: users/serializers/v1.py:51 +#: users/serializers/user.py:45 msgid "Avatar url" msgstr "头像路径" -#: users/serializers/v1.py:72 +#: users/serializers/user.py:66 msgid "Role limit to {}" msgstr "角色只能为 {}" -#: users/serializers/v1.py:84 +#: users/serializers/user.py:78 msgid "Password does not match security rules" msgstr "密码不满足安全规则" -#: users/serializers/v1.py:157 -msgid "Auditors cannot be join in the user group" -msgstr "审计员不能被加入到用户组" - #: users/serializers_v2/user.py:36 msgid "name not unique" msgstr "名称重复" @@ -5092,14 +5260,6 @@ msgstr "删除" msgid "User Deleting failed." msgstr "用户删除失败" -#: users/templates/users/user_list.html:327 -msgid "User is expired" -msgstr "用户已失效" - -#: users/templates/users/user_list.html:330 -msgid "User is inactive" -msgstr "用户已禁用" - #: users/templates/users/user_otp_authentication.html:6 #: users/templates/users/user_password_authentication.html:6 msgid "Authenticate" @@ -6391,9 +6551,6 @@ msgstr "创建" #~ msgid "Start" #~ msgstr "开始" -#~ msgid "User login settings" -#~ msgstr "用户登录设置" - #~ msgid "Bit" #~ msgstr " 位" diff --git a/apps/orders/api.py b/apps/orders/api.py new file mode 100644 index 000000000..a588dd684 --- /dev/null +++ b/apps/orders/api.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import viewsets, generics +from django.shortcuts import get_object_or_404 + +from common.permissions import IsValidUser +from common.mixins import CommonApiMixin +from . import serializers +from .models import LoginConfirmOrder + + +class LoginConfirmOrderViewSet(CommonApiMixin, viewsets.ModelViewSet): + serializer_class = serializers.LoginConfirmOrderSerializer + permission_classes = (IsValidUser,) + search_fields = ['user_display', 'title', 'ip', 'city'] + + def get_queryset(self): + queryset = LoginConfirmOrder.objects.all()\ + .filter(assignees=self.request.user) + return queryset + + +class LoginConfirmOrderCreateActionApi(generics.CreateAPIView): + permission_classes = (IsValidUser,) + serializer_class = serializers.LoginConfirmOrderActionSerializer + + def get_order(self): + order_id = self.kwargs.get('pk') + queryset = LoginConfirmOrder.objects.all()\ + .filter(assignees=self.request.user) + order = get_object_or_404(queryset, id=order_id) + return order + + def get_serializer_context(self): + context = super().get_serializer_context() + order = self.get_order() + context['order'] = order + return context diff --git a/apps/orders/apps.py b/apps/orders/apps.py index 384ab4368..3e58af6ea 100644 --- a/apps/orders/apps.py +++ b/apps/orders/apps.py @@ -3,3 +3,7 @@ from django.apps import AppConfig class OrdersConfig(AppConfig): name = 'orders' + + def ready(self): + from . import signals_handler + return super().ready() diff --git a/apps/orders/models.py b/apps/orders/models.py index b2614bd17..c574cacb9 100644 --- a/apps/orders/models.py +++ b/apps/orders/models.py @@ -3,31 +3,56 @@ from django.utils.translation import ugettext_lazy as _ from common.mixins.models import CommonModelMixin +__all__ = ['LoginConfirmOrder', 'Comment'] -class Order(CommonModelMixin): + +class Comment(CommonModelMixin): + order_id = models.UUIDField() + user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_("User"), related_name='comments') + user_display = models.CharField(max_length=128, verbose_name=_("User display name")) + body = models.TextField(verbose_name=_("Body")) + + class Meta: + ordering = ('date_created', ) + + +class BaseOrder(CommonModelMixin): + STATUS_ACCEPTED = 'accepted' + STATUS_REJECTED = 'rejected' + STATUS_PENDING = 'pending' STATUS_CHOICES = ( - ('accepted', _("Accepted")), - ('rejected', _("Rejected")), - ('pending', _("Pending")) + (STATUS_ACCEPTED, _("Accepted")), + (STATUS_REJECTED, _("Rejected")), + (STATUS_PENDING, _("Pending")) ) - TYPE_LOGIN_REQUEST = 'login_request' + TYPE_LOGIN_CONFIRM = 'login_confirm' TYPE_CHOICES = ( - (TYPE_LOGIN_REQUEST, _("Login request")), + (TYPE_LOGIN_CONFIRM, 'Login confirm'), ) - user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='orders', verbose_name=_("User")) + user = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_requested', verbose_name=_("User")) user_display = models.CharField(max_length=128, verbose_name=_("User display name")) title = models.CharField(max_length=256, verbose_name=_("Title")) body = models.TextField(verbose_name=_("Body")) - assignees = models.ManyToManyField('users.User', related_name='assign_orders', verbose_name=_("Assignees")) + assignee = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, related_name='%(class)s_handled', verbose_name=_("Assignee")) + assignee_display = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Assignee display name")) + assignees = models.ManyToManyField('users.User', related_name='%(class)s_assigned', verbose_name=_("Assignees")) assignees_display = models.CharField(max_length=128, verbose_name=_("Assignees display name"), blank=True) - - type = models.CharField(choices=TYPE_CHOICES, max_length=64) + type = models.CharField(choices=TYPE_CHOICES, max_length=16, verbose_name=_('Type')) status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='pending') def __str__(self): return '{}: {}'.format(self.user_display, self.title) - class Meta: - ordering = ('date_created',) + @property + def comments(self): + return Comment.objects.filter(order_id=self.id) + class Meta: + abstract = True + ordering = ('-date_created',) + + +class LoginConfirmOrder(BaseOrder): + ip = models.GenericIPAddressField(blank=True, null=True) + city = models.CharField(max_length=16, blank=True, default='') diff --git a/apps/orders/serializers.py b/apps/orders/serializers.py new file mode 100644 index 000000000..d74e33208 --- /dev/null +++ b/apps/orders/serializers.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + +from .models import LoginConfirmOrder, Comment + + +class LoginConfirmOrderSerializer(serializers.ModelSerializer): + class Meta: + model = LoginConfirmOrder + fields = [ + 'id', 'user', 'user_display', 'title', 'body', + 'ip', 'city', 'assignees', 'assignees_display', + 'type', 'status', 'date_created', 'date_updated', + ] + + +class LoginConfirmOrderActionSerializer(serializers.Serializer): + ACTION_CHOICES = ( + ('accept', _('Accept')), + ('reject', _('Reject')), + ('comment', _('Comment')) + ) + action = serializers.ChoiceField(choices=ACTION_CHOICES) + comment = serializers.CharField(allow_blank=True) + + def update(self, instance, validated_data): + pass + + def create_comments(self, order, user, validated_data): + comment_data = validated_data.get('comment') + action = validated_data.get('action') + comments_data = [] + if comment_data: + comments_data.append(comment_data) + Comment.objects.create( + order_id=order.id, body=comment_data, user=user, + user_display=str(user) + ) + if action != "comment": + action_display = dict(self.ACTION_CHOICES).get(action) + comment_data = '{} {} {}'.format(user, action_display, _("this order")) + comments_data.append(comment_data) + comments = [ + Comment(order_id=order.id, body=data, user=user, user_display=str(user)) + for data in comments_data + ] + Comment.objects.bulk_create(comments) + + @staticmethod + def perform_action(order, user, validated_data): + action = validated_data.get('action') + if action == "accept": + status = "accepted" + elif action == "reject": + status = "rejected" + else: + status = None + + if status: + order.status = status + order.assignee = user + order.assignee_display = str(user) + order.save() + + def create(self, validated_data): + order = self.context['order'] + user = self.context['request'].user + self.create_comments(order, user, validated_data) + self.perform_action(order, user, validated_data) + return validated_data diff --git a/apps/orders/signals_handler.py b/apps/orders/signals_handler.py new file mode 100644 index 000000000..9e2cdd2e7 --- /dev/null +++ b/apps/orders/signals_handler.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +from django.utils.translation import ugettext as _ +from django.dispatch import receiver +from django.db.models.signals import m2m_changed +from django.conf import settings + +from common.tasks import send_mail_async +from common.utils import get_logger, reverse +from .models import LoginConfirmOrder + +logger = get_logger(__name__) + + +def send_mail(order, assignees): + recipient_list = [user.email for user in assignees] + user = order.user + if not recipient_list: + logger.error("Order not has assignees: {}".format(order.id)) + return + subject = '{}: {}'.format(_("New order"), order.title) + detail_url = reverse('orders:login-confirm-order-detail', + kwargs={'pk': order.id}, external=True) + message = _(""" +
+

Your has a new order

+
+ Title: {order.title} +
+ User: {user} +
+ City: {order.city} +
+ IP: {order.ip} +
+ click here to review +
+
+ """).format(order=order, user=user, url=detail_url) + if settings.DEBUG: + try: + print(message) + except OSError: + pass + + send_mail_async.delay(subject, message, recipient_list, html_message=message) + + +@receiver(m2m_changed, sender=LoginConfirmOrder.assignees.through) +def on_login_confirm_order_assignee_set(sender, instance=None, action=None, + model=None, pk_set=None, **kwargs): + print(">>>>>>>>>>>>>>>>>>>>>>>.") + print(action) + if action == 'post_add': + print("<<<<<<<<<<<<<<<<<<<<") + logger.debug('New order create, send mail: {}'.format(instance.id)) + assignees = model.objects.filter(pk__in=pk_set) + send_mail(instance, assignees) + diff --git a/apps/orders/templates/orders/login_confirm_order_detail.html b/apps/orders/templates/orders/login_confirm_order_detail.html new file mode 100644 index 000000000..55a86e009 --- /dev/null +++ b/apps/orders/templates/orders/login_confirm_order_detail.html @@ -0,0 +1,137 @@ +{% extends 'base.html' %} +{% load static %} +{% load i18n %} + +{% block content %} +
+
+
+
+
+
+ {{ object.title }} +
+ +
+
+
+
+
+
+
+
{% trans 'User' %}:
{{ object.user_display }}
+
{% trans 'IP' %}:
{{ object.ip }}
+
{% trans 'Assignees' %}:
{{ object.assignees_display }}
+
{% trans 'Status' %}:
+
+ {% if object.status == "accpeted" %} + + {{ object.get_status_display }} + + {% endif %} + {% if object.status == "rejected" %} + + {{ object.get_status_display }} + + {% endif %} + {% if object.status == "pending" %} + + {{ object.get_status_display }} + + {% endif %} +
+
+
+
+
+

+
{% trans 'City' %}:
{{ object.city }}
+
{% trans 'Assignee' %}:
{{ object.assignee_display | default_if_none:"" }}
+
{% trans 'Date created' %}:
{{ object.date_created }}
+
+
+
+
+
+
+
+
+ {% for comment in object.comments %} +
+ + image + +
+ {{ comment.user_display }} {{ comment.date_created|timesince}} {% trans 'ago' %} +
+ {{ comment.date_created }} +
+ {{ comment.body }} +
+
+
+ {% endfor %} +
+
+ + image + +
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +{% endblock %} +{% block custom_foot_js %} + +{% endblock %} diff --git a/apps/orders/templates/orders/login_confirm_order_list.html b/apps/orders/templates/orders/login_confirm_order_list.html new file mode 100644 index 000000000..e21fb8c9f --- /dev/null +++ b/apps/orders/templates/orders/login_confirm_order_list.html @@ -0,0 +1,91 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %} + +{% endblock %} +{% block table_container %} + + + + + + + + + + + + + + + +
+ + {% trans 'Title' %}{% trans 'User' %}{% trans 'IP' %}{% trans 'City' %}{% trans 'Status' %}{% trans 'Datetime' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} + diff --git a/apps/orders/urls/__init__.py b/apps/orders/urls/__init__.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/orders/urls/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/orders/urls/api_urls.py b/apps/orders/urls/api_urls.py new file mode 100644 index 000000000..81828d3fe --- /dev/null +++ b/apps/orders/urls/api_urls.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +from django.urls import path +from rest_framework.routers import DefaultRouter + +from .. import api + +app_name = 'orders' +router = DefaultRouter() + +router.register('login-confirm-orders', api.LoginConfirmOrderViewSet, 'login-confirm-order') + +urlpatterns = [ + path('login-confirm-order//actions/', + api.LoginConfirmOrderCreateActionApi.as_view(), + name='login-confirm-order-create-action' + ), +] + +urlpatterns += router.urls diff --git a/apps/orders/urls/views_urls.py b/apps/orders/urls/views_urls.py new file mode 100644 index 000000000..f4fe0ba05 --- /dev/null +++ b/apps/orders/urls/views_urls.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# +from django.urls import path +from .. import views + +app_name = 'orders' + +urlpatterns = [ + path('login-confirm-orders/', views.LoginConfirmOrderListView.as_view(), name='login-confirm-order-list'), + path('login-confirm-orders//', views.LoginConfirmOrderDetailView.as_view(), name='login-confirm-order-detail') +] diff --git a/apps/orders/utils.py b/apps/orders/utils.py new file mode 100644 index 000000000..ec51c5a2b --- /dev/null +++ b/apps/orders/utils.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# diff --git a/apps/orders/views.py b/apps/orders/views.py index 91ea44a21..93fb5abde 100644 --- a/apps/orders/views.py +++ b/apps/orders/views.py @@ -1,3 +1,34 @@ -from django.shortcuts import render +from django.views.generic import TemplateView, DetailView +from django.utils.translation import ugettext as _ -# Create your views here. +from common.permissions import PermissionsMixin, IsOrgAdmin +from .models import LoginConfirmOrder + + +class LoginConfirmOrderListView(PermissionsMixin, TemplateView): + template_name = 'orders/login_confirm_order_list.html' + permission_classes = (IsOrgAdmin,) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'app': _("Orders"), + 'action': _("Login confirm order list") + }) + return context + + +class LoginConfirmOrderDetailView(PermissionsMixin, DetailView): + template_name = 'orders/login_confirm_order_detail.html' + permission_classes = (IsOrgAdmin,) + + def get_queryset(self): + return LoginConfirmOrder.objects.filter(assignees=self.request.user) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'app': _("Orders"), + 'action': _("Login confirm order detail") + }) + return context diff --git a/apps/static/js/jumpserver.js b/apps/static/js/jumpserver.js index 33be3ba61..edea12c5b 100644 --- a/apps/static/js/jumpserver.js +++ b/apps/static/js/jumpserver.js @@ -307,7 +307,7 @@ function requestApi(props) { toastr.error(msg); } if (typeof props.error === 'function') { - return props.error(jqXHR.responseText, jqXHR.status); + return props.error(jqXHR.responseText, jqXHR.responseJSON, jqXHR.status); } }); // return true; diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 5690c961d..36a2cb9ed 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -121,6 +121,16 @@ {% endif %} +{% if request.user.can_admin_current_org %} +
  • + + {% trans 'Orders' %} + + +
  • +{% endif %} {# Audits #} {% if request.user.can_admin_or_audit_current_org %} @@ -175,4 +185,4 @@ \ No newline at end of file + diff --git a/apps/users/utils.py b/apps/users/utils.py index 30868358c..9ab2c914e 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -193,7 +193,6 @@ def send_reset_ssh_key_mail(user): send_mail_async.delay(subject, message, recipient_list, html_message=message) - def get_user_or_tmp_user(request): user = request.user tmp_user = get_tmp_user_from_cache(request) @@ -212,8 +211,8 @@ def get_tmp_user_from_cache(request): return user -def set_tmp_user_to_cache(request, user): - cache.set(request.session.session_key+'user', user, 600) +def set_tmp_user_to_cache(request, user, ttl=3600): + cache.set(request.session.session_key+'user', user, ttl) def redirect_user_first_login_or_index(request, redirect_field_name):