mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-16 09:02:49 +00:00
Compare commits
40 Commits
pr@dev@per
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d0db2ba8b | ||
|
|
e617245b26 | ||
|
|
9280884c1c | ||
|
|
f31994fdcd | ||
|
|
71766418bb | ||
|
|
a9399dd709 | ||
|
|
d0cb9e5432 | ||
|
|
558188da90 | ||
|
|
ad5460dab8 | ||
|
|
4d37dca0de | ||
|
|
2ca4002624 | ||
|
|
053d640e4c | ||
|
|
f3acc28ded | ||
|
|
25987545db | ||
|
|
6720ecc6e0 | ||
|
|
0b3a7bb020 | ||
|
|
56373e362b | ||
|
|
02fc045370 | ||
|
|
e4ac73896f | ||
|
|
1518f792d6 | ||
|
|
67277dd622 | ||
|
|
82e7f020ea | ||
|
|
f20b9e01ab | ||
|
|
8cf8a3701b | ||
|
|
7ba24293d1 | ||
|
|
f10114c9ed | ||
|
|
cf31cbfb07 | ||
|
|
0edad24d5d | ||
|
|
1f1c1a9157 | ||
|
|
6c9d271ae1 | ||
|
|
6ff852e225 | ||
|
|
baa75dc735 | ||
|
|
8a9f0436b8 | ||
|
|
a9620a3cbe | ||
|
|
769e7dc8a0 | ||
|
|
2a70449411 | ||
|
|
8df720f19e | ||
|
|
dabbb45f6e | ||
|
|
ce24c1c3fd | ||
|
|
3c54c82ce9 |
29
.github/workflows/jms-generic-action-handler.yml
vendored
29
.github/workflows/jms-generic-action-handler.yml
vendored
@@ -1,33 +1,10 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
release:
|
||||
types: [created]
|
||||
on: [push, pull_request, release]
|
||||
|
||||
name: JumpServer repos generic handler
|
||||
|
||||
jobs:
|
||||
handle_pull_request:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jumpserver/action-generic-handler@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
||||
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}
|
||||
|
||||
handle_push:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jumpserver/action-generic-handler@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
||||
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}
|
||||
|
||||
handle_release:
|
||||
if: github.event_name == 'release'
|
||||
generic_handler:
|
||||
name: Run generic handler
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jumpserver/action-generic-handler@master
|
||||
|
||||
@@ -268,14 +268,6 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
||||
'port_from_addr': True,
|
||||
'required': True,
|
||||
'secret_types': ['token'],
|
||||
'setting': {
|
||||
'namespace': {
|
||||
'type': 'str',
|
||||
'required': False,
|
||||
'default': '',
|
||||
'label': _('Namespace')
|
||||
}
|
||||
}
|
||||
},
|
||||
cls.http: {
|
||||
'port': 80,
|
||||
|
||||
@@ -50,7 +50,7 @@ class UserLoginForm(forms.Form):
|
||||
|
||||
class UserCheckOtpCodeForm(forms.Form):
|
||||
code = forms.CharField(label=_('MFA Code'), max_length=128, required=False)
|
||||
mfa_type = forms.CharField(label=_('MFA type'), max_length=128, required=False)
|
||||
mfa_type = forms.CharField(label=_('MFA type'), max_length=128)
|
||||
|
||||
|
||||
class CustomCaptchaTextInput(CaptchaTextInput):
|
||||
|
||||
@@ -72,9 +72,10 @@ class BaseMFA(abc.ABC):
|
||||
def is_active(self):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return cls.name in settings.SECURITY_MFA_ENABLED_BACKENDS
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def global_enabled():
|
||||
return False
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_enable_url(self) -> str:
|
||||
|
||||
@@ -39,9 +39,9 @@ class MFACustom(BaseMFA):
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return super().global_enabled() and settings.MFA_CUSTOM and callable(mfa_custom_method)
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.MFA_CUSTOM and callable(mfa_custom_method)
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return ''
|
||||
|
||||
@@ -50,9 +50,9 @@ class MFAEmail(BaseMFA):
|
||||
)
|
||||
sender_util.gen_and_send_async()
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return super().global_enabled and settings.SECURITY_MFA_BY_EMAIL
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.SECURITY_MFA_BY_EMAIL
|
||||
|
||||
def disable(self):
|
||||
return '/ui/#/profile/index'
|
||||
|
||||
@@ -29,10 +29,9 @@ class MFAFace(BaseMFA, AuthFaceMixin):
|
||||
return True
|
||||
return bool(self.user.face_vector)
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return (
|
||||
super().global_enabled() and
|
||||
settings.XPACK_LICENSE_IS_VALID and
|
||||
settings.XPACK_LICENSE_EDITION_ULTIMATE and
|
||||
settings.FACE_RECOGNITION_ENABLED
|
||||
|
||||
@@ -25,6 +25,10 @@ class MFAOtp(BaseMFA):
|
||||
return True
|
||||
return self.user.otp_secret_key
|
||||
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return True
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return reverse('authentication:user-otp-enable-start')
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ class MFAPasskey(BaseMFA):
|
||||
return False
|
||||
return self.user.passkey_set.count()
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return super().global_enabled() and settings.AUTH_PASSKEY
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.AUTH_PASSKEY
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return '/ui/#/profile/passkeys'
|
||||
|
||||
@@ -27,9 +27,9 @@ class MFARadius(BaseMFA):
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return super().global_enabled() and settings.OTP_IN_RADIUS
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.OTP_IN_RADIUS
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return ''
|
||||
|
||||
@@ -46,9 +46,9 @@ class MFASms(BaseMFA):
|
||||
def send_challenge(self):
|
||||
self.sms.gen_and_send_async()
|
||||
|
||||
@classmethod
|
||||
def global_enabled(cls):
|
||||
return super().global_enabled() and settings.SMS_ENABLED
|
||||
@staticmethod
|
||||
def global_enabled():
|
||||
return settings.SMS_ENABLED
|
||||
|
||||
def get_enable_url(self) -> str:
|
||||
return '/ui/#/profile/index'
|
||||
|
||||
@@ -376,7 +376,7 @@
|
||||
</div>
|
||||
{% if form.challenge %}
|
||||
{% bootstrap_field form.challenge show_label=False %}
|
||||
{% elif form.mfa_type and mfa_backends %}
|
||||
{% elif form.mfa_type %}
|
||||
<div class="form-group" style="display: flex">
|
||||
{% include '_mfa_login_field.html' %}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
import datetime
|
||||
import inspect
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 10:
|
||||
@@ -335,10 +334,6 @@ class ES(object):
|
||||
def is_keyword(props: dict, field: str) -> bool:
|
||||
return props.get(field, {}).get("type", "keyword") == "keyword"
|
||||
|
||||
@staticmethod
|
||||
def is_long(props: dict, field: str) -> bool:
|
||||
return props.get(field, {}).get("type") == "long"
|
||||
|
||||
def get_query_body(self, **kwargs):
|
||||
new_kwargs = {}
|
||||
for k, v in kwargs.items():
|
||||
@@ -366,10 +361,10 @@ class ES(object):
|
||||
if index_in_field in kwargs:
|
||||
index['values'] = kwargs[index_in_field]
|
||||
|
||||
mapping = self.es.indices.get_mapping(index=self.index)
|
||||
mapping = self.es.indices.get_mapping(index=self.query_index)
|
||||
props = (
|
||||
mapping
|
||||
.get(self.index, {})
|
||||
.get(self.query_index, {})
|
||||
.get('mappings', {})
|
||||
.get('properties', {})
|
||||
)
|
||||
@@ -380,9 +375,6 @@ class ES(object):
|
||||
if k in ("org_id", "session") and self.is_keyword(props, k):
|
||||
exact[k] = v
|
||||
|
||||
elif self.is_long(props, k):
|
||||
exact[k] = v
|
||||
|
||||
elif k in common_keyword_able:
|
||||
exact[f"{k}.keyword"] = v
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ def get_ip_city(ip):
|
||||
|
||||
info = get_ip_city_by_ipip(ip)
|
||||
if info:
|
||||
city = info.get('city') or _("Unknown")
|
||||
city = info.get('city', _("Unknown"))
|
||||
country = info.get('country')
|
||||
|
||||
# 国内城市 并且 语言是中文就使用国内
|
||||
|
||||
@@ -569,7 +569,7 @@ class Config(dict):
|
||||
'SAFE_MODE': False,
|
||||
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
|
||||
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY': True,
|
||||
'SECURITY_MFA_ENABLED_BACKENDS': [],
|
||||
'SECURITY_MFA_BY_EMAIL': False,
|
||||
'SECURITY_COMMAND_EXECUTION': False,
|
||||
'SECURITY_COMMAND_BLACKLIST': [
|
||||
'reboot', 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top'
|
||||
|
||||
@@ -43,7 +43,6 @@ class ComponentI18nApi(RetrieveAPIView):
|
||||
if not lang:
|
||||
return Response(data)
|
||||
|
||||
lang = lang.lower()
|
||||
if lang not in dict(Language.get_code_mapper()).keys():
|
||||
lang = 'en'
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 4.1.13 on 2025-11-27 02:54
|
||||
|
||||
from django.db import migrations, connections
|
||||
|
||||
|
||||
def refresh_pg_collation(apps, schema_editor):
|
||||
for alias, conn in connections.databases.items():
|
||||
if connections[alias].vendor == "postgresql":
|
||||
dbname = connections[alias].settings_dict["NAME"]
|
||||
connections[alias].cursor().execute(
|
||||
f'ALTER DATABASE "{dbname}" REFRESH COLLATION VERSION;'
|
||||
)
|
||||
print(f"Refreshed postgresql collation version for database: {dbname} successfully.")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('settings', '0002_leakpasswords'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(refresh_pg_collation, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -1,7 +1,3 @@
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -121,35 +117,6 @@ class SecurityLoginLimitSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
|
||||
class DynamicMFAChoiceField(serializers.MultipleChoiceField):
|
||||
def __init__(self, **kwargs):
|
||||
_choices = self._get_dynamic_choices()
|
||||
super().__init__(choices=_choices, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _get_dynamic_choices():
|
||||
choices = []
|
||||
mfa_dir = os.path.join(settings.APPS_DIR, 'authentication', 'mfa')
|
||||
for filename in os.listdir(mfa_dir):
|
||||
if not filename.endswith('.py') or filename.startswith('__init__'):
|
||||
continue
|
||||
|
||||
module_name = f'authentication.mfa.{filename[:-3]}'
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
for attr_name in dir(module):
|
||||
item = getattr(module, attr_name)
|
||||
if not isinstance(item, type) or not attr_name.startswith('MFA'):
|
||||
continue
|
||||
if 'BaseMFA' != item.__base__.__name__:
|
||||
continue
|
||||
choices.append((item.name, item.display_name))
|
||||
return choices
|
||||
|
||||
|
||||
class SecurityAuthSerializer(serializers.Serializer):
|
||||
SECURITY_MFA_AUTH = serializers.ChoiceField(
|
||||
choices=(
|
||||
@@ -163,10 +130,10 @@ class SecurityAuthSerializer(serializers.Serializer):
|
||||
required=False, default=True,
|
||||
label=_('Third-party login MFA'),
|
||||
)
|
||||
SECURITY_MFA_ENABLED_BACKENDS = DynamicMFAChoiceField(
|
||||
default=[], allow_empty=True,
|
||||
label=_('MFA Backends'),
|
||||
help_text=_('MFA methods supported for user login')
|
||||
SECURITY_MFA_BY_EMAIL = serializers.BooleanField(
|
||||
required=False, default=False,
|
||||
label=_('MFA via Email'),
|
||||
help_text=_('Email as a method for multi-factor authentication')
|
||||
)
|
||||
OTP_ISSUER_NAME = serializers.CharField(
|
||||
required=False, max_length=16, label=_('OTP issuer name'),
|
||||
|
||||
@@ -22,13 +22,13 @@ p {
|
||||
{% trans 'JumpServerClient, currently used to launch the client' %}
|
||||
</p>
|
||||
<ul>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64_en-US.msi">JumpServerClient_{{ CLIENT_VERSION }}_x64_en-US.msi</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64-setup.exe">JumpServerClient_{{ CLIENT_VERSION }}_x64-setup.exe</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_aarch64.dmg">JumpServerClient_{{ CLIENT_VERSION }}_aarch64.dmg</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64.dmg">JumpServerClient_{{ CLIENT_VERSION }}_x64.dmg</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_amd64.AppImage">JumpServerClient_{{ CLIENT_VERSION }}_amd64.AppImage</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_amd64.deb">JumpServerClient_{{ CLIENT_VERSION }}_amd64.deb</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient-{{ CLIENT_VERSION }}-1.x86_64.rpm">JumpServerClient-{{ CLIENT_VERSION }}-1.x86_64.rpm</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64_en-US.msi">JumpServerClient-x64_en-US.msi</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64-setup.exe">JumpServerClient-x64-setup.exe</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_aarch64.dmg">JumpServerClient-aarch64.dmg</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_x64.dmg">JumpServerClient-x64.dmg</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_amd64.AppImage">JumpServerClient-amd64.AppImage</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient_{{ CLIENT_VERSION }}_amd64.deb">JumpServerClient-amd64.deb</a></li>
|
||||
<li> <a href="/download/public/JumpServerClient-{{ CLIENT_VERSION }}-1.x86_64.rpm">JumpServerClient-1.x86_64.rpm</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
|
||||
from common.plugins.es import ES
|
||||
from datetime import datetime
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.plugins.es import ES
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -26,8 +27,8 @@ class CommandStore(ES):
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
exact_fields = {'risk_level'}
|
||||
fuzzy_fields = {'input', 'user', 'asset', 'account'}
|
||||
exact_fields = {}
|
||||
fuzzy_fields = {'input', 'risk_level', 'user', 'asset', 'account'}
|
||||
match_fields = {'input'}
|
||||
keyword_fields = {'session', 'org_id'}
|
||||
|
||||
|
||||
@@ -160,8 +160,7 @@ class SessionSerializer(BulkOrgResourceModelSerializer):
|
||||
)
|
||||
|
||||
validated_data['user'] = str(user)
|
||||
# web 资产 url 太长,超出限制
|
||||
validated_data['asset'] = str(asset)[:128]
|
||||
validated_data['asset'] = str(asset)
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
||||
@@ -6,7 +6,6 @@ import os
|
||||
import re
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from urllib.parse import unquote
|
||||
|
||||
import pyotp
|
||||
from django.conf import settings
|
||||
@@ -61,8 +60,6 @@ def redirect_user_first_login_or_index(request, redirect_field_name):
|
||||
# 防止 next 地址为 None
|
||||
if not url or url.lower() in ['none']:
|
||||
url = reverse('index')
|
||||
# 处理下载地址编码问题 '%2Fui%2F'
|
||||
url = unquote(url)
|
||||
return url
|
||||
|
||||
|
||||
|
||||
@@ -37,25 +37,12 @@ logger = get_logger(__name__)
|
||||
class UserOtpEnableStartView(AuthMixin, TemplateView):
|
||||
template_name = 'users/user_otp_check_password.html'
|
||||
|
||||
@staticmethod
|
||||
def get_redirect_url():
|
||||
message_data = {
|
||||
'title': _('Redirecting'),
|
||||
'message': _('No MFA services are available. Please contact the administrator'),
|
||||
'redirect_url': reverse('authentication:login'),
|
||||
'auto_redirect': True,
|
||||
}
|
||||
return FlashMessageUtil.gen_message_url(message_data)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
self.get_user_from_session()
|
||||
except SessionEmptyError:
|
||||
url = reverse('authentication:login') + '?_=otp_enable_start'
|
||||
return redirect(url)
|
||||
|
||||
if not MFAOtp.global_enabled():
|
||||
return redirect(self.get_redirect_url())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user