mirror of https://github.com/jumpserver/jumpserver
[Update] 添加解除用户登录限制功能
parent
1182313c1a
commit
812078331e
Binary file not shown.
|
@ -2774,11 +2774,11 @@ msgstr "发送重置密钥邮件"
|
||||||
#: users/templates/users/user_detail.html:186
|
#: users/templates/users/user_detail.html:186
|
||||||
#: users/templates/users/user_detail.html:444
|
#: users/templates/users/user_detail.html:444
|
||||||
msgid "Unblock user"
|
msgid "Unblock user"
|
||||||
msgstr "解锁用户"
|
msgstr "解除登录限制"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:189
|
#: users/templates/users/user_detail.html:189
|
||||||
msgid "Unblock"
|
msgid "Unblock"
|
||||||
msgstr "解锁"
|
msgstr "解除"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:303
|
#: users/templates/users/user_detail.html:303
|
||||||
msgid "Goto profile page enable MFA"
|
msgid "Goto profile page enable MFA"
|
||||||
|
@ -2820,7 +2820,7 @@ msgstr "ssh密钥"
|
||||||
|
|
||||||
#: users/templates/users/user_detail.html:454
|
#: users/templates/users/user_detail.html:454
|
||||||
msgid "After unlocking the user, the user can log in normally."
|
msgid "After unlocking the user, the user can log in normally."
|
||||||
msgstr "解锁用户后,此用户即可正常登录"
|
msgstr "解除用户登录限制后,此用户即可正常登录"
|
||||||
|
|
||||||
#: users/templates/users/user_group_create_update.html:31
|
#: users/templates/users/user_group_create_update.html:31
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
|
@ -3181,7 +3181,7 @@ msgid "MFA disable success, return login page"
|
||||||
msgstr "MFA 解绑成功,返回登录页面"
|
msgstr "MFA 解绑成功,返回登录页面"
|
||||||
|
|
||||||
#~ msgid "Unblock user successfully. "
|
#~ msgid "Unblock user successfully. "
|
||||||
#~ msgstr "解锁用户成功"
|
#~ msgstr "解除登录限制成功"
|
||||||
|
|
||||||
#~ msgid "Clear"
|
#~ msgid "Clear"
|
||||||
#~ msgstr "清除"
|
#~ msgstr "清除"
|
||||||
|
|
|
@ -100,12 +100,15 @@ class UserUnblockPKApi(generics.UpdateAPIView):
|
||||||
permission_classes = (IsSuperUser,)
|
permission_classes = (IsSuperUser,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
||||||
|
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
def perform_update(self, serializer):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
username = user.username if user else ''
|
username = user.username if user else ''
|
||||||
key_limit = self.key_prefix_limit.format(username, '*')
|
key_limit = self.key_prefix_limit.format(username, '*')
|
||||||
|
key_block = self.key_prefix_block.format(username)
|
||||||
cache.delete_pattern(key_limit)
|
cache.delete_pattern(key_limit)
|
||||||
|
cache.delete(key_block)
|
||||||
|
|
||||||
|
|
||||||
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
class UserGroupViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||||
|
@ -210,6 +213,7 @@ class UserAuthApi(APIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
||||||
|
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
# limit login
|
# limit login
|
||||||
|
@ -217,6 +221,7 @@ class UserAuthApi(APIView):
|
||||||
ip = request.data.get('remote_addr', None)
|
ip = request.data.get('remote_addr', None)
|
||||||
ip = ip if ip else get_login_ip(request)
|
ip = ip if ip else get_login_ip(request)
|
||||||
key_limit = self.key_prefix_limit.format(username, ip)
|
key_limit = self.key_prefix_limit.format(username, ip)
|
||||||
|
key_block = self.key_prefix_block.format(username)
|
||||||
if is_block_login(key_limit):
|
if is_block_login(key_limit):
|
||||||
msg = _("Log in frequently and try again later")
|
msg = _("Log in frequently and try again later")
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
@ -231,7 +236,7 @@ class UserAuthApi(APIView):
|
||||||
}
|
}
|
||||||
self.write_login_log(request, data)
|
self.write_login_log(request, data)
|
||||||
|
|
||||||
set_user_login_failed_count_to_cache(key_limit)
|
set_user_login_failed_count_to_cache(key_limit, key_block)
|
||||||
return Response({'msg': msg}, status=401)
|
return Response({'msg': msg}, status=401)
|
||||||
|
|
||||||
if not user.otp_enabled:
|
if not user.otp_enabled:
|
||||||
|
|
|
@ -182,7 +182,7 @@
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr id="id_unblock_user" style="{% if not unblock %}display:none{% endif %}">
|
||||||
<td>{% trans 'Unblock user' %}</td>
|
<td>{% trans 'Unblock user' %}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pull-right">
|
<span class="pull-right">
|
||||||
|
@ -283,7 +283,7 @@ $(document).ready(function() {
|
||||||
.on('select2:unselect', function(evt) {
|
.on('select2:unselect', function(evt) {
|
||||||
var data = evt.params.data;
|
var data = evt.params.data;
|
||||||
delete jumpserver.nodes_selected[data.id];
|
delete jumpserver.nodes_selected[data.id];
|
||||||
})
|
});
|
||||||
})
|
})
|
||||||
.on('click', '#is_active', function() {
|
.on('click', '#is_active', function() {
|
||||||
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
||||||
|
@ -301,7 +301,7 @@ $(document).ready(function() {
|
||||||
.on('click', '#force_enable_otp', function() {
|
.on('click', '#force_enable_otp', function() {
|
||||||
{% if request.user == user_object %}
|
{% if request.user == user_object %}
|
||||||
toastr.error("{% trans 'Goto profile page enable MFA' %}");
|
toastr.error("{% trans 'Goto profile page enable MFA' %}");
|
||||||
return
|
return;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
var the_url = "{% url 'api-users:user-detail' pk=user_object.id %}";
|
||||||
|
@ -441,7 +441,15 @@ $(document).ready(function() {
|
||||||
var body = {};
|
var body = {};
|
||||||
var success = function() {
|
var success = function() {
|
||||||
var msg = "{% trans "Success" %}";
|
var msg = "{% trans "Success" %}";
|
||||||
swal("{% trans 'Unblock user' %}", msg, "success");
|
{#swal("{% trans 'Unblock user' %}", msg, "success");#}
|
||||||
|
swal({
|
||||||
|
title: "{% trans 'Unblock user' %}",
|
||||||
|
text: msg,
|
||||||
|
type: "success"
|
||||||
|
}, function() {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
APIUpdateAttr({
|
APIUpdateAttr({
|
||||||
url: the_url,
|
url: the_url,
|
||||||
|
@ -460,7 +468,6 @@ $(document).ready(function() {
|
||||||
}, function() {
|
}, function() {
|
||||||
doReset();
|
doReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -333,7 +333,7 @@ def check_password_rules(password):
|
||||||
return bool(match_obj)
|
return bool(match_obj)
|
||||||
|
|
||||||
|
|
||||||
def set_user_login_failed_count_to_cache(key_limit):
|
def set_user_login_failed_count_to_cache(key_limit, key_block):
|
||||||
count = cache.get(key_limit)
|
count = cache.get(key_limit)
|
||||||
count = count + 1 if count else 1
|
count = count + 1 if count else 1
|
||||||
|
|
||||||
|
@ -343,6 +343,15 @@ def set_user_login_failed_count_to_cache(key_limit):
|
||||||
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
|
limit_time = setting_limit_time.cleaned_value if setting_limit_time \
|
||||||
else settings.DEFAULT_LOGIN_LIMIT_TIME
|
else settings.DEFAULT_LOGIN_LIMIT_TIME
|
||||||
|
|
||||||
|
setting_limit_count = Setting.objects.filter(
|
||||||
|
name='SECURITY_LOGIN_LIMIT_COUNT'
|
||||||
|
).first()
|
||||||
|
limit_count = setting_limit_count.cleaned_value if setting_limit_count \
|
||||||
|
else settings.DEFAULT_LOGIN_LIMIT_COUNT
|
||||||
|
|
||||||
|
if count >= limit_count:
|
||||||
|
cache.set(key_block, 1, int(limit_time)*60)
|
||||||
|
|
||||||
cache.set(key_limit, count, int(limit_time)*60)
|
cache.set(key_limit, count, int(limit_time)*60)
|
||||||
|
|
||||||
|
|
||||||
|
@ -357,3 +366,9 @@ def is_block_login(key_limit):
|
||||||
|
|
||||||
if count and count >= limit_count:
|
if count and count >= limit_count:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_need_unblock(key_block):
|
||||||
|
if not cache.get(key_block):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
|
@ -51,6 +51,7 @@ class UserLoginView(FormView):
|
||||||
redirect_field_name = 'next'
|
redirect_field_name = 'next'
|
||||||
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
key_prefix_captcha = "_LOGIN_INVALID_{}"
|
||||||
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
key_prefix_limit = "_LOGIN_LIMIT_{}_{}"
|
||||||
|
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if request.user.is_staff:
|
if request.user.is_staff:
|
||||||
|
@ -91,7 +92,8 @@ class UserLoginView(FormView):
|
||||||
# limit user login failed count
|
# limit user login failed count
|
||||||
ip = get_login_ip(self.request)
|
ip = get_login_ip(self.request)
|
||||||
key_limit = self.key_prefix_limit.format(username, ip)
|
key_limit = self.key_prefix_limit.format(username, ip)
|
||||||
set_user_login_failed_count_to_cache(key_limit)
|
key_block = self.key_prefix_block.format(username)
|
||||||
|
set_user_login_failed_count_to_cache(key_limit, key_block)
|
||||||
|
|
||||||
# show captcha
|
# show captcha
|
||||||
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
cache.set(self.key_prefix_captcha.format(ip), 1, 3600)
|
||||||
|
|
|
@ -36,7 +36,9 @@ from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
|
||||||
from common.models import Setting
|
from common.models import Setting
|
||||||
from .. import forms
|
from .. import forms
|
||||||
from ..models import User, UserGroup
|
from ..models import User, UserGroup
|
||||||
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, get_user_or_tmp_user, get_password_check_rules, check_password_rules
|
from ..utils import AdminUserRequiredMixin, generate_otp_uri, check_otp_code, \
|
||||||
|
get_user_or_tmp_user, get_password_check_rules, check_password_rules, \
|
||||||
|
is_need_unblock
|
||||||
from ..signals import post_user_create
|
from ..signals import post_user_create
|
||||||
from ..tasks import write_login_log_async
|
from ..tasks import write_login_log_async
|
||||||
|
|
||||||
|
@ -168,13 +170,17 @@ class UserDetailView(AdminUserRequiredMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'users/user_detail.html'
|
template_name = 'users/user_detail.html'
|
||||||
context_object_name = "user_object"
|
context_object_name = "user_object"
|
||||||
|
key_prefix_block = "_LOGIN_BLOCK_{}"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
user = self.get_object()
|
||||||
|
key_block = self.key_prefix_block.format(user.username)
|
||||||
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
|
groups = UserGroup.objects.exclude(id__in=self.object.groups.all())
|
||||||
context = {
|
context = {
|
||||||
'app': _('Users'),
|
'app': _('Users'),
|
||||||
'action': _('User detail'),
|
'action': _('User detail'),
|
||||||
'groups': groups
|
'groups': groups,
|
||||||
|
'unblock': is_need_unblock(key_block),
|
||||||
}
|
}
|
||||||
kwargs.update(context)
|
kwargs.update(context)
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
Loading…
Reference in New Issue