mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-07-02 07:01:30 +00:00
Merge branch 'osm' of github.com:jumpserver/jumpserver into osm
This commit is contained in:
@@ -51,7 +51,9 @@ class AssetFilterSet(BaseFilterSet):
|
||||
exclude_platform = drf_filters.CharFilter(field_name="platform__name", lookup_expr='exact', exclude=True)
|
||||
zone = drf_filters.CharFilter(method='filter_zone')
|
||||
type = drf_filters.CharFilter(field_name="platform__type", lookup_expr="exact")
|
||||
exclude_type = drf_filters.CharFilter(field_name="platform__type", lookup_expr="exact", exclude=True)
|
||||
category = drf_filters.CharFilter(field_name="platform__category", lookup_expr="exact")
|
||||
exclude_category = drf_filters.CharFilter(field_name="platform__category", lookup_expr="exact", exclude=True)
|
||||
protocols = drf_filters.CharFilter(method='filter_protocols')
|
||||
gateway_enabled = drf_filters.BooleanFilter(
|
||||
field_name="platform__gateway_enabled", lookup_expr="exact"
|
||||
|
||||
@@ -173,7 +173,7 @@ class RDPFileClientProtocolURLMixin:
|
||||
return name
|
||||
|
||||
def get_connect_filename(self, prefix_name):
|
||||
filename = f'{prefix_name}-jumpserver'
|
||||
filename = prefix_name
|
||||
filename = self.escape_name(filename)
|
||||
return filename
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
from common.utils import get_logger
|
||||
from users.models import User
|
||||
from authentication.signals import backend_auth_failed
|
||||
from authentication.errors import reason_choices, reason_user_invalid
|
||||
|
||||
UserModel = get_user_model()
|
||||
logger = get_logger(__file__)
|
||||
@@ -65,3 +67,14 @@ class JMSBaseAuthBackend:
|
||||
class JMSModelBackend(JMSBaseAuthBackend, ModelBackend):
|
||||
def user_can_authenticate(self, user):
|
||||
return True
|
||||
|
||||
|
||||
class RedirectAuthBackend(JMSBaseAuthBackend):
|
||||
backend = None
|
||||
|
||||
def send_backend_auth_failed_signal(self, request, username=None, reason=None):
|
||||
default_reason = reason_choices.get(reason_user_invalid, reason)
|
||||
backend_auth_failed.send(
|
||||
sender=self.__class__, username=username, request=request,
|
||||
reason=default_reason, backend=self.backend
|
||||
)
|
||||
|
||||
@@ -5,13 +5,15 @@ from django.conf import settings
|
||||
from django_cas_ng.backends import CASBackend as _CASBackend
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..base import JMSBaseAuthBackend
|
||||
from ..base import RedirectAuthBackend
|
||||
|
||||
__all__ = ['CASBackend']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CASBackend(JMSBaseAuthBackend, _CASBackend):
|
||||
class CASBackend(RedirectAuthBackend, _CASBackend):
|
||||
backend = settings.AUTH_BACKEND_CAS
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
return settings.AUTH_CAS
|
||||
@@ -19,4 +21,7 @@ class CASBackend(JMSBaseAuthBackend, _CASBackend):
|
||||
def authenticate(self, request, ticket, service):
|
||||
# 这里做个hack ,让父类始终走CAS_CREATE_USER=True的逻辑,然后调用 authentication/mixins.py 中的 custom_get_or_create 方法
|
||||
settings.CAS_CREATE_USER = True
|
||||
return super().authenticate(request, ticket, service)
|
||||
user = super().authenticate(request, ticket, service)
|
||||
if user is None:
|
||||
self.send_backend_auth_failed_signal(request=request)
|
||||
return user
|
||||
|
||||
@@ -3,7 +3,6 @@ from django.contrib.auth import get_user_model
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from common.utils import get_logger
|
||||
from .base import JMSBaseAuthBackend
|
||||
|
||||
@@ -48,16 +47,7 @@ class CustomAuthBackend(JMSBaseAuthBackend):
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.info(f'Custom authenticate success: {user.username}')
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.info(f'Custom authenticate failed: {user.username}')
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -12,13 +12,12 @@ from django.urls import reverse
|
||||
from common.utils import get_logger
|
||||
from users.utils import construct_user_email
|
||||
from authentication.utils import build_absolute_uri
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from common.exceptions import JMSException
|
||||
|
||||
from .signals import (
|
||||
oauth2_create_or_update_user
|
||||
)
|
||||
from ..base import JMSBaseAuthBackend
|
||||
from ..base import RedirectAuthBackend
|
||||
|
||||
|
||||
__all__ = ['OAuth2Backend']
|
||||
@@ -26,7 +25,9 @@ __all__ = ['OAuth2Backend']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class OAuth2Backend(JMSBaseAuthBackend):
|
||||
class OAuth2Backend(RedirectAuthBackend):
|
||||
backend = settings.AUTH_BACKEND_OAUTH2
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
return settings.AUTH_OAUTH2
|
||||
@@ -144,18 +145,9 @@ class OAuth2Backend(JMSBaseAuthBackend):
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OAuth2 user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login success'))
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OAuth2 user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
self.send_backend_auth_failed_signal(request=request, username=user.username)
|
||||
return None
|
||||
|
||||
@@ -16,7 +16,6 @@ from django.contrib.auth.backends import ModelBackend
|
||||
from django.db import transaction
|
||||
from django.urls import reverse
|
||||
|
||||
from authentication.signals import user_auth_success, user_auth_failed
|
||||
from authentication.utils import build_absolute_uri_for_oidc
|
||||
from common.utils import get_logger
|
||||
from users.utils import construct_user_email
|
||||
@@ -25,7 +24,7 @@ from .signals import (
|
||||
openid_create_or_update_user
|
||||
)
|
||||
from .utils import validate_and_return_id_token
|
||||
from ..base import JMSBaseAuthBackend
|
||||
from ..base import RedirectAuthBackend, JMSBaseAuthBackend
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
@@ -66,14 +65,14 @@ class UserMixin:
|
||||
return user, created
|
||||
|
||||
|
||||
class OIDCBaseBackend(UserMixin, JMSBaseAuthBackend, ModelBackend):
|
||||
class OIDCBaseBackend(UserMixin, ModelBackend):
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
return settings.AUTH_OPENID
|
||||
|
||||
|
||||
class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
class OIDCAuthCodeBackend(RedirectAuthBackend, OIDCBaseBackend):
|
||||
""" Allows to authenticate users using an OpenID Connect Provider (OP).
|
||||
|
||||
This authentication backend is able to authenticate users in the case of the OpenID Connect
|
||||
@@ -84,6 +83,8 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
|
||||
"""
|
||||
|
||||
backend = settings.AUTH_BACKEND_OIDC_CODE
|
||||
|
||||
@ssl_verification
|
||||
def authenticate(self, request, nonce=None, code_verifier=None):
|
||||
""" Authenticates users in case of the OpenID Connect Authorization code flow. """
|
||||
@@ -212,23 +213,15 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason="User is invalid", backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
)
|
||||
self.send_backend_auth_failed_signal(request=request, username=user.username)
|
||||
return None
|
||||
|
||||
|
||||
class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
||||
class OIDCAuthPasswordBackend(JMSBaseAuthBackend, OIDCBaseBackend):
|
||||
|
||||
@ssl_verification
|
||||
def authenticate(self, request, username=None, password=None):
|
||||
@@ -274,11 +267,6 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
||||
error = "Json token response error, token response " \
|
||||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
# Retrieves the access token
|
||||
@@ -303,11 +291,6 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
||||
error = "Json claims response error, claims response " \
|
||||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
logger.debug(log_prompt.format('Get or create user from claims'))
|
||||
@@ -317,17 +300,7 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason="User is invalid",
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -5,19 +5,19 @@ from django.conf import settings
|
||||
from django.db import transaction
|
||||
|
||||
from common.utils import get_logger
|
||||
from authentication.errors import reason_choices, reason_user_invalid
|
||||
from .signals import (
|
||||
saml2_create_or_update_user
|
||||
)
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from ..base import JMSBaseAuthBackend
|
||||
from ..base import RedirectAuthBackend
|
||||
|
||||
__all__ = ['SAML2Backend']
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SAML2Backend(JMSBaseAuthBackend):
|
||||
class SAML2Backend(RedirectAuthBackend):
|
||||
backend = settings.AUTH_BACKEND_SAML2
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
return settings.AUTH_SAML2
|
||||
@@ -59,16 +59,8 @@ class SAML2Backend(JMSBaseAuthBackend):
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('SAML2 user login success'))
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user, created=created,
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('SAML2 user login failed'))
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username,
|
||||
reason=reason_choices.get(reason_user_invalid),
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
self.send_backend_auth_failed_signal(request=request, username=user.username)
|
||||
return None
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from apps.authentication import mixins
|
||||
from audits.signal_handlers import send_login_info_to_reviewers
|
||||
from authentication.signals import post_auth_failed
|
||||
from authentication.signals import post_auth_failed, post_auth_success
|
||||
from common.utils import gen_key_pair, gen_gm_key_pair
|
||||
from common.utils import get_request_ip
|
||||
|
||||
@@ -101,6 +101,9 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
|
||||
response = render(request, 'authentication/auth_fail_flash_message_standalone.html', context)
|
||||
else:
|
||||
if not self.request.session.get('auth_confirm_required'):
|
||||
post_auth_success.send(
|
||||
sender=self.__class__, user=request.user, request=self.request
|
||||
)
|
||||
return response
|
||||
guard_url = reverse('authentication:login-guard')
|
||||
args = request.META.get('QUERY_STRING', '')
|
||||
|
||||
@@ -177,6 +177,9 @@ def authenticate(request=None, **credentials):
|
||||
if user is None:
|
||||
continue
|
||||
|
||||
if request:
|
||||
request.session['auth_backend'] = backend_path
|
||||
|
||||
if not user.is_valid:
|
||||
temp_user = user
|
||||
temp_user.backend = backend_path
|
||||
|
||||
@@ -7,7 +7,7 @@ from django_cas_ng.signals import cas_user_authenticated
|
||||
from apps.jumpserver.settings.auth import AUTHENTICATION_BACKENDS_THIRD_PARTY
|
||||
from audits.models import UserSession
|
||||
from common.sessions.cache import user_session_manager
|
||||
from .signals import post_auth_success, post_auth_failed, user_auth_failed, user_auth_success
|
||||
from .signals import post_auth_failed, backend_auth_failed
|
||||
|
||||
from .backends.oauth2_provider.signal_handlers import *
|
||||
|
||||
@@ -43,19 +43,7 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
|
||||
user.lang = lang
|
||||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def on_cas_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_CAS
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(user_auth_success)
|
||||
def on_user_login_success(sender, request, user, backend, create=False, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(user_auth_failed)
|
||||
@receiver(backend_auth_failed)
|
||||
def on_user_login_failed(sender, username, request, reason, backend, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
||||
@@ -3,5 +3,4 @@ from django.dispatch import Signal
|
||||
post_auth_success = Signal()
|
||||
post_auth_failed = Signal()
|
||||
|
||||
user_auth_success = Signal()
|
||||
user_auth_failed = Signal()
|
||||
backend_auth_failed = Signal()
|
||||
|
||||
@@ -10,10 +10,30 @@ class AsyncLocal:
|
||||
"""
|
||||
|
||||
def __init__(self, context_var_name: str = "_async_local_storage"):
|
||||
self._storage: contextvars.ContextVar[Dict[str, Any]] = contextvars.ContextVar(
|
||||
object.__setattr__(self, "_storage", contextvars.ContextVar(
|
||||
context_var_name,
|
||||
default={}
|
||||
)
|
||||
))
|
||||
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
if key.startswith("_"):
|
||||
object.__setattr__(self, key, value)
|
||||
return
|
||||
self.set(key, value)
|
||||
|
||||
def __getattr__(self, key: str) -> Any:
|
||||
value = self.get(key, default=None)
|
||||
if value is None:
|
||||
raise AttributeError(f"{self.__class__.__name__!s} has no attribute {key!r}")
|
||||
return value
|
||||
|
||||
def __delattr__(self, key: str) -> None:
|
||||
if key.startswith("_"):
|
||||
object.__delattr__(self, key)
|
||||
return
|
||||
if key not in self._storage.get():
|
||||
raise AttributeError(f"{self.__class__.__name__!s} has no attribute {key!r}")
|
||||
self.delete(key)
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
current_data = self._storage.get().copy()
|
||||
|
||||
@@ -1690,5 +1690,6 @@
|
||||
"ReportRecipientsTip": "Currently only supports email sending",
|
||||
"ReportSchedulePriorityTip": "If both interval and crontab are set, crontab takes priority",
|
||||
"FooterContentTooLong200": "Footer content is too long, please limit it to 200 characters",
|
||||
"ImageFileCorruptedOrUnreadable": "The image file is corrupted or unreadable, please check the file and try again"
|
||||
"ImageFileCorruptedOrUnreadable": "The image file is corrupted or unreadable, please check the file and try again",
|
||||
"DeviceManager": "Device manager"
|
||||
}
|
||||
|
||||
@@ -1698,5 +1698,6 @@
|
||||
"ReportRecipientsTip": "當前僅支持郵件發送",
|
||||
"ReportSchedulePriorityTip": "如果同時設置了 interval 和 crontab,則優先考慮 crontab",
|
||||
"FooterContentTooLong200": "頁腳內容過長,請限制在 200 字以內",
|
||||
"ImageFileCorruptedOrUnreadable": "影像檔案已損壞或無法讀取,請檢查檔案並重試。"
|
||||
"ImageFileCorruptedOrUnreadable": "影像檔案已損壞或無法讀取,請檢查檔案並重試。",
|
||||
"DeviceManager": "設備管理"
|
||||
}
|
||||
|
||||
@@ -78,11 +78,11 @@
|
||||
}
|
||||
|
||||
$('.input-style').each(function (i, ele) {
|
||||
$(ele).attr('name', 'code-test')
|
||||
$(ele).prop('disabled', true).removeAttr('name')
|
||||
})
|
||||
|
||||
const currentMFAInputRef = $('#mfa-' + name + ' .input-style')
|
||||
currentMFAInputRef.attr('name', 'code')
|
||||
currentMFAInputRef.prop('disabled', false).attr('name', 'code')
|
||||
|
||||
// 登录页时,不应该默认focus
|
||||
const usernameRef = $('input[name="username"]')
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
PYTHON_VERSION: 3.11.11
|
||||
CHROME_VERSION: 118.0.5993.118
|
||||
CHROME_DRIVER_VERSION: 118.0.5993.70
|
||||
TINKER_VERSION: v0.2.2
|
||||
TINKER_VERSION: v0.2.3
|
||||
|
||||
tasks:
|
||||
- block:
|
||||
|
||||
Reference in New Issue
Block a user