mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-18 01:52:46 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a497b3cf94 | ||
|
|
8548b73063 | ||
|
|
182320f492 | ||
|
|
40d326d6a6 | ||
|
|
b87554f9db | ||
|
|
3c255f9fa6 | ||
|
|
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")},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -472,6 +472,8 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
||||
rbac_perms = {
|
||||
'create': 'authentication.add_superconnectiontoken',
|
||||
'renewal': 'authentication.add_superconnectiontoken',
|
||||
'list': 'authentication.view_superconnectiontoken',
|
||||
'retrieve': 'authentication.view_superconnectiontoken',
|
||||
'get_secret_detail': 'authentication.view_superconnectiontokensecret',
|
||||
'get_applet_info': 'authentication.view_superconnectiontoken',
|
||||
'release_applet_account': 'authentication.view_superconnectiontoken',
|
||||
@@ -479,7 +481,12 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return ConnectionToken.objects.all()
|
||||
return ConnectionToken.objects.none()
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get(self.lookup_field)
|
||||
token = get_object_or_404(ConnectionToken, pk=pk)
|
||||
return token
|
||||
|
||||
def get_user(self, serializer):
|
||||
return serializer.validated_data.get('user')
|
||||
|
||||
@@ -128,7 +128,7 @@ class SignatureAuthentication(signature.SignatureAuthentication):
|
||||
# example implementation:
|
||||
try:
|
||||
key = AccessKey.objects.get(id=key_id)
|
||||
if not key.is_active:
|
||||
if not key.is_valid:
|
||||
return None, None
|
||||
user, secret = key.user, str(key.secret)
|
||||
after_authenticate_update_date(user, key)
|
||||
|
||||
@@ -36,7 +36,7 @@ class MFAMiddleware:
|
||||
# 这个是 mfa 登录页需要的请求, 也得放出来, 用户其实已经在 CAS/OIDC 中完成登录了
|
||||
white_urls = [
|
||||
'login/mfa', 'mfa/select', 'jsi18n/', '/static/',
|
||||
'/profile/otp', '/logout/',
|
||||
'/profile/otp', '/logout/', '/media/'
|
||||
]
|
||||
for url in white_urls:
|
||||
if request.path.find(url) > -1:
|
||||
@@ -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:
|
||||
|
||||
@@ -26,6 +26,10 @@ class AccessKey(models.Model):
|
||||
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
return self.is_active and self.user.is_valid
|
||||
|
||||
def get_id(self):
|
||||
return str(self.id)
|
||||
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from urllib.parse import urlparse, quote
|
||||
|
||||
import pytz
|
||||
import time
|
||||
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,38 @@ 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
|
||||
target_host, target_port = self._split_host_port(parsed.netloc)
|
||||
origin_host, origin_port = self._split_host_port(request.get_host())
|
||||
if target_host != origin_host:
|
||||
safe_redirect_url = '%s?%s' % (reverse('redirect-confirm'), f'next={quote(location)}')
|
||||
return redirect(safe_redirect_url)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _split_host_port(netloc):
|
||||
if ':' in netloc:
|
||||
host, port = netloc.split(':', 1)
|
||||
return host, port
|
||||
return netloc, '80'
|
||||
|
||||
@@ -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
30
apps/orgs/mixins/ws.py
Normal file
30
apps/orgs/mixins/ws.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
from orgs.utils import tmp_to_org
|
||||
|
||||
|
||||
class OrgMixin:
|
||||
cookie = None
|
||||
org = None
|
||||
|
||||
def get_cookie(self):
|
||||
try:
|
||||
headers = self.scope['headers']
|
||||
headers_dict = {key.decode('utf-8'): value.decode('utf-8') for key, value in headers}
|
||||
cookie = SimpleCookie(headers_dict.get('cookie', ''))
|
||||
except Exception as e:
|
||||
cookie = SimpleCookie()
|
||||
return cookie
|
||||
|
||||
def get_current_org(self):
|
||||
oid = self.cookie.get('X-JMS-ORG')
|
||||
return oid.value if oid else None
|
||||
|
||||
@sync_to_async
|
||||
def has_perms(self, user, perms):
|
||||
self.cookie = self.get_cookie()
|
||||
self.org = self.get_current_org()
|
||||
with tmp_to_org(self.org):
|
||||
return user.has_perms(perms)
|
||||
@@ -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()
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import json
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.db.utils import close_old_connections
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.ws import OrgMixin
|
||||
from orgs.models import Organization
|
||||
from orgs.utils import current_org
|
||||
from settings.serializers import (
|
||||
LDAPTestConfigSerializer,
|
||||
LDAPTestLoginSerializer
|
||||
)
|
||||
from orgs.models import Organization
|
||||
from orgs.utils import current_org
|
||||
from settings.tasks import sync_ldap_user
|
||||
from settings.utils import (
|
||||
LDAPServerUtil, LDAPCacheUtil, LDAPImportUtil, LDAPSyncUtil,
|
||||
@@ -97,10 +98,10 @@ class ToolsWebsocket(AsyncJsonWebsocketConsumer):
|
||||
close_old_connections()
|
||||
|
||||
|
||||
class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||
class LdapWebsocket(AsyncJsonWebsocketConsumer, OrgMixin):
|
||||
async def connect(self):
|
||||
user = self.scope["user"]
|
||||
if user.is_authenticated:
|
||||
if user.is_authenticated and await self.has_perms(user, ['settings.view_setting']):
|
||||
await self.accept()
|
||||
else:
|
||||
await self.close()
|
||||
@@ -133,7 +134,7 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
||||
auth_ldap = serializer.validated_data.get('AUTH_LDAP', False)
|
||||
|
||||
if not password:
|
||||
if not password and server_uri == settings.AUTH_LDAP_SERVER_URI:
|
||||
password = settings.AUTH_LDAP_BIND_PASSWORD
|
||||
|
||||
config = {
|
||||
|
||||
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
@@ -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.14"
|
||||
lxml = "5.2.1"
|
||||
receptorctl = "^1.4.5"
|
||||
pydantic = "^2.7.4"
|
||||
|
||||
Reference in New Issue
Block a user