Merge pull request #4519 from jumpserver/dev

Dev
pull/4561/head v2.2.0
BaiJiangJie 2020-08-20 12:48:19 +08:00 committed by GitHub
commit 692dd6c8c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 384 additions and 220 deletions

View File

@ -107,7 +107,7 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet): class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
serializer_class = CommandExecutionHostsRelationSerializer serializer_class = CommandExecutionHostsRelationSerializer
m2m_field = CommandExecution.hosts.field m2m_field = CommandExecution.hosts.field
permission_classes = (IsOrgAdmin,) permission_classes = [IsOrgAdmin | IsOrgAuditor]
filter_fields = [ filter_fields = [
'id', 'asset', 'commandexecution' 'id', 'asset', 'commandexecution'
] ]

View File

@ -34,16 +34,6 @@ class LoginConfirmSettingUpdateApi(UpdateAPIView):
class TicketStatusApi(mixins.AuthMixin, APIView): class TicketStatusApi(mixins.AuthMixin, APIView):
permission_classes = () permission_classes = ()
def get_ticket(self):
from tickets.models import Ticket
ticket_id = self.request.session.get("auth_ticket_id")
logger.debug('Login confirm ticket id: {}'.format(ticket_id))
if not ticket_id:
ticket = None
else:
ticket = get_object_or_none(Ticket, pk=ticket_id)
return ticket
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try: try:
self.check_user_login_confirm() self.check_user_login_confirm()

View File

@ -7,6 +7,7 @@ import time
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.shortcuts import reverse from django.shortcuts import reverse
from django.contrib.auth import BACKEND_SESSION_KEY
from common.utils import get_object_or_none, get_request_ip, get_logger from common.utils import get_object_or_none, get_request_ip, get_logger
from users.models import User from users.models import User
@ -27,8 +28,14 @@ class AuthMixin:
def get_user_from_session(self): def get_user_from_session(self):
if self.request.session.is_empty(): if self.request.session.is_empty():
raise errors.SessionEmptyError() raise errors.SessionEmptyError()
if self.request.user and not self.request.user.is_anonymous:
return self.request.user if all((self.request.user,
not self.request.user.is_anonymous,
BACKEND_SESSION_KEY in self.request.session)):
user = self.request.user
user.backend = self.request.session[BACKEND_SESSION_KEY]
return user
user_id = self.request.session.get('user_id') user_id = self.request.session.get('user_id')
if not user_id: if not user_id:
user = None user = None
@ -163,7 +170,7 @@ class AuthMixin:
if not ticket_id: if not ticket_id:
ticket = None ticket = None
else: else:
ticket = get_object_or_none(Ticket, pk=ticket_id) ticket = Ticket.origin_objects.get(pk=ticket_id)
return ticket return ticket
def get_ticket_or_create(self, confirm_setting): def get_ticket_or_create(self, confirm_setting):

View File

@ -34,7 +34,13 @@ def rsa_decrypt(cipher_text, rsa_private_key=None):
if rsa_private_key is None: if rsa_private_key is None:
# rsa_private_key 为 None可以能是API请求认证不需要解密 # rsa_private_key 为 None可以能是API请求认证不需要解密
return cipher_text return cipher_text
key = RSA.importKey(rsa_private_key) key = RSA.importKey(rsa_private_key)
cipher = PKCS1_v1_5.new(key) cipher = PKCS1_v1_5.new(key)
message = cipher.decrypt(base64.b64decode(cipher_text.encode()), 'error').decode() cipher_decoded = base64.b64decode(cipher_text.encode())
# Todo: 弄明白为何要以下这么写https://xbuba.com/questions/57035263
if len(cipher_decoded) == 127:
hex_fixed = '00' + cipher_decoded.hex()
cipher_decoded = base64.b16decode(hex_fixed.upper())
message = cipher.decrypt(cipher_decoded, b'error').decode()
return message return message

View File

@ -18,6 +18,7 @@ from django.views.generic.edit import FormView
from django.conf import settings from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from common.const.front_urls import TICKET_DETAIL
from common.utils import get_request_ip, get_object_or_none from common.utils import get_request_ip, get_object_or_none
from users.utils import ( from users.utils import (
redirect_user_first_login_or_index redirect_user_first_login_or_index
@ -185,7 +186,7 @@ class UserLoginWaitConfirmView(TemplateView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
if ticket: if ticket:
timestamp_created = datetime.datetime.timestamp(ticket.date_created) timestamp_created = datetime.datetime.timestamp(ticket.date_created)
ticket_detail_url = reverse('tickets:ticket-detail', kwargs={'pk': ticket_id}) ticket_detail_url = TICKET_DETAIL.format(id=ticket_id)
msg = _("""Wait for <b>{}</b> confirm, You also can copy link to her/him <br/> msg = _("""Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>
Don't close this page""").format(ticket.assignees_display) Don't close this page""").format(ticket.assignees_display)
else: else:

View File

@ -0,0 +1,2 @@
TICKET_DETAIL = '/ui/#/tickets/tickets/{id}'

View File

@ -182,3 +182,9 @@ class CanUpdateDeleteUser(permissions.BasePermission):
if request.method in ['PUT', 'PATCH']: if request.method in ['PUT', 'PATCH']:
return self.has_update_object_permission(request, view, obj) return self.has_update_object_permission(request, view, obj)
return True return True
class IsObjectOwner(IsValidUser):
def has_object_permission(self, request, view, obj):
return (super().has_object_permission(request, view, obj) and
request.user == getattr(obj, 'user', None))

View File

@ -247,7 +247,7 @@ class Config(dict):
'HTTP_BIND_HOST': '0.0.0.0', 'HTTP_BIND_HOST': '0.0.0.0',
'HTTP_LISTEN_PORT': 8080, 'HTTP_LISTEN_PORT': 8080,
'WS_LISTEN_PORT': 8070, 'WS_LISTEN_PORT': 8070,
'LOGIN_LOG_KEEP_DAYS': 90, 'LOGIN_LOG_KEEP_DAYS': 9999,
'TASK_LOG_KEEP_DAYS': 10, 'TASK_LOG_KEEP_DAYS': 10,
'ASSETS_PERM_CACHE_TIME': 3600 * 24, 'ASSETS_PERM_CACHE_TIME': 3600 * 24,
'SECURITY_MFA_VERIFY_TTL': 3600, 'SECURITY_MFA_VERIFY_TTL': 3600,

View File

@ -52,6 +52,7 @@ def redirect_format_api(request, *args, **kwargs):
return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404) return JsonResponse({"msg": "Redirect url failed: {}".format(_path)}, status=404)
@csrf_exempt
def redirect_old_apps_view(request, *args, **kwargs): def redirect_old_apps_view(request, *args, **kwargs):
path = request.get_full_path() path = request.get_full_path()
if path.find('/core') != -1: if path.find('/core') != -1:

Binary file not shown.

View File

@ -8,7 +8,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: 2020-08-07 18:48+0800\n" "POT-Creation-Date: 2020-08-19 18:10+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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"
@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: applications/const.py:52 #: applications/const.py:53
msgid "Custom" msgid "Custom"
msgstr "自定义" msgstr "自定义"
@ -27,9 +27,9 @@ msgstr "自定义"
#: assets/models/cmd_filter.py:21 assets/models/domain.py:20 #: assets/models/cmd_filter.py:21 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24 #: assets/models/group.py:20 assets/models/label.py:18 ops/mixin.py:24
#: orgs/models.py:22 perms/models/base.py:48 settings/models.py:27 #: orgs/models.py:22 perms/models/base.py:48 settings/models.py:27
#: terminal/models.py:26 terminal/models.py:342 terminal/models.py:374 #: terminal/models.py:27 terminal/models.py:344 terminal/models.py:376
#: terminal/models.py:411 users/forms/profile.py:20 users/models/group.py:15 #: terminal/models.py:413 users/forms/profile.py:20 users/models/group.py:15
#: users/models/user.py:489 users/templates/users/_select_user_modal.html:13 #: users/models/user.py:491 users/templates/users/_select_user_modal.html:13
#: users/templates/users/user_asset_permission.html:37 #: users/templates/users/user_asset_permission.html:37
#: users/templates/users/user_asset_permission.html:154 #: users/templates/users/user_asset_permission.html:154
#: users/templates/users/user_database_app_permission.html:36 #: users/templates/users/user_database_app_permission.html:36
@ -47,7 +47,7 @@ msgid "Name"
msgstr "名称" msgstr "名称"
#: applications/models/database_app.py:22 applications/models/k8s_app.py:14 #: applications/models/database_app.py:22 applications/models/k8s_app.py:14
#: assets/models/cmd_filter.py:52 terminal/models.py:376 terminal/models.py:413 #: assets/models/cmd_filter.py:52 terminal/models.py:378 terminal/models.py:415
#: tickets/models/ticket.py:40 #: tickets/models/ticket.py:40
#: users/templates/users/user_granted_database_app.html:35 #: users/templates/users/user_granted_database_app.html:35
msgid "Type" msgid "Type"
@ -77,9 +77,9 @@ msgstr "数据库"
#: assets/models/cmd_filter.py:57 assets/models/domain.py:21 #: assets/models/cmd_filter.py:57 assets/models/domain.py:21
#: assets/models/domain.py:54 assets/models/group.py:23 #: assets/models/domain.py:54 assets/models/group.py:23
#: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:25 #: assets/models/label.py:23 ops/models/adhoc.py:37 orgs/models.py:25
#: perms/models/base.py:56 settings/models.py:32 terminal/models.py:36 #: perms/models/base.py:56 settings/models.py:32 terminal/models.py:37
#: terminal/models.py:381 terminal/models.py:418 users/models/group.py:16 #: terminal/models.py:383 terminal/models.py:420 users/models/group.py:16
#: users/models/user.py:522 users/templates/users/user_detail.html:115 #: users/models/user.py:524 users/templates/users/user_detail.html:115
#: users/templates/users/user_granted_database_app.html:38 #: users/templates/users/user_granted_database_app.html:38
#: users/templates/users/user_granted_remote_app.html:37 #: users/templates/users/user_granted_remote_app.html:37
#: users/templates/users/user_group_detail.html:62 #: users/templates/users/user_group_detail.html:62
@ -116,11 +116,11 @@ msgstr "Kubernetes应用"
#: applications/models/remote_app.py:23 assets/models/asset.py:352 #: applications/models/remote_app.py:23 assets/models/asset.py:352
#: assets/models/authbook.py:26 assets/models/gathered_user.py:14 #: assets/models/authbook.py:26 assets/models/gathered_user.py:14
#: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47 #: assets/serializers/admin_user.py:32 assets/serializers/asset_user.py:47
#: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:44 #: assets/serializers/asset_user.py:84 assets/serializers/system_user.py:46
#: assets/serializers/system_user.py:176 audits/models.py:38 #: assets/serializers/system_user.py:179 audits/models.py:38
#: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:90 #: perms/forms/asset_permission.py:89 perms/models/asset_permission.py:90
#: templates/index.html:82 terminal/backends/command/models.py:19 #: templates/index.html:82 terminal/backends/command/models.py:19
#: terminal/backends/command/serializers.py:13 terminal/models.py:187 #: terminal/backends/command/serializers.py:13 terminal/models.py:188
#: users/templates/users/user_asset_permission.html:40 #: users/templates/users/user_asset_permission.html:40
#: users/templates/users/user_asset_permission.html:70 #: users/templates/users/user_asset_permission.html:70
#: users/templates/users/user_granted_remote_app.html:36 #: users/templates/users/user_granted_remote_app.html:36
@ -146,8 +146,8 @@ msgstr "参数"
#: assets/models/base.py:240 assets/models/cluster.py:28 #: assets/models/base.py:240 assets/models/cluster.py:28
#: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60 #: assets/models/cmd_filter.py:26 assets/models/cmd_filter.py:60
#: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49 #: assets/models/group.py:21 common/db/models.py:67 common/mixins/models.py:49
#: orgs/models.py:23 orgs/models.py:326 perms/models/base.py:54 #: orgs/models.py:23 orgs/models.py:389 perms/models/base.py:54
#: users/models/user.py:530 users/serializers/group.py:35 #: users/models/user.py:532 users/serializers/group.py:35
#: users/templates/users/user_detail.html:97 #: users/templates/users/user_detail.html:97
#: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56 #: xpack/plugins/change_auth_plan/models.py:81 xpack/plugins/cloud/models.py:56
#: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30 #: xpack/plugins/cloud/models.py:146 xpack/plugins/gathered_user/models.py:30
@ -161,7 +161,7 @@ msgstr "创建者"
#: assets/models/domain.py:23 assets/models/gathered_user.py:19 #: assets/models/domain.py:23 assets/models/gathered_user.py:19
#: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:69 #: assets/models/group.py:22 assets/models/label.py:25 common/db/models.py:69
#: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27 #: common/mixins/models.py:50 ops/models/adhoc.py:38 ops/models/command.py:27
#: orgs/models.py:24 orgs/models.py:324 perms/models/base.py:55 #: orgs/models.py:24 orgs/models.py:387 perms/models/base.py:55
#: users/models/group.py:18 users/templates/users/user_group_detail.html:58 #: users/models/group.py:18 users/templates/users/user_group_detail.html:58
#: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149 #: xpack/plugins/cloud/models.py:59 xpack/plugins/cloud/models.py:149
msgid "Date created" msgid "Date created"
@ -233,7 +233,7 @@ msgid "Hostname"
msgstr "主机名" msgstr "主机名"
#: assets/models/asset.py:190 assets/models/domain.py:52 #: assets/models/asset.py:190 assets/models/domain.py:52
#: assets/models/user.py:116 terminal/serializers/session.py:29 #: assets/models/user.py:117 terminal/serializers/session.py:29
msgid "Protocol" msgid "Protocol"
msgstr "协议" msgstr "协议"
@ -247,7 +247,7 @@ msgstr "协议组"
msgid "Domain" msgid "Domain"
msgstr "网域" msgstr "网域"
#: assets/models/asset.py:195 assets/models/user.py:111 #: assets/models/asset.py:195 assets/models/user.py:112
#: perms/models/asset_permission.py:91 #: perms/models/asset_permission.py:91
#: xpack/plugins/change_auth_plan/models.py:56 #: xpack/plugins/change_auth_plan/models.py:56
#: xpack/plugins/gathered_user/models.py:24 #: xpack/plugins/gathered_user/models.py:24
@ -261,7 +261,7 @@ msgid "Is active"
msgstr "激活" msgstr "激活"
#: assets/models/asset.py:199 assets/models/cluster.py:19 #: assets/models/asset.py:199 assets/models/cluster.py:19
#: assets/models/user.py:65 templates/_nav.html:44 #: assets/models/user.py:66 templates/_nav.html:44
#: xpack/plugins/cloud/models.py:133 xpack/plugins/cloud/serializers.py:83 #: xpack/plugins/cloud/models.py:133 xpack/plugins/cloud/serializers.py:83
msgid "Admin user" msgid "Admin user"
msgstr "管理用户" msgstr "管理用户"
@ -354,7 +354,7 @@ msgstr ""
#: audits/models.py:99 authentication/forms.py:11 #: audits/models.py:99 authentication/forms.py:11
#: authentication/templates/authentication/login.html:21 #: authentication/templates/authentication/login.html:21
#: authentication/templates/authentication/xpack_login.html:101 #: authentication/templates/authentication/xpack_login.html:101
#: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:487 #: ops/models/adhoc.py:148 users/forms/profile.py:19 users/models/user.py:489
#: users/templates/users/_select_user_modal.html:14 #: users/templates/users/_select_user_modal.html:14
#: users/templates/users/user_detail.html:53 #: users/templates/users/user_detail.html:53
#: users/templates/users/user_list.html:15 #: users/templates/users/user_list.html:15
@ -395,7 +395,7 @@ msgstr "SSH公钥"
#: assets/models/base.py:239 assets/models/gathered_user.py:20 #: assets/models/base.py:239 assets/models/gathered_user.py:20
#: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39 #: common/db/models.py:70 common/mixins/models.py:51 ops/models/adhoc.py:39
#: orgs/models.py:325 #: orgs/models.py:388
msgid "Date updated" msgid "Date updated"
msgstr "更新日期" msgstr "更新日期"
@ -407,7 +407,7 @@ msgstr "带宽"
msgid "Contact" msgid "Contact"
msgstr "联系人" msgstr "联系人"
#: assets/models/cluster.py:22 users/models/user.py:508 #: assets/models/cluster.py:22 users/models/user.py:510
#: users/templates/users/user_detail.html:62 #: users/templates/users/user_detail.html:62
msgid "Phone" msgid "Phone"
msgstr "手机" msgstr "手机"
@ -433,7 +433,7 @@ msgid "Default"
msgstr "默认" msgstr "默认"
#: assets/models/cluster.py:36 assets/models/label.py:14 #: assets/models/cluster.py:36 assets/models/label.py:14
#: users/models/user.py:655 #: users/models/user.py:657
msgid "System" msgid "System"
msgstr "系统" msgstr "系统"
@ -453,7 +453,7 @@ msgstr "北京电信"
msgid "BGP full netcom" msgid "BGP full netcom"
msgstr "BGP全网通" msgstr "BGP全网通"
#: assets/models/cmd_filter.py:33 assets/models/user.py:121 #: assets/models/cmd_filter.py:33 assets/models/user.py:122
msgid "Command filter" msgid "Command filter"
msgstr "命令过滤器" msgstr "命令过滤器"
@ -462,7 +462,7 @@ msgid "Regex"
msgstr "正则表达式" msgstr "正则表达式"
#: assets/models/cmd_filter.py:41 ops/models/command.py:23 #: assets/models/cmd_filter.py:41 ops/models/command.py:23
#: terminal/backends/command/serializers.py:15 terminal/models.py:196 #: terminal/backends/command/serializers.py:15 terminal/models.py:197
msgid "Command" msgid "Command"
msgstr "命令" msgstr "命令"
@ -478,7 +478,7 @@ msgstr "允许"
msgid "Filter" msgid "Filter"
msgstr "过滤器" msgstr "过滤器"
#: assets/models/cmd_filter.py:53 assets/models/user.py:115 #: assets/models/cmd_filter.py:53 assets/models/user.py:116
msgid "Priority" msgid "Priority"
msgstr "优先级" msgstr "优先级"
@ -547,15 +547,15 @@ msgstr "默认资产组"
#: assets/models/label.py:15 audits/models.py:36 audits/models.py:56 #: assets/models/label.py:15 audits/models.py:36 audits/models.py:56
#: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46 #: audits/models.py:69 audits/serializers.py:77 authentication/models.py:46
#: authentication/models.py:90 orgs/models.py:16 orgs/models.py:322 #: authentication/models.py:90 orgs/models.py:16 orgs/models.py:385
#: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38 #: perms/forms/asset_permission.py:83 perms/forms/database_app_permission.py:38
#: perms/forms/remote_app_permission.py:40 perms/models/base.py:49 #: perms/forms/remote_app_permission.py:40 perms/models/base.py:49
#: templates/index.html:78 terminal/backends/command/models.py:18 #: templates/index.html:78 terminal/backends/command/models.py:18
#: terminal/backends/command/serializers.py:12 terminal/models.py:185 #: terminal/backends/command/serializers.py:12 terminal/models.py:186
#: tickets/models/ticket.py:30 tickets/models/ticket.py:137 #: tickets/models/ticket.py:30 tickets/models/ticket.py:137
#: tickets/serializers/request_asset_perm.py:65 #: tickets/serializers/request_asset_perm.py:65
#: tickets/serializers/ticket.py:31 users/forms/group.py:15 #: tickets/serializers/ticket.py:31 users/forms/group.py:15
#: users/models/user.py:157 users/models/user.py:643 #: users/models/user.py:158 users/models/user.py:645
#: users/serializers/group.py:20 #: users/serializers/group.py:20
#: users/templates/users/user_asset_permission.html:38 #: users/templates/users/user_asset_permission.html:38
#: users/templates/users/user_asset_permission.html:64 #: users/templates/users/user_asset_permission.html:64
@ -597,8 +597,8 @@ msgstr "收藏夹"
msgid "Key" msgid "Key"
msgstr "键" msgstr "键"
#: assets/models/node.py:511 assets/serializers/system_user.py:43 #: assets/models/node.py:511 assets/serializers/system_user.py:45
#: assets/serializers/system_user.py:175 perms/forms/asset_permission.py:92 #: assets/serializers/system_user.py:178 perms/forms/asset_permission.py:92
#: perms/forms/asset_permission.py:99 #: perms/forms/asset_permission.py:99
#: users/templates/users/user_asset_permission.html:41 #: users/templates/users/user_asset_permission.html:41
#: users/templates/users/user_asset_permission.html:73 #: users/templates/users/user_asset_permission.html:73
@ -607,65 +607,73 @@ msgstr "键"
msgid "Node" msgid "Node"
msgstr "节点" msgstr "节点"
#: assets/models/user.py:107 #: assets/models/user.py:108
msgid "Automatic login" msgid "Automatic login"
msgstr "自动登录" msgstr "自动登录"
#: assets/models/user.py:108 #: assets/models/user.py:109
msgid "Manually login" msgid "Manually login"
msgstr "手动登录" msgstr "手动登录"
#: assets/models/user.py:110 #: assets/models/user.py:111
msgid "Username same with user" msgid "Username same with user"
msgstr "用户名与用户相同" msgstr "用户名与用户相同"
#: assets/models/user.py:112 templates/_nav.html:39 #: assets/models/user.py:113 templates/_nav.html:39
#: xpack/plugins/change_auth_plan/models.py:52 #: xpack/plugins/change_auth_plan/models.py:52
msgid "Assets" msgid "Assets"
msgstr "资产管理" msgstr "资产管理"
#: assets/models/user.py:113 templates/_nav.html:17 #: assets/models/user.py:114 templates/_nav.html:17
#: users/views/profile/password.py:42 users/views/profile/pubkey.py:36 #: users/views/profile/password.py:42 users/views/profile/pubkey.py:36
msgid "Users" msgid "Users"
msgstr "用户管理" msgstr "用户管理"
#: assets/models/user.py:114 users/templates/users/user_group_list.html:90 #: assets/models/user.py:115 users/templates/users/user_group_list.html:90
#: users/templates/users/user_profile.html:124 #: users/templates/users/user_profile.html:124
msgid "User groups" msgid "User groups"
msgstr "用户组" msgstr "用户组"
#: assets/models/user.py:117 #: assets/models/user.py:118
msgid "Auto push" msgid "Auto push"
msgstr "自动推送" msgstr "自动推送"
#: assets/models/user.py:118 #: assets/models/user.py:119
msgid "Sudo" msgid "Sudo"
msgstr "Sudo" msgstr "Sudo"
#: assets/models/user.py:119 #: assets/models/user.py:120
msgid "Shell" msgid "Shell"
msgstr "Shell" msgstr "Shell"
#: assets/models/user.py:120 #: assets/models/user.py:121
msgid "Login mode" msgid "Login mode"
msgstr "登录模式" msgstr "登录模式"
#: assets/models/user.py:122 #: assets/models/user.py:123
msgid "SFTP Root" msgid "SFTP Root"
msgstr "SFTP根路径" msgstr "SFTP根路径"
#: assets/models/user.py:123 authentication/models.py:88 #: assets/models/user.py:124 authentication/models.py:88
msgid "Token" msgid "Token"
msgstr "" msgstr ""
#: assets/models/user.py:198 audits/models.py:39 #: assets/models/user.py:125
msgid "Home"
msgstr "家目录"
#: assets/models/user.py:126
msgid "System groups"
msgstr "用户组"
#: assets/models/user.py:201 audits/models.py:39
#: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49 #: perms/forms/asset_permission.py:95 perms/forms/remote_app_permission.py:49
#: perms/models/asset_permission.py:92 #: perms/models/asset_permission.py:92
#: perms/models/database_app_permission.py:22 #: perms/models/database_app_permission.py:22
#: perms/models/k8s_app_permission.py:22 #: perms/models/k8s_app_permission.py:22
#: perms/models/remote_app_permission.py:16 templates/_nav.html:45 #: perms/models/remote_app_permission.py:16 templates/_nav.html:45
#: terminal/backends/command/models.py:20 #: terminal/backends/command/models.py:20
#: terminal/backends/command/serializers.py:14 terminal/models.py:189 #: terminal/backends/command/serializers.py:14 terminal/models.py:190
#: tickets/serializers/request_asset_perm.py:27 #: tickets/serializers/request_asset_perm.py:27
#: users/templates/users/_granted_assets.html:27 #: users/templates/users/_granted_assets.html:27
#: users/templates/users/user_asset_permission.html:42 #: users/templates/users/user_asset_permission.html:42
@ -725,14 +733,14 @@ msgid "Backend"
msgstr "后端" msgstr "后端"
#: assets/serializers/asset_user.py:75 users/forms/profile.py:148 #: assets/serializers/asset_user.py:75 users/forms/profile.py:148
#: users/models/user.py:519 users/templates/users/user_password_update.html:48 #: users/models/user.py:521 users/templates/users/user_password_update.html:48
#: users/templates/users/user_profile.html:69 #: users/templates/users/user_profile.html:69
#: users/templates/users/user_profile_update.html:46 #: users/templates/users/user_profile_update.html:46
#: users/templates/users/user_pubkey_update.html:46 #: users/templates/users/user_pubkey_update.html:46
msgid "Public key" msgid "Public key"
msgstr "SSH公钥" msgstr "SSH公钥"
#: assets/serializers/asset_user.py:79 users/models/user.py:516 #: assets/serializers/asset_user.py:79 users/models/user.py:518
msgid "Private key" msgid "Private key"
msgstr "ssh私钥" msgstr "ssh私钥"
@ -753,23 +761,23 @@ msgstr "值"
msgid "The same level node name cannot be the same" msgid "The same level node name cannot be the same"
msgstr "同级别节点名字不能重复" msgstr "同级别节点名字不能重复"
#: assets/serializers/system_user.py:45 assets/serializers/system_user.py:177 #: assets/serializers/system_user.py:47 assets/serializers/system_user.py:180
msgid "Login mode display" msgid "Login mode display"
msgstr "登录模式显示" msgstr "登录模式显示"
#: assets/serializers/system_user.py:85 #: assets/serializers/system_user.py:87
msgid "Username same with user with protocol {} only allow 1" msgid "Username same with user with protocol {} only allow 1"
msgstr "用户名和用户相同的一种协议只允许存在一个" msgstr "用户名和用户相同的一种协议只允许存在一个"
#: assets/serializers/system_user.py:98 #: assets/serializers/system_user.py:100
msgid "* Automatic login mode must fill in the username." msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名" msgstr "自动登录模式,必须填写用户名"
#: assets/serializers/system_user.py:106 #: assets/serializers/system_user.py:108
msgid "Path should starts with /" msgid "Path should starts with /"
msgstr "路径应该以 / 开头" msgstr "路径应该以 / 开头"
#: assets/serializers/system_user.py:117 #: assets/serializers/system_user.py:119
msgid "Password or private key required" msgid "Password or private key required"
msgstr "密码或密钥密码需要一个" msgstr "密码或密钥密码需要一个"
@ -821,25 +829,25 @@ msgstr "更新节点资产硬件信息: {}"
msgid "Gather assets users" msgid "Gather assets users"
msgstr "收集资产上的用户" msgstr "收集资产上的用户"
#: assets/tasks/push_system_user.py:148 #: assets/tasks/push_system_user.py:176
#: assets/tasks/system_user_connectivity.py:89 #: assets/tasks/system_user_connectivity.py:89
msgid "System user is dynamic: {}" msgid "System user is dynamic: {}"
msgstr "系统用户是动态的: {}" msgstr "系统用户是动态的: {}"
#: assets/tasks/push_system_user.py:179 #: assets/tasks/push_system_user.py:207
msgid "Start push system user for platform: [{}]" msgid "Start push system user for platform: [{}]"
msgstr "推送系统用户到平台: [{}]" msgstr "推送系统用户到平台: [{}]"
#: assets/tasks/push_system_user.py:180 #: assets/tasks/push_system_user.py:208
#: assets/tasks/system_user_connectivity.py:81 #: assets/tasks/system_user_connectivity.py:81
msgid "Hosts count: {}" msgid "Hosts count: {}"
msgstr "主机数量: {}" msgstr "主机数量: {}"
#: assets/tasks/push_system_user.py:197 assets/tasks/push_system_user.py:213 #: assets/tasks/push_system_user.py:225 assets/tasks/push_system_user.py:241
msgid "Push system users to assets: {}" msgid "Push system users to assets: {}"
msgstr "推送系统用户到入资产: {}" msgstr "推送系统用户到入资产: {}"
#: assets/tasks/push_system_user.py:205 #: assets/tasks/push_system_user.py:233
msgid "Push system users to asset: {}({}) => {}" msgid "Push system users to asset: {}({}) => {}"
msgstr "推送系统用户到入资产: {}({}) => {}" msgstr "推送系统用户到入资产: {}({}) => {}"
@ -919,7 +927,7 @@ msgid "Symlink"
msgstr "建立软链接" msgstr "建立软链接"
#: audits/models.py:37 audits/models.py:60 audits/models.py:71 #: audits/models.py:37 audits/models.py:60 audits/models.py:71
#: terminal/models.py:192 #: terminal/models.py:193
msgid "Remote addr" msgid "Remote addr"
msgstr "远端地址" msgstr "远端地址"
@ -937,7 +945,7 @@ msgid "Success"
msgstr "成功" msgstr "成功"
#: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52 #: audits/models.py:43 ops/models/command.py:28 perms/models/base.py:52
#: terminal/models.py:199 tickets/serializers/request_asset_perm.py:29 #: terminal/models.py:200 tickets/serializers/request_asset_perm.py:29
#: xpack/plugins/change_auth_plan/models.py:177 #: xpack/plugins/change_auth_plan/models.py:177
#: xpack/plugins/change_auth_plan/models.py:308 #: xpack/plugins/change_auth_plan/models.py:308
#: xpack/plugins/gathered_user/models.py:76 #: xpack/plugins/gathered_user/models.py:76
@ -1017,7 +1025,7 @@ msgstr "Agent"
#: audits/models.py:104 #: audits/models.py:104
#: authentication/templates/authentication/_mfa_confirm_modal.html:14 #: authentication/templates/authentication/_mfa_confirm_modal.html:14
#: authentication/templates/authentication/login_otp.html:6 #: authentication/templates/authentication/login_otp.html:6
#: users/forms/profile.py:52 users/models/user.py:511 #: users/forms/profile.py:52 users/models/user.py:513
#: users/serializers/user.py:240 users/templates/users/user_detail.html:77 #: users/serializers/user.py:240 users/templates/users/user_detail.html:77
#: users/templates/users/user_profile.html:87 #: users/templates/users/user_profile.html:87
msgid "MFA" msgid "MFA"
@ -1191,7 +1199,7 @@ msgstr "登录复核 {}"
msgid "SSO auth closed" msgid "SSO auth closed"
msgstr "SSO 认证关闭了" msgstr "SSO 认证关闭了"
#: authentication/errors.py:218 authentication/views/login.py:237 #: authentication/errors.py:218 authentication/views/login.py:243
msgid "Your password is too simple, please change it for security" msgid "Your password is too simple, please change it for security"
msgstr "你的密码过于简单,为了安全,请修改" msgstr "你的密码过于简单,为了安全,请修改"
@ -1257,7 +1265,7 @@ msgid "Show"
msgstr "显示" msgstr "显示"
#: authentication/templates/authentication/_access_key_modal.html:66 #: authentication/templates/authentication/_access_key_modal.html:66
#: users/models/user.py:409 users/serializers/user.py:237 #: users/models/user.py:411 users/serializers/user.py:237
#: users/templates/users/user_profile.html:94 #: users/templates/users/user_profile.html:94
#: users/templates/users/user_profile.html:163 #: users/templates/users/user_profile.html:163
#: users/templates/users/user_profile.html:166 #: users/templates/users/user_profile.html:166
@ -1266,7 +1274,7 @@ msgid "Disable"
msgstr "禁用" msgstr "禁用"
#: authentication/templates/authentication/_access_key_modal.html:67 #: authentication/templates/authentication/_access_key_modal.html:67
#: users/models/user.py:410 users/serializers/user.py:238 #: users/models/user.py:412 users/serializers/user.py:238
#: users/templates/users/user_profile.html:92 #: users/templates/users/user_profile.html:92
#: users/templates/users/user_profile.html:170 #: users/templates/users/user_profile.html:170
msgid "Enable" msgid "Enable"
@ -1374,11 +1382,11 @@ msgstr "复制成功"
msgid "Welcome back, please enter username and password to login" msgid "Welcome back, please enter username and password to login"
msgstr "欢迎回来,请输入用户名和密码登录" msgstr "欢迎回来,请输入用户名和密码登录"
#: authentication/views/login.py:83 #: authentication/views/login.py:84
msgid "Please enable cookies and try again." msgid "Please enable cookies and try again."
msgstr "设置你的浏览器支持cookie" msgstr "设置你的浏览器支持cookie"
#: authentication/views/login.py:183 #: authentication/views/login.py:189
msgid "" msgid ""
"Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n" "Wait for <b>{}</b> confirm, You also can copy link to her/him <br/>\n"
" Don't close this page" " Don't close this page"
@ -1386,19 +1394,19 @@ msgstr ""
"等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n" "等待 <b>{}</b> 确认, 你也可以复制链接发给他/她 <br/>\n"
" 不要关闭本页面" " 不要关闭本页面"
#: authentication/views/login.py:188 #: authentication/views/login.py:194
msgid "No ticket found" msgid "No ticket found"
msgstr "没有发现工单" msgstr "没有发现工单"
#: authentication/views/login.py:220 #: authentication/views/login.py:226
msgid "Logout success" msgid "Logout success"
msgstr "退出登录成功" msgstr "退出登录成功"
#: authentication/views/login.py:221 #: authentication/views/login.py:227
msgid "Logout success, return login page" msgid "Logout success, return login page"
msgstr "退出登录成功,返回到登录页面" msgstr "退出登录成功,返回到登录页面"
#: authentication/views/login.py:236 #: authentication/views/login.py:242
msgid "Please change your password" msgid "Please change your password"
msgstr "请修改密码" msgstr "请修改密码"
@ -1670,14 +1678,14 @@ msgstr "磁盘使用率超过 80%: {} => {}"
#: orgs/api.py:58 #: orgs/api.py:58
msgid "Organization contains undeleted resources" msgid "Organization contains undeleted resources"
msgstr "" msgstr "组织包含未删除的资源"
#: orgs/api.py:62 #: orgs/api.py:62
msgid "The current organization cannot be deleted" msgid "The current organization cannot be deleted"
msgstr "" msgstr "当前组织不能被删除"
#: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:40 #: orgs/mixins/models.py:56 orgs/mixins/serializers.py:25 orgs/models.py:40
#: orgs/models.py:321 #: orgs/models.py:384
msgid "Organization" msgid "Organization"
msgstr "组织" msgstr "组织"
@ -1689,7 +1697,7 @@ msgstr "组织管理员"
msgid "Organization auditor" msgid "Organization auditor"
msgstr "组织审计员" msgstr "组织审计员"
#: orgs/models.py:323 users/forms/user.py:27 users/models/user.py:499 #: orgs/models.py:386 users/forms/user.py:27 users/models/user.py:501
#: users/templates/users/_select_user_modal.html:15 #: users/templates/users/_select_user_modal.html:15
#: users/templates/users/user_detail.html:73 #: users/templates/users/user_detail.html:73
#: users/templates/users/user_list.html:16 #: users/templates/users/user_list.html:16
@ -1714,7 +1722,7 @@ msgstr "提示RDP 协议不支持单独控制上传或下载文件"
#: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41 #: perms/forms/asset_permission.py:86 perms/forms/database_app_permission.py:41
#: perms/forms/remote_app_permission.py:43 perms/models/base.py:50 #: perms/forms/remote_app_permission.py:43 perms/models/base.py:50
#: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31 #: templates/_nav.html:21 users/forms/user.py:168 users/models/group.py:31
#: users/models/user.py:495 users/serializers/user.py:49 #: users/models/user.py:497 users/serializers/user.py:48
#: users/templates/users/_select_user_modal.html:16 #: users/templates/users/_select_user_modal.html:16
#: users/templates/users/user_asset_permission.html:39 #: users/templates/users/user_asset_permission.html:39
#: users/templates/users/user_asset_permission.html:67 #: users/templates/users/user_asset_permission.html:67
@ -1781,7 +1789,7 @@ msgid "Asset permission"
msgstr "资产授权" msgstr "资产授权"
#: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:31 #: perms/models/base.py:53 tickets/serializers/request_asset_perm.py:31
#: users/models/user.py:527 users/templates/users/user_detail.html:93 #: users/models/user.py:529 users/templates/users/user_detail.html:93
#: users/templates/users/user_profile.html:120 #: users/templates/users/user_profile.html:120
msgid "Date expired" msgid "Date expired"
msgstr "失效日期" msgstr "失效日期"
@ -2469,96 +2477,97 @@ msgstr "风险等级"
msgid "Bulk create not support" msgid "Bulk create not support"
msgstr "不支持批量创建" msgstr "不支持批量创建"
#: terminal/models.py:27 #: terminal/models.py:28
msgid "Remote Address" msgid "Remote Address"
msgstr "远端地址" msgstr "远端地址"
#: terminal/models.py:28 #: terminal/models.py:29
msgid "SSH Port" msgid "SSH Port"
msgstr "SSH端口" msgstr "SSH端口"
#: terminal/models.py:29 #: terminal/models.py:30
msgid "HTTP Port" msgid "HTTP Port"
msgstr "HTTP端口" msgstr "HTTP端口"
#: terminal/models.py:30 #: terminal/models.py:31
msgid "Command storage" msgid "Command storage"
msgstr "命令存储" msgstr "命令存储"
#: terminal/models.py:31 #: terminal/models.py:32
msgid "Replay storage" msgid "Replay storage"
msgstr "录像存储" msgstr "录像存储"
#: terminal/models.py:154 #: terminal/models.py:155
msgid "Session Online" msgid "Session Online"
msgstr "在线会话" msgstr "在线会话"
#: terminal/models.py:155 #: terminal/models.py:156
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU使用" msgstr "CPU使用"
#: terminal/models.py:156 #: terminal/models.py:157
msgid "Memory Used" msgid "Memory Used"
msgstr "内存使用" msgstr "内存使用"
#: terminal/models.py:157 #: terminal/models.py:158
msgid "Connections" msgid "Connections"
msgstr "连接数" msgstr "连接数"
#: terminal/models.py:158 #: terminal/models.py:159
msgid "Threads" msgid "Threads"
msgstr "线程数" msgstr "线程数"
#: terminal/models.py:159 #: terminal/models.py:160
msgid "Boot Time" msgid "Boot Time"
msgstr "运行时间" msgstr "运行时间"
#: terminal/models.py:191 #: terminal/models.py:192
msgid "Login from" msgid "Login from"
msgstr "登录来源" msgstr "登录来源"
#: terminal/models.py:195 #: terminal/models.py:196
msgid "Replay" msgid "Replay"
msgstr "回放" msgstr "回放"
#: terminal/models.py:200 #: terminal/models.py:201
msgid "Date end" msgid "Date end"
msgstr "结束日期" msgstr "结束日期"
#: terminal/models.py:343 #: terminal/models.py:345
msgid "Args" msgid "Args"
msgstr "参数" msgstr "参数"
#: tickets/api/request_asset_perm.py:45 #: tickets/api/request_asset_perm.py:46
#, python-format #, python-format
msgid "Ticket has %s" msgid "Ticket has %s"
msgstr "工单已%s" msgstr "工单已%s"
#: tickets/api/request_asset_perm.py:90 #: tickets/api/request_asset_perm.py:91
msgid "Confirm assets first" msgid "Confirm assets first"
msgstr "请先确认资产" msgstr "请先确认资产"
#: tickets/api/request_asset_perm.py:93 #: tickets/api/request_asset_perm.py:94
msgid "Confirmed assets changed" msgid "Confirmed assets changed"
msgstr "确认的资产变更了" msgstr "确认的资产变更了"
#: tickets/api/request_asset_perm.py:97 #: tickets/api/request_asset_perm.py:98
msgid "Confirm system-user first" msgid "Confirm system-user first"
msgstr "请先确认系统用户" msgstr "请先确认系统用户"
#: tickets/api/request_asset_perm.py:101 #: tickets/api/request_asset_perm.py:102
msgid "Confirmed system-user changed" msgid "Confirmed system-user changed"
msgstr "确认的系统用户变更了" msgstr "确认的系统用户变更了"
#: tickets/api/request_asset_perm.py:104 xpack/plugins/cloud/models.py:202 #: tickets/api/request_asset_perm.py:105 tickets/api/request_asset_perm.py:112
#: xpack/plugins/cloud/models.py:202
msgid "Succeed" msgid "Succeed"
msgstr "成功" msgstr "成功"
#: tickets/api/request_asset_perm.py:112 #: tickets/api/request_asset_perm.py:120
msgid "From request ticket: {} {}" msgid "From request ticket: {} {}"
msgstr "来自工单申请: {} {}" msgstr "来自工单申请: {} {}"
#: tickets/api/request_asset_perm.py:114 #: tickets/api/request_asset_perm.py:122
msgid "{} request assets, approved by {}" msgid "{} request assets, approved by {}"
msgstr "{} 申请资产,通过人 {}" msgstr "{} 申请资产,通过人 {}"
@ -2576,11 +2585,11 @@ msgstr "不能操作该工单"
#: tickets/models/ticket.py:18 tickets/models/ticket.py:69 #: tickets/models/ticket.py:18 tickets/models/ticket.py:69
msgid "Open" msgid "Open"
msgstr "开启" msgstr "待处理"
#: tickets/models/ticket.py:19 #: tickets/models/ticket.py:19
msgid "Closed" msgid "Closed"
msgstr "关闭" msgstr "已完成"
#: tickets/models/ticket.py:22 #: tickets/models/ticket.py:22
msgid "General" msgid "General"
@ -2741,7 +2750,7 @@ msgstr ""
" </div>\n" " </div>\n"
" " " "
#: users/api/user.py:147 #: users/api/user.py:158
msgid "Could not reset self otp, use profile reset instead" msgid "Could not reset self otp, use profile reset instead"
msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置" msgstr "不能在该页面重置多因子认证, 请去个人信息页面重置"
@ -2787,7 +2796,7 @@ msgstr "确认密码"
msgid "Password does not match" msgid "Password does not match"
msgstr "密码不一致" msgstr "密码不一致"
#: users/forms/profile.py:89 users/models/user.py:491 #: users/forms/profile.py:89 users/models/user.py:493
#: users/templates/users/user_detail.html:57 #: users/templates/users/user_detail.html:57
#: users/templates/users/user_profile.html:59 #: users/templates/users/user_profile.html:59
msgid "Email" msgid "Email"
@ -2828,7 +2837,7 @@ msgstr "不能和原来的密钥相同"
msgid "Not a valid ssh public key" msgid "Not a valid ssh public key"
msgstr "SSH密钥不合法" msgstr "SSH密钥不合法"
#: users/forms/user.py:31 users/models/user.py:534 #: users/forms/user.py:31 users/models/user.py:536
#: users/templates/users/user_detail.html:89 #: users/templates/users/user_detail.html:89
#: users/templates/users/user_list.html:18 #: users/templates/users/user_list.html:18
#: users/templates/users/user_profile.html:102 #: users/templates/users/user_profile.html:102
@ -2848,93 +2857,93 @@ msgstr "添加到用户组"
msgid "* Your password does not meet the requirements" msgid "* Your password does not meet the requirements"
msgstr "* 您的密码不符合要求" msgstr "* 您的密码不符合要求"
#: users/forms/user.py:124 users/serializers/user.py:37 #: users/forms/user.py:124 users/serializers/user.py:36
msgid "Reset link will be generated and sent to the user" msgid "Reset link will be generated and sent to the user"
msgstr "生成重置密码链接,通过邮件发送给用户" msgstr "生成重置密码链接,通过邮件发送给用户"
#: users/forms/user.py:125 users/serializers/user.py:38 #: users/forms/user.py:125 users/serializers/user.py:37
msgid "Set password" msgid "Set password"
msgstr "设置密码" msgstr "设置密码"
#: users/forms/user.py:132 users/serializers/user.py:45 #: users/forms/user.py:132 users/serializers/user.py:44
#: xpack/plugins/change_auth_plan/models.py:61 #: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/serializers.py:30 #: xpack/plugins/change_auth_plan/serializers.py:30
msgid "Password strategy" msgid "Password strategy"
msgstr "密码策略" msgstr "密码策略"
#: users/models/user.py:156 #: users/models/user.py:157
msgid "Super administrator" msgid "System administrator"
msgstr "超级管理员" msgstr "系统管理员"
#: users/models/user.py:158
msgid "Super auditor"
msgstr "超级审计员"
#: users/models/user.py:159 #: users/models/user.py:159
msgid "System auditor"
msgstr "系统审计员"
#: users/models/user.py:160
msgid "Application" msgid "Application"
msgstr "应用程序" msgstr "应用程序"
#: users/models/user.py:411 users/templates/users/user_profile.html:90 #: users/models/user.py:413 users/templates/users/user_profile.html:90
msgid "Force enable" msgid "Force enable"
msgstr "强制启用" msgstr "强制启用"
#: users/models/user.py:478 #: users/models/user.py:480
msgid "Local" msgid "Local"
msgstr "数据库" msgstr "数据库"
#: users/models/user.py:502 #: users/models/user.py:504
msgid "Avatar" msgid "Avatar"
msgstr "头像" msgstr "头像"
#: users/models/user.py:505 users/templates/users/user_detail.html:68 #: users/models/user.py:507 users/templates/users/user_detail.html:68
msgid "Wechat" msgid "Wechat"
msgstr "微信" msgstr "微信"
#: users/models/user.py:538 #: users/models/user.py:540
msgid "Date password last updated" msgid "Date password last updated"
msgstr "最后更新密码日期" msgstr "最后更新密码日期"
#: users/models/user.py:651 #: users/models/user.py:653
msgid "Administrator" msgid "Administrator"
msgstr "管理员" msgstr "管理员"
#: users/models/user.py:654 #: users/models/user.py:656
msgid "Administrator is the super user of system" msgid "Administrator is the super user of system"
msgstr "Administrator是初始的超级管理员" msgstr "Administrator是初始的超级管理员"
#: users/serializers/user.py:55 users/serializers/user.py:93 #: users/serializers/user.py:53 users/serializers/user.py:88
msgid "Organization role name" msgid "Organization role name"
msgstr "组织角色名称" msgstr "组织角色名称"
#: users/serializers/user.py:59 #: users/serializers/user.py:55
msgid "Total role name" msgid "Total role name"
msgstr "汇总角色名称" msgstr "汇总角色名称"
#: users/serializers/user.py:84 users/serializers/user.py:253 #: users/serializers/user.py:79 users/serializers/user.py:253
msgid "Is first login" msgid "Is first login"
msgstr "首次登录" msgstr "首次登录"
#: users/serializers/user.py:85 #: users/serializers/user.py:80
msgid "Is valid" msgid "Is valid"
msgstr "账户是否有效" msgstr "账户是否有效"
#: users/serializers/user.py:86 #: users/serializers/user.py:81
msgid "Is expired" msgid "Is expired"
msgstr " 是否过期" msgstr " 是否过期"
#: users/serializers/user.py:87 #: users/serializers/user.py:82
msgid "Avatar url" msgid "Avatar url"
msgstr "头像路径" msgstr "头像路径"
#: users/serializers/user.py:91 #: users/serializers/user.py:86
msgid "Groups name" msgid "Groups name"
msgstr "用户组名" msgstr "用户组名"
#: users/serializers/user.py:92 #: users/serializers/user.py:87
msgid "Source name" msgid "Source name"
msgstr "用户来源名" msgstr "用户来源名"
#: users/serializers/user.py:94 #: users/serializers/user.py:89
msgid "Super role name" msgid "Super role name"
msgstr "超级角色名称" msgstr "超级角色名称"
@ -4072,6 +4081,12 @@ msgstr "企业版"
msgid "Ultimate edition" msgid "Ultimate edition"
msgstr "旗舰版" msgstr "旗舰版"
#~ msgid "Organization User"
#~ msgstr "组织用户"
#~ msgid "System User"
#~ msgstr "系统用户"
#~ msgid "Auditor" #~ msgid "Auditor"
#~ msgstr "审计员" #~ msgstr "审计员"
@ -4489,9 +4504,6 @@ msgstr "旗舰版"
#~ msgid "Have existed: " #~ msgid "Have existed: "
#~ msgstr "已经存在: " #~ msgstr "已经存在: "
#~ msgid "Home"
#~ msgstr "家目录"
#~ msgid "Uid" #~ msgid "Uid"
#~ msgstr "Uid" #~ msgstr "Uid"

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-08-19 12:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orgs', '0007_auto_20200728_1805'),
]
operations = [
migrations.AlterField(
model_name='organizationmember',
name='role',
field=models.CharField(choices=[('Admin', 'Organization administrator'), ('Auditor', 'Organization auditor'), ('User', 'User')], default='User', max_length=16, verbose_name='Role'),
),
]

View File

@ -13,8 +13,8 @@ from common.db.models import ChoiceSet
class ROLE(ChoiceSet): class ROLE(ChoiceSet):
ADMIN = choices.ADMIN, _('Organization administrator') ADMIN = choices.ADMIN, _('Organization administrator')
USER = choices.USER, _('User')
AUDITOR = choices.AUDITOR, _("Organization auditor") AUDITOR = choices.AUDITOR, _("Organization auditor")
USER = choices.USER, _('User')
class Organization(models.Model): class Organization(models.Model):
@ -229,15 +229,44 @@ def _none2list(*args):
return ([] if v is None else v for v in args) return ([] if v is None else v for v in args)
class UserRoleMapper(dict):
def __init__(self, container=set):
super().__init__()
self.users = container()
self.admins = container()
self.auditors = container()
self[ROLE.USER] = self.users
self[ROLE.ADMIN] = self.admins
self[ROLE.AUDITOR] = self.auditors
class OrgMemeberManager(models.Manager): class OrgMemeberManager(models.Manager):
def remove_users(self, org, users):
from users.models import User
pk_set = []
for user in users:
if hasattr(user, 'pk'):
pk_set.append(user.pk)
else:
pk_set.append(user)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=User, pk_set=pk_set, using=self.db)
send(action="pre_remove")
self.filter(org_id=org.id, user_id__in=pk_set).delete()
send(action="post_remove")
def remove_users_by_role(self, org, users=None, admins=None, auditors=None): def remove_users_by_role(self, org, users=None, admins=None, auditors=None):
from users.models import User
if not any((users, admins, auditors)): if not any((users, admins, auditors)):
return return
users, admins, auditors = _none2list(users, admins, auditors) users, admins, auditors = _none2list(users, admins, auditors)
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=Organization, pk_set=[*users, *admins, *auditors], using=self.db) model=User, pk_set=[*users, *admins, *auditors], using=self.db)
send(action="pre_remove") send(action="pre_remove")
self.filter(org_id=org.id).filter( self.filter(org_id=org.id).filter(
@ -248,6 +277,8 @@ class OrgMemeberManager(models.Manager):
send(action="post_remove") send(action="post_remove")
def add_users_by_role(self, org, users=None, admins=None, auditors=None): def add_users_by_role(self, org, users=None, admins=None, auditors=None):
from users.models import User
if not any((users, admins, auditors)): if not any((users, admins, auditors)):
return return
users, admins, auditors = _none2list(users, admins, auditors) users, admins, auditors = _none2list(users, admins, auditors)
@ -266,7 +297,7 @@ class OrgMemeberManager(models.Manager):
oms_add.append(self.model(org_id=org.id, user_id=user, role=role)) oms_add.append(self.model(org_id=org.id, user_id=user, role=role))
send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False, send = partial(signals.m2m_changed.send, sender=self.model, instance=org, reverse=False,
model=Organization, pk_set=[*users, *admins, *auditors], using=self.db) model=User, pk_set=[*users, *admins, *auditors], using=self.db)
send(action='pre_add') send(action='pre_add')
self.bulk_create(oms_add) self.bulk_create(oms_add)
@ -278,24 +309,56 @@ class OrgMemeberManager(models.Manager):
new_users = _convert_to_uuid_set(new_users) new_users = _convert_to_uuid_set(new_users)
return (old_users - new_users), (new_users - old_users) return (old_users - new_users), (new_users - old_users)
def set_user_roles(self, org, user, roles):
"""
设置某个用户在某个组织里的角色
"""
old_roles = set(self.filter(org_id=org.id, user=user).values_list('role', flat=True))
new_roles = set(roles)
roles_remove = old_roles - new_roles
roles_add = new_roles - old_roles
to_remove = UserRoleMapper()
to_add = UserRoleMapper()
for role in roles_remove:
if role in to_remove:
to_remove[role].add(user)
for role in roles_add:
if role in to_add:
to_add[role].add(user)
self.remove_users_by_role(
org,
to_remove.users,
to_remove.admins,
to_remove.auditors
)
self.add_users_by_role(
org,
to_add.users,
to_add.admins,
to_add.auditors
)
def set_users_by_role(self, org, users=None, admins=None, auditors=None): def set_users_by_role(self, org, users=None, admins=None, auditors=None):
"""
给组织设置带角色的用户
"""
oms = self.filter(org_id=org.id).values_list('role', 'user_id') oms = self.filter(org_id=org.id).values_list('role', 'user_id')
old_users, old_admins, old_auditors = set(), set(), set() old_mapper = UserRoleMapper()
mapper = {
ROLE.USER: old_users,
ROLE.ADMIN: old_admins,
ROLE.AUDITOR: old_auditors
}
for role, user_id in oms: for role, user_id in oms:
if role in mapper: if role in old_mapper:
mapper[role].add(user_id) old_mapper[role].add(user_id)
users_remove, users_add = self._get_remove_add_set(users, old_users) users_remove, users_add = self._get_remove_add_set(users, old_mapper.users)
admins_remove, admins_add = self._get_remove_add_set(admins, old_admins) admins_remove, admins_add = self._get_remove_add_set(admins, old_mapper.admins)
auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_auditors) auditors_remove, auditors_add = self._get_remove_add_set(auditors, old_mapper.auditors)
self.remove_users_by_role( self.remove_users_by_role(
org, org,

View File

@ -6,13 +6,13 @@ from users.models.user import User
from common.serializers import AdaptedBulkListSerializer from common.serializers import AdaptedBulkListSerializer
from common.drf.serializers import BulkModelSerializer from common.drf.serializers import BulkModelSerializer
from common.db.models import concated_display as display from common.db.models import concated_display as display
from .models import Organization, OrganizationMember, ROLE as ORG_ROLE from .models import Organization, OrganizationMember
class OrgSerializer(ModelSerializer): class OrgSerializer(ModelSerializer):
users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True) users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
admins = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True) admins = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
auditors = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True) auditors = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), write_only=True, required=False)
class Meta: class Meta:
model = Organization model = Organization

View File

@ -1,3 +1,5 @@
import textwrap
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action from rest_framework.decorators import action
@ -8,7 +10,7 @@ from orgs.models import Organization, ROLE as ORG_ROLE
from users.models.user import User from users.models.user import User
from common.const.http import POST, GET from common.const.http import POST, GET
from common.drf.api import JMSModelViewSet from common.drf.api import JMSModelViewSet
from common.permissions import IsValidUser from common.permissions import IsValidUser, IsObjectOwner
from common.utils.django import get_object_or_none from common.utils.django import get_object_or_none
from common.utils.timezone import dt_parser from common.utils.timezone import dt_parser
from common.drf.serializers import EmptySerializer from common.drf.serializers import EmptySerializer
@ -31,6 +33,7 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
'default': serializers.RequestAssetPermTicketSerializer, 'default': serializers.RequestAssetPermTicketSerializer,
'approve': EmptySerializer, 'approve': EmptySerializer,
'reject': EmptySerializer, 'reject': EmptySerializer,
'close': EmptySerializer,
'assignees': serializers.AssigneeSerializer, 'assignees': serializers.AssigneeSerializer,
} }
permission_classes = (IsValidUser,) permission_classes = (IsValidUser,)
@ -61,13 +64,13 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
ips = ', '.join(meta.get('ips', [])) ips = ', '.join(meta.get('ips', []))
confirmed_assets = ', '.join(meta.get('confirmed_assets', [])) confirmed_assets = ', '.join(meta.get('confirmed_assets', []))
return f''' return textwrap.dedent(f'''\
{_('IP group')}: {ips} {_('IP group')}: {ips}
{_('Hostname')}: {meta.get('hostname', '')} {_('Hostname')}: {meta.get('hostname', '')}
{_('System user')}: {meta.get('system_user', '')} {_('System user')}: {meta.get('system_user', '')}
{_('Confirmed assets')}: {confirmed_assets} {_('Confirmed assets')}: {confirmed_assets}
{_('Confirmed system user')}: {meta.get('confirmed_system_user', '')} {_('Confirmed system user')}: {meta.get('confirmed_system_user', '')}
''' ''')
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser]) @action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def reject(self, request, *args, **kwargs): def reject(self, request, *args, **kwargs):
@ -103,6 +106,13 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
self._create_asset_permission(instance, assets, system_user) self._create_asset_permission(instance, assets, system_user)
return Response({'detail': _('Succeed')}) return Response({'detail': _('Succeed')})
@action(detail=True, methods=[POST], permission_classes=[IsAssignee | IsObjectOwner])
def close(self, request, *args, **kwargs):
instance = self.get_object()
instance.status = Ticket.STATUS.CLOSED
instance.save()
return Response({'detail': _('Succeed')})
def _create_asset_permission(self, instance: Ticket, assets, system_user): def _create_asset_permission(self, instance: Ticket, assets, system_user):
meta = instance.meta meta = instance.meta
request = self.request request = self.request

View File

@ -4,6 +4,7 @@ from urllib.parse import urljoin
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from common.const.front_urls import TICKET_DETAIL
from common.utils import get_logger from common.utils import get_logger
from common.tasks import send_mail_async from common.tasks import send_mail_async
@ -20,11 +21,7 @@ def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees):
subject = '{}: {}'.format(_("New ticket"), ticket.title) subject = '{}: {}'.format(_("New ticket"), ticket.title)
# 这里要设置前端地址,因为要直接跳转到页面 # 这里要设置前端地址,因为要直接跳转到页面
if ticket.type == ticket.TYPE.REQUEST_ASSET_PERM: detail_url = urljoin(settings.SITE_URL, TICKET_DETAIL.format(id=ticket.id))
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/request-asset-perm/{ticket.id}')
else:
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/{ticket.id}')
message = _(""" message = _("""
<div> <div>
<p>Your has a new ticket</p> <p>Your has a new ticket</p>

View File

@ -56,31 +56,25 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
validated_data = serializer.validated_data validated_data = serializer.validated_data
if isinstance(validated_data, list):
org_roles = [item.pop('org_role', None) for item in validated_data]
else:
org_roles = [validated_data.pop('org_role', None)]
# `org_roles` 先 `pop`
if isinstance(validated_data, list):
org_roles = [item.pop('org_roles', []) for item in validated_data]
else:
org_roles = [validated_data.pop('org_roles', [])]
# 创建用户
users = serializer.save() users = serializer.save()
if isinstance(users, User): if isinstance(users, User):
users = [users] users = [users]
if current_org and current_org.is_real():
mapper = {
ORG_ROLE.USER: [],
ORG_ROLE.ADMIN: [],
ORG_ROLE.AUDITOR: []
}
for user, role in zip(users, org_roles): # 只有真实存在的组织才真正关联用户
if role in mapper: if current_org and current_org.is_real():
mapper[role].append(user) for user, roles in zip(users, org_roles):
else: if not roles:
mapper[ORG_ROLE.USER].append(user) # 当前组织创建的用户,至少是该组织的`User`
OrganizationMember.objects.set_users_by_role( roles.append(ORG_ROLE.USER)
current_org, users=mapper[ORG_ROLE.USER], OrganizationMember.objects.set_user_roles(current_org, user, roles)
admins=mapper[ORG_ROLE.ADMIN],
auditors=mapper[ORG_ROLE.AUDITOR]
)
self.send_created_signal(users) self.send_created_signal(users)
def get_permissions(self): def get_permissions(self):
@ -101,6 +95,23 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
self.check_object_permissions(self.request, obj) self.check_object_permissions(self.request, obj)
self.perform_destroy(obj) self.perform_destroy(obj)
def perform_update(self, serializer):
validated_data = serializer.validated_data
# `org_roles` 先 `pop`
if isinstance(validated_data, list):
org_roles = [item.pop('org_roles', None) for item in validated_data]
else:
org_roles = [validated_data.pop('org_roles', None)]
users = serializer.save()
if isinstance(users, User):
users = [users]
if current_org and current_org.is_real():
for user, roles in zip(users, org_roles):
if roles is not None:
# roles 是 `Node` 表明不需要更新
OrganizationMember.objects.set_user_roles(current_org, user, roles)
def perform_bulk_update(self, serializer): def perform_bulk_update(self, serializer):
# TODO: 需要测试 # TODO: 需要测试
users_ids = [ users_ids = [

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-08-14 08:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0028_auto_20200728_1805'),
]
operations = [
migrations.AlterField(
model_name='user',
name='role',
field=models.CharField(blank=True, choices=[('Admin', 'System administrator'), ('User', 'User'), ('Auditor', 'System auditor'), ('App', 'Application')], default='User', max_length=10, verbose_name='Role'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-08-19 12:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0029_auto_20200814_1650'),
]
operations = [
migrations.AlterField(
model_name='user',
name='role',
field=models.CharField(blank=True, choices=[('Admin', 'System administrator'), ('Auditor', 'System auditor'), ('User', 'User'), ('App', 'Application')], default='User', max_length=10, verbose_name='Role'),
),
]

View File

@ -18,6 +18,7 @@ from django.shortcuts import reverse
from common.local import LOCAL_DYNAMIC_SETTINGS from common.local import LOCAL_DYNAMIC_SETTINGS
from orgs.utils import current_org from orgs.utils import current_org
from orgs.models import OrganizationMember
from common.utils import date_expired_default, get_logger, lazyproperty from common.utils import date_expired_default, get_logger, lazyproperty
from common import fields from common import fields
from common.const import choices from common.const import choices
@ -153,9 +154,9 @@ class AuthMixin:
class RoleMixin: class RoleMixin:
class ROLE(ChoiceSet): class ROLE(ChoiceSet):
ADMIN = choices.ADMIN, _('Super administrator') ADMIN = choices.ADMIN, _('System administrator')
AUDITOR = choices.AUDITOR, _('System auditor')
USER = choices.USER, _('User') USER = choices.USER, _('User')
AUDITOR = choices.AUDITOR, _('Super auditor')
APP = 'App', _('Application') APP = 'App', _('Application')
role = ROLE.USER role = ROLE.USER
@ -164,15 +165,15 @@ class RoleMixin:
def role_display(self): def role_display(self):
return self.get_role_display() return self.get_role_display()
@property @lazyproperty
def org_role_display(self): def org_roles(self):
from orgs.models import ROLE as ORG_ROLE from orgs.models import ROLE as ORG_ROLE
if not current_org.is_real(): if not current_org.is_real():
if self.is_superuser: if self.is_superuser:
return ORG_ROLE.ADMIN.label return [ORG_ROLE.ADMIN]
else: else:
return ORG_ROLE.USER.label return [ORG_ROLE.USER]
if hasattr(self, 'gc_m2m_org_members__role'): if hasattr(self, 'gc_m2m_org_members__role'):
names = self.gc_m2m_org_members__role names = self.gc_m2m_org_members__role
@ -184,8 +185,24 @@ class RoleMixin:
roles = set(self.m2m_org_members.filter( roles = set(self.m2m_org_members.filter(
org_id=current_org.id org_id=current_org.id
).values_list('role', flat=True)) ).values_list('role', flat=True))
roles = list(roles)
roles.sort()
return roles
return ' | '.join([str(ORG_ROLE[role]) for role in roles if role in ORG_ROLE]) @lazyproperty
def org_roles_label_list(self):
from orgs.models import ROLE as ORG_ROLE
return [str(ORG_ROLE[role]) for role in self.org_roles if role in ORG_ROLE]
@lazyproperty
def org_role_display(self):
return ' | '.join(self.org_roles_label_list)
@lazyproperty
def total_role_display(self):
roles = list({self.role_display, *self.org_roles_label_list})
roles.sort()
return ' | '.join(roles)
def current_org_roles(self): def current_org_roles(self):
from orgs.models import OrganizationMember, ROLE as ORG_ROLE from orgs.models import OrganizationMember, ROLE as ORG_ROLE
@ -314,12 +331,7 @@ class RoleMixin:
def remove(self): def remove(self):
if not current_org.is_real(): if not current_org.is_real():
return return
if self.can_user_current_org: OrganizationMember.objects.remove_users(current_org, [self])
current_org.users.remove(self)
if self.can_admin_current_org:
current_org.admins.remove(self)
if self.can_audit_current_org:
current_org.auditors.remove(self)
class TokenMixin: class TokenMixin:

View File

@ -7,7 +7,6 @@ from rest_framework import serializers
from common.utils import validate_ssh_public_key from common.utils import validate_ssh_public_key
from common.mixins import CommonBulkSerializerMixin from common.mixins import CommonBulkSerializerMixin
from common.serializers import AdaptedBulkListSerializer
from common.permissions import CanUpdateDeleteUser from common.permissions import CanUpdateDeleteUser
from common.drf.fields import GroupConcatedPrimaryKeyRelatedField from common.drf.fields import GroupConcatedPrimaryKeyRelatedField
from orgs.models import ROLE as ORG_ROLE from orgs.models import ROLE as ORG_ROLE
@ -51,17 +50,12 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
login_blocked = serializers.SerializerMethodField() login_blocked = serializers.SerializerMethodField()
can_update = serializers.SerializerMethodField() can_update = serializers.SerializerMethodField()
can_delete = serializers.SerializerMethodField() can_delete = serializers.SerializerMethodField()
org_role = serializers.ChoiceField( org_roles = serializers.ListField(label=_('Organization role name'), allow_null=True, required=False,
label=_('Organization role name'), write_only=True, child=serializers.ChoiceField(choices=ORG_ROLE.choices))
allow_null=True, required=False, allow_blank=True,
choices=ORG_ROLE.choices
)
total_role_display = serializers.SerializerMethodField(label=_('Total role name'))
key_prefix_block = "_LOGIN_BLOCK_{}" key_prefix_block = "_LOGIN_BLOCK_{}"
class Meta: class Meta:
model = User model = User
list_serializer_class = AdaptedBulkListSerializer
# mini 是指能识别对象的最小单元 # mini 是指能识别对象的最小单元
fields_mini = ['id', 'name', 'username'] fields_mini = ['id', 'name', 'username']
# small 指的是 不需要计算的直接能从一张表中获取到的数据 # small 指的是 不需要计算的直接能从一张表中获取到的数据
@ -75,7 +69,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
] ]
fields = fields_small + [ fields = fields_small + [
'groups', 'role', 'groups_display', 'role_display', 'groups', 'role', 'groups_display', 'role_display',
'can_update', 'can_delete', 'login_blocked', 'org_role' 'can_update', 'can_delete', 'login_blocked', 'org_roles'
] ]
extra_kwargs = { extra_kwargs = {
@ -92,6 +86,7 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
'source_display': {'label': _('Source name')}, 'source_display': {'label': _('Source name')},
'org_role_display': {'label': _('Organization role name')}, 'org_role_display': {'label': _('Organization role name')},
'role_display': {'label': _('Super role name')}, 'role_display': {'label': _('Super role name')},
'total_role_display': {'label': _('Total role name')}
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -110,9 +105,6 @@ class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
choices.pop(User.ROLE.AUDITOR, None) choices.pop(User.ROLE.AUDITOR, None)
role._choices = choices role._choices = choices
def get_total_role_display(self, instance):
return ' | '.join({str(instance.role_display), str(instance.org_role_display)})
def validate_role(self, value): def validate_role(self, value):
request = self.context.get('request') request = self.context.get('request')
if not request.user.is_superuser and value != User.ROLE.USER: if not request.user.is_superuser and value != User.ROLE.USER: