mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-07-01 22:49:06 +00:00
Merge branch 'osm' of github.com:jumpserver/jumpserver into osm
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -297,7 +297,7 @@ operations:
|
||||
url_format:
|
||||
user_id: "{{ user.id }}"
|
||||
body:
|
||||
ukey_sn: null
|
||||
ukey_sn: ""
|
||||
register: user
|
||||
|
||||
- key: issueCert
|
||||
|
||||
@@ -359,7 +359,7 @@ operations:
|
||||
url_format:
|
||||
user_id: "{{ user.id }}"
|
||||
body:
|
||||
ukey_sn: null
|
||||
ukey_sn: ""
|
||||
register: user
|
||||
|
||||
- key: issueCert
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
|
||||
function redirect_page() {
|
||||
if (time >= 0) {
|
||||
if (time > 0) {
|
||||
var msg = message + ', <b>' + time + '</b> ...';
|
||||
$('#messages').html(msg);
|
||||
time--;
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
"Audits": "Audits",
|
||||
"AuditsDashboard": "Audits dashboard",
|
||||
"Auth": "Authentication",
|
||||
"Author": "Author",
|
||||
"AuthConfig": "Authentication",
|
||||
"AuthIntegration": "Auth Integration",
|
||||
"AuthLimit": "Login restriction",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"Audits": "監査台",
|
||||
"AuditsDashboard": "監査ダッシュボード",
|
||||
"Auth": "認証設定",
|
||||
"Author": "作成者",
|
||||
"AuthConfig": "資格認定構成",
|
||||
"AuthIntegration": "認証統合",
|
||||
"AuthLimit": "ログイン制限",
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
"Audits": "감사 대시보드",
|
||||
"AuditsDashboard": "감사 대시보드",
|
||||
"Auth": "인증 설정",
|
||||
"Author": "작성자",
|
||||
"AuthConfig": "인증 구성",
|
||||
"AuthIntegration": "인증 통합",
|
||||
"AuthLimit": "로그인 제한",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
"Audits": "Аудит",
|
||||
"AuditsDashboard": "Панель аудита",
|
||||
"Auth": "Аутентификация",
|
||||
"Author": "Автор",
|
||||
"AuthConfig": "Настройка аутентификации",
|
||||
"AuthIntegration": "Интеграция аутентификации",
|
||||
"AuthLimit": "Ограничение входа",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
"Audits": "审计台",
|
||||
"AuditsDashboard": "审计台仪表盘",
|
||||
"Auth": "认证设置",
|
||||
"Author": "作者",
|
||||
"AuthConfig": "配置认证",
|
||||
"AuthIntegration": "认证集成",
|
||||
"AuthLimit": "登录限制",
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"Audits": "審計台",
|
||||
"AuditsDashboard": "審計台儀表盤",
|
||||
"Auth": "認證設置",
|
||||
"Author": "作者",
|
||||
"AuthConfig": "配寘認證資訊",
|
||||
"AuthIntegration": "認證整合",
|
||||
"AuthLimit": "登入限制",
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user