Compare commits

...

21 Commits
v3 ... v3.8

Author SHA1 Message Date
ibuler
e3cbaa52d5 fix: 修复用户最后登录禁用 2023-11-30 10:04:58 +08:00
ibuler
d96b220619 perf: 优化 api key 认证记录用户的时间 2023-11-16 18:17:01 +08:00
fit2bot
11db46e61e fix:修复es6.8查询不到数据问题 (#12070)
Co-authored-by: feng <1304903146@qq.com>
2023-11-09 14:18:04 +08:00
ibuler
07bb44dfc3 fix: 修改可能迁移的问题 2023-11-01 03:41:20 -05:00
ibuler
1204a869a9 fix: 优化选择发布机 2023-10-30 16:06:40 +08:00
halo
0c8b36a6f4 fix: 修复DB2平台已经存在的问题 2023-10-26 01:25:26 -05:00
feng
d6cfda693f fix: 改密校验可连接性失败 2023-10-25 03:15:59 -05:00
Bai
7aa96462a4 fix: 修复登录日志和在线用户会话的 IP 地址获取方式 2023-10-25 01:39:15 -05:00
ibuler
1986653214 perf: 修复资产类型的 bug 2023-10-24 16:19:29 +08:00
fit2bot
4dc46cc754 fix: change secret perm 没有生成 (#11952)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 15:18:49 +08:00
fit2bot
dcb9270d02 fix: 改密切换至检测可连接性 失败 (#11951)
Co-authored-by: feng <1304903146@qq.com>
2023-10-24 15:18:27 +08:00
jiangweidong
f51d375f48 perf: 使用scan命令扫描在线用户 2023-10-23 04:33:17 -05:00
feng
cea24f0ccf fix: 删除错误的改密权限 2023-10-23 04:32:23 -05:00
ibuler
0292f2161f fix: 优化替换 DOMAINS 中端口 的问题 2023-10-22 22:32:30 -05:00
老广
778918be48 Merge pull request #11928 from jumpserver/pr@v3.8@database_list
fix: 资产数据库 不分页时list接口错误
2023-10-20 03:50:37 -05:00
feng
36fdf754e9 fix: 资产数据库 不分页时list接口错误 2023-10-20 16:40:00 +08:00
fit2bot
ce9515a19a perf: ticket 迁移文件 (#11921)
Co-authored-by: feng <1304903146@qq.com>
2023-10-19 20:00:54 +08:00
Bai
83108f84a3 fix: 修复账号改密密码规则提交不生效的问题 2023-10-19 04:30:28 -05:00
jiangweidong
e0b38fbcb6 fix: 可以清空云同步中的策略 2023-10-19 04:11:33 -05:00
Bryan
ce24c1c3fd Merge pull request #11914 from jumpserver/dev
v3.8.0
2023-10-19 03:37:39 -05:00
Bryan
3c54c82ce9 Merge pull request #11636 from jumpserver/dev
v3.7.0
2023-09-21 17:02:48 +08:00
23 changed files with 127 additions and 54 deletions

View File

@@ -136,7 +136,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
'username': account.username, 'username': account.username,
'secret_type': secret_type, 'secret_type': secret_type,
'secret': new_secret, 'secret': new_secret,
'private_key_path': private_key_path 'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
} }
if asset.platform.type == 'oracle': if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None h['account']['mode'] = 'sysdba' if account.privileged else None

View File

@@ -56,7 +56,8 @@ class PushAccountManager(ChangeSecretManager, AccountBasePlaybookManager):
'username': account.username, 'username': account.username,
'secret_type': secret_type, 'secret_type': secret_type,
'secret': new_secret, 'secret': new_secret,
'private_key_path': private_key_path 'private_key_path': private_key_path,
'become': account.get_ansible_become_auth(),
} }
if asset.platform.type == 'oracle': if asset.platform.type == 'oracle':
h['account']['mode'] = 'sysdba' if account.privileged else None h['account']['mode'] = 'sysdba' if account.privileged else None

View File

@@ -38,7 +38,7 @@ class Migration(migrations.Migration):
'verbose_name': 'Automation execution', 'verbose_name': 'Automation execution',
'verbose_name_plural': 'Automation executions', 'verbose_name_plural': 'Automation executions',
'permissions': [('view_changesecretexecution', 'Can view change secret execution'), 'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'), ('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'), ('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution')], ('add_gatheraccountsexecution', 'Can add gather accounts execution')],
'proxy': True, 'proxy': True,
@@ -184,7 +184,7 @@ class Migration(migrations.Migration):
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='automationexecution', name='automationexecution',
options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'), options={'permissions': [('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexection', 'Can add change secret execution'), ('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'), ('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'), ('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'), ('view_pushaccountexecution', 'Can view push account execution'),

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.1.10 on 2023-10-24 05:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0016_accounttemplate_password_rules'),
]
operations = [
migrations.AlterModelOptions(
name='automationexecution',
options={
'permissions': [
('view_changesecretexecution', 'Can view change secret execution'),
('add_changesecretexecution', 'Can add change secret execution'),
('view_gatheraccountsexecution', 'Can view gather accounts execution'),
('add_gatheraccountsexecution', 'Can add gather accounts execution'),
('view_pushaccountexecution', 'Can view push account execution'),
('add_pushaccountexecution', 'Can add push account execution')
],
'verbose_name': 'Automation execution', 'verbose_name_plural': 'Automation executions'},
),
]

View File

@@ -33,7 +33,7 @@ class AutomationExecution(AssetAutomationExecution):
verbose_name_plural = _("Automation executions") verbose_name_plural = _("Automation executions")
permissions = [ permissions = [
('view_changesecretexecution', _('Can view change secret execution')), ('view_changesecretexecution', _('Can view change secret execution')),
('add_changesecretexection', _('Can add change secret execution')), ('add_changesecretexecution', _('Can add change secret execution')),
('view_gatheraccountsexecution', _('Can view gather accounts execution')), ('view_gatheraccountsexecution', _('Can view gather accounts execution')),
('add_gatheraccountsexecution', _('Can add gather accounts execution')), ('add_gatheraccountsexecution', _('Can add gather accounts execution')),

View File

@@ -71,7 +71,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
return password_rules return password_rules
length = password_rules.get('length') length = password_rules.get('length')
symbol_set = password_rules.get('symbol_set', '')
try: try:
length = int(length) length = int(length)
@@ -84,10 +83,6 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
msg = _('* Password length range 6-30 bits') msg = _('* Password length range 6-30 bits')
raise serializers.ValidationError(msg) raise serializers.ValidationError(msg)
if not isinstance(symbol_set, str):
symbol_set = str(symbol_set)
password_rules = {'length': length, 'symbol_set': ''.join(symbol_set)}
return password_rules return password_rules
def validate(self, attrs): def validate(self, attrs):

View File

@@ -64,14 +64,14 @@ class BaseType(TextChoices):
@classmethod @classmethod
def _parse_protocols(cls, protocol, tp): def _parse_protocols(cls, protocol, tp):
from .protocol import Protocol from .protocol import Protocol
settings = Protocol.settings() _settings = Protocol.settings()
choices = protocol.get('choices', []) choices = protocol.get('choices', [])
if choices == '__self__': if choices == '__self__':
choices = [tp] choices = [tp]
protocols = [] protocols = []
for name in choices: for name in choices:
protocol = {'name': name, **settings.get(name, {})} protocol = {'name': name, **_settings.get(name, {})}
setting = protocol.pop('setting', {}) setting = protocol.pop('setting', {})
setting_values = {k: v.get('default', None) for k, v in setting.items()} setting_values = {k: v.get('default', None) for k, v in setting.items()}
protocol['setting'] = setting_values protocol['setting'] = setting_values
@@ -112,7 +112,7 @@ class BaseType(TextChoices):
@classmethod @classmethod
def get_choices(cls): def get_choices(cls):
if not settings.XPACK_LICENSE_IS_VALID: if not settings.XPACK_ENABLED:
return [ return [
(tp.value, tp.label) (tp.value, tp.label)
for tp in cls.get_community_types() for tp in cls.get_community_types()

View File

@@ -107,8 +107,9 @@ def create_app_nodes(apps, org_id):
'key': next_key, 'value': name, 'parent_key': parent_key, 'key': next_key, 'value': name, 'parent_key': parent_key,
'full_value': full_value, 'org_id': org_id 'full_value': full_value, 'org_id': org_id
} }
node, created = node_model.objects.get_or_create( node, __ = node_model.objects.get_or_create(
defaults=defaults, value=name, org_id=org_id, defaults=defaults, value=name, org_id=org_id,
parent_key=parent_key
) )
node.parent = parent node.parent = parent
return node return node

View File

@@ -6,17 +6,22 @@ from django.db import migrations
def add_db2_platform(apps, schema_editor): def add_db2_platform(apps, schema_editor):
platform_cls = apps.get_model('assets', 'Platform') platform_cls = apps.get_model('assets', 'Platform')
automation_cls = apps.get_model('assets', 'PlatformAutomation') automation_cls = apps.get_model('assets', 'PlatformAutomation')
platform = platform_cls.objects.create( platform, _ = platform_cls.objects.update_or_create(
name='DB2', internal=True, category='database', type='db2', name='DB2', defaults={
domain_enabled=True, su_enabled=False, comment='DB2', 'name': 'DB2', 'category': 'database',
created_by='System', updated_by='System', 'internal': True, 'type': 'db2',
'domain_enabled': True, 'su_enabled': False,
'su_method': None, 'comment': 'DB2', 'created_by': 'System',
'updated_by': 'System', 'custom_fields': []
}
) )
platform.protocols.create(name='db2', port=5000, primary=True, setting={}) platform.protocols.update_or_create(name='db2', defaults={
automation_cls.objects.create(ansible_enabled=False, platform=platform) 'name': 'db2', 'port': 50000, 'primary': True, 'setting': {}
})
automation_cls.objects.update_or_create(platform=platform, defaults={'ansible_enabled': False})
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('assets', '0123_device_automation_ansible_enabled'), ('assets', '0123_device_automation_ansible_enabled'),
] ]

View File

@@ -1,3 +1,4 @@
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
@@ -34,7 +35,7 @@ class DatabaseSerializer(AssetSerializer):
if not platform_id and self.instance: if not platform_id and self.instance:
platform = self.instance.platform platform = self.instance.platform
elif getattr(self, 'instance', None): elif getattr(self, 'instance', None):
if isinstance(self.instance, list): if isinstance(self.instance, (list, QuerySet)):
return return
platform = self.instance.platform platform = self.instance.platform
elif self.context.get('request'): elif self.context.get('request'):

View File

@@ -287,7 +287,7 @@ class UserSession(models.Model):
def get_keys(): def get_keys():
session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore session_store_cls = import_module(settings.SESSION_ENGINE).SessionStore
cache_key_prefix = session_store_cls.cache_key_prefix cache_key_prefix = session_store_cls.cache_key_prefix
keys = caches[settings.SESSION_CACHE_ALIAS].keys('*') keys = caches[settings.SESSION_CACHE_ALIAS].iter_keys('*')
return [k.replace(cache_key_prefix, '') for k in keys] return [k.replace(cache_key_prefix, '') for k in keys]
@classmethod @classmethod

View File

@@ -14,7 +14,7 @@ from acls.notifications import UserLoginReminderMsg
from audits.models import UserLoginLog from audits.models import UserLoginLog
from authentication.signals import post_auth_failed, post_auth_success from authentication.signals import post_auth_failed, post_auth_success
from authentication.utils import check_different_city_login_if_need from authentication.utils import check_different_city_login_if_need
from common.utils import get_request_ip, get_logger from common.utils import get_request_ip_or_data, get_logger
from users.models import User from users.models import User
from ..const import LoginTypeChoices from ..const import LoginTypeChoices
from ..models import UserSession from ..models import UserSession
@@ -60,7 +60,7 @@ def get_login_backend(request):
def generate_data(username, request, login_type=None): def generate_data(username, request, login_type=None):
user_agent = request.META.get('HTTP_USER_AGENT', '') user_agent = request.META.get('HTTP_USER_AGENT', '')
login_ip = get_request_ip(request) or '0.0.0.0' login_ip = get_request_ip_or_data(request) or '0.0.0.0'
if login_type is None and isinstance(request, Request): if login_type is None and isinstance(request, Request):
login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U') login_type = request.META.get('HTTP_X_JMS_LOGIN_TYPE', 'U')

View File

@@ -39,6 +39,7 @@ def on_m2m_changed(sender, action, instance, reverse, model, pk_set, **kwargs):
log_id, before_instance = get_instance_dict_from_cache(instance_id) log_id, before_instance = get_instance_dict_from_cache(instance_id)
field_name = str(model._meta.verbose_name) field_name = str(model._meta.verbose_name)
pk_set = pk_set or {}
objs = model.objects.filter(pk__in=pk_set) objs = model.objects.filter(pk__in=pk_set)
objs_display = [str(o) for o in objs] objs_display = [str(o) for o in objs]
action = M2M_ACTION[action] action = M2M_ACTION[action]

View File

@@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
from rest_framework import authentication, exceptions from rest_framework import authentication, exceptions
from common.auth import signature from common.auth import signature
from common.decorators import delay_run
from common.utils import get_object_or_none from common.utils import get_object_or_none
from ..models import AccessKey, PrivateToken from ..models import AccessKey, PrivateToken
@@ -16,14 +17,22 @@ def date_more_than(d, seconds):
return d is None or (timezone.now() - d).seconds > seconds return d is None or (timezone.now() - d).seconds > seconds
def after_authenticate_update_date(user, token=None): @delay_run(ttl=60)
if date_more_than(user.date_api_key_last_used, 60): def update_token_last_used(token):
user.date_api_key_last_used = timezone.now() token.date_last_used = timezone.now()
user.save(update_fields=['date_api_key_last_used']) token.save(update_fields=['date_last_used'])
if token and hasattr(token, 'date_last_used') and date_more_than(token.date_last_used, 60):
token.date_last_used = timezone.now() @delay_run(ttl=60)
token.save(update_fields=['date_last_used']) def update_user_last_used(user):
user.date_api_key_last_used = timezone.now()
user.save(update_fields=['date_api_key_last_used'])
def after_authenticate_update_date(user, token=None):
update_user_last_used(user)
if token:
update_token_last_used(token)
class AccessTokenAuthentication(authentication.BaseAuthentication): class AccessTokenAuthentication(authentication.BaseAuthentication):

View File

@@ -177,9 +177,10 @@ class ES(object):
body = self.get_query_body(**query) body = self.get_query_body(**query)
data = self.es.search( data = self.es.search(
index=self.query_index, doc_type=self.doc_type, body=body, index=self.query_index, body=body,
from_=from_, size=size, sort=sort from_=from_, size=size, sort=sort
) )
source_data = [] source_data = []
for item in data['hits']['hits']: for item in data['hits']['hits']:
if item: if item:
@@ -191,7 +192,7 @@ class ES(object):
def count(self, **query): def count(self, **query):
try: try:
body = self.get_query_body(**query) body = self.get_query_body(**query)
data = self.es.count(index=self.query_index, doc_type=self.doc_type, body=body) data = self.es.count(index=self.query_index, body=body)
count = data["count"] count = data["count"]
except Exception as e: except Exception as e:
logger.error('ES count error: {}'.format(e)) logger.error('ES count error: {}'.format(e))

View File

@@ -1,5 +1,6 @@
import os import os
import platform import platform
import re
from redis.sentinel import SentinelManagedSSLConnection from redis.sentinel import SentinelManagedSSLConnection
@@ -79,8 +80,8 @@ if CONFIG.SITE_URL:
ALLOWED_DOMAINS = DOMAINS.split(',') if DOMAINS else ['localhost:8080'] ALLOWED_DOMAINS = DOMAINS.split(',') if DOMAINS else ['localhost:8080']
ALLOWED_DOMAINS = [host.strip() for host in ALLOWED_DOMAINS] ALLOWED_DOMAINS = [host.strip() for host in ALLOWED_DOMAINS]
ALLOWED_DOMAINS = [host.replace('http://', '').replace('https://', '') for host in ALLOWED_DOMAINS if host] ALLOWED_DOMAINS = [host.replace('http://', '').replace('https://', '') for host in ALLOWED_DOMAINS if host]
ALLOWED_DOMAINS = [host.replace(':80', '').replace(':443', '') for host in ALLOWED_DOMAINS]
ALLOWED_DOMAINS = [host.split('/')[0] for host in ALLOWED_DOMAINS if host] ALLOWED_DOMAINS = [host.split('/')[0] for host in ALLOWED_DOMAINS if host]
ALLOWED_DOMAINS = [re.sub(':80$|:443$', '', host) for host in ALLOWED_DOMAINS]
DEBUG_HOSTS = ('127.0.0.1', 'localhost', 'core') DEBUG_HOSTS = ('127.0.0.1', 'localhost', 'core')
DEBUG_PORT = ['8080', '80', ] DEBUG_PORT = ['8080', '80', ]

View File

@@ -26,6 +26,9 @@ def common_argument_spec():
class SSHClient: class SSHClient:
SLEEP_INTERVAL = 0.3
COMPLETE_FLAG = 'complete'
def __init__(self, module): def __init__(self, module):
self.module = module self.module = module
self.channel = None self.channel = None
@@ -85,7 +88,10 @@ class SSHClient:
su_output, err_msg = self.execute(commands) su_output, err_msg = self.execute(commands)
if err_msg: if err_msg:
return err_msg return err_msg
i_output, err_msg = self.execute(['whoami'], delay_time=1) i_output, err_msg = self.execute(
['whoami && echo "complete"'],
validate_output=True
)
if err_msg: if err_msg:
return err_msg return err_msg
@@ -153,15 +159,21 @@ class SSHClient:
output = self.channel.recv(size).decode(encoding) output = self.channel.recv(size).decode(encoding)
return output return output
def execute(self, commands, delay_time=0.3): def execute(self, commands, validate_output=False):
if not self.is_connect: if not self.is_connect:
self.connect() self.connect()
output, error_msg = '', '' output, error_msg = '', ''
try: try:
for command in commands: for command in commands:
self.channel.send(command + '\n') self.channel.send(command + '\n')
time.sleep(delay_time) if not validate_output:
output = self._get_recv() time.sleep(self.SLEEP_INTERVAL)
output += self._get_recv()
continue
while self.COMPLETE_FLAG not in output:
time.sleep(self.SLEEP_INTERVAL)
received_output = self._get_recv().replace(f'"{self.COMPLETE_FLAG}"', '')
output += received_output
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
return output, error_msg return output, error_msg

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.10 on 2023-10-20 07:01
from django.db import migrations
def migrate_remove_add_changesecretexection_permission(apps, *args):
perm_model = apps.get_model('auth', 'Permission')
perm_model.objects.filter(codename='add_changesecretexection').delete()
class Migration(migrations.Migration):
dependencies = [
('rbac', '0011_remove_redundant_permission'),
]
operations = [
migrations.RunPython(migrate_remove_add_changesecretexection_permission)
]

View File

@@ -88,7 +88,7 @@ special_pid_mapper = {
"accounts.add_gatheraccountsexecution": "gather_account_node", "accounts.add_gatheraccountsexecution": "gather_account_node",
"accounts.changesecretautomation": "asset_change_plan_node", "accounts.changesecretautomation": "asset_change_plan_node",
"accounts.view_changesecretexecution": "asset_change_plan_node", "accounts.view_changesecretexecution": "asset_change_plan_node",
"accounts.add_changesecretexection": "asset_change_plan_node", "accounts.add_changesecretexecution": "asset_change_plan_node",
"accounts.view_changesecretrecord": "asset_change_plan_node", "accounts.view_changesecretrecord": "asset_change_plan_node",
'orgs.organization': 'view_setting', 'orgs.organization': 'view_setting',
'settings.setting': 'view_setting', 'settings.setting': 'view_setting',

View File

@@ -1,26 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils import translation
from django.utils import timezone from django.utils import timezone
from rest_framework import generics from rest_framework import generics
from rest_framework.fields import DateTimeField from rest_framework.fields import DateTimeField
from rest_framework.response import Response from rest_framework.response import Response
from acls.models import CommandFilterACL, CommandGroup from acls.models import CommandFilterACL, CommandGroup
from terminal.models import CommandStorage, Session, Command
from terminal.filters import CommandFilter
from orgs.utils import current_org
from common.api import JMSBulkModelViewSet from common.api import JMSBulkModelViewSet
from common.utils import get_logger from common.utils import get_logger
from terminal.serializers import ( from orgs.utils import current_org
SessionCommandSerializer, InsecureCommandAlertSerializer
)
from terminal.exceptions import StorageInvalid
from terminal.backends import ( from terminal.backends import (
get_command_storage, get_multi_command_storage get_command_storage, get_multi_command_storage
) )
from terminal.notifications import CommandAlertMessage, CommandWarningMessage
from terminal.const import RiskLevelChoices from terminal.const import RiskLevelChoices
from terminal.exceptions import StorageInvalid
from terminal.filters import CommandFilter
from terminal.models import CommandStorage, Session, Command
from terminal.notifications import CommandAlertMessage, CommandWarningMessage
from terminal.serializers import (
SessionCommandSerializer, InsecureCommandAlertSerializer
)
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = ['CommandViewSet', 'InsecureCommandAlertAPI'] __all__ = ['CommandViewSet', 'InsecureCommandAlertAPI']
@@ -140,8 +139,8 @@ class CommandViewSet(JMSBulkModelViewSet):
if session_id and not command_storage_id: if session_id and not command_storage_id:
# 会话里的命令列表肯定会提供 session_id这里防止 merge 的时候取全量的数据 # 会话里的命令列表肯定会提供 session_id这里防止 merge 的时候取全量的数据
return self.merge_all_storage_list(request, *args, **kwargs) return self.merge_all_storage_list(request, *args, **kwargs)
queryset = self.get_queryset()
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(queryset)
page = self.paginate_queryset(queryset) page = self.paginate_queryset(queryset)
if page is not None: if page is not None:

View File

@@ -162,7 +162,7 @@ class Applet(JMSBaseModel):
for host_id in using_host_ids.values(): for host_id in using_host_ids.values():
counts[host_id] += 1 counts[host_id] += 1
hosts = list(sorted(hosts, key=lambda h: counts[h.id])) hosts = list(sorted(hosts, key=lambda h: counts[str(h.id)]))
return hosts[0] if hosts else None return hosts[0] if hosts else None
def select_host(self, user, asset): def select_host(self, user, asset):

View File

@@ -27,6 +27,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
'abstract': False, 'abstract': False,
'verbose_name': 'Apply Login Ticket'
}, },
bases=('tickets.ticket',), bases=('tickets.ticket',),
), ),
@@ -101,6 +102,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
'abstract': False, 'abstract': False,
'verbose_name': 'Apply Login Asset Ticket'
}, },
bases=('tickets.ticket',), bases=('tickets.ticket',),
), ),
@@ -130,6 +132,7 @@ class Migration(migrations.Migration):
], ],
options={ options={
'abstract': False, 'abstract': False,
'verbose_name': 'Apply Command Ticket'
}, },
bases=('tickets.ticket',), bases=('tickets.ticket',),
), ),

View File

@@ -88,8 +88,8 @@ def check_unused_users():
uncommon_users_ttl = settings.SECURITY_UNCOMMON_USERS_TTL uncommon_users_ttl = settings.SECURITY_UNCOMMON_USERS_TTL
seconds_to_subtract = uncommon_users_ttl * 24 * 60 * 60 seconds_to_subtract = uncommon_users_ttl * 24 * 60 * 60
t = timezone.now() - timedelta(seconds=seconds_to_subtract) t = timezone.now() - timedelta(seconds=seconds_to_subtract)
last_login_q = Q(last_login__lte=t) | Q(last_login__isnull=True) last_login_q = Q(last_login__lte=t) | (Q(last_login__isnull=True) & Q(date_joined__lte=t))
api_key_q = Q(date_api_key_last_used__lte=t) | Q(date_api_key_last_used__isnull=True) api_key_q = Q(date_api_key_last_used__lte=t) | (Q(date_api_key_last_used__isnull=True) & Q(date_joined__lte=t))
users = User.objects \ users = User.objects \
.filter(date_joined__lt=t) \ .filter(date_joined__lt=t) \