mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-01-05 15:44:09 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a6e7be4c4 | ||
|
|
a6b5437f6a | ||
|
|
71c690ef9e | ||
|
|
bee07db900 | ||
|
|
115eb7c15a | ||
|
|
88810263cd | ||
|
|
3d95bc4656 | ||
|
|
69de08bb5d | ||
|
|
5c234fdd0c | ||
|
|
be5baa5a3f | ||
|
|
2f1a65f120 | ||
|
|
e6d02eaf4c | ||
|
|
6d6dec2752 | ||
|
|
c6c067c44b | ||
|
|
84ec1b047a | ||
|
|
e6dca2ec14 | ||
|
|
8793003d18 |
@@ -53,3 +53,5 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||
connection_options:
|
||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
||||
append_privs: "{{ db_name != '' | bool }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
|
||||
|
||||
@@ -39,3 +39,5 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
db: "{{ jms_asset.spec_info.db_name }}"
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
@@ -28,4 +28,6 @@
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_password: "{{ account.secret }}"
|
||||
when: account.secret_type == "password"
|
||||
when:
|
||||
- account.secret_type == "password"
|
||||
- check_conn_after_change
|
||||
|
||||
@@ -31,5 +31,7 @@
|
||||
login_password: "{{ account.secret }}"
|
||||
login_secret_type: "{{ account.secret_type }}"
|
||||
login_private_key_path: "{{ account.private_key_path }}"
|
||||
when: account.secret_type == "password"
|
||||
when:
|
||||
- account.secret_type == "password"
|
||||
- check_conn_after_change
|
||||
delegate_to: localhost
|
||||
|
||||
@@ -53,3 +53,5 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||
connection_options:
|
||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
name: "{{ account.username }}"
|
||||
password: "{{ account.secret }}"
|
||||
host: "%"
|
||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
||||
append_privs: "{{ db_name != '' | bool }}"
|
||||
ignore_errors: true
|
||||
when: db_info is succeeded
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
role_attr_flags: LOGIN
|
||||
ignore_errors: true
|
||||
when: result is succeeded
|
||||
register: change_info
|
||||
|
||||
- name: Verify password
|
||||
community.postgresql.postgresql_ping:
|
||||
@@ -40,8 +39,5 @@
|
||||
login_host: "{{ jms_asset.address }}"
|
||||
login_port: "{{ jms_asset.port }}"
|
||||
db: "{{ jms_asset.spec_info.db_name }}"
|
||||
when:
|
||||
- result is succeeded
|
||||
- change_info is succeeded
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -28,4 +28,6 @@
|
||||
vars:
|
||||
ansible_user: "{{ account.username }}"
|
||||
ansible_password: "{{ account.secret }}"
|
||||
when: account.secret_type == "password"
|
||||
when:
|
||||
- account.secret_type == "password"
|
||||
- check_conn_after_change
|
||||
|
||||
@@ -31,5 +31,7 @@
|
||||
login_password: "{{ account.secret }}"
|
||||
login_secret_type: "{{ account.secret_type }}"
|
||||
login_private_key_path: "{{ account.private_key_path }}"
|
||||
when: account.secret_type == "password"
|
||||
when:
|
||||
- account.secret_type == "password"
|
||||
- check_conn_after_change
|
||||
delegate_to: localhost
|
||||
|
||||
@@ -16,3 +16,5 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||
connection_options:
|
||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -2,10 +2,11 @@ import copy
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
|
||||
from common.utils import ssh_key_gen, random_string
|
||||
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
|
||||
from common.utils import (
|
||||
validate_ssh_private_key, parse_ssh_private_key_str, ssh_key_gen,
|
||||
random_string
|
||||
)
|
||||
|
||||
|
||||
class SecretGenerator:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -8,7 +10,7 @@ from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
|
||||
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text', 'address_validator']
|
||||
|
||||
|
||||
def ip_group_child_validator(ip_group_child):
|
||||
@@ -21,6 +23,19 @@ def ip_group_child_validator(ip_group_child):
|
||||
raise serializers.ValidationError(error)
|
||||
|
||||
|
||||
def address_validator(value):
|
||||
parsed = urlparse(value)
|
||||
is_basic_url = parsed.scheme in ('http', 'https') and parsed.netloc
|
||||
is_valid = value == '*' \
|
||||
or is_ip_address(value) \
|
||||
or is_ip_network(value) \
|
||||
or is_ip_segment(value) \
|
||||
or is_basic_url
|
||||
if not is_valid:
|
||||
error = _('address invalid: `{}`').format(value)
|
||||
raise serializers.ValidationError(error)
|
||||
|
||||
|
||||
ip_group_help_text = _(
|
||||
'With * indicating a match all. '
|
||||
'Such as: '
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections import Counter
|
||||
|
||||
__all__ = ['FormatAssetInfo']
|
||||
|
||||
|
||||
@@ -7,13 +9,28 @@ class FormatAssetInfo:
|
||||
self.tp = tp
|
||||
|
||||
@staticmethod
|
||||
def posix_format(info):
|
||||
for cpu_model in info.get('cpu_model', []):
|
||||
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
|
||||
break
|
||||
else:
|
||||
cpu_model = ''
|
||||
info['cpu_model'] = cpu_model[:48]
|
||||
def get_cpu_model_count(cpus):
|
||||
try:
|
||||
if len(cpus) % 3 == 0:
|
||||
step = 3
|
||||
models = [cpus[i + 2] for i in range(0, len(cpus), step)]
|
||||
elif len(cpus) % 2 == 0:
|
||||
step = 2
|
||||
models = [cpus[i + 1] for i in range(0, len(cpus), step)]
|
||||
else:
|
||||
raise ValueError("CPU list format not recognized")
|
||||
|
||||
model_counts = Counter(models)
|
||||
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
|
||||
except Exception as e:
|
||||
print(f"Error processing CPU model list: {e}")
|
||||
result = ''
|
||||
return result
|
||||
|
||||
def posix_format(self, info):
|
||||
cpus = self.get_cpu_model_count(info.get('cpu_model', []))
|
||||
|
||||
info['cpu_model'] = cpus
|
||||
info['cpu_count'] = info.get('cpu_count', 0)
|
||||
return info
|
||||
|
||||
|
||||
@@ -16,3 +16,5 @@
|
||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||
connection_options:
|
||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||
register: result
|
||||
failed_when: not result.is_available
|
||||
|
||||
@@ -37,6 +37,7 @@ class DatabaseTypes(BaseType):
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True,
|
||||
'remove_account_enabled': True,
|
||||
},
|
||||
cls.REDIS: {
|
||||
'ansible_enabled': False,
|
||||
|
||||
@@ -53,7 +53,8 @@ class HostTypes(BaseType):
|
||||
'gather_accounts_enabled': True,
|
||||
'verify_account_enabled': True,
|
||||
'change_secret_enabled': True,
|
||||
'push_account_enabled': True
|
||||
'push_account_enabled': True,
|
||||
'remove_account_enabled': True,
|
||||
},
|
||||
cls.WINDOWS: {
|
||||
'ansible_config': {
|
||||
|
||||
@@ -27,6 +27,7 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
||||
"change_secret_enabled", "change_secret_method", "change_secret_params",
|
||||
"verify_account_enabled", "verify_account_method", "verify_account_params",
|
||||
"gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params",
|
||||
"remove_account_enabled", "remove_account_method", "remove_account_params",
|
||||
]
|
||||
extra_kwargs = {
|
||||
# 启用资产探测
|
||||
@@ -42,6 +43,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
||||
"push_account_method": {"label": _("Push account method")},
|
||||
"gather_accounts_enabled": {"label": _("Gather accounts enabled")},
|
||||
"gather_accounts_method": {"label": _("Gather accounts method")},
|
||||
"remove_account_method": {"label": _("Remove account method")},
|
||||
"remove_account_enabled": {"label": _("Remove account enabled")},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
|
||||
ip = get_request_ip(request)
|
||||
try:
|
||||
self.request = request
|
||||
self.check_is_block()
|
||||
self._check_third_party_login_acl()
|
||||
self._check_login_acl(request.user, ip)
|
||||
except Exception as e:
|
||||
|
||||
@@ -8,7 +8,7 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG']
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||
VERSION = '2.0.0'
|
||||
VERSION = 'v3.10.19'
|
||||
CONFIG = ConfigManager.load_user_config()
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from urllib.parse import urlparse, quote
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import MiddlewareNotUsed
|
||||
from django.http.response import HttpResponseForbidden
|
||||
from django.shortcuts import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from .utils import set_current_request
|
||||
@@ -137,3 +140,31 @@ class EndMiddleware:
|
||||
response = self.get_response(request)
|
||||
request._e_time_end = time.time()
|
||||
return response
|
||||
|
||||
|
||||
class SafeRedirectMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
|
||||
if not (300 <= response.status_code < 400):
|
||||
return response
|
||||
if request.resolver_match and request.resolver_match.namespace.startswith('authentication'):
|
||||
# 认证相关的路由跳过验证(core/auth/xxxx
|
||||
return response
|
||||
location = response.get('Location')
|
||||
if not location:
|
||||
return response
|
||||
parsed = urlparse(location)
|
||||
if parsed.scheme and parsed.netloc:
|
||||
target_host = parsed.netloc
|
||||
if target_host in [*settings.ALLOWED_HOSTS]:
|
||||
return response
|
||||
origin = f"{request.scheme}://{request.get_host()}"
|
||||
target_origin = f"{parsed.scheme}://{target_host}"
|
||||
if not target_origin.startswith(origin):
|
||||
safe_redirect_url = '%s?%s' % (reverse('redirect-confirm'), f'next={quote(location)}')
|
||||
return redirect(safe_redirect_url)
|
||||
return response
|
||||
|
||||
@@ -181,6 +181,7 @@ MIDDLEWARE = [
|
||||
'authentication.middleware.ThirdPartyLoginMiddleware',
|
||||
'authentication.middleware.SessionCookieMiddleware',
|
||||
'simple_history.middleware.HistoryRequestMiddleware',
|
||||
'jumpserver.middleware.SafeRedirectMiddleware',
|
||||
'jumpserver.middleware.EndMiddleware',
|
||||
]
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ app_view_patterns = [
|
||||
path('common/', include('common.urls.view_urls'), name='common'),
|
||||
re_path(r'flower/(?P<path>.*)', views.celery_flower_view, name='flower-view'),
|
||||
path('download/', views.ResourceDownload.as_view(), name='download'),
|
||||
path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'),
|
||||
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.http import HttpResponseRedirect, JsonResponse, Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -16,7 +18,7 @@ from common.views.http import HttpResponseTemporaryRedirect
|
||||
__all__ = [
|
||||
'LunaView', 'I18NView', 'KokoView', 'WsView',
|
||||
'redirect_format_api', 'redirect_old_apps_view', 'UIView',
|
||||
'ResourceDownload',
|
||||
'ResourceDownload', 'RedirectConfirm'
|
||||
]
|
||||
|
||||
|
||||
@@ -94,3 +96,24 @@ def csrf_failure(request, reason=""):
|
||||
from django.shortcuts import reverse
|
||||
login_url = reverse('authentication:login') + '?csrf_failure=1&admin=1'
|
||||
return redirect(login_url)
|
||||
|
||||
|
||||
class RedirectConfirm(TemplateView):
|
||||
template_name = 'redirect_confirm.html'
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
next_url = self.request.GET.get("next")
|
||||
if not self.is_valid_url(next_url):
|
||||
return HttpResponseBadRequest("Invalid next url")
|
||||
return self.render_to_response({"target_url": next_url})
|
||||
|
||||
@staticmethod
|
||||
def is_valid_url(url):
|
||||
if not url:
|
||||
return False
|
||||
parsed = urlparse(url)
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return False
|
||||
if parsed.scheme not in ['http', 'https']:
|
||||
return False
|
||||
return True
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2dd9ffcfe15b130a5b3d7b4fcfe806eaae979973e8bd29ad9a473b9215424c57
|
||||
size 178725
|
||||
oid sha256:ab3807ddd5c7b4c2dae9ebe80a6cf20c8ec4e189ea95a1581f8acc3d377e52f2
|
||||
size 178402
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4517c6a7464c68f949912b97c8a9abcc766ca19e32267a1d1da3f0e012471c1a
|
||||
size 146255
|
||||
oid sha256:9d5e378a6e129625a1cc2b7b043e8feb3a48b0c239239d0ab954525b51244969
|
||||
size 146503
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7d4d2709e597e055072474f08be2f363d43df239240051024a77213ba48ecfac
|
||||
size 146366
|
||||
oid sha256:884dde4efaa8a8a80efee337248c6a7c1ff5fc83e486e34db685a1d20479e109
|
||||
size 146115
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -185,6 +185,11 @@ class UserPermedNodeChildrenWithAssetsAsCategoryTreeApi(BaseUserNodeWithAssetAsT
|
||||
return [], []
|
||||
if not self.tp or not all(self.tp):
|
||||
nodes = UserPermAssetUtil.get_type_nodes_tree_or_cached(self.user)
|
||||
if self.request.query_params.get('count_resource'):
|
||||
# 解决在 lina 使用该 api 类型树套娃问题
|
||||
for node in nodes:
|
||||
if node.get('meta'):
|
||||
node['isParent'] = False
|
||||
return nodes, []
|
||||
|
||||
category, tp = self.tp
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.1.13 on 2025-08-25 03:03
|
||||
|
||||
import common.utils.django
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('perms', '0036_auto_20231108_1626'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='assetpermission',
|
||||
name='date_expired',
|
||||
field=models.DateTimeField(db_index=True, default=common.utils.django.asset_permission_date_expired_default, verbose_name='Date expired'),
|
||||
),
|
||||
]
|
||||
@@ -49,6 +49,7 @@ def check_asset_permission_will_expired():
|
||||
org_perm_remain_day_mapper = defaultdict(dict)
|
||||
|
||||
asset_perms = AssetPermission.objects.filter(
|
||||
is_active=True,
|
||||
date_expired__gte=start,
|
||||
date_expired__lte=end
|
||||
).distinct()
|
||||
|
||||
44
apps/templates/redirect_confirm.html
Normal file
44
apps/templates/redirect_confirm.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block html_title %} {{ INTERFACE.login_title }} {% endblock %}
|
||||
{% block title %} {{ INTERFACE.login_title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.alert.alert-msg {
|
||||
background: #F5F5F7;
|
||||
}
|
||||
|
||||
.target-url {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
min-width: 100px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<p>
|
||||
<div class="alert {% if error %} alert-danger {% else %} alert-info {% endif %}" id="messages">
|
||||
{% trans 'You are about to be redirected to an external website. Please confirm that you trust this link: ' %}
|
||||
<a class="target-url" href="{{ target_url }}">{{ target_url }}</a>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<a href="/" class="btn btn-default block full-width m-b">
|
||||
{% trans 'Cancel' %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<a href="{{ target_url }}" class="btn btn-primary block full-width m-b">
|
||||
{% trans 'Confirm' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from acls.serializers.rules import ip_group_child_validator, ip_group_help_text
|
||||
from acls.serializers.rules import address_validator, ip_group_help_text
|
||||
from common.serializers import BulkModelSerializer
|
||||
from common.serializers.fields import ObjectRelatedField
|
||||
from ..models import Endpoint, EndpointRule
|
||||
@@ -61,13 +61,13 @@ class EndpointSerializer(BulkModelSerializer):
|
||||
|
||||
class EndpointRuleSerializer(BulkModelSerializer):
|
||||
_ip_group_help_text = '{}, {} <br>{}'.format(
|
||||
_('The assets within this IP range, the following endpoint will be used for the connection'),
|
||||
_('The assets within this IP range or Host, the following endpoint will be used for the connection'),
|
||||
_('If asset IP addresses under different endpoints conflict, use asset labels'),
|
||||
ip_group_help_text,
|
||||
)
|
||||
ip_group = serializers.ListField(
|
||||
default=['*'], label=_('Asset IP'), help_text=_ip_group_help_text,
|
||||
child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator])
|
||||
default=['*'], label=_('Address'), help_text=_ip_group_help_text,
|
||||
child=serializers.CharField(max_length=1024, validators=[address_validator]),
|
||||
)
|
||||
endpoint = ObjectRelatedField(
|
||||
allow_null=True, required=False, queryset=Endpoint.objects, label=_('Endpoint')
|
||||
|
||||
@@ -23,7 +23,8 @@ class TicketFilter(BaseFilterSet):
|
||||
|
||||
def filter_assignees_id(self, queryset, name, value):
|
||||
return queryset.filter(
|
||||
ticket_steps__ticket_assignees__assignee__id=value
|
||||
ticket_steps__level=F('approval_step'),
|
||||
ticket_steps__ticket_assignees__assignee_id=value
|
||||
)
|
||||
|
||||
def filter_relevant_asset(self, queryset, name, value):
|
||||
|
||||
19
apps/users/migrations/0051_alter_user_date_expired.py
Normal file
19
apps/users/migrations/0051_alter_user_date_expired.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.1.13 on 2025-08-25 03:03
|
||||
|
||||
import common.utils.django
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0050_user_lark_id_alter_user_source'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='date_expired',
|
||||
field=models.DateTimeField(blank=True, db_index=True, default=common.utils.django.user_date_expired_default, null=True, verbose_name='Date expired'),
|
||||
),
|
||||
]
|
||||
@@ -125,7 +125,7 @@ class BlockUtilBase:
|
||||
BLOCK_KEY_TMPL: str
|
||||
|
||||
def __init__(self, username, ip):
|
||||
self.username = username
|
||||
username = username.lower() if username else ''
|
||||
self.ip = ip
|
||||
self.limit_key = self.LIMIT_KEY_TMPL.format(username, ip)
|
||||
self.block_key = self.BLOCK_KEY_TMPL.format(username)
|
||||
|
||||
518
poetry.lock
generated
518
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "jumpserver"
|
||||
version = "v3.9"
|
||||
version = "v3.10.19"
|
||||
description = "广受欢迎的开源堡垒机"
|
||||
authors = ["ibuler <ibuler@qq.com>"]
|
||||
license = "GPLv3"
|
||||
@@ -147,7 +147,7 @@ mistune = "2.0.3"
|
||||
openai = "^1.29.0"
|
||||
xlsxwriter = "^3.1.9"
|
||||
exchangelib = "^5.1.0"
|
||||
xmlsec = "^1.3.13"
|
||||
xmlsec = "1.3.13"
|
||||
lxml = "5.2.1"
|
||||
receptorctl = "^1.4.5"
|
||||
pydantic = "^2.7.4"
|
||||
|
||||
Reference in New Issue
Block a user