Compare commits

...

17 Commits

Author SHA1 Message Date
Bai
e77d5c9e60 perf: tag top 2023-06-08 19:22:27 +08:00
fit2bot
39eb953acd feat: Update v3.3.1 2023-05-26 16:36:35 +08:00
Bai
c7ea62488d perf: 优化短信设置的字段名称显示 2023-05-26 15:18:47 +08:00
老广
4ef5a19c03 Merge pull request #10563 from O-Jiangweidong/pr@v3.3@perf_operate_log_display
perf: 优化命令过滤规则操作日志显示问题
2023-05-26 14:10:11 +08:00
jiangweidong
4e099fd9fc perf: 优化命令过滤规则操作日志显示问题 2023-05-26 14:03:54 +08:00
老广
c34cf23cc7 Merge pull request #10555 from jumpserver/pr@v3.3@fix_chrome_path
fix: 修正 Chrome 环境变量
2023-05-25 19:20:08 +08:00
Bai
ae47003d2c fix: 修复用户登录认证 MFA 输入错误时没有记录具体错误信息的问题 2023-05-25 18:24:48 +08:00
吴小白
da48811335 fix: 修正 Chrome 环境变量 2023-05-25 09:42:46 +00:00
feng
04175a4c1a perf: 改密过程原子性优化 2023-05-25 17:12:30 +08:00
feng
1147e5b5aa fix: 自定义平台无自动化任务 2023-05-25 16:52:07 +08:00
halo
6e014cee81 perf: 优化账号收集,使用正则处理结果 2023-05-25 13:39:55 +08:00
Bai
bf4ef35e5b fix: 修复终端端点使用资产标签匹配机制时 500 的问题 2023-05-24 17:35:35 +08:00
jiangweidong
e33dbb6aef fix: 验证账号可连接性(自定义ssh)使用的key值错误 (#10524) 2023-05-22 17:45:09 +08:00
fit2bot
e6b894ea61 fix: 账号导入500 (#10521)
Co-authored-by: feng <1304903146@qq.com>
2023-05-22 14:22:14 +08:00
ibuler
e2602127c4 fix: 修复 applet 账号选择问题 2023-05-19 17:01:57 +08:00
ibuler
a6b59ad7d6 perf: 修改过期默认值 2023-05-19 10:57:39 +08:00
jiangweidong
64f7eb2416 fix: 修复某待审核用户返回时,登录其他用户可绕开mfa的问题 2023-05-19 10:57:19 +08:00
24 changed files with 91 additions and 59 deletions

1
GITSHA Normal file
View File

@@ -0,0 +1 @@
c7ea62488dff0daee514bbcfe6ca31afa7dc3a73

View File

@@ -9,6 +9,7 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('des') }}" password: "{{ account.secret | password_hash('des') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed - name: create user If it already exists, no operation will be performed

View File

@@ -9,6 +9,7 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: create user If it already exists, no operation will be performed - name: create user If it already exists, no operation will be performed

View File

@@ -21,6 +21,7 @@
groups: "{{ user_info.groups[0].name }}" groups: "{{ user_info.groups[0].name }}"
groups_action: add groups_action: add
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: Refresh connection - name: Refresh connection

View File

@@ -1,3 +1,5 @@
import re
from django.utils import timezone from django.utils import timezone
__all__ = ['GatherAccountsFilter'] __all__ = ['GatherAccountsFilter']
@@ -27,18 +29,25 @@ class GatherAccountsFilter:
@staticmethod @staticmethod
def posix_filter(info): def posix_filter(info):
username_pattern = re.compile(r'^(\S+)')
ip_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
login_time_pattern = re.compile(r'\w{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}')
result = {} result = {}
for line in info: for line in info:
data = line.split('@') usernames = username_pattern.findall(line)
if len(data) == 1: username = ''.join(usernames)
result[line] = {} if username:
result[username] = {}
else:
continue continue
ip_addrs = ip_pattern.findall(line)
if len(data) != 3: ip_addr = ''.join(ip_addrs)
continue if ip_addr:
username, address, dt = data result[username].update({'address': ip_addr})
date = timezone.datetime.strptime(f'{dt} +0800', '%b %d %H:%M:%S %Y %z') login_times = login_time_pattern.findall(line)
result[username] = {'address': address, 'date': date} if login_times:
date = timezone.datetime.strptime(f'{login_times[0]} +0800', '%b %d %H:%M:%S %Y %z')
result[username].update({'date': date})
return result return result
@staticmethod @staticmethod

View File

@@ -5,7 +5,7 @@
ansible.builtin.shell: ansible.builtin.shell:
cmd: > cmd: >
users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users; users=$(getent passwd | grep -v nologin | grep -v shutdown | awk -F":" '{ print $1 }');for i in $users;
do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $1"@"$3"@"$5,$6,$7,$8 }') do k=$(last -w -F $i -1 | head -1 | grep -v ^$ | awk '{ print $0 }')
if [ -n "$k" ]; then if [ -n "$k" ]; then
echo $k echo $k
else else

View File

@@ -43,6 +43,7 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key

View File

@@ -43,6 +43,7 @@
name: "{{ account.username }}" name: "{{ account.username }}"
password: "{{ account.secret | password_hash('sha512') }}" password: "{{ account.secret | password_hash('sha512') }}"
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: remove jumpserver ssh key - name: remove jumpserver ssh key

View File

@@ -17,6 +17,7 @@
groups: "{{ params.groups }}" groups: "{{ params.groups }}"
groups_action: add groups_action: add
update_password: always update_password: always
ignore_errors: true
when: account.secret_type == "password" when: account.secret_type == "password"
- name: Refresh connection - name: Refresh connection

View File

@@ -10,5 +10,5 @@
login_port: "{{ jms_asset.port }}" login_port: "{{ jms_asset.port }}"
login_user: "{{ account.username }}" login_user: "{{ account.username }}"
login_password: "{{ account.secret }}" login_password: "{{ account.secret }}"
login_secret_type: "{{ jms_account.secret_type }}" login_secret_type: "{{ account.secret_type }}"
login_private_key_path: "{{ jms_account.private_key_path }}" login_private_key_path: "{{ account.private_key_path }}"

View File

@@ -22,8 +22,8 @@ logger = get_logger(__name__)
class AccountCreateUpdateSerializerMixin(serializers.Serializer): class AccountCreateUpdateSerializerMixin(serializers.Serializer):
template = serializers.PrimaryKeyRelatedField( template = serializers.PrimaryKeyRelatedField(
queryset=AccountTemplate.objects, queryset=AccountTemplate.objects, required=False,
required=False, label=_("Template"), write_only=True label=_("Template"), write_only=True, allow_null=True
) )
push_now = serializers.BooleanField( push_now = serializers.BooleanField(
default=False, label=_("Push now"), write_only=True default=False, label=_("Push now"), write_only=True
@@ -33,7 +33,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
) )
on_invalid = LabeledChoiceField( on_invalid = LabeledChoiceField(
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR, choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
write_only=True, label=_('Exist policy') write_only=True, allow_null=True, label=_('Exist policy'),
) )
_template = None _template = None
clean_auth_fields: callable clean_auth_fields: callable

View File

@@ -1,6 +1,6 @@
from common.api import JMSBulkModelViewSet from common.api import JMSBulkModelViewSet
from ..models import LoginACL
from .. import serializers from .. import serializers
from ..models import LoginACL
from ..filters import LoginAclFilter from ..filters import LoginAclFilter
__all__ = ['LoginACLViewSet'] __all__ = ['LoginACLViewSet']

View File

@@ -46,10 +46,8 @@ class OperatorLogHandler(metaclass=Singleton):
pre_value, value = self._consistent_type_to_str(pre_value, value) pre_value, value = self._consistent_type_to_str(pre_value, value)
if sorted(str(value)) == sorted(str(pre_value)): if sorted(str(value)) == sorted(str(pre_value)):
continue continue
if pre_value: before[key] = pre_value
before[key] = pre_value after[key] = value
if value:
after[key] = value
return before, after return before, after
def cache_instance_before_data(self, instance_dict): def cache_instance_before_data(self, instance_dict):

View File

@@ -70,8 +70,10 @@ def _get_instance_field_value(
if getattr(f, 'primary_key', False): if getattr(f, 'primary_key', False):
f.verbose_name = 'id' f.verbose_name = 'id'
elif isinstance(value, (list, dict)): elif isinstance(value, list):
value = copy.deepcopy(value) value = copy.deepcopy(value)
elif isinstance(value, dict):
value = dict(copy.deepcopy(value))
elif isinstance(value, datetime): elif isinstance(value, datetime):
value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S') value = as_current_tz(value).strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(f, models.OneToOneField) and isinstance(value, models.Model): elif isinstance(f, models.OneToOneField) and isinstance(value, models.Model):

View File

@@ -3,6 +3,7 @@ import json
import os import os
import urllib.parse import urllib.parse
from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils import timezone from django.utils import timezone
@@ -379,19 +380,18 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
expire_now = request.data.get('expire_now', None) expire_now = request.data.get('expire_now', None)
asset_type = token.asset.type asset_type = token.asset.type
asset_category = token.asset.category
# 设置默认值 # 设置默认值
if expire_now is None: if expire_now is None:
# TODO 暂时特殊处理 k8s 不过期 # TODO 暂时特殊处理 k8s 不过期
if asset_type in ['k8s', 'kubernetes']: if asset_type in ['k8s', 'kubernetes']:
expire_now = False expire_now = False
elif asset_category in ['database', 'db']:
expire_now = False
else: else:
expire_now = True expire_now = not settings.CONNECTION_TOKEN_REUSABLE
if is_false(expire_now) or token.is_reusable: if is_false(expire_now):
logger.debug('Token is reusable or specify, not expire') logger.debug('Api specified, now expire now')
elif token.is_reusable and settings.CONNECTION_TOKEN_REUSABLE:
logger.debug('Token is reusable, not expire now')
else: else:
token.expire() token.expire()

View File

@@ -1,5 +1,6 @@
from django.conf import settings from django.conf import settings
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from authentication.signals import user_auth_failed, user_auth_success from authentication.signals import user_auth_failed, user_auth_success

View File

@@ -89,8 +89,6 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
msg: str msg: str
def __init__(self, username, request, ip, mfa_type, error): def __init__(self, username, request, ip, mfa_type, error):
super().__init__(username=username, request=request)
util = MFABlockUtils(username, ip) util = MFABlockUtils(username, ip)
times_remainder = util.incr_failed_count() times_remainder = util.incr_failed_count()
block_time = settings.SECURITY_LOGIN_LIMIT_TIME block_time = settings.SECURITY_LOGIN_LIMIT_TIME
@@ -101,6 +99,7 @@ class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
) )
else: else:
self.msg = const.block_mfa_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME) self.msg = const.block_mfa_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
super().__init__(username=username, request=request)
class BlockMFAError(AuthFailedNeedLogMixin, AuthFailedError): class BlockMFAError(AuthFailedNeedLogMixin, AuthFailedError):

View File

@@ -221,7 +221,8 @@ class MFAMixin:
self._do_check_user_mfa(code, mfa_type, user=user) self._do_check_user_mfa(code, mfa_type, user=user)
def check_user_mfa_if_need(self, user): def check_user_mfa_if_need(self, user):
if self.request.session.get('auth_mfa'): if self.request.session.get('auth_mfa') and \
self.request.session.get('auth_mfa_username') == user.username:
return return
if not user.mfa_enabled: if not user.mfa_enabled:
return return
@@ -229,15 +230,16 @@ class MFAMixin:
active_mfa_names = user.active_mfa_backends_mapper.keys() active_mfa_names = user.active_mfa_backends_mapper.keys()
raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names)) raise errors.MFARequiredError(mfa_types=tuple(active_mfa_names))
def mark_mfa_ok(self, mfa_type): def mark_mfa_ok(self, mfa_type, user):
self.request.session['auth_mfa'] = 1 self.request.session['auth_mfa'] = 1
self.request.session['auth_mfa_username'] = user.username
self.request.session['auth_mfa_time'] = time.time() self.request.session['auth_mfa_time'] = time.time()
self.request.session['auth_mfa_required'] = 0 self.request.session['auth_mfa_required'] = 0
self.request.session['auth_mfa_type'] = mfa_type self.request.session['auth_mfa_type'] = mfa_type
MFABlockUtils(self.request.user.username, self.get_request_ip()).clean_failed_count() MFABlockUtils(user.username, self.get_request_ip()).clean_failed_count()
def clean_mfa_mark(self): def clean_mfa_mark(self):
keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type'] keys = ['auth_mfa', 'auth_mfa_time', 'auth_mfa_required', 'auth_mfa_type', 'auth_mfa_username']
for k in keys: for k in keys:
self.request.session.pop(k, '') self.request.session.pop(k, '')
@@ -272,7 +274,7 @@ class MFAMixin:
ok, msg = mfa_backend.check_code(code) ok, msg = mfa_backend.check_code(code)
if ok: if ok:
self.mark_mfa_ok(mfa_type) self.mark_mfa_ok(mfa_type, user)
return return
raise errors.MFAFailedError( raise errors.MFAFailedError(

View File

@@ -1,4 +1,5 @@
from collections import defaultdict from collections import defaultdict
from functools import reduce
class DefaultCallback: class DefaultCallback:
@@ -18,6 +19,7 @@ class DefaultCallback:
failures=defaultdict(dict), failures=defaultdict(dict),
dark=defaultdict(dict), dark=defaultdict(dict),
skipped=defaultdict(dict), skipped=defaultdict(dict),
ignored=defaultdict(dict),
) )
self.summary = dict( self.summary = dict(
ok=[], ok=[],
@@ -59,6 +61,14 @@ class DefaultCallback:
} }
self.result['ok'][host][task] = detail self.result['ok'][host][task] = detail
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs):
detail = {
'action': event_data.get('task_action', ''),
'res': {},
'rc': 0,
}
self.result['skipped'][host][task] = detail
def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs): def runner_on_failed(self, event_data, host=None, task=None, res=None, **kwargs):
detail = { detail = {
'action': event_data.get('task_action', ''), 'action': event_data.get('task_action', ''),
@@ -67,15 +77,9 @@ class DefaultCallback:
'stdout': res.get('stdout', ''), 'stdout': res.get('stdout', ''),
'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';') 'stderr': ';'.join([res.get('stderr', ''), res.get('msg', '')]).strip(';')
} }
self.result['failures'][host][task] = detail ignore_errors = event_data.get('ignore_errors', False)
error_key = 'ignored' if ignore_errors else 'failures'
def runner_on_skipped(self, event_data, host=None, task=None, **kwargs): self.result[error_key][host][task] = detail
detail = {
'action': event_data.get('task_action', ''),
'res': {},
'rc': 0,
}
self.result['skipped'][host][task] = detail
def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs): def runner_on_unreachable(self, event_data, host=None, task=None, res=None, **kwargs):
detail = { detail = {
@@ -106,13 +110,18 @@ class DefaultCallback:
def playbook_on_stats(self, event_data, **kwargs): def playbook_on_stats(self, event_data, **kwargs):
failed = [] failed = []
for i in ['dark', 'failures']: error_func = lambda err, task_detail: err + f"{task_detail[0]}: {task_detail[1]['stderr']};"
for host, tasks in self.result[i].items(): for tp in ['dark', 'failures']:
for host, tasks in self.result[tp].items():
failed.append(host) failed.append(host)
error = '' error = reduce(error_func, tasks.items(), '').strip(';')
for task, detail in tasks.items(): self.summary[tp][host] = error
error += f'{task}: {detail["stderr"]};'
self.summary[i][host] = error.strip(';') for host, tasks in self.result.get('ignored', {}).items():
ignore_errors = reduce(error_func, tasks.items(), '').strip(';')
if host in failed:
self.summary['failures'][host] += {ignore_errors}
self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed)) self.summary['ok'] = list(set(self.result['ok'].keys()) - set(failed))
self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed)) self.summary['skipped'] = list(set(self.result['skipped'].keys()) - set(failed))

View File

@@ -166,6 +166,7 @@ class UsernameHintsAPI(APIView):
top_accounts = Account.objects \ top_accounts = Account.objects \
.exclude(username__startswith='jms_') \ .exclude(username__startswith='jms_') \
.exclude(username__startswith='js_') \
.filter(username__icontains=query) \ .filter(username__icontains=query) \
.filter(asset__in=assets) \ .filter(asset__in=assets) \
.values('username') \ .values('username') \

View File

@@ -38,25 +38,25 @@ class BaseSMSSettingSerializer(serializers.Serializer):
class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer): class AlibabaSMSSettingSerializer(BaseSMSSettingSerializer):
ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='AccessKeyId') ALIBABA_ACCESS_KEY_ID = serializers.CharField(max_length=256, required=True, label='Access Key ID')
ALIBABA_ACCESS_KEY_SECRET = EncryptedField( ALIBABA_ACCESS_KEY_SECRET = EncryptedField(
max_length=256, required=False, label='access_key_secret', max_length=256, required=False, label='Access Key Secret',
) )
ALIBABA_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) ALIBABA_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
ALIBABA_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code')) ALIBABA_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
class TencentSMSSettingSerializer(BaseSMSSettingSerializer): class TencentSMSSettingSerializer(BaseSMSSettingSerializer):
TENCENT_SECRET_ID = serializers.CharField(max_length=256, required=True, label='Secret id') TENCENT_SECRET_ID = serializers.CharField(max_length=256, required=True, label='Secret ID')
TENCENT_SECRET_KEY = EncryptedField(max_length=256, required=False, label='Secret key') TENCENT_SECRET_KEY = EncryptedField(max_length=256, required=False, label='Secret Key')
TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK app id') TENCENT_SDKAPPID = serializers.CharField(max_length=256, required=True, label='SDK APP ID')
TENCENT_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) TENCENT_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code')) TENCENT_VERIFY_TEMPLATE_CODE = serializers.CharField(max_length=256, required=True, label=_('Template code'))
class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer): class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer):
HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App key') HUAWEI_APP_KEY = serializers.CharField(max_length=256, required=True, label='App Key')
HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App secret') HUAWEI_APP_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
HUAWEI_SMS_ENDPOINT = serializers.CharField(max_length=1024, required=True, label=_('App Access Address')) HUAWEI_SMS_ENDPOINT = serializers.CharField(max_length=1024, required=True, label=_('App Access Address'))
HUAWEI_SIGN_CHANNEL_NUM = serializers.CharField(max_length=1024, required=True, label=_('Signature channel number')) HUAWEI_SIGN_CHANNEL_NUM = serializers.CharField(max_length=1024, required=True, label=_('Signature channel number'))
HUAWEI_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature')) HUAWEI_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))

View File

@@ -182,7 +182,7 @@
- name: Set chromium and driver on the global system path (Chromium) - name: Set chromium and driver on the global system path (Chromium)
ansible.windows.win_path: ansible.windows.win_path:
elements: elements:
- 'C:\Program Files\Chrome\chrome-win' - 'C:\Program Files\Chrome\chrome-win32'
- 'C:\Program Files\JumpServer\drivers\chromedriver_win32' - 'C:\Program Files\JumpServer\drivers\chromedriver_win32'
- name: Set Chromium variables disable Google Api (Chromium) - name: Set Chromium variables disable Google Api (Chromium)

View File

@@ -94,6 +94,7 @@ class Applet(JMSBaseModel):
@classmethod @classmethod
def load_platform_if_need(cls, d): def load_platform_if_need(cls, d):
from assets.serializers import PlatformSerializer from assets.serializers import PlatformSerializer
from assets.const import CustomTypes
if not os.path.exists(os.path.join(d, 'platform.yml')): if not os.path.exists(os.path.join(d, 'platform.yml')):
return return
@@ -111,6 +112,9 @@ class Applet(JMSBaseModel):
except KeyError: except KeyError:
raise ValidationError({'error': _('Missing type in platform.yml')}) raise ValidationError({'error': _('Missing type in platform.yml')})
if not data.get('automation'):
data['automation'] = CustomTypes._get_automation_constrains()['*']
s = PlatformSerializer(data=data) s = PlatformSerializer(data=data)
s.add_type_choices(tp, tp) s.add_type_choices(tp, tp)
s.is_valid(raise_exception=True) s.is_valid(raise_exception=True)
@@ -193,7 +197,7 @@ class Applet(JMSBaseModel):
if private_account and private_account.username not in accounts_username_used: if private_account and private_account.username not in accounts_username_used:
account = private_account account = private_account
else: else:
accounts = accounts.exclude(username__in=accounts_username_used) accounts = accounts.exclude(username__in=accounts_username_used).filter(username__startswith='jms_')
account = self.random_select_prefer_account(user, host, accounts) account = self.random_select_prefer_account(user, host, accounts)
if not account: if not account:
return return

View File

@@ -82,7 +82,7 @@ class Endpoint(JMSBaseModel):
return None return None
endpoints = cls.objects.filter(name__in=values).order_by('-date_updated') endpoints = cls.objects.filter(name__in=values).order_by('-date_updated')
for endpoint in endpoints: for endpoint in endpoints:
if endpoint.is_valid_for(protocol): if endpoint.is_valid_for(instance, protocol):
return endpoint return endpoint