mirror of https://github.com/jumpserver/jumpserver
commit
4642804077
|
@ -126,9 +126,12 @@ class CommandExecutionViewSet(ListModelMixin, OrgGenericViewSet):
|
||||||
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
|
class CommandExecutionHostRelationViewSet(OrgRelationMixin, OrgBulkModelViewSet):
|
||||||
serializer_class = CommandExecutionHostsRelationSerializer
|
serializer_class = CommandExecutionHostsRelationSerializer
|
||||||
m2m_field = CommandExecution.hosts.field
|
m2m_field = CommandExecution.hosts.field
|
||||||
filterset_fields = [
|
filterset_fields = {
|
||||||
'id', 'asset', 'commandexecution'
|
'id': ['exact'],
|
||||||
]
|
'asset': ['exact'],
|
||||||
|
'asset__hostname': ['icontains'],
|
||||||
|
'commandexecution': ['exact'],
|
||||||
|
}
|
||||||
search_fields = ('asset__hostname', )
|
search_fields = ('asset__hostname', )
|
||||||
http_method_names = ['options', 'get']
|
http_method_names = ['options', 'get']
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
|
|
|
@ -22,7 +22,10 @@ class TicketStatusApi(mixins.AuthMixin, APIView):
|
||||||
self.request.session['auth_third_party_done'] = 1
|
self.request.session['auth_third_party_done'] = 1
|
||||||
return Response({"msg": "ok"})
|
return Response({"msg": "ok"})
|
||||||
except errors.LoginConfirmOtherError as e:
|
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)
|
auth_logout(request)
|
||||||
return Response(e.as_data(), status=200)
|
return Response(e.as_data(), status=200)
|
||||||
except errors.NeedMoreInfoError as e:
|
except errors.NeedMoreInfoError as e:
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
import django_cas_ng.views
|
import django_cas_ng.views
|
||||||
|
|
||||||
|
from .views import CASLoginView
|
||||||
|
|
||||||
urlpatterns = [
|
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('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'),
|
||||||
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
|
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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('/')
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,8 @@ class BlockGlobalIpLoginError(AuthFailedError):
|
||||||
error = 'block_global_ip_login'
|
error = 'block_global_ip_login'
|
||||||
|
|
||||||
def __init__(self, username, ip, **kwargs):
|
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()
|
LoginIpBlockUtil(ip).set_block_if_need()
|
||||||
super().__init__(username=username, ip=ip, **kwargs)
|
super().__init__(username=username, ip=ip, **kwargs)
|
||||||
|
|
||||||
|
@ -66,22 +67,21 @@ class CredentialError(
|
||||||
BlockGlobalIpLoginError, AuthFailedError
|
BlockGlobalIpLoginError, AuthFailedError
|
||||||
):
|
):
|
||||||
def __init__(self, error, username, ip, request):
|
def __init__(self, error, username, ip, request):
|
||||||
super().__init__(error=error, username=username, ip=ip, request=request)
|
|
||||||
util = LoginBlockUtil(username, ip)
|
util = LoginBlockUtil(username, ip)
|
||||||
times_remainder = util.get_remainder_times()
|
times_remainder = util.get_remainder_times()
|
||||||
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
block_time = settings.SECURITY_LOGIN_LIMIT_TIME
|
||||||
|
|
||||||
if times_remainder < 1:
|
if times_remainder < 1:
|
||||||
self.msg = const.block_user_login_msg.format(settings.SECURITY_LOGIN_LIMIT_TIME)
|
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:
|
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):
|
class MFAFailedError(AuthFailedNeedLogMixin, AuthFailedError):
|
||||||
|
|
|
@ -69,10 +69,16 @@ class LoginConfirmWaitError(LoginConfirmBaseError):
|
||||||
class LoginConfirmOtherError(LoginConfirmBaseError):
|
class LoginConfirmOtherError(LoginConfirmBaseError):
|
||||||
error = 'login_confirm_error'
|
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)
|
msg = const.login_confirm_error_msg.format(status)
|
||||||
super().__init__(ticket_id=ticket_id, msg=msg)
|
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):
|
class PasswordTooSimple(NeedRedirectError):
|
||||||
default_code = 'passwd_too_simple'
|
default_code = 'passwd_too_simple'
|
||||||
|
|
|
@ -377,7 +377,10 @@ class AuthACLMixin:
|
||||||
raise errors.LoginConfirmWaitError(ticket.id)
|
raise errors.LoginConfirmWaitError(ticket.id)
|
||||||
else:
|
else:
|
||||||
# rejected, closed
|
# 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):
|
def get_ticket(self):
|
||||||
from tickets.models import ApplyLoginTicket
|
from tickets.models import ApplyLoginTicket
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import ipaddress
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from django.conf import settings
|
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 validate_ip, get_ip_city, get_request_ip
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -23,8 +25,9 @@ def check_different_city_login_if_need(user, request):
|
||||||
else:
|
else:
|
||||||
city = get_ip_city(ip) or DEFAULT_CITY
|
city = get_ip_city(ip) or DEFAULT_CITY
|
||||||
|
|
||||||
city_white = ['LAN', ]
|
city_white = [_('LAN'), 'LAN']
|
||||||
if city not in city_white:
|
is_private = ipaddress.ip_address(ip).is_private
|
||||||
|
if not is_private:
|
||||||
last_user_login = UserLoginLog.objects.exclude(city__in=city_white) \
|
last_user_login = UserLoginLog.objects.exclude(city__in=city_white) \
|
||||||
.filter(username=user.username, status=True).first()
|
.filter(username=user.username, status=True).first()
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class SimpleMetadataWithFilters(SimpleMetadata):
|
||||||
|
|
||||||
default = getattr(field, 'default', None)
|
default = getattr(field, 'default', None)
|
||||||
if default is not None and default != empty:
|
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
|
field_info['default'] = default
|
||||||
|
|
||||||
for attr in self.attrs:
|
for attr in self.attrs:
|
||||||
|
|
|
@ -40,12 +40,11 @@ class OrgManager(models.Manager):
|
||||||
set_current_org(org)
|
set_current_org(org)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
|
def bulk_create(self, objs, batch_size=None, ignore_conflicts=False):
|
||||||
org = get_current_org()
|
org = get_current_org()
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if org.is_root():
|
if org.is_root():
|
||||||
if not self.org_id:
|
if not obj.org_id:
|
||||||
raise ValidationError('Please save in a organization')
|
raise ValidationError('Please save in a organization')
|
||||||
else:
|
else:
|
||||||
obj.org_id = org.id
|
obj.org_id = org.id
|
||||||
|
|
|
@ -17,39 +17,39 @@ class SettingImageField(serializers.ImageField):
|
||||||
|
|
||||||
class OAuth2SettingSerializer(serializers.Serializer):
|
class OAuth2SettingSerializer(serializers.Serializer):
|
||||||
AUTH_OAUTH2 = serializers.BooleanField(
|
AUTH_OAUTH2 = serializers.BooleanField(
|
||||||
default=False, required=False, label=_('Enable OAuth2 Auth')
|
default=False, label=_('Enable OAuth2 Auth')
|
||||||
)
|
)
|
||||||
AUTH_OAUTH2_LOGO_PATH = SettingImageField(
|
AUTH_OAUTH2_LOGO_PATH = SettingImageField(
|
||||||
allow_null=True, required=False, label=_('Logo')
|
allow_null=True, required=False, label=_('Logo')
|
||||||
)
|
)
|
||||||
AUTH_OAUTH2_PROVIDER = serializers.CharField(
|
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(
|
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(
|
AUTH_OAUTH2_CLIENT_SECRET = EncryptedField(
|
||||||
required=False, max_length=1024, label=_('Client Secret')
|
required=False, max_length=1024, label=_('Client Secret')
|
||||||
)
|
)
|
||||||
AUTH_OAUTH2_SCOPE = serializers.CharField(
|
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(
|
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(
|
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(
|
AUTH_OAUTH2_ACCESS_TOKEN_METHOD = serializers.ChoiceField(
|
||||||
default='GET', label=_('Client authentication method'),
|
default='GET', label=_('Client authentication method'),
|
||||||
choices=(('GET', 'GET'), ('POST', 'POST'))
|
choices=(('GET', 'GET'), ('POST', 'POST'))
|
||||||
)
|
)
|
||||||
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
|
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(
|
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(
|
AUTH_OAUTH2_ALWAYS_UPDATE_USER = serializers.BooleanField(
|
||||||
required=False, label=_('Always update user')
|
default=True, label=_('Always update user')
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.drf.fields import EncryptedField
|
from common.drf.fields import EncryptedField
|
||||||
|
from common.validators import PhoneValidator
|
||||||
from common.sdk.sms import BACKENDS
|
from common.sdk.sms import BACKENDS
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -23,7 +24,10 @@ class SignTmplPairSerializer(serializers.Serializer):
|
||||||
|
|
||||||
|
|
||||||
class BaseSMSSettingSerializer(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):
|
def to_representation(self, instance):
|
||||||
data = super().to_representation(instance)
|
data = super().to_representation(instance)
|
||||||
|
|
|
@ -87,7 +87,7 @@ class BaseTicketMessage(UserMessage):
|
||||||
@property
|
@property
|
||||||
def spec_items(self):
|
def spec_items(self):
|
||||||
fields = self.ticket._meta.local_fields + self.ticket._meta.local_many_to_many
|
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]
|
item_names = [field.name for field in fields if field.name not in excludes]
|
||||||
return self._get_fields_items(item_names)
|
return self._get_fields_items(item_names)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<p> {{ approve_info }}</p>
|
<p> {{ approve_info }}</p>
|
||||||
|
{% if content %}
|
||||||
<br>
|
<br>
|
||||||
<div style="width:100%; overflow-x:scroll;">
|
<div style="width:100%; overflow-x:scroll;">
|
||||||
<table style="width:1000px; text-align:left">
|
<table style="width:1000px; text-align:left">
|
||||||
|
@ -20,5 +21,6 @@
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue