Merge pull request #8756 from jumpserver/dev

v2.25.0-rc3
pull/8778/head
Jiangjie.Bai 2022-08-16 19:07:42 +08:00 committed by GitHub
commit 4642804077
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 73 additions and 34 deletions

View File

@ -126,9 +126,12 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
serializer_class = CommandExecutionHostsRelationSerializer
m2m_field = CommandExecution.hosts.field
filterset_fields = [
'id', 'asset', 'commandexecution'
]
filterset_fields = {
'id': ['exact'],
'asset': ['exact'],
'asset__hostname': ['icontains'],
'commandexecution': ['exact'],
}
search_fields = ('asset__hostname', )
http_method_names = ['options', 'get']
rbac_perms = {

View File

@ -22,7 +22,10 @@ class TicketStatusApi(mixins.AuthMixin, APIView):
self.request.session['auth_third_party_done'] = 1
return Response({"msg": "ok"})
except errors.LoginConfirmOtherError as e:
self.send_auth_signal(success=False, user=request.user, username=request.user.name, reason=e.as_data().get('msg'))
reason = e.msg
username = e.username
self.send_auth_signal(success=False, username=username, reason=reason)
# 若为三方登录,此时应退出登录
auth_logout(request)
return Response(e.as_data(), status=200)
except errors.NeedMoreInfoError as e:

View File

@ -3,9 +3,10 @@
from django.urls import path
import django_cas_ng.views
from .views import CASLoginView
urlpatterns = [
path('login/', django_cas_ng.views.LoginView.as_view(), name='cas-login'),
path('login/', CASLoginView.as_view(), name='cas-login'),
path('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'),
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
]

View File

@ -0,0 +1,15 @@
from django_cas_ng.views import LoginView
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
__all__ = ['LoginView']
class CASLoginView(LoginView):
def get(self, request):
try:
return super().get(request)
except PermissionDenied:
return HttpResponseRedirect('/')

View File

@ -56,7 +56,8 @@ class BlockGlobalIpLoginError(AuthFailedError):
error = 'block_global_ip_login'
def __init__(self, username, ip, **kwargs):
self.msg = const.block_ip_login_msg.format(settings.SECURITY_LOGIN_IP_LIMIT_TIME)
if not self.msg:
self.msg = const.block_ip_login_msg.format(settings.SECURITY_LOGIN_IP_LIMIT_TIME)
LoginIpBlockUtil(ip).set_block_if_need()
super().__init__(username=username, ip=ip, **kwargs)
@ -66,22 +67,21 @@ class CredentialError(
BlockGlobalIpLoginError, AuthFailedError
):
def __init__(self, error, username, ip, request):
super().__init__(error=error, username=username, ip=ip, request=request)
util = LoginBlockUtil(username, ip)
times_remainder = util.get_remainder_times()
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
if times_remainder < 1:
self.msg = const.block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
return
default_msg = const.invalid_login_msg.format(
times_try=times_remainder, block_time=block_time
)
if error == const.reason_password_failed:
self.msg = default_msg
else:
self.msg = const.reason_choices.get(error, default_msg)
default_msg = const.invalid_login_msg.format(
times_try=times_remainder, block_time=block_time
)
if error == const.reason_password_failed:
self.msg = default_msg
else:
self.msg = const.reason_choices.get(error, default_msg)
# 先处理 msg 在 super记录日志时原因才准确
super().__init__(error=error, username=username, ip=ip, request=request)
class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):

View File

@ -69,10 +69,16 @@ class LoginConfirmWaitError(LoginConfirmBaseError):
class LoginConfirmOtherError(LoginConfirmBaseError):
error = 'login_confirm_error'
def __init__(self, ticket_id, status):
def __init__(self, ticket_id, status, username):
self.username = username
msg = const.login_confirm_error_msg.format(status)
super().__init__(ticket_id=ticket_id, msg=msg)
def as_data(self):
ret = super().as_data()
ret['data']['username'] = self.username
return ret
class PasswordTooSimple(NeedRedirectError):
default_code = 'passwd_too_simple'

View File

@ -377,7 +377,10 @@ class AuthACLMixin:
raise errors.LoginConfirmWaitError(ticket.id)
else:
# rejected, closed
raise errors.LoginConfirmOtherError(ticket.id, ticket.get_state_display())
ticket_id = ticket.id
status = ticket.get_state_display()
username = ticket.applicant.username
raise errors.LoginConfirmOtherError(ticket_id, status, username)
def get_ticket(self):
from tickets.models import ApplyLoginTicket

View File

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
#
import ipaddress
from urllib.parse import urljoin
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from common.utils import validate_ip, get_ip_city, get_request_ip
from common.utils import get_logger
@ -23,8 +25,9 @@ def check_different_city_login_if_need(user, request):
else:
city = get_ip_city(ip) or DEFAULT_CITY
city_white = ['LAN', ]
if city not in city_white:
city_white = [_('LAN'), 'LAN']
is_private = ipaddress.ip_address(ip).is_private
if not is_private:
last_user_login = UserLoginLog.objects.exclude(city__in=city_white) \
.filter(username=user.username, status=True).first()

View File

@ -67,7 +67,7 @@ class SimpleMetadataWithFilters(SimpleMetadata):
default = getattr(field, 'default', None)
if default is not None and default != empty:
if isinstance(default, (str, int, bool, datetime.datetime, list)):
if isinstance(default, (str, int, bool, float, datetime.datetime, list)):
field_info['default'] = default
for attr in self.attrs:

View File

@ -40,12 +40,11 @@ class OrgManager(models.Manager):
set_current_org(org)
return self
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
org = get_current_org()
for obj in objs:
if org.is_root():
if not self.org_id:
if not obj.org_id:
raise ValidationError('Please save in a organization')
else:
obj.org_id = org.id

View File

@ -17,39 +17,39 @@ class SettingImageField(serializers.ImageField):
class OAuth2SettingSerializer(serializers.Serializer):
AUTH_OAUTH2 = serializers.BooleanField(
default=False, required=False, label=_('Enable OAuth2 Auth')
default=False, label=_('Enable OAuth2 Auth')
)
AUTH_OAUTH2_LOGO_PATH = SettingImageField(
allow_null=True, required=False, label=_('Logo')
)
AUTH_OAUTH2_PROVIDER = serializers.CharField(
required=False, max_length=16, label=_('Service provider')
required=True, max_length=16, label=_('Service provider')
)
AUTH_OAUTH2_CLIENT_ID = serializers.CharField(
required=False, max_length=1024, label=_('Client Id')
required=True, max_length=1024, label=_('Client Id')
)
AUTH_OAUTH2_CLIENT_SECRET = EncryptedField(
required=False, max_length=1024, label=_('Client Secret')
)
AUTH_OAUTH2_SCOPE = serializers.CharField(
required=False, max_length=1024, label=_('Scope'), allow_blank=True
required=True, max_length=1024, label=_('Scope'), allow_blank=True
)
AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider auth endpoint')
required=True, max_length=1024, label=_('Provider auth endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider token endpoint')
required=True, max_length=1024, label=_('Provider token endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_METHOD = serializers.ChoiceField(
default='GET', label=_('Client authentication method'),
choices=(('GET', 'GET'), ('POST', 'POST'))
)
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider userinfo endpoint')
required=True, max_length=1024, label=_('Provider userinfo endpoint')
)
AUTH_OAUTH2_USER_ATTR_MAP = serializers.DictField(
required=False, label=_('User attr map')
required=True, label=_('User attr map')
)
AUTH_OAUTH2_ALWAYS_UPDATE_USER = serializers.BooleanField(
required=False, label=_('Always update user')
default=True, label=_('Always update user')
)

View File

@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.drf.fields import EncryptedField
from common.validators import PhoneValidator
from common.sdk.sms import BACKENDS
__all__ = [
@ -23,7 +24,10 @@ class SignTmplPairSerializer(serializers.Serializer):
class BaseSMSSettingSerializer(serializers.Serializer):
SMS_TEST_PHONE = serializers.CharField(max_length=256, required=False, allow_blank=True, label=_('Test phone'))
SMS_TEST_PHONE = serializers.CharField(
max_length=256, required=False, validators=[PhoneValidator(), ],
allow_blank=True, label=_('Test phone')
)
def to_representation(self, instance):
data = super().to_representation(instance)

View File

@ -87,7 +87,7 @@ class BaseTicketMessage(UserMessage):
@property
def spec_items(self):
fields = self.ticket._meta.local_fields + self.ticket._meta.local_many_to_many
excludes = ['ticket_ptr']
excludes = ['ticket_ptr', 'flow']
item_names = [field.name for field in fields if field.name not in excludes]
return self._get_fields_items(item_names)

View File

@ -2,6 +2,7 @@
<html lang="en">
<body>
<p> {{ approve_info }}</p>
{% if content %}
<br>
<div style="width:100%; overflow-x:scroll;">
<table style="width:1000px; text-align:left">
@ -20,5 +21,6 @@
</table>
</div>
{% endif %}
</body>
</html>