Merge branch 'osm' of github.com:jumpserver/jumpserver into osm

This commit is contained in:
ibuler
2026-06-15 15:43:17 +08:00
23 changed files with 81 additions and 42 deletions

View File

@@ -1,27 +1,5 @@
ARG VERSION=dev
FROM python:3.14-slim-trixie AS gmssl-builder
WORKDIR /app
ARG GMSSL_VERSION=3.1.1
RUN set -ex \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
git \
cmake \
make \
gcc \
g++ \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN set -ex \
&& git clone --branch v${GMSSL_VERSION} https://github.com/guanzhi/GmSSL.git \
&& cd GmSSL \
&& mkdir build \
&& cd build \
&& cmake .. \
&& make \
&& make -j"$(nproc)" \
&& make install
FROM jumpserver/gmssl:3.1.1-trixie AS gmssl-builder
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack
FROM jumpserver/core:${VERSION}-ce
@@ -44,8 +22,6 @@ WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.org/simple
ARG GMSSL_VERSION=3.1.1
RUN set -ex \
&& uv pip install -i${PIP_MIRROR} --group xpack \
&& rm -rf /root/.cache/

View File

@@ -13,6 +13,7 @@ from accounts.models import (
)
from accounts.serializers import AuthValidateMixin, PasswordRulesSerializer
from assets.models import Asset
from common.serializers import SecretReadableCheckMixin
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
from common.utils import get_logger
from .base import BaseAutomationSerializer
@@ -134,12 +135,13 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
return obj.status == ChangeSecretRecordStatusChoice.success
class ChangeSecretRecordViewSecretSerializer(serializers.ModelSerializer):
class ChangeSecretRecordViewSecretSerializer(SecretReadableCheckMixin, serializers.ModelSerializer):
class Meta:
model = ChangeSecretRecord
fields = [
'id', 'old_secret', 'new_secret',
]
secret_fields = ['old_secret', 'new_secret']
read_only_fields = fields

View File

@@ -7,6 +7,7 @@ import tempfile
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.utils.translation import gettext_lazy as _
from users.models import User
from common.utils import get_logger
@@ -24,6 +25,7 @@ from .exceptions import (
UKeyCertUnsupportedAlgorithmError,
)
from .utils import is_sm2_pem
from authentication.errors.const import reason_user_inactive, reason_choices
__all__ = ['UKeyBackend']
@@ -45,9 +47,14 @@ class UKeyBackend(JMSBaseAuthBackend):
user = self._check_user_and_ukey_sn(username, ukey_sn)
cert_pem = self._load_cert_pem(cert)
if self._is_sm2_cert(cert_pem):
return self._authenticate_sm2(cert_pem, username, signature, challenge, user)
user = self._authenticate_sm2(cert_pem, username, signature, challenge, user)
else:
return self._authenticate_other(cert_pem, username, signature, challenge, user)
user = self._authenticate_other(cert_pem, username, signature, challenge, user)
if self.user_can_authenticate(user):
return user
else:
error = reason_choices[reason_user_inactive]
raise PermissionDenied(error)
except Exception as e:
if request:
request.error_message = str(e)

View File

@@ -297,7 +297,7 @@ operations:
url_format:
user_id: "{{ user.id }}"
body:
ukey_sn: null
ukey_sn: ""
register: user
- key: issueCert

View File

@@ -359,7 +359,7 @@ operations:
url_format:
user_id: "{{ user.id }}"
body:
ukey_sn: null
ukey_sn: ""
register: user
- key: issueCert

View File

@@ -5,7 +5,6 @@ from urllib.parse import urlencode
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib import messages
from django.core.cache import cache
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
@@ -27,6 +26,7 @@ from .sdk import ukey_sdk_config
__all__ = ['UKeyLoginView']
_CHALLENGE_CACHE_KEY_PREFIX = 'ukey_login_challenge'
_UKEY_ERROR_SESSION_KEY = 'ukey_login_error'
@method_decorator(sensitive_post_parameters(), name='dispatch')
@method_decorator(csrf_protect, name='dispatch')
@@ -79,6 +79,7 @@ class UKeyLoginView(AuthMixin, FormView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['challenge'] = self._generate_and_store_challenge()
context['error_msg'] = self.request.session.pop(_UKEY_ERROR_SESSION_KEY, '')
return context
def form_valid(self, form):
@@ -155,7 +156,7 @@ class UKeyLoginView(AuthMixin, FormView):
return field_name
def get_failed_response(self, form, username, error_msg):
messages.error(self.request, error_msg)
self.request.session[_UKEY_ERROR_SESSION_KEY] = str(error_msg or _('Unknown'))
self.send_auth_signal(success=False, reason=error_msg, username=username)
return redirect(self._build_login_redirect_url())

View File

@@ -56,7 +56,7 @@
}
function redirect_page() {
if (time >= 0) {
if (time > 0) {
var msg = message + ' <b>' + time + '</b> ...';
$('#messages').html(msg);
time--;

View File

@@ -202,11 +202,9 @@
<form id="ukey-login-form" autocomplete="off" action="" method="post" role="form" novalidate="novalidate">
{% csrf_token %}
{% if messages %}
{% if error_msg %}
<div style="margin-bottom: 16px;">
{% for message in messages %}
<p class="help-block red-fonts">{% trans "Error" %}: {{ message }}</p>
{% endfor %}
<p class="help-block red-fonts">{% trans "Error" %}: {{ error_msg }}</p>
</div>
{% endif %}

View File

@@ -198,6 +198,7 @@
"Audits": "Audits",
"AuditsDashboard": "Audits dashboard",
"Auth": "Authentication",
"Author": "Author",
"AuthConfig": "Authentication",
"AuthIntegration": "Auth Integration",
"AuthLimit": "Login restriction",

View File

@@ -200,6 +200,7 @@
"Audits": "panel de auditoría",
"AuditsDashboard": "Panel de auditoría",
"Auth": "Configuración de autenticación",
"Author": "Autor",
"AuthConfig": "Configuración de autenticación",
"AuthIntegration": "Integración de certificación",
"AuthLimit": "Restricción de inicio de sesión",

View File

@@ -202,6 +202,7 @@
"Audits": "監査台",
"AuditsDashboard": "監査ダッシュボード",
"Auth": "認証設定",
"Author": "作成者",
"AuthConfig": "資格認定構成",
"AuthIntegration": "認証統合",
"AuthLimit": "ログイン制限",

View File

@@ -200,6 +200,7 @@
"Audits": "감사 대시보드",
"AuditsDashboard": "감사 대시보드",
"Auth": "인증 설정",
"Author": "작성자",
"AuthConfig": "인증 구성",
"AuthIntegration": "인증 통합",
"AuthLimit": "로그인 제한",

View File

@@ -200,6 +200,7 @@
"Audits": "Auditório",
"AuditsDashboard": "Painel de Auditoria",
"Auth": "Configurações de autenticação",
"Author": "Autor",
"AuthConfig": "Configurar Autenticação",
"AuthIntegration": "Integração de Certificação",
"AuthLimit": "Restrições de login",

View File

@@ -200,6 +200,7 @@
"Audits": "Аудит",
"AuditsDashboard": "Панель аудита",
"Auth": "Аутентификация",
"Author": "Автор",
"AuthConfig": "Настройка аутентификации",
"AuthIntegration": "Интеграция аутентификации",
"AuthLimit": "Ограничение входа",

View File

@@ -200,6 +200,7 @@
"Audits": "Bảng kiểm tra",
"AuditsDashboard": "Bảng điều khiển kiểm toán",
"Auth": "Cài đặt xác thực",
"Author": "Tác giả",
"AuthConfig": "Cấu hình xác thực",
"AuthIntegration": "Tích hợp xác thực",
"AuthLimit": "Giới hạn đăng nhập",

View File

@@ -197,6 +197,7 @@
"Audits": "审计台",
"AuditsDashboard": "审计台仪表盘",
"Auth": "认证设置",
"Author": "作者",
"AuthConfig": "配置认证",
"AuthIntegration": "认证集成",
"AuthLimit": "登录限制",

View File

@@ -202,6 +202,7 @@
"Audits": "審計台",
"AuditsDashboard": "審計台儀表盤",
"Auth": "認證設置",
"Author": "作者",
"AuthConfig": "配寘認證資訊",
"AuthIntegration": "認證整合",
"AuthLimit": "登入限制",

View File

@@ -1,6 +1,7 @@
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from common.api import JMSGenericViewSet
from common.const.http import GET, PATCH, POST
@@ -11,6 +12,8 @@ from ..serializers import (
SiteMessageSendSerializer,
)
from ..site_msg import SiteMessageUtil
from ..models import MessageContent
__all__ = ('SiteMessageViewSet',)
@@ -59,5 +62,14 @@ class SiteMessageViewSet(ListModelMixin, RetrieveModelMixin, JMSGenericViewSet):
def send(self, request, **kwargs):
s = self.get_serializer(data=request.data)
s.is_valid(raise_exception=True)
SiteMessageUtil.send_msg(**s.validated_data, sender=request.user)
site_msg = SiteMessageUtil.send_msg(**s.validated_data, sender=request.user)
if site_msg:
return Response({'detail': 'ok', 'site_msg_id': str(site_msg.id)})
else:
return Response({'detail': 'error'})
@action(methods=[PATCH], detail=True, permission_classes=[OnlySuperUser,])
def revoke(self, request, **kwargs):
msg = get_object_or_404(MessageContent, id=kwargs['pk'])
msg.revoke_msg()
return Response({'detail': 'ok'})

View File

@@ -52,8 +52,15 @@ class MessageContent(JMSBaseModel):
return {
'id': str(self.id),
'subject': self.subject,
'is_broadcast': self.is_broadcast,
'message': self.message,
'display_mode': self.display_mode,
'date_created': str(self.date_created),
'sender': str(self.sender) if self.sender else ''
}
def revoke_msg(self):
if not self.is_broadcast:
return
self.is_broadcast = False
self.save()

View File

@@ -41,6 +41,7 @@ class SiteMessageUtil:
site_msg.users.add(*user_ids)
# 只有调用 save 才能触发 post_save 信号
site_msg.save()
return site_msg
@classmethod
def get_user_all_msgs(cls, user_id):

View File

@@ -44,7 +44,7 @@
{% block custom_foot_js %}
<script>
var ttl = 2
var ttl = 1
var message = ''
var time = '{{ interval }}'
{% if error %}
@@ -55,7 +55,7 @@
var redirect_url = '{{ redirect_url|escapejs }}'
function redirect_page() {
if (time >= 0) {
if (time > 0) {
var msg = message + ' <b>' + time + '</b> ...';
$('#messages').html(msg);
time -= ttl;

View File

@@ -6,9 +6,11 @@ from typing import Callable
from django.conf import settings
from django.core.files.storage import default_storage
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from django_filters import rest_framework as filters
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
@@ -16,6 +18,7 @@ from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from common.api import JMSBulkModelViewSet
from common.drf.filters import BaseFilterSet
from common.serializers import FileSerializer
from common.utils import is_uuid
from common.utils.http import is_true
@@ -140,8 +143,22 @@ class AppletViewSet(DownloadUploadMixin, JMSBulkModelViewSet):
instance.delete()
class AppletPublicationFilterSet(BaseFilterSet):
host = filters.CharFilter(method='filter_host')
class Meta:
model = AppletPublication
fields = ['host', 'applet', 'status']
@staticmethod
def filter_host(queryset, name, value):
if is_uuid(value):
return queryset.filter(host_id=value)
return queryset.filter(Q(host__name=value) | Q(host__address=value))
class AppletPublicationViewSet(viewsets.ModelViewSet):
queryset = AppletPublication.objects.all()
serializer_class = serializers.AppletPublicationSerializer
filterset_fields = ['host', 'applet', 'status']
search_fields = ['applet__name', 'applet__display_name', 'host__name']
filterset_class = AppletPublicationFilterSet
search_fields = ['applet__name', 'applet__display_name', 'host__name', 'host__address']

View File

@@ -325,6 +325,14 @@ class UserSerializer(
attrs.pop(field, None)
return attrs
@staticmethod
def clean_ukey_fields(attrs):
for field in ("ukey_sn",):
value = attrs.get(field)
if value is None:
attrs.pop(field, None)
return attrs
def check_disallow_self_update_fields(self, attrs):
request = self.context.get("request")
if not request or not request.user.is_authenticated:
@@ -348,6 +356,7 @@ class UserSerializer(
attrs = self.check_disallow_self_update_fields(attrs)
attrs = self.change_password_to_raw(attrs)
attrs = self.clean_auth_fields(attrs)
attrs = self.clean_ukey_fields(attrs)
password_strategy = attrs.pop("password_strategy", None)
request = get_current_request()
if request: