Unverified Commit 7e8f7343 authored by BaiJiangJie's avatar BaiJiangJie Committed by GitHub

Merge pull request #3033 from jumpserver/dev

Dev
parents b7915ccd 69841a41
......@@ -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:
......
from rest_framework.request import Request
from django.http.request import QueryDict
from django.conf import settings
from django.dispatch import receiver
......@@ -52,14 +53,15 @@ def on_ldap_create_user(sender, user, ldap_user, **kwargs):
def generate_data(username, request):
if not request.user.is_anonymous and request.user.is_app:
user_agent = request.META.get('HTTP_USER_AGENT', '')
if isinstance(request, Request):
login_ip = request.data.get('remote_addr', None)
login_type = request.data.get('login_type', '')
user_agent = request.data.get('HTTP_USER_AGENT', '')
else:
login_ip = get_request_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '')
login_type = 'W'
data = {
'username': username,
'ip': login_ip,
......
......@@ -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
......
This diff is collapsed.
......@@ -9,13 +9,15 @@ from rest_framework.generics import (
)
from rest_framework.pagination import LimitOffsetPagination
from common.permissions import IsValidUser, IsOrgAdminOrAppUser
from common.permissions import IsValidUser, IsOrgAdminOrAppUser, IsOrgAdmin
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from ..utils import (
AssetPermissionUtil, ParserNode,
)
from .mixin import UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
from .mixin import (
UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
)
from .. import const
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
from .. import serializers
......@@ -29,6 +31,7 @@ __all__ = [
'UserGrantedNodesWithAssetsApi', 'UserGrantedNodeAssetsApi',
'ValidateUserAssetPermissionApi', 'UserGrantedNodesAsTreeApi',
'UserGrantedNodesWithAssetsAsTreeApi', 'GetUserAssetPermissionActionsApi',
'RefreshAssetPermissionCacheApi'
]
......@@ -365,3 +368,12 @@ class GetUserAssetPermissionActionsApi(UserPermissionCacheMixin, RetrieveAPIView
actions = asset["system_users"].get(system_id, 0)
break
return {"actions": actions}
class RefreshAssetPermissionCacheApi(RetrieveAPIView):
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
# expire all cache
AssetPermissionUtil.expire_all_cache()
return Response({'msg': True}, status=200)
......@@ -33,10 +33,14 @@
</div>
</div>
<div class="mail-box-header">
<div class="uc pull-left m-r-5">
<a class="btn btn-sm btn-primary btn-create-permission">
{% trans "Create permission" %}
</a>
<div class="btn-group uc pull-left m-r-5">
<button class="btn btn-sm btn-primary btn-create-permission">
{% trans "Create permission" %}
</button>
<button data-toggle="dropdown" class="btn btn-primary btn-sm dropdown-toggle"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="refresh-asset-permission-cache" href="#">{% trans 'Refresh permission cache' %}</a></li>
</ul>
</div>
<table class="table table-striped table-bordered table-hover" id="permission_list_table" style="width: 100%">
<thead>
......@@ -232,6 +236,14 @@ $(document).ready(function(){
.replace('{{ DEFAULT_PK }}', uid);
objectDelete($this, name, the_url);
})
.on('click', '.refresh-asset-permission-cache', function () {
var the_url = "{% url 'api-perms:refresh-asset-permission-cache' %}";
requestApi({
url: the_url,
method: 'GET',
success_message: "{% trans 'Refresh success' %}"
});
})
.on('click', '.btn-create-permission', function () {
var url = "{% url 'perms:asset-permission-create' %}";
var nodes = zTree.getSelectedNodes();
......
......@@ -57,6 +57,9 @@ asset_permission_urlpatterns = [
# 验证用户是否有某个资产和系统用户的权限
path('asset-permissions/user/validate/', api.ValidateUserAssetPermissionApi.as_view(), name='validate-user-asset-permission'),
path('asset-permissions/user/actions/', api.GetUserAssetPermissionActionsApi.as_view(), name='get-user-asset-permission-actions'),
# 刷新缓存
path('asset-permissions/user/cache/refresh/', api.RefreshAssetPermissionCacheApi.as_view(), name='refresh-asset-permission-cache'),
]
......
......@@ -414,15 +414,12 @@ class AssetPermissionCacheMixin:
cache.delete_pattern(key)
self.expire_cache_meta()
@classmethod
def expire_all_cache_meta(cls):
key = cls.CACHE_META_KEY_PREFIX + '*'
cache.delete_pattern(key)
@classmethod
def expire_all_cache(cls):
key = cls.CACHE_KEY_PREFIX + '*'
cache.delete_pattern(key)
meta_key = cls.CACHE_META_KEY_PREFIX + '*'
cache.delete_pattern(meta_key)
class AssetPermissionUtil(AssetPermissionCacheMixin):
......
......@@ -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):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment