mirror of https://github.com/jumpserver/jumpserver
commit
0de9b29fa9
|
@ -39,14 +39,14 @@ class RemoteAppSerializer(serializers.Serializer):
|
|||
@staticmethod
|
||||
def get_asset_info(obj):
|
||||
asset_id = obj.get('asset')
|
||||
if not asset_id or is_uuid(asset_id):
|
||||
if not asset_id or not is_uuid(asset_id):
|
||||
return {}
|
||||
try:
|
||||
asset = Asset.objects.filter(id=str(asset_id)).values_list('id', 'hostname')
|
||||
asset = Asset.objects.get(id=str(asset_id))
|
||||
except ObjectDoesNotExist as e:
|
||||
logger.error(e)
|
||||
return {}
|
||||
if not asset:
|
||||
return {}
|
||||
asset_info = {'id': str(asset[0]), 'hostname': asset[1]}
|
||||
asset_info = {'id': str(asset.id), 'hostname': asset.hostname}
|
||||
return asset_info
|
||||
|
|
|
@ -21,7 +21,7 @@ class DingTalkQRUnBindBase(APIView):
|
|||
if not user.dingtalk_id:
|
||||
raise errors.DingTalkNotBound
|
||||
|
||||
user.dingtalk_id = ''
|
||||
user.dingtalk_id = None
|
||||
user.save()
|
||||
return Response()
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class WeComQRUnBindBase(APIView):
|
|||
if not user.wecom_id:
|
||||
raise errors.WeComNotBound
|
||||
|
||||
user.wecom_id = ''
|
||||
user.wecom_id = None
|
||||
user.save()
|
||||
return Response()
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.core.cache import cache
|
|||
from django.utils.translation import ugettext as _
|
||||
from six import text_type
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.backends import ModelBackend as DJModelBackend
|
||||
from rest_framework import HTTP_HEADER_ENCODING
|
||||
from rest_framework import authentication, exceptions
|
||||
from common.auth import signature
|
||||
|
@ -25,6 +25,11 @@ def get_request_date_header(request):
|
|||
return date
|
||||
|
||||
|
||||
class ModelBackend(DJModelBackend):
|
||||
def user_can_authenticate(self, user):
|
||||
return user.is_valid
|
||||
|
||||
|
||||
class AccessKeyAuthentication(authentication.BaseAuthentication):
|
||||
"""App使用Access key进行签名认证, 目前签名算法比较简单,
|
||||
app注册或者手动建立后,会生成 access_key_id 和 access_key_secret,
|
||||
|
|
|
@ -8,7 +8,9 @@ from django.views.generic import TemplateView
|
|||
from django.views import View
|
||||
from django.conf import settings
|
||||
from django.http.request import HttpRequest
|
||||
from django.db.utils import IntegrityError
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
from users.views import UserVerifyPasswordView
|
||||
from users.utils import is_auth_password_time_valid
|
||||
|
@ -29,6 +31,20 @@ DINGTALK_STATE_SESSION_KEY = '_dingtalk_state'
|
|||
|
||||
|
||||
class DingTalkQRMixin(PermissionsMixin, View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
except APIException as e:
|
||||
try:
|
||||
msg = e.detail['errmsg']
|
||||
except Exception:
|
||||
msg = _('DingTalk Error, Please contact your system administrator')
|
||||
return self.get_failed_reponse(
|
||||
'/',
|
||||
_('DingTalk Error'),
|
||||
msg
|
||||
)
|
||||
|
||||
def verify_state(self):
|
||||
state = self.request.GET.get('state')
|
||||
session_state = self.request.session.get(DINGTALK_STATE_SESSION_KEY)
|
||||
|
@ -130,8 +146,15 @@ class DingTalkQRBindCallbackView(DingTalkQRMixin, View):
|
|||
response = self.get_failed_reponse(redirect_url, msg, msg)
|
||||
return response
|
||||
|
||||
user.dingtalk_id = userid
|
||||
user.save()
|
||||
try:
|
||||
user.dingtalk_id = userid
|
||||
user.save()
|
||||
except IntegrityError as e:
|
||||
if e.args[0] == 1062:
|
||||
msg = _('The DingTalk is already bound to another user')
|
||||
response = self.get_failed_reponse(redirect_url, msg, msg)
|
||||
return response
|
||||
raise e
|
||||
|
||||
msg = _('Binding DingTalk successfully')
|
||||
response = self.get_success_reponse(redirect_url, msg, msg)
|
||||
|
|
|
@ -8,7 +8,9 @@ from django.views.generic import TemplateView
|
|||
from django.views import View
|
||||
from django.conf import settings
|
||||
from django.http.request import HttpRequest
|
||||
from django.db.utils import IntegrityError
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
from users.views import UserVerifyPasswordView
|
||||
from users.utils import is_auth_password_time_valid
|
||||
|
@ -29,6 +31,20 @@ WECOM_STATE_SESSION_KEY = '_wecom_state'
|
|||
|
||||
|
||||
class WeComQRMixin(PermissionsMixin, View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
except APIException as e:
|
||||
try:
|
||||
msg = e.detail['errmsg']
|
||||
except Exception:
|
||||
msg = _('WeCom Error, Please contact your system administrator')
|
||||
return self.get_failed_reponse(
|
||||
'/',
|
||||
_('WeCom Error'),
|
||||
msg
|
||||
)
|
||||
|
||||
def verify_state(self):
|
||||
state = self.request.GET.get('state')
|
||||
session_state = self.request.session.get(WECOM_STATE_SESSION_KEY)
|
||||
|
@ -128,8 +144,15 @@ class WeComQRBindCallbackView(WeComQRMixin, View):
|
|||
response = self.get_failed_reponse(redirect_url, msg, msg)
|
||||
return response
|
||||
|
||||
user.wecom_id = wecom_userid
|
||||
user.save()
|
||||
try:
|
||||
user.wecom_id = wecom_userid
|
||||
user.save()
|
||||
except IntegrityError as e:
|
||||
if e.args[0] == 1062:
|
||||
msg = _('The WeCom is already bound to another user')
|
||||
response = self.get_failed_reponse(redirect_url, msg, msg)
|
||||
return response
|
||||
raise e
|
||||
|
||||
msg = _('Binding WeCom successfully')
|
||||
response = self.get_success_reponse(redirect_url, msg, msg)
|
||||
|
|
|
@ -57,6 +57,8 @@ class BaseFileParser(BaseParser):
|
|||
|
||||
@staticmethod
|
||||
def _replace_chinese_quote(s):
|
||||
if not isinstance(s, str):
|
||||
return s
|
||||
trans_table = str.maketrans({
|
||||
'“': '"',
|
||||
'”': '"',
|
||||
|
|
|
@ -21,8 +21,3 @@ class ResponseDataKeyError(APIException):
|
|||
class NetError(APIException):
|
||||
default_code = 'net_error'
|
||||
default_detail = _('Network error, please contact system administrator')
|
||||
|
||||
|
||||
class AccessTokenError(APIException):
|
||||
default_code = 'access_token_error'
|
||||
default_detail = 'Access token error, check config'
|
||||
|
|
|
@ -22,7 +22,7 @@ class RequestMixin:
|
|||
logger.error(f'Response 200 but errcode is not 0: '
|
||||
f'errcode={errcode} '
|
||||
f'errmsg={errmsg} ')
|
||||
raise exce.ErrCodeNot0(detail=str(data.raw_data))
|
||||
raise exce.ErrCodeNot0(detail=data.raw_data)
|
||||
|
||||
def check_http_is_200(self, response):
|
||||
if response.status_code != 200:
|
||||
|
@ -31,7 +31,7 @@ class RequestMixin:
|
|||
f'status_code={response.status_code} '
|
||||
f'url={response.url}'
|
||||
f'\ncontent={response.content}')
|
||||
raise exce.HTTPNot200
|
||||
raise exce.HTTPNot200(detail=response.json())
|
||||
|
||||
|
||||
class BaseRequest(RequestMixin):
|
||||
|
|
|
@ -39,7 +39,7 @@ class DictWrapper:
|
|||
except KeyError as e:
|
||||
msg = f'Response 200 but get field from json error: error={e} data={self.raw_data}'
|
||||
logger.error(msg)
|
||||
raise exce.ResponseDataKeyError(detail=msg)
|
||||
raise exce.ResponseDataKeyError(detail=self.raw_data)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self.raw_data, item)
|
||||
|
|
|
@ -33,6 +33,9 @@ class ErrorCode:
|
|||
# https://open.work.weixin.qq.com/api/doc/90000/90139/90313#%E9%94%99%E8%AF%AF%E7%A0%81%EF%BC%9A81013
|
||||
RECIPIENTS_INVALID = 81013 # UserID、部门ID、标签ID全部非法或无权限。
|
||||
|
||||
# https: // open.work.weixin.qq.com / devtool / query?e = 82001
|
||||
RECIPIENTS_EMPTY = 82001 # 指定的成员/部门/标签全部为空
|
||||
|
||||
# https://open.work.weixin.qq.com/api/doc/90000/90135/91437
|
||||
INVALID_CODE = 40029
|
||||
|
||||
|
@ -141,7 +144,7 @@ class WeCom(RequestMixin):
|
|||
data = self._requests.post(URL.SEND_MESSAGE, json=body, check_errcode_is_0=False)
|
||||
|
||||
errcode = data['errcode']
|
||||
if errcode == ErrorCode.RECIPIENTS_INVALID:
|
||||
if errcode in (ErrorCode.RECIPIENTS_INVALID, ErrorCode.RECIPIENTS_EMPTY):
|
||||
# 全部接收人无权限或不存在
|
||||
return users
|
||||
self.check_errcode_is_0(data)
|
||||
|
|
|
@ -120,7 +120,7 @@ LOGIN_CONFIRM_ENABLE = CONFIG.LOGIN_CONFIRM_ENABLE
|
|||
OTP_IN_RADIUS = CONFIG.OTP_IN_RADIUS
|
||||
|
||||
|
||||
AUTH_BACKEND_MODEL = 'django.contrib.auth.backends.ModelBackend'
|
||||
AUTH_BACKEND_MODEL = 'authentication.backends.api.ModelBackend'
|
||||
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
|
||||
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
|
||||
AUTH_BACKEND_OIDC_PASSWORD = 'jms_oidc_rp.backends.OIDCAuthPasswordBackend'
|
||||
|
|
|
@ -16,7 +16,7 @@ PROJECT_DIR = const.PROJECT_DIR
|
|||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = CONFIG.SECRET_KEY
|
||||
|
||||
# SECURITY WARNING: keep the token secret, remove it if all coco, guacamole ok
|
||||
# SECURITY WARNING: keep the token secret, remove it if all koko, lion ok
|
||||
BOOTSTRAP_TOKEN = CONFIG.BOOTSTRAP_TOKEN
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-05-14 16:12+0800\n"
|
||||
"POT-Creation-Date: 2021-05-17 18:56+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||
|
@ -1547,44 +1547,48 @@ msgstr "返回"
|
|||
msgid "Copy success"
|
||||
msgstr "复制成功"
|
||||
|
||||
#: authentication/views/dingtalk.py:40 authentication/views/wecom.py:40
|
||||
#: authentication/views/dingtalk.py:41 authentication/views/wecom.py:41
|
||||
msgid "You've been hacked"
|
||||
msgstr "你被攻击了"
|
||||
|
||||
#: authentication/views/dingtalk.py:76
|
||||
#: authentication/views/dingtalk.py:77
|
||||
msgid "DingTalk is already bound"
|
||||
msgstr "钉钉已经绑定"
|
||||
|
||||
#: authentication/views/dingtalk.py:89 authentication/views/wecom.py:88
|
||||
#: authentication/views/dingtalk.py:90 authentication/views/wecom.py:89
|
||||
msgid "Please verify your password first"
|
||||
msgstr "请检查密码"
|
||||
|
||||
#: authentication/views/dingtalk.py:113 authentication/views/wecom.py:112
|
||||
#: authentication/views/dingtalk.py:114 authentication/views/wecom.py:113
|
||||
msgid "Invalid user_id"
|
||||
msgstr "无效的 user_id"
|
||||
|
||||
#: authentication/views/dingtalk.py:129
|
||||
#: authentication/views/dingtalk.py:130
|
||||
msgid "DingTalk query user failed"
|
||||
msgstr "钉钉查询用户失败"
|
||||
|
||||
#: authentication/views/dingtalk.py:136 authentication/views/dingtalk.py:219
|
||||
#: authentication/views/dingtalk.py:220
|
||||
#: authentication/views/dingtalk.py:139
|
||||
msgid "The DingTalk is already bound to another user"
|
||||
msgstr "该钉钉已经绑定其他用户"
|
||||
|
||||
#: authentication/views/dingtalk.py:144 authentication/views/dingtalk.py:227
|
||||
#: authentication/views/dingtalk.py:228
|
||||
msgid "Binding DingTalk successfully"
|
||||
msgstr "绑定 钉钉 成功"
|
||||
|
||||
#: authentication/views/dingtalk.py:188
|
||||
#: authentication/views/dingtalk.py:196
|
||||
msgid "Failed to get user from DingTalk"
|
||||
msgstr "从钉钉获取用户失败"
|
||||
|
||||
#: authentication/views/dingtalk.py:194
|
||||
#: authentication/views/dingtalk.py:202
|
||||
msgid "DingTalk is not bound"
|
||||
msgstr "钉钉没有绑定"
|
||||
|
||||
#: authentication/views/dingtalk.py:195 authentication/views/wecom.py:193
|
||||
#: authentication/views/dingtalk.py:203 authentication/views/wecom.py:201
|
||||
msgid "Please login with a password and then bind the WeCom"
|
||||
msgstr "请使用密码登录,然后绑定企业微信"
|
||||
|
||||
#: authentication/views/dingtalk.py:237 authentication/views/dingtalk.py:238
|
||||
#: authentication/views/dingtalk.py:245 authentication/views/dingtalk.py:246
|
||||
msgid "Binding DingTalk failed"
|
||||
msgstr "绑定钉钉失败"
|
||||
|
||||
|
@ -1620,28 +1624,32 @@ msgstr "退出登录成功"
|
|||
msgid "Logout success, return login page"
|
||||
msgstr "退出登录成功,返回到登录页面"
|
||||
|
||||
#: authentication/views/wecom.py:75
|
||||
#: authentication/views/wecom.py:76
|
||||
msgid "WeCom is already bound"
|
||||
msgstr "企业微信已经绑定"
|
||||
|
||||
#: authentication/views/wecom.py:127
|
||||
#: authentication/views/wecom.py:128
|
||||
msgid "WeCom query user failed"
|
||||
msgstr "企业微信查询用户失败"
|
||||
|
||||
#: authentication/views/wecom.py:134 authentication/views/wecom.py:217
|
||||
#: authentication/views/wecom.py:218
|
||||
#: authentication/views/wecom.py:137
|
||||
msgid "The WeCom is already bound to another user"
|
||||
msgstr "该企业微信已经绑定其他用户"
|
||||
|
||||
#: authentication/views/wecom.py:142 authentication/views/wecom.py:225
|
||||
#: authentication/views/wecom.py:226
|
||||
msgid "Binding WeCom successfully"
|
||||
msgstr "绑定 企业微信 成功"
|
||||
|
||||
#: authentication/views/wecom.py:186
|
||||
#: authentication/views/wecom.py:194
|
||||
msgid "Failed to get user from WeCom"
|
||||
msgstr "从企业微信获取用户失败"
|
||||
|
||||
#: authentication/views/wecom.py:192
|
||||
#: authentication/views/wecom.py:200
|
||||
msgid "WeCom is not bound"
|
||||
msgstr "没有绑定企业微信"
|
||||
|
||||
#: authentication/views/wecom.py:235 authentication/views/wecom.py:236
|
||||
#: authentication/views/wecom.py:243 authentication/views/wecom.py:244
|
||||
msgid "Binding WeCom failed"
|
||||
msgstr "绑定企业微信失败"
|
||||
|
||||
|
@ -1803,36 +1811,36 @@ msgstr "没有该主机 {} 权限"
|
|||
msgid "Operations"
|
||||
msgstr "运维"
|
||||
|
||||
#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:160
|
||||
#: ops/mixin.py:29 ops/mixin.py:92 ops/mixin.py:162
|
||||
msgid "Cycle perform"
|
||||
msgstr "周期执行"
|
||||
|
||||
#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:148
|
||||
#: ops/mixin.py:33 ops/mixin.py:90 ops/mixin.py:109 ops/mixin.py:150
|
||||
msgid "Regularly perform"
|
||||
msgstr "定期执行"
|
||||
|
||||
#: ops/mixin.py:106 ops/mixin.py:145
|
||||
#: ops/mixin.py:106 ops/mixin.py:147
|
||||
#: xpack/plugins/change_auth_plan/serializers.py:53
|
||||
msgid "Periodic perform"
|
||||
msgstr "定时执行"
|
||||
|
||||
#: ops/mixin.py:111
|
||||
#: ops/mixin.py:112
|
||||
msgid "Interval"
|
||||
msgstr "间隔"
|
||||
|
||||
#: ops/mixin.py:120
|
||||
#: ops/mixin.py:122
|
||||
msgid "* Please enter a valid crontab expression"
|
||||
msgstr "* 请输入有效的 crontab 表达式"
|
||||
|
||||
#: ops/mixin.py:127
|
||||
#: ops/mixin.py:129
|
||||
msgid "Range {} to {}"
|
||||
msgstr "输入在 {} - {} 范围之间"
|
||||
|
||||
#: ops/mixin.py:138
|
||||
#: ops/mixin.py:140
|
||||
msgid "Require periodic or regularly perform setting"
|
||||
msgstr "需要周期或定期设置"
|
||||
|
||||
#: ops/mixin.py:149
|
||||
#: ops/mixin.py:151
|
||||
msgid ""
|
||||
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
|
||||
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
|
||||
|
@ -1843,7 +1851,7 @@ msgstr ""
|
|||
"分 时 日 月 星期> (<a href='https://tool.lu/crontab/' target='_blank'>在线工"
|
||||
"具</a>) <br>注意: 如果同时设置了定期执行和周期执行,优先使用定期执行"
|
||||
|
||||
#: ops/mixin.py:160
|
||||
#: ops/mixin.py:162
|
||||
msgid "Tips: (Units: hour)"
|
||||
msgstr "提示:(单位: 时)"
|
||||
|
||||
|
@ -1954,12 +1962,11 @@ msgstr "更新任务内容: {}"
|
|||
msgid "Disk used more than 80%: {} => {}"
|
||||
msgstr "磁盘使用率超过 80%: {} => {}"
|
||||
|
||||
#: orgs/api.py:76
|
||||
#, python-brace-format
|
||||
msgid "Have `{model._meta.verbose_name}` exists, Please delete"
|
||||
msgstr "`{model._meta.verbose_name}` 存在数据, 请先删除"
|
||||
#: orgs/api.py:79
|
||||
msgid "Have {} exists, Please delete"
|
||||
msgstr "{} 存在数据, 请先删除"
|
||||
|
||||
#: orgs/api.py:80
|
||||
#: orgs/api.py:83
|
||||
msgid "The current organization cannot be deleted"
|
||||
msgstr "当前组织不能被删除"
|
||||
|
||||
|
@ -2123,7 +2130,11 @@ msgstr "邮件已经发送{}, 请检查"
|
|||
msgid "Welcome to the JumpServer open source Bastion Host"
|
||||
msgstr "欢迎使用JumpServer开源堡垒机"
|
||||
|
||||
#: settings/api/dingtalk.py:36 settings/api/wecom.py:36
|
||||
#: settings/api/dingtalk.py:29
|
||||
msgid "AppSecret is required"
|
||||
msgstr "AppSecret 是必须的"
|
||||
|
||||
#: settings/api/dingtalk.py:35 settings/api/wecom.py:35
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2135,6 +2146,10 @@ msgstr "获取 LDAP 用户为 None"
|
|||
msgid "Imported {} users successfully"
|
||||
msgstr "导入 {} 个用户成功"
|
||||
|
||||
#: settings/api/wecom.py:29
|
||||
msgid "Secret is required"
|
||||
msgstr "Secret 是必须的"
|
||||
|
||||
#: settings/models.py:123 users/templates/users/reset_password.html:29
|
||||
msgid "Setting"
|
||||
msgstr "设置"
|
||||
|
@ -2419,16 +2434,13 @@ msgstr ""
|
|||
|
||||
#: settings/serializers/settings.py:172
|
||||
msgid "Number of repeated historical passwords"
|
||||
msgstr "历史密码可重复次数"
|
||||
msgstr "不能设置近几次密码"
|
||||
|
||||
#: settings/serializers/settings.py:173
|
||||
msgid ""
|
||||
"Tip: When the user resets the password, it cannot be the previous n "
|
||||
"historical passwords of the user (the value of n here is the value filled in "
|
||||
"the input box)"
|
||||
msgstr ""
|
||||
"提示:用户重置密码时,不能为该用户前n次历史密码 (此处的n值即为输入框中填写的"
|
||||
"值)"
|
||||
"historical passwords of the user"
|
||||
msgstr "提示:用户重置密码时,不能为该用户前几次使用过的密码"
|
||||
|
||||
#: settings/serializers/settings.py:177
|
||||
msgid "Password minimum length"
|
||||
|
@ -2462,34 +2474,10 @@ msgstr "邮件收件人"
|
|||
msgid "Multiple user using , split"
|
||||
msgstr "多个用户,使用 , 分割"
|
||||
|
||||
#: settings/serializers/settings.py:193
|
||||
msgid "Corporation ID(corpid)"
|
||||
msgstr "企业 ID(CorpId)"
|
||||
|
||||
#: settings/serializers/settings.py:194
|
||||
msgid "Agent ID(agentid)"
|
||||
msgstr "应用 ID(AgentId)"
|
||||
|
||||
#: settings/serializers/settings.py:195
|
||||
msgid "Secret(secret)"
|
||||
msgstr "秘钥(secret)"
|
||||
|
||||
#: settings/serializers/settings.py:196
|
||||
msgid "Enable WeCom Auth"
|
||||
msgstr "启用企业微信认证"
|
||||
|
||||
#: settings/serializers/settings.py:200
|
||||
msgid "AgentId"
|
||||
msgstr "应用 ID(AgentId)"
|
||||
|
||||
#: settings/serializers/settings.py:201
|
||||
msgid "AppKey"
|
||||
msgstr "应用 Key(AppKey)"
|
||||
|
||||
#: settings/serializers/settings.py:202
|
||||
msgid "AppSecret"
|
||||
msgstr "应用密文(AppSecret)"
|
||||
|
||||
#: settings/serializers/settings.py:203
|
||||
msgid "Enable DingTalk Auth"
|
||||
msgstr "启用钉钉认证"
|
||||
|
@ -2896,7 +2884,7 @@ msgstr "取消"
|
|||
|
||||
#: templates/flash_message_standalone.html:37
|
||||
msgid "Go"
|
||||
msgstr ""
|
||||
msgstr "立即"
|
||||
|
||||
#: templates/index.html:11
|
||||
msgid "Total users"
|
||||
|
@ -3048,19 +3036,19 @@ msgstr "登录了"
|
|||
msgid "Filters"
|
||||
msgstr "过滤"
|
||||
|
||||
#: terminal/api/session.py:189
|
||||
#: terminal/api/session.py:185
|
||||
msgid "Session does not exist: {}"
|
||||
msgstr "会话不存在: {}"
|
||||
|
||||
#: terminal/api/session.py:192
|
||||
#: terminal/api/session.py:188
|
||||
msgid "Session is finished or the protocol not supported"
|
||||
msgstr "会话已经完成或协议不支持"
|
||||
|
||||
#: terminal/api/session.py:197
|
||||
#: terminal/api/session.py:193
|
||||
msgid "User does not exist: {}"
|
||||
msgstr "用户不存在: {}"
|
||||
|
||||
#: terminal/api/session.py:201
|
||||
#: terminal/api/session.py:197
|
||||
msgid "User does not have permission"
|
||||
msgstr "用户没有权限"
|
||||
|
||||
|
@ -3094,7 +3082,7 @@ msgstr "测试失败: 账户无效"
|
|||
|
||||
#: terminal/backends/command/es.py:27
|
||||
msgid "Invalid elasticsearch config"
|
||||
msgstr ""
|
||||
msgstr "无效的 Elasticsearch 配置"
|
||||
|
||||
#: terminal/backends/command/models.py:14
|
||||
msgid "Ordinary"
|
||||
|
@ -4953,7 +4941,7 @@ msgstr "实例个数"
|
|||
msgid "Periodic display"
|
||||
msgstr "定时执行"
|
||||
|
||||
#: xpack/plugins/cloud/utils.py:65
|
||||
#: xpack/plugins/cloud/utils.py:64
|
||||
msgid "Account unavailable"
|
||||
msgstr "账户无效"
|
||||
|
||||
|
@ -5041,5 +5029,23 @@ msgstr "旗舰版"
|
|||
msgid "Community edition"
|
||||
msgstr "社区版"
|
||||
|
||||
#~ msgid "Corporation ID(corpid)"
|
||||
#~ msgstr "企业 ID(CorpId)"
|
||||
|
||||
#~ msgid "Agent ID(agentid)"
|
||||
#~ msgstr "应用 ID(AgentId)"
|
||||
|
||||
#~ msgid "Secret(secret)"
|
||||
#~ msgstr "秘钥(secret)"
|
||||
|
||||
#~ msgid "AgentId"
|
||||
#~ msgstr "应用 ID(AgentId)"
|
||||
|
||||
#~ msgid "AppKey"
|
||||
#~ msgstr "应用 Key(AppKey)"
|
||||
|
||||
#~ msgid "AppSecret"
|
||||
#~ msgstr "应用密文(AppSecret)"
|
||||
|
||||
#~ msgid "No"
|
||||
#~ msgstr "无"
|
||||
|
|
|
@ -103,12 +103,14 @@ class PeriodTaskModelMixin(models.Model):
|
|||
|
||||
|
||||
class PeriodTaskSerializerMixin(serializers.Serializer):
|
||||
is_periodic = serializers.BooleanField(default=False, label=_("Periodic perform"))
|
||||
is_periodic = serializers.BooleanField(default=True, label=_("Periodic perform"))
|
||||
crontab = serializers.CharField(
|
||||
max_length=128, allow_blank=True,
|
||||
allow_null=True, required=False, label=_('Regularly perform')
|
||||
)
|
||||
interval = serializers.IntegerField(allow_null=True, required=False, label=_('Interval'))
|
||||
interval = serializers.IntegerField(
|
||||
default=24, allow_null=True, required=False, label=_('Interval')
|
||||
)
|
||||
|
||||
INTERVAL_MAX = 65535
|
||||
INTERVAL_MIN = 1
|
||||
|
@ -122,7 +124,7 @@ class PeriodTaskSerializerMixin(serializers.Serializer):
|
|||
return crontab
|
||||
|
||||
def validate_interval(self, interval):
|
||||
if not interval:
|
||||
if not interval and not isinstance(interval, int):
|
||||
return interval
|
||||
msg = _("Range {} to {}").format(self.INTERVAL_MIN, self.INTERVAL_MAX)
|
||||
if interval > self.INTERVAL_MAX or interval < self.INTERVAL_MIN:
|
||||
|
|
|
@ -60,7 +60,10 @@ class OrgViewSet(BulkModelViewSet):
|
|||
@tmp_to_root_org()
|
||||
def get_data_from_model(self, model):
|
||||
if model == User:
|
||||
data = model.objects.filter(orgs__id=self.org.id, m2m_org_members__role=ROLE.USER)
|
||||
data = model.objects.filter(
|
||||
orgs__id=self.org.id,
|
||||
m2m_org_members__role__in=[ROLE.USER, ROLE.ADMIN, ROLE.AUDITOR]
|
||||
)
|
||||
elif model == Node:
|
||||
# 跟节点不能手动删除,所以排除检查
|
||||
data = model.objects.filter(org_id=self.org.id).exclude(parent_key='', key__regex=r'^[0-9]+$')
|
||||
|
@ -73,7 +76,7 @@ class OrgViewSet(BulkModelViewSet):
|
|||
for model in org_related_models:
|
||||
data = self.get_data_from_model(model)
|
||||
if data:
|
||||
msg = _(f'Have `{model._meta.verbose_name}` exists, Please delete')
|
||||
msg = _('Have {} exists, Please delete').format(model._meta.verbose_name)
|
||||
return Response(data={'error': msg}, status=status.HTTP_403_FORBIDDEN)
|
||||
else:
|
||||
if str(current_org) == str(self.org):
|
||||
|
|
|
@ -87,6 +87,8 @@ class OrgResourceStatisticsCache(OrgRelatedCache):
|
|||
return users_amount
|
||||
|
||||
def compute_assets_amount(self):
|
||||
if self.org.is_root():
|
||||
return Asset.objects.all().count()
|
||||
node = Node.org_root()
|
||||
return node.assets_amount
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ def refresh_user_amount_on_user_create_or_delete(user_id):
|
|||
for org in orgs:
|
||||
org_cache = OrgResourceStatisticsCache(org)
|
||||
org_cache.expire('users_amount')
|
||||
OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
|
@ -44,6 +45,7 @@ def on_org_user_changed_refresh_cache(sender, action, instance, reverse, pk_set,
|
|||
for org in orgs:
|
||||
org_cache = OrgResourceStatisticsCache(org)
|
||||
org_cache.expire('users_amount')
|
||||
OrgResourceStatisticsCache(Organization.root()).expire('users_amount')
|
||||
|
||||
|
||||
class OrgResourceStatisticsRefreshUtil:
|
||||
|
@ -67,6 +69,7 @@ class OrgResourceStatisticsRefreshUtil:
|
|||
if cache_field_name:
|
||||
org_cache = OrgResourceStatisticsCache(instance.org)
|
||||
org_cache.expire(*cache_field_name)
|
||||
OrgResourceStatisticsCache(Organization.root()).expire(*cache_field_name)
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import requests
|
||||
|
||||
from rest_framework.views import Response
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework import status
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from settings.models import Setting
|
||||
from common.permissions import IsSuperUser
|
||||
from common.message.backends.dingtalk import URL
|
||||
from common.message.backends.dingtalk import DingTalk
|
||||
|
||||
from .. import serializers
|
||||
|
||||
|
@ -20,19 +21,17 @@ class DingTalkTestingAPI(GenericAPIView):
|
|||
|
||||
dingtalk_appkey = serializer.validated_data['DINGTALK_APPKEY']
|
||||
dingtalk_agentid = serializer.validated_data['DINGTALK_AGENTID']
|
||||
dingtalk_appsecret = serializer.validated_data['DINGTALK_APPSECRET']
|
||||
dingtalk_appsecret = serializer.validated_data.get('DINGTALK_APPSECRET')
|
||||
|
||||
if not dingtalk_appsecret:
|
||||
secret = Setting.objects.filter(name='DINGTALK_APPSECRET').first()
|
||||
if not secret:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('AppSecret is required')})
|
||||
dingtalk_appsecret = secret.cleaned_value
|
||||
|
||||
try:
|
||||
params = {'appkey': dingtalk_appkey, 'appsecret': dingtalk_appsecret}
|
||||
resp = requests.get(url=URL.GET_TOKEN, params=params)
|
||||
if resp.status_code != 200:
|
||||
return Response(status=400, data={'error': resp.json()})
|
||||
|
||||
data = resp.json()
|
||||
errcode = data['errcode']
|
||||
if errcode != 0:
|
||||
return Response(status=400, data={'error': data['errmsg']})
|
||||
|
||||
return Response(status=200, data={'msg': _('OK')})
|
||||
except Exception as e:
|
||||
return Response(status=400, data={'error': str(e)})
|
||||
dingtalk = DingTalk(appid=dingtalk_appkey, appsecret=dingtalk_appsecret, agentid=dingtalk_agentid)
|
||||
dingtalk.send_text(['test'], 'test')
|
||||
return Response(status=status.HTTP_200_OK, data={'msg': _('OK')})
|
||||
except APIException as e:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail})
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import requests
|
||||
|
||||
from rest_framework.views import Response
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework import status
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from settings.models import Setting
|
||||
from common.permissions import IsSuperUser
|
||||
from common.message.backends.wecom import URL
|
||||
from common.message.backends.wecom import WeCom
|
||||
|
||||
from .. import serializers
|
||||
|
||||
|
@ -20,19 +21,17 @@ class WeComTestingAPI(GenericAPIView):
|
|||
|
||||
wecom_corpid = serializer.validated_data['WECOM_CORPID']
|
||||
wecom_agentid = serializer.validated_data['WECOM_AGENTID']
|
||||
wecom_corpsecret = serializer.validated_data['WECOM_SECRET']
|
||||
wecom_corpsecret = serializer.validated_data.get('WECOM_SECRET')
|
||||
|
||||
if not wecom_corpsecret:
|
||||
secret = Setting.objects.filter(name='WECOM_SECRET').first()
|
||||
if not secret:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': _('Secret is required')})
|
||||
wecom_corpsecret = secret.cleaned_value
|
||||
|
||||
try:
|
||||
params = {'corpid': wecom_corpid, 'corpsecret': wecom_corpsecret}
|
||||
resp = requests.get(url=URL.GET_TOKEN, params=params)
|
||||
if resp.status_code != 200:
|
||||
return Response(status=400, data={'error': resp.json()})
|
||||
|
||||
data = resp.json()
|
||||
errcode = data['errcode']
|
||||
if errcode != 0:
|
||||
return Response(status=400, data={'error': data['errmsg']})
|
||||
|
||||
return Response(status=200, data={'msg': _('OK')})
|
||||
except Exception as e:
|
||||
return Response(status=400, data={'error': str(e)})
|
||||
wecom = WeCom(corpid=wecom_corpid, corpsecret=wecom_corpsecret, agentid=wecom_agentid)
|
||||
wecom.send_text(['test'], 'test')
|
||||
return Response(status=status.HTTP_200_OK, data={'msg': _('OK')})
|
||||
except APIException as e:
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST, data={'error': e.detail})
|
||||
|
|
|
@ -170,7 +170,7 @@ class SecuritySettingSerializer(serializers.Serializer):
|
|||
OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField(
|
||||
min_value=0, max_value=99999, required=True,
|
||||
label=_('Number of repeated historical passwords'),
|
||||
help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user (the value of n here is the value filled in the input box)')
|
||||
help_text=_('Tip: When the user resets the password, it cannot be the previous n historical passwords of the user')
|
||||
)
|
||||
SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField(
|
||||
min_value=6, max_value=30, required=True,
|
||||
|
@ -190,16 +190,16 @@ class SecuritySettingSerializer(serializers.Serializer):
|
|||
|
||||
|
||||
class WeComSettingSerializer(serializers.Serializer):
|
||||
WECOM_CORPID = serializers.CharField(max_length=256, required=True, label=_('Corporation ID(corpid)'))
|
||||
WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label=_("Agent ID(agentid)"))
|
||||
WECOM_SECRET = serializers.CharField(max_length=256, required=True, label=_("Secret(secret)"), write_only=True)
|
||||
WECOM_CORPID = serializers.CharField(max_length=256, required=True, label='corpid')
|
||||
WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid')
|
||||
WECOM_SECRET = serializers.CharField(max_length=256, required=False, label='secret', write_only=True)
|
||||
AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth'))
|
||||
|
||||
|
||||
class DingTalkSettingSerializer(serializers.Serializer):
|
||||
DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label=_("AgentId"))
|
||||
DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label=_("AppKey"))
|
||||
DINGTALK_APPSECRET = serializers.CharField(max_length=256, required=False, label=_("AppSecret"), write_only=True)
|
||||
DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label='AgentId')
|
||||
DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey')
|
||||
DINGTALK_APPSECRET = serializers.CharField(max_length=256, required=False, label='AppSecret', write_only=True)
|
||||
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Enable DingTalk Auth'))
|
||||
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class SessionViewSet(OrgBulkModelViewSet):
|
|||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
# 解决guacamole更新session时并发导致幽灵会话的问题
|
||||
# 解决guacamole更新session时并发导致幽灵会话的问题,暂不处理
|
||||
if self.request.method in ('PATCH',):
|
||||
queryset = queryset.select_for_update()
|
||||
return queryset
|
||||
|
@ -98,11 +98,6 @@ class SessionViewSet(OrgBulkModelViewSet):
|
|||
def perform_create(self, serializer):
|
||||
if hasattr(self.request.user, 'terminal'):
|
||||
serializer.validated_data["terminal"] = self.request.user.terminal
|
||||
sid = serializer.validated_data["system_user"]
|
||||
# guacamole提交的是id
|
||||
if is_uuid(sid):
|
||||
_system_user = get_object_or_404(SystemUser, id=sid)
|
||||
serializer.validated_data["system_user"] = _system_user.name
|
||||
return super().perform_create(serializer)
|
||||
|
||||
def get_permissions(self):
|
||||
|
@ -140,6 +135,7 @@ class SessionReplayViewSet(AsyncApiMixin, viewsets.ViewSet):
|
|||
def get_replay_data(session, url):
|
||||
tp = 'json'
|
||||
if session.protocol in ('rdp', 'vnc'):
|
||||
# 需要考虑录像播放和离线播放器的约定,暂时不处理
|
||||
tp = 'guacamole'
|
||||
|
||||
download_url = reverse('api-terminal:session-replay-download', kwargs={'pk': session.id})
|
||||
|
|
|
@ -39,11 +39,6 @@ class StatusViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def handle_sessions(self):
|
||||
session_ids = self.request.data.get('sessions', [])
|
||||
# guacamole 上报的 session 是字符串
|
||||
# "[53cd3e47-210f-41d8-b3c6-a184f3, 53cd3e47-210f-41d8-b3c6-a184f4]"
|
||||
if isinstance(session_ids, str):
|
||||
session_ids = session_ids[1:-1].split(',')
|
||||
session_ids = [sid.strip() for sid in session_ids if sid.strip()]
|
||||
Session.set_sessions_active(session_ids)
|
||||
|
||||
def get_queryset(self):
|
||||
|
|
|
@ -43,6 +43,7 @@ class TerminalTypeChoices(TextChoices):
|
|||
guacamole = 'guacamole', 'Guacamole'
|
||||
omnidb = 'omnidb', 'OmniDB'
|
||||
xrdp = 'xrdp', 'Xrdp'
|
||||
lion = 'lion', 'Lion'
|
||||
|
||||
@classmethod
|
||||
def types(cls):
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.1.6 on 2021-05-17 06:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('terminal', '0034_auto_20210406_1434'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='terminal',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('koko', 'KoKo'), ('guacamole', 'Guacamole'), ('omnidb', 'OmniDB'), ('xrdp', 'Xrdp'), ('lion', 'Lion')], default='koko', max_length=64, verbose_name='type'),
|
||||
),
|
||||
]
|
|
@ -109,7 +109,9 @@ class Session(OrgModelMixin):
|
|||
_PROTOCOL = self.PROTOCOL
|
||||
if self.is_finished:
|
||||
return False
|
||||
if self.protocol in [_PROTOCOL.SSH, _PROTOCOL.TELNET, _PROTOCOL.K8S]:
|
||||
if self.protocol in [
|
||||
_PROTOCOL.SSH, _PROTOCOL.VNC, _PROTOCOL.RDP, _PROTOCOL.TELNET, _PROTOCOL.K8S
|
||||
]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -7,8 +7,6 @@ import string
|
|||
import random
|
||||
import datetime
|
||||
|
||||
from functools import partial
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.auth.hashers import check_password, make_password
|
||||
|
@ -30,7 +28,7 @@ from users.exceptions import MFANotEnabled
|
|||
from ..signals import post_user_change_password
|
||||
|
||||
|
||||
__all__ = ['User']
|
||||
__all__ = ['User', 'UserPasswordHistory']
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -83,12 +81,6 @@ class AuthMixin:
|
|||
else:
|
||||
return False
|
||||
|
||||
def save_history_password(self, password):
|
||||
UserPasswordHistory.objects.create(
|
||||
user=self, password=make_password(password),
|
||||
date_created=self.date_password_last_updated
|
||||
)
|
||||
|
||||
def is_public_key_valid(self):
|
||||
"""
|
||||
Check if the user's ssh public key is valid.
|
||||
|
@ -771,3 +763,9 @@ class UserPasswordHistory(models.Model):
|
|||
user = models.ForeignKey("users.User", related_name='history_passwords',
|
||||
on_delete=models.CASCADE, verbose_name=_('User'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user} set at {self.date_created}'
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
|
|
@ -39,8 +39,6 @@ class UserUpdatePasswordSerializer(serializers.ModelSerializer):
|
|||
limit_count = settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT
|
||||
msg = _('The new password cannot be the last {} passwords').format(limit_count)
|
||||
raise serializers.ValidationError(msg)
|
||||
else:
|
||||
self.instance.save_history_password(value)
|
||||
return value
|
||||
|
||||
def validate_new_password_again(self, value):
|
||||
|
|
|
@ -6,17 +6,33 @@ from django_auth_ldap.backend import populate_user
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django_cas_ng.signals import cas_user_authenticated
|
||||
from django.db.models.signals import post_save
|
||||
|
||||
from jms_oidc_rp.signals import openid_create_or_update_user
|
||||
|
||||
from common.utils import get_logger
|
||||
from .signals import post_user_create
|
||||
from .models import User
|
||||
from .models import User, UserPasswordHistory
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def save_passwd_change(sender, instance: User, **kwargs):
|
||||
passwds = UserPasswordHistory.objects.filter(user=instance).order_by('-date_created')\
|
||||
.values_list('password', flat=True)[:int(settings.OLD_PASSWORD_HISTORY_LIMIT_COUNT)]
|
||||
|
||||
for p in passwds:
|
||||
if instance.password == p:
|
||||
break
|
||||
else:
|
||||
UserPasswordHistory.objects.create(
|
||||
user=instance, password=instance.password,
|
||||
date_created=instance.date_password_last_updated
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_user_create)
|
||||
def on_user_create(sender, user=None, **kwargs):
|
||||
logger.debug("Receive user `{}` create signal".format(user.name))
|
||||
|
|
|
@ -111,8 +111,6 @@ class UserResetPasswordView(FormView):
|
|||
error = _('* The new password cannot be the last {} passwords').format(limit_count)
|
||||
form.add_error('new_password', error)
|
||||
return self.form_invalid(form)
|
||||
else:
|
||||
user.save_history_password(password)
|
||||
|
||||
user.reset_password(password)
|
||||
User.expired_reset_password_token(token)
|
||||
|
|
Loading…
Reference in New Issue