Commit ce8f4b4a authored by 八千流's avatar 八千流 Committed by 老广

Ldap synchronization (#2512)

* [Add]初步实现ldap一键导入用户到jumpserver用户表里

* [update]增加定时延迟一秒刷新页面

* [Update]更改前端以表格形式显示用户信息,优化代码结构

* [Update]增加用户显示表格

* [Update]settings配置文件取消注释

* [Update]优化ldap同步用功能代码

* [Update]删除ldap同步用户旧html模版

* [Update]修改登录页面图片拉伸问题

* [Update]增加 是否已经导入,在前端提示用户

* [Update]优化ldap同步用户的代码,以及翻译

* [Update] 更新翻译(改密计划) (#2525)

* [Update] 更新翻译(添加改密计划)

* [Update] 更新翻译(改密计划)

* [Update] 更新翻译

* [Update] 更新翻译

* Export login log (#2511)

* [Add]增加登录日志导出功能

* [Update]优化导出登录日志代码

* [Update]优化导出登录日志代码

* [Update]更改导出登录日志按钮
parent 9aae1069
This diff is collapsed.
...@@ -5,17 +5,16 @@ import os ...@@ -5,17 +5,16 @@ import os
import json import json
import jms_storage import jms_storage
from rest_framework.views import Response, APIView
from ldap3 import Server, Connection from ldap3 import Server, Connection
from rest_framework.views import Response, APIView
from django.conf import settings
from django.core.mail import send_mail from django.core.mail import send_mail
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.permissions import IsOrgAdmin, IsSuperUser
from .serializers import (
MailTestSerializer, LDAPTestSerializer
)
from .models import Setting from .models import Setting
from .utils import get_ldap_users_list, save_user
from common.permissions import IsOrgAdmin, IsSuperUser
from .serializers import MailTestSerializer, LDAPTestSerializer
class MailTestingAPI(APIView): class MailTestingAPI(APIView):
...@@ -91,6 +90,36 @@ class LDAPTestingAPI(APIView): ...@@ -91,6 +90,36 @@ class LDAPTestingAPI(APIView):
return Response({"error": str(serializer.errors)}, status=401) return Response({"error": str(serializer.errors)}, status=401)
class LDAPSyncAPI(APIView):
permission_classes = (IsOrgAdmin,)
def get(self, request):
ldap_users_list = get_ldap_users_list()
if not isinstance(ldap_users_list, list):
return Response(ldap_users_list, status=401)
return Response(ldap_users_list)
class LDAPConfirmSyncAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request):
user_names = request.data.get('user_names', '')
if not user_names:
error = _('User is not currently selected, please check the user '
'you want to import')
return Response({'error': error}, status=401)
ldap_users_list = get_ldap_users_list(user_names=user_names)
if not isinstance(ldap_users_list, list):
return Response(ldap_users_list, status=401)
save_result = save_user(ldap_users_list)
if 'error' in save_result.keys():
return Response(save_result, status=401)
return Response(save_result)
class ReplayStorageCreateAPI(APIView): class ReplayStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
......
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}ldap_list_users_modal{% endblock %}
{% block modal_title%}{% trans "Ldap users" %}{% endblock %}
{% block modal_body %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style>
.inmodal .modal-header {
padding: 10px 10px;
text-align: center;
}
#assetTree2.ztree * {
background-color: #f8fafb;
}
#assetTree2.ztree {
background-color: #f8fafb;
}
</style>
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-12 animated fadeInRight" id="split-right">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="ldap_list_users_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Email' %}</th>
<th class="text-center">{% trans 'Is imported' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
var ldap_users_table = 0;
function initLdapTable() {
if(ldap_users_table){
return
}
var options = {
ele: $('#ldap_list_users_table'),
ajax_url: '{% url "api-settings:ldap-sync" %}',
columns: [
{data: "username" },{data: "username" }, {data: "name" },
{data:"email"}, {data:'is_imported'}
],
pageLength: 10
};
ldap_users_table = jumpserver.initDataTable(options);
return ldap_users_table
}
$(document).ready(function(){
}).on('show.bs.modal', function () {
initLdapTable();
})
.on('click','.close_btn1',function () {
window.location.reload()
})
.on('click','.close_btn2',function () {
window.location.reload()
})
</script>
{% endblock %}
{% block modal_button %}
{{ block.super }}
{% endblock %}
{% block modal_confirm_id %}btn_ldap_modal_confirm{% endblock %}
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<div class="tab-content"> <div class="tab-content">
<div class="col-sm-12" style="padding-left:0"> <div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;"> <div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal"> <form id="ldap_form" action="" method="post" class="form-horizontal">
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="alert alert-danger"> <div class="alert alert-danger">
{{ form.non_field_errors }} {{ form.non_field_errors }}
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
<button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button> <button class="btn btn-default btn-test" type="button"> {% trans 'Test connection' %}</button>
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button> <button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button> <button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
{# <button class="btn btn-primary sync_button " data-toggle="modal" data-target="#sync_users_modal" type="button">{% trans 'Synchronization' %}</button>#}
<button class="btn btn-primary sync_button " data-toggle="modal" data-target="#ldap_list_users_modal" type="button">{% trans 'Sync User' %}</button>
</div> </div>
</div> </div>
</form> </form>
...@@ -72,10 +74,12 @@ ...@@ -72,10 +74,12 @@
</div> </div>
</div> </div>
</div> </div>
{% include 'settings/_ldap_list_users_modal.html' %}
{% endblock %} {% endblock %}
{% block custom_foot_js %} {% block custom_foot_js %}
<script> <script>
$(document).ready(function () { $(document).ready(function () {
}) })
.on("click", ".btn-test", function () { .on("click", ".btn-test", function () {
var data = {}; var data = {};
...@@ -102,5 +106,30 @@ $(document).ready(function () { ...@@ -102,5 +106,30 @@ $(document).ready(function () {
error: error error: error
}); });
}) })
.on("click","#btn_ldap_modal_confirm",function () {
var user_names=[];
var cheked = $("tbody input[type='checkbox']:checked").each(function () {
user_names.push($(this).attr('id'));
});
var the_url = "{% url "api-settings:ldap-comfirm-sync" %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify({'user_names':user_names}),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
</script> </script>
{% endblock %} {% endblock %}
...@@ -9,6 +9,8 @@ app_name = 'common' ...@@ -9,6 +9,8 @@ app_name = 'common'
urlpatterns = [ urlpatterns = [
path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'), path('mail/testing/', api.MailTestingAPI.as_view(), name='mail-testing'),
path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'), path('ldap/testing/', api.LDAPTestingAPI.as_view(), name='ldap-testing'),
path('ldap/sync/', api.LDAPSyncAPI.as_view(), name='ldap-sync'),
path('ldap/comfirm/sync/', api.LDAPConfirmSyncAPI.as_view(), name='ldap-comfirm-sync'),
path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'), path('terminal/replay-storage/create/', api.ReplayStorageCreateAPI.as_view(), name='replay-storage-create'),
path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'), path('terminal/replay-storage/delete/', api.ReplayStorageDeleteAPI.as_view(), name='replay-storage-delete'),
path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'), path('terminal/command-storage/create/', api.CommandStorageCreateAPI.as_view(), name='command-storage-create'),
......
# -*- coding: utf-8 -*-
#
from ldap3 import Server, Connection
from django.utils.translation import ugettext_lazy as _
from .models import settings
from users.models import User
def ldap_conn(host, use_ssl, bind_dn, password):
server = Server(host, use_ssl=use_ssl)
conn = Connection(server, bind_dn, password)
return conn
def ldap_search(conn, search_ougroup, search_filter, attr_map, user_names=None):
users_list = []
for search_ou in str(search_ougroup).split("|"):
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values()))
if not ok:
error = _("Search no entry matched in ou {}").format(search_ou)
return {"error": error}
ldap_map_users(conn, attr_map, users_list, user_names)
if len(users_list) > 0:
return users_list
return {"error": _("Have user but attr mapping error")}
def get_ldap_users_list(user_names=None):
ldap_setting = get_ldap_setting()
conn = ldap_conn(ldap_setting['host'], ldap_setting['use_ssl'],
ldap_setting['bind_dn'], ldap_setting['password'])
try:
conn.bind()
except Exception as e:
return {"error": str(e)}
result_search = ldap_search(conn, ldap_setting['search_ougroup'],
ldap_setting['search_filter'],
ldap_setting['attr_map'], user_names=user_names)
return result_search
def ldap_map_users(conn, attr_map, users, user_names=None):
for entry in conn.entries:
user = entry_user(entry, attr_map)
if user_names:
if user.get('username', '') in user_names:
users.append(user)
else:
users.append(user)
def entry_user(entry, attr_map):
user = {}
user['is_imported'] = _('No')
for attr, mapping in attr_map.items():
if not hasattr(entry, mapping):
continue
value = getattr(entry, mapping).value
user[attr] = value if value else ''
if attr != 'username':
continue
if User.objects.filter(username=user[attr]):
user['is_imported'] = _('Yes')
return user
def get_ldap_setting():
host = settings.AUTH_LDAP_SERVER_URI
bind_dn = settings.AUTH_LDAP_BIND_DN
password = settings.AUTH_LDAP_BIND_PASSWORD
use_ssl = settings.AUTH_LDAP_START_TLS
search_ougroup = settings.AUTH_LDAP_SEARCH_OU
search_filter = settings.AUTH_LDAP_SEARCH_FILTER
attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
auth_ldap = settings.AUTH_LDAP
ldap_setting = {
'host': host, 'bind_dn': bind_dn, 'password': password,
'search_ougroup': search_ougroup, 'search_filter': search_filter,
'attr_map': attr_map, 'auth_ldap': auth_ldap, 'use_ssl': use_ssl,
}
return ldap_setting
def save_user(users):
exist = []
username_list = [item.get('username') for item in users]
for name in username_list:
if User.objects.filter(username=name).exclude(source='ldap'):
exist.append(name)
users = [user for user in users if (user.get('username') not in exist)]
result_save = save(users, exist)
return result_save
def save(users, exist):
fail_user = []
for item in users:
item = set_default_item(item)
user = User.objects.filter(username=item['username'], source='ldap')
user = user.first()
if not user:
try:
user = User.objects.create(**item)
except Exception as e:
fail_user.append(item.get('username'))
continue
for key, value in item.items():
user.key = value
user.save()
get_msg = get_messages(users, exist, fail_user)
return get_msg
def set_default_item(item):
item['source'] = 'ldap'
if not item.get('email', ''):
item['email'] = item['username'] + '@' + item['username'] + '.com'
if 'is_imported' in item.keys():
item.pop('is_imported')
return item
def get_messages(users, exist, fail_user):
if exist:
info = _("Import {} users successfully; import {} users failed, the "
"database already exists with the same name")
msg = info.format(len(users), str(exist))
if fail_user:
info = _("Import {} users successfully; import {} users failed, "
"the database already exists with the same name; import {}"
"users failed, Because’TypeError' object has no attribute "
"'keys'")
msg = info.format(len(users)-len(fail_user), str(exist), str(fail_user))
else:
msg = _("Import {} users successfully").format(len(users))
if fail_user:
info = _("Import {} users successfully;import {} users failed, "
"Because’TypeError' object has no attribute 'keys'")
msg = info.format(len(users)-len(fail_user), str(fail_user))
return {'msg': msg}
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<div class="modal-dialog {% block modal_class %}{% endblock %}"> <div class="modal-dialog {% block modal_class %}{% endblock %}">
<div class="modal-content animated fadeIn"> <div class="modal-content animated fadeIn">
<div class="modal-header"> <div class="modal-header">
<button data-dismiss="modal" class="close" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button> <button data-dismiss="modal" class="close close_btn1" type="button"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{% block modal_title %}{% endblock %}</h4> <h4 class="modal-title">{% block modal_title %}{% endblock %}</h4>
<small>{% block modal_comment %}{% endblock %}</small> <small>{% block modal_comment %}{% endblock %}</small>
</div> </div>
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
{% block modal_button %} {% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button> <button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
<button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button> <button class="btn btn-primary" type="button" id="{% block modal_confirm_id %}{% endblock %}">{% trans 'Confirm' %}</button>
{% endblock %} {% endblock %}
</div> </div>
......
...@@ -32,27 +32,53 @@ ...@@ -32,27 +32,53 @@
<script src="{% static 'js/bootstrap.min.js' %}"></script> <script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script> <script src="{% static 'js/plugins/datatables/datatables.min.js' %}"></script>
{# <script src="{% static 'js/angular.min.js' %}"></script>#}
<style> <style>
.box-1{
height: 472px;
width: 984px;
margin-right: auto;
margin-left: auto;
margin-top: calc((100vh - 470px)/2);
}
.box-2{
height: 100%;
width: 50%;
float: right;
}
.box-3{
text-align: center;
background-color: white;
height: 100%;
width: 50%;
}
.captcha { .captcha {
float: right; float: right;
} }
</style> </style>
</head> </head>
<body> <body style="height: 100%">
<div>
<div class="login-dialog"> <div class="box-1">
<div class=""> <div class="box-2">
<div class="row" style="height: 472px"> {% if interface.login_image %}
<div class="col-md-4 col-md-offset-2 input_shadow-1" style="text-align: center;background-color: white; padding-right: 0px;height: 100%"> <img src="{{ MEDIA_URL }}{{ interface.login_image }}" style="height: 100%; width: 100%"/>
{% else %}
<img src="{% static 'img/login/login_image_1.png' %}" style=" height: 100%; width: 100%"/>
{% endif %}
</div>
<div class="box-3">
<div style="background-color: white"> <div style="background-color: white">
{% if interface.login_title %} {% if interface.login_title %}
<div style="margin-top: 40px"> <div style="margin-top: 40px;padding-top: 50px;">
<span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{{ interface.login_title }}</span> <span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{{ interface.login_title }}</span>
</div> </div>
{% else %} {% else %}
<div style="margin-top: 40px"> <div style="margin-top: 40px;padding-top: 50px;">
<span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{% trans 'Welcome to the Jumpserver open source fortress' %}</span> <span style="font-size: 24px;font-weight:400;color: #151515;letter-spacing: 0;">{% trans 'Welcome to the Jumpserver open source fortress' %}</span>
</div> </div>
{% endif %} {% endif %}
...@@ -81,15 +107,12 @@ ...@@ -81,15 +107,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" name="{{ form.username.html_name }}" <input type="text" class="form-control" name="{{ form.username.html_name }}" placeholder="{% trans 'Username' %}" required="" value="{% if form.username.value %}{{ form.username.value }}{% endif %}" style="height: 35px">
placeholder="{% trans 'Username' %}" required=""
value="{% if form.username.value %}{{ form.username.value }}{% endif %}" style="height: 35px">
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="password" class="form-control" name="{{ form.password.html_name }}" <input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
placeholder="{% trans 'Password' %}" required="">
</div> </div>
<div class="form-group" style="height: 50px;margin-bottom: 0px"> <div class="form-group" style="height: 50px;margin-bottom: 0">
{{ form.captcha }} {{ form.captcha }}
</div> </div>
<div class="form-group" style="margin-top: 10px"> <div class="form-group" style="margin-top: 10px">
...@@ -104,23 +127,11 @@ ...@@ -104,23 +127,11 @@
</div> </div>
<div class="col-md-1"></div> <div class="col-md-1"></div>
</div> </div>
</div>
</div> </div>
</div> </div>
<div class="col-md-4 " style="padding-left: 0px; height: 100%">
{% if interface.login_image %}
<img src="{{ MEDIA_URL }}{{ interface.login_image }}" style="width: 100%; height: 100%;" class="input_shadow-1" />
{% else %}
<img src="{% static 'img/login/login-image.jpg' %}" style="width: 100%; height: 100%;" class="input_shadow-1" />
{% endif %}
</div> </div>
</div>
<div class="col-md-2"></div>
</div> </div>
</div> </div>
</div>
</body> </body>
</html> </html>
\ No newline at end of file
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