mirror of https://github.com/jumpserver/jumpserver
[Bugfix] 解决下载导入模版时KeyError的问题(数据为空时) (#3017)
* [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时) * [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时)2 * [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时)3 * [Update] 解决LDAP用户禁用后,终端还可以登录成功一次的问题 * [Update] 解决LDAP用户禁用后,终端还可以登录成功一次的问题2 * [Update] LDAP AD Server可以通过UserAccountControl映射is_active字段 * [Update] 限制只有local用户可以更新ssh key * [Update] 限制只有local用户可以更新ssh key 2pull/3024/head
parent
421e98696c
commit
c929c1a87e
|
@ -8,6 +8,7 @@ from django_auth_ldap.backend import _LDAPUser, LDAPBackend
|
|||
from django_auth_ldap.config import _LDAPConfig, LDAPSearch, LDAPSearchUnion
|
||||
|
||||
from users.utils import construct_user_email
|
||||
from common.const import LDAP_AD_ACCOUNT_DISABLE
|
||||
|
||||
logger = _LDAPConfig.get_logger()
|
||||
|
||||
|
@ -17,6 +18,15 @@ class LDAPAuthorizationBackend(LDAPBackend):
|
|||
Override this class to override _LDAPUser to LDAPUser
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def user_can_authenticate(user):
|
||||
"""
|
||||
Reject users with is_active=False. Custom user models that don't have
|
||||
that attribute are allowed.
|
||||
"""
|
||||
is_valid = getattr(user, 'is_valid', None)
|
||||
return is_valid or is_valid is None
|
||||
|
||||
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||
logger.info('Authentication LDAP backend')
|
||||
if not username:
|
||||
|
@ -25,34 +35,29 @@ class LDAPAuthorizationBackend(LDAPBackend):
|
|||
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||
user = self.authenticate_ldap_user(ldap_user, password)
|
||||
logger.info('Authenticate user: {}'.format(user))
|
||||
return user
|
||||
return user if self.user_can_authenticate(user) else None
|
||||
|
||||
def get_user(self, user_id):
|
||||
user = None
|
||||
|
||||
try:
|
||||
user = self.get_user_model().objects.get(pk=user_id)
|
||||
LDAPUser(self, user=user) # This sets user.ldap_user
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
return user
|
||||
|
||||
def get_group_permissions(self, user, obj=None):
|
||||
if not hasattr(user, 'ldap_user') and self.settings.AUTHORIZE_ALL_USERS:
|
||||
LDAPUser(self, user=user) # This sets user.ldap_user
|
||||
|
||||
if hasattr(user, 'ldap_user'):
|
||||
permissions = user.ldap_user.get_group_permissions()
|
||||
else:
|
||||
permissions = set()
|
||||
|
||||
return permissions
|
||||
|
||||
def populate_user(self, username):
|
||||
ldap_user = LDAPUser(self, username=username)
|
||||
user = ldap_user.populate_user()
|
||||
|
||||
return user
|
||||
|
||||
|
||||
|
@ -91,13 +96,19 @@ class LDAPUser(_LDAPUser):
|
|||
for field, attr in self.settings.USER_ATTR_MAP.items():
|
||||
try:
|
||||
value = self.attrs[attr][0]
|
||||
if attr.lower() == 'useraccountcontrol' \
|
||||
and field == 'is_active' and value:
|
||||
value = int(value) & LDAP_AD_ACCOUNT_DISABLE \
|
||||
!= LDAP_AD_ACCOUNT_DISABLE
|
||||
except LookupError:
|
||||
logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr))
|
||||
else:
|
||||
if not hasattr(self._user, field):
|
||||
continue
|
||||
if isinstance(getattr(self._user, field), bool):
|
||||
value = value.lower() in ['true', '1']
|
||||
if isinstance(value, str):
|
||||
value = value.lower()
|
||||
value = value in ['true', '1', True]
|
||||
setattr(self._user, field, value)
|
||||
|
||||
email = getattr(self._user, 'email', '')
|
||||
|
|
|
@ -26,8 +26,8 @@ class BaseOpenIDAuthorizationBackend(object):
|
|||
Reject users with is_active=False. Custom user models that don't have
|
||||
that attribute are allowed.
|
||||
"""
|
||||
is_active = getattr(user, 'is_active', None)
|
||||
return is_active or is_active is None
|
||||
is_valid = getattr(user, 'is_valid', None)
|
||||
return is_valid or is_valid is None
|
||||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
|
|
|
@ -8,3 +8,7 @@ update_success_msg = _("%(name)s was updated successfully")
|
|||
FILE_END_GUARD = ">>> Content End <<<"
|
||||
celery_task_pre_key = "CELERY_"
|
||||
KEY_CACHE_RESOURCES_ID = "RESOURCES_ID_{}"
|
||||
|
||||
# AD User AccountDisable
|
||||
# https://blog.csdn.net/bytxl/article/details/17763975
|
||||
LDAP_AD_ACCOUNT_DISABLE = 2
|
||||
|
|
|
@ -137,6 +137,16 @@ class PermissionsMixin(UserPassesTestMixin):
|
|||
return True
|
||||
|
||||
|
||||
class UserCanUpdatePassword:
|
||||
def has_permission(self, request, view):
|
||||
return request.user.can_update_password()
|
||||
|
||||
|
||||
class UserCanUpdateSSHKey:
|
||||
def has_permission(self, request, view):
|
||||
return request.user.can_update_ssh_key()
|
||||
|
||||
|
||||
class NeedMFAVerify(permissions.BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
mfa_verify_time = request.session.get('MFA_VERIFY_TIME', 0)
|
||||
|
|
|
@ -58,8 +58,8 @@ class JMSCSVRender(BaseRenderer):
|
|||
template = request.query_params.get('template', 'export')
|
||||
view = renderer_context['view']
|
||||
|
||||
if isinstance(data, dict) and data.get("count"):
|
||||
data = data["results"]
|
||||
if isinstance(data, dict):
|
||||
data = data.get("results", [])
|
||||
|
||||
if template == 'import':
|
||||
data = [data[0]] if data else data
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from users.models import User
|
||||
from users.utils import construct_user_email
|
||||
from common.utils import get_logger
|
||||
from common.const import LDAP_AD_ACCOUNT_DISABLE
|
||||
|
||||
from .models import settings
|
||||
|
||||
|
@ -70,7 +71,12 @@ class LDAPUtil:
|
|||
for attr, mapping in self.attr_map.items():
|
||||
if not hasattr(entry, mapping):
|
||||
continue
|
||||
user_item[attr] = getattr(entry, mapping).value or ''
|
||||
value = getattr(entry, mapping).value or ''
|
||||
if mapping.lower() == 'useraccountcontrol' and attr == 'is_active'\
|
||||
and value:
|
||||
value = int(value) & LDAP_AD_ACCOUNT_DISABLE \
|
||||
!= LDAP_AD_ACCOUNT_DISABLE
|
||||
user_item[attr] = value
|
||||
return user_item
|
||||
|
||||
def search_user_items(self):
|
||||
|
@ -102,7 +108,9 @@ class LDAPUtil:
|
|||
if not hasattr(user, field):
|
||||
continue
|
||||
if isinstance(getattr(user, field), bool):
|
||||
value = value.lower() in ['true', 1]
|
||||
if isinstance(value, str):
|
||||
value = value.lower()
|
||||
value = value in ['true', 1, True]
|
||||
setattr(user, field, value)
|
||||
user.save()
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ UserProfileForm.verbose_name = _("Profile")
|
|||
class UserMFAForm(forms.ModelForm):
|
||||
|
||||
mfa_description = _(
|
||||
'Tip: when enabled, '
|
||||
'When enabled, '
|
||||
'you will enter the MFA binding process the next time you log in. '
|
||||
'you can also directly bind in '
|
||||
'"personal information -> quick modification -> change MFA Settings"!')
|
||||
|
|
|
@ -54,6 +54,9 @@ class AuthMixin:
|
|||
def can_update_password(self):
|
||||
return self.is_local
|
||||
|
||||
def can_update_ssh_key(self):
|
||||
return self.is_local
|
||||
|
||||
def check_otp(self, code):
|
||||
from ..utils import check_otp_code
|
||||
return check_otp_code(self.otp_secret_key, code)
|
||||
|
|
|
@ -73,14 +73,17 @@
|
|||
<p id="noTerms" class="red-fonts" style="visibility: hidden; font-size: 10px; margin-top: 10px;">* {% trans 'Please choose the terms and conditions.' %}</p>
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_form wizard.form %}
|
||||
{% if wizard.steps.current == '1' and not request.user.can_update_ssh_key %}
|
||||
<b id="ssh_key_help_text">{% trans 'User auth from {}, ssh key login is not supported' %}</b>
|
||||
{% else %}
|
||||
{% bootstrap_form wizard.form %}
|
||||
{% endif %}
|
||||
|
||||
{% if form.mfa_description %}
|
||||
<b>{{ form.mfa_description }}</b>
|
||||
{% endif %}
|
||||
|
||||
{% if form.pubkey_description %}
|
||||
<span>或者:</span>
|
||||
{% if form.pubkey_description and request.user.can_update_ssh_key %}
|
||||
<a type="button" id="btn-reset-pubkey">{{ form.pubkey_description }}</a>
|
||||
{% endif %}
|
||||
|
||||
|
@ -121,26 +124,33 @@
|
|||
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
$(document).on('click', ".fl_goto", function(){
|
||||
var $form = $('#fl_form');
|
||||
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
||||
$form.submit();
|
||||
return false;
|
||||
}).on('click', '#fl_submit', function(){
|
||||
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
|
||||
var noChecked = !$('#acceptTerms').prop('checked');
|
||||
if ( isFinish && noChecked){
|
||||
$('#noTerms').css('visibility', 'visible');
|
||||
}
|
||||
else{
|
||||
$('#fl_form').submit();
|
||||
return false;
|
||||
}
|
||||
}).on('click', '#btn-reset-pubkey', function () {
|
||||
var the_url = '{% url "users:user-pubkey-generate" %}';
|
||||
window.open(the_url, "_blank");
|
||||
$(document).ready(function(){
|
||||
var origin_ssh_key_text = $("#ssh_key_help_text").text();
|
||||
var new_ssh_key_text = origin_ssh_key_text.replace('{}', "{{ request.user.source_display }}");
|
||||
$("#ssh_key_help_text").html(new_ssh_key_text)
|
||||
})
|
||||
.on('click', ".fl_goto", function(){
|
||||
var $form = $('#fl_form');
|
||||
$('<input />', {'name': 'wizard_goto_step', 'value': $(this).data('goto'), 'type': 'hidden'}).appendTo($form);
|
||||
$form.submit();
|
||||
return false;
|
||||
})
|
||||
.on('click', '#fl_submit', function(){
|
||||
var isFinish = $('#fl_submit').html() === "{% trans 'Finish' %}";
|
||||
var noChecked = !$('#acceptTerms').prop('checked');
|
||||
if ( isFinish && noChecked){
|
||||
$('#noTerms').css('visibility', 'visible');
|
||||
}
|
||||
else{
|
||||
$('#fl_form').submit();
|
||||
})
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.on('click', '#btn-reset-pubkey', function () {
|
||||
var the_url = '{% url "users:user-pubkey-generate" %}';
|
||||
window.open(the_url, "_blank");
|
||||
$('#fl_form').submit();
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -119,10 +119,12 @@
|
|||
<td>{% trans 'Last login' %}:</td>
|
||||
<td><b>{{ user_object.last_login|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
{% if user_object.can_update_password %}
|
||||
<tr>
|
||||
<td>{% trans 'Last password updated' %}:</td>
|
||||
<td><b>{{ user_object.date_password_last_updated|date:"Y-m-j H:i:s" }}</b></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ user_object.comment }}</b></td>
|
||||
|
@ -187,6 +189,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if user_object.can_update_ssh_key %}
|
||||
<tr>
|
||||
<td>{% trans 'Send reset ssh key mail' %}:</td>
|
||||
<td>
|
||||
|
@ -195,6 +198,7 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr style="{% if not unblock %}display:none{% endif %}">
|
||||
<td>{% trans 'Unblock user' %}</td>
|
||||
<td>
|
||||
|
|
|
@ -39,12 +39,16 @@
|
|||
<li>
|
||||
<a href="{% url 'users:user-profile-update' %}" class="text-center">{% trans 'Profile' %} </a>
|
||||
</li>
|
||||
{% if request.user.can_update_password %}
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-password-update' %}" class="text-center">{% trans 'Password' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.can_update_ssh_key %}
|
||||
<li>
|
||||
<a href="{% url 'users:user-pubkey-update' %}" class="text-center">{% trans 'Public key' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content" style="background-color: #ffffff">
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<td>{{ user.is_active|yesno:"Yes,No,Unkown" }}</td>
|
||||
</tr>
|
||||
|
||||
{% if user.can_update_ssh_key %}
|
||||
<tr>
|
||||
<td class="text-navy">{% trans 'Public key' %}</td>
|
||||
<td>
|
||||
|
@ -81,6 +82,7 @@
|
|||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-navy">{% trans 'MFA certification' %}</td>
|
||||
<td>
|
||||
|
@ -108,10 +110,12 @@
|
|||
<td class="text-navy">{% trans 'Last login' %}</td>
|
||||
<td>{{ user.last_login|date:"Y-m-d H:i:s" }}</td>
|
||||
</tr>
|
||||
{% if user.can_update_password %}
|
||||
<tr>
|
||||
<td class="text-navy">{% trans 'Last password updated' %}</td>
|
||||
<td>{{ user.date_password_last_updated|date:"Y-m-d H:i:s" }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-navy">{% trans 'Date expired' %}</td>
|
||||
<td>{{ user.date_expired|date:"Y-m-d H:i:s" }}</td>
|
||||
|
@ -189,6 +193,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if request.user.can_update_ssh_key %}
|
||||
<tr>
|
||||
<td>{% trans 'Update SSH public key' %}:</td>
|
||||
<td>
|
||||
|
@ -205,6 +210,7 @@
|
|||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -36,12 +36,16 @@
|
|||
<li class="active">
|
||||
<a href="{% url 'users:user-profile-update' %}" class="text-center">{% trans 'Profile' %} </a>
|
||||
</li>
|
||||
{% if request.user.can_update_password %}
|
||||
<li>
|
||||
<a href="{% url 'users:user-password-update' %}" class="text-center">{% trans 'Password' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.can_update_ssh_key %}
|
||||
<li>
|
||||
<a href="{% url 'users:user-pubkey-update' %}" class="text-center">{% trans 'Public key' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content" style="background-color: #ffffff">
|
||||
|
|
|
@ -36,12 +36,16 @@
|
|||
<li>
|
||||
<a href="{% url 'users:user-profile-update' %}" class="text-center">{% trans 'Profile' %} </a>
|
||||
</li>
|
||||
{% if request.user.can_update_password %}
|
||||
<li>
|
||||
<a href="{% url 'users:user-password-update' %}" class="text-center">{% trans 'Password' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.can_update_ssh_key %}
|
||||
<li class="active">
|
||||
<a href="{% url 'users:user-pubkey-update' %}" class="text-center">{% trans 'Public key' %} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content" style="background-color: #ffffff">
|
||||
|
|
|
@ -23,7 +23,16 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.can_update_ssh_key %}
|
||||
{% bootstrap_field form.public_key layout="horizontal" %}
|
||||
{% else %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">{% trans 'ssh public key' %}</label>
|
||||
<div class="col-sm-8 controls" style="margin-top: 8px;" id="ssh_key_help_text">
|
||||
{% trans 'User auth from {}, ssh key login is not supported' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
|
@ -77,9 +86,13 @@ function passwordCheck() {
|
|||
$(document).ready(function(){
|
||||
passwordCheck();
|
||||
|
||||
var origin_text = $("#password_help_text").text();
|
||||
var new_text = origin_text.replace('{}', "{{ object.source_display }}");
|
||||
$("#password_help_text").html(new_text);
|
||||
var origin_password_text = $("#password_help_text").text();
|
||||
var new_password_text = origin_password_text.replace('{}', "{{ object.source_display }}");
|
||||
$("#password_help_text").html(new_password_text);
|
||||
|
||||
var origin_ssh_key_text = $("#ssh_key_help_text").text();
|
||||
var new_ssh_key_text = origin_ssh_key_text.replace('{}', "{{ object.source_display }}");
|
||||
$("#ssh_key_help_text").html(new_ssh_key_text)
|
||||
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
|
|
|
@ -198,7 +198,7 @@ def check_user_valid(**kwargs):
|
|||
if password and authenticate(username=username, password=password):
|
||||
return user, ''
|
||||
|
||||
if public_key and user.public_key:
|
||||
if public_key and user.public_key and user.is_local:
|
||||
public_key_saved = user.public_key.split()
|
||||
if len(public_key_saved) == 1:
|
||||
if public_key == public_key_saved[0]:
|
||||
|
|
|
@ -2,40 +2,32 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import csv
|
||||
import codecs
|
||||
import chardet
|
||||
from io import StringIO
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login as auth_login
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.db import transaction
|
||||
from django.views.generic.edit import (
|
||||
CreateView, UpdateView, FormView
|
||||
)
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
|
||||
from common.const import (
|
||||
create_success_msg, update_success_msg, KEY_CACHE_RESOURCES_ID
|
||||
)
|
||||
from common.mixins import JSONResponseMixin
|
||||
from common.utils import get_logger, get_object_or_none, is_uuid, ssh_key_gen
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.utils import get_logger, ssh_key_gen
|
||||
from common.permissions import (
|
||||
PermissionsMixin, IsOrgAdmin, IsValidUser,
|
||||
UserCanUpdatePassword, UserCanUpdateSSHKey,
|
||||
)
|
||||
from orgs.utils import current_org
|
||||
from .. import forms
|
||||
from ..models import User, UserGroup
|
||||
|
@ -260,6 +252,7 @@ class UserPasswordUpdateView(PermissionsMixin, UpdateView):
|
|||
model = User
|
||||
form_class = forms.UserPasswordForm
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
permission_classes = [IsValidUser, UserCanUpdatePassword]
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
@ -279,12 +272,6 @@ class UserPasswordUpdateView(PermissionsMixin, UpdateView):
|
|||
return super().get_success_url()
|
||||
|
||||
def form_valid(self, form):
|
||||
if not self.request.user.can_update_password():
|
||||
error = _("User auth from {}, go there change password").format(
|
||||
self.request.source_display
|
||||
)
|
||||
form.add_error("password", error)
|
||||
return self.form_invalid(form)
|
||||
password = form.cleaned_data.get('new_password')
|
||||
is_ok = check_password_rules(password)
|
||||
if not is_ok:
|
||||
|
@ -300,7 +287,7 @@ class UserPublicKeyUpdateView(PermissionsMixin, UpdateView):
|
|||
template_name = 'users/user_pubkey_update.html'
|
||||
model = User
|
||||
form_class = forms.UserPublicKeyForm
|
||||
permission_classes = [IsValidUser]
|
||||
permission_classes = [IsValidUser, UserCanUpdateSSHKey]
|
||||
success_url = reverse_lazy('users:user-profile')
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
|
|
Loading…
Reference in New Issue