mirror of https://github.com/jumpserver/jumpserver
1.5.7 Merge to dev (#3766)
* [Update] 暂存,优化解决不了问题 * [Update] 待续(小白) * [Update] 修改asset user * [Update] 计划再次更改 * [Update] 修改asset user * [Update] 暂存与喜爱 * [Update] Add id in * [Update] 阶段性完成ops task该做 * [Update] 修改asset user api * [Update] 修改asset user 任务,查看认证等 * [Update] 基本完成asset user改造 * [Update] dynamic user only allow 1 * [Update] 修改asset user task * [Update] 修改node admin user task api * [Update] remove file header license * [Update] 添加sftp root * [Update] 暂存 * [Update] 暂存 * [Update] 修改翻译 * [Update] 修改系统用户改为同名后,用户名改为空 * [Update] 基本完成CAS调研 * [Update] 支持cas server * [Update] 支持cas server * [Update] 添加requirements * [Update] 为方便调试添加mysql ipython到包中 * [Update] 添加huaweiyun翻译 * [Update] 增加下载session 录像 * [Update] 只有第一次通知replay离线的使用方法 * [Update] 暂存一下 * [Bugfix] 获取系统用户信息报错 * [Bugfix] 修改system user info * [Update] 改成清理10天git status * [Update] 修改celery日志保留时间 * [Update]修复部分pip包依赖的版本不兼容问题 (#3672) * [Update] 修复用户更新页面会清空用户public_key的问题 * Fix broken dependencies Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> * [Update] 修改获取系统用户auth info * [Update] Remove log * [Bugfix] 修复sftp home设置的bug * [Update] 授权的系统用户添加sftp root * [Update] 修改系统用户关联的用户 * [Update] 修改placeholder * [Update] 优化获取授权的系统用户 * [Update] 修改tasks * [Update] tree service update * [Update] 暂存 * [Update] 基本完成用户授权树和资产树改造 * [Update] Dashbaord perf * [update] Add huawei cloud sdk requirements * [Updte] 优化dashboard页面 * [Update] system user auth info 添加id * [Update] 修改系统用户serializer * [Update] 优化api * [Update] LDAP Test Util (#3720) * [Update] LDAPTestUtil 1 * [Update] LDAPTestUtil 2 * [Update] LDAPTestUtil 3 * [Update] LDAPTestUtil 4 * [Update] LDAPTestUtil 5 * [Update] LDAPTestUtil 6 * [Update] LDAPTestUtil 7 * [Update] session 已添加is success,并且添加display serializer * [Bugfix] 修复无法删除空节点的bug * [Update] 命令记录分组织显示 * [Update] Session is_success 添加迁移文件 * [Update] 批量命令添加org_id * [Update] 修复一些文案,修改不绑定MFA,不能ssh登录 * [Update] 修改replay api, 返回session信息 * [Update] 解决无效es导致访问命令记录页面失败的问题 * [Update] 拆分profile view * [Update] 修改一个翻译 * [Update] 修改aysnc api框架 * [Update] 命令列表添加risk level * [Update] 完成录像打包下载 * [Update] 更改登陆otp页面 * [Update] 修改command 存储redis_level * [Update] 修改翻译 * [Update] 修改系统用户的用户列表字段 * [Update] 使用新logo和统一Jumpserver为JumpServer * [Update] 优化cloud task * [Update] 统一period task * [Update] 统一period form serializer字段 * [Update] 修改period task * [Update] 修改资产网关信息 * [Update] 用户授权资产树资产信息添加domain * [Update] 修改翻译 * [Update] 测试可连接性 * 1.5.7 bai (#3764) * [Update] 修复index页面Bug;修复测试资产用户可连接性问题; * [Update] 修改测试资产用户可连接 * [Bugfix] 修复backends问题 * [Update] 修改marksafe依赖版本 * [Update] 修改测试资产用户可连接性 * [Update] 修改检测服务器性能时获取percent值 * [Update] 更新依赖boto3=1.12.14 Co-authored-by: Yanzhe Lee <lee.yanzhe@yanzhe.org> Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Co-authored-by: Bai <bugatti_it@163.com>pull/3770/head
parent
1f6a8e8f02
commit
1fd2e782f8
@ -1,10 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AdminUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AdminUserBackend(AssetUserBackend):
|
||||
model = AdminUser
|
||||
backend = 'AdminUser'
|
@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
model = None
|
||||
backend = "AssetUser"
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, **kwargs):
|
||||
queryset = cls.model.objects.all()
|
||||
prefer_id = kwargs.get('prefer_id')
|
||||
if prefer_id:
|
||||
queryset = queryset.filter(id=prefer_id)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
if username:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(assets__in=assets).distinct()
|
||||
|
||||
queryset = cls.filter_queryset_more(queryset)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, asset_users, assets):
|
||||
instances = []
|
||||
assets_user_assets_map = defaultdict(set)
|
||||
if isinstance(asset_users, list):
|
||||
assets_user_assets_map = {
|
||||
asset_user.id: asset_user.assets.values_list('id', flat=True)
|
||||
for asset_user in asset_users
|
||||
}
|
||||
else:
|
||||
assets_user_assets = asset_users.values_list('id', 'assets')
|
||||
for i, asset_id in assets_user_assets:
|
||||
assets_user_assets_map[i].add(asset_id)
|
||||
|
||||
for asset_user in asset_users:
|
||||
if not assets:
|
||||
related_assets = asset_user.assets.all()
|
||||
else:
|
||||
assets_map = {a.id: a for a in assets}
|
||||
related_assets = [
|
||||
assets_map.get(i) for i in assets_user_assets_map.get(asset_user.id) if i in assets_map
|
||||
]
|
||||
for asset in related_assets:
|
||||
instance = asset_user.construct_to_authbook(asset)
|
||||
instance.backend = cls.backend
|
||||
instances.append(instance)
|
||||
return instances
|
@ -1,94 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
|
||||
from ..models import Asset
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:param prefer: 优先使用
|
||||
:param prefer_id: 使用id
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
def all(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, username=None, hostname=None, ip=None, assets=None,
|
||||
node=None, prefer_id=None, **kwargs):
|
||||
pass
|
||||
|
||||
class AssetUserQuerySet(list):
|
||||
def order_by(self, *ordering):
|
||||
_ordering = []
|
||||
reverse = False
|
||||
for i in ordering:
|
||||
if i[0] == '-':
|
||||
reverse = True
|
||||
i = i[1:]
|
||||
_ordering.append(i)
|
||||
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
|
||||
return self
|
||||
|
||||
def filter_in(self, kwargs):
|
||||
in_kwargs = {}
|
||||
queryset = []
|
||||
for k, v in kwargs.items():
|
||||
if len(v) == 0:
|
||||
return self
|
||||
if k.find("__in") >= 0:
|
||||
_k = k.split('__')[0]
|
||||
in_kwargs[_k] = v
|
||||
else:
|
||||
in_kwargs[k] = v
|
||||
for k in in_kwargs:
|
||||
kwargs.pop(k, None)
|
||||
|
||||
if len(in_kwargs) == 0:
|
||||
return self
|
||||
for i in self:
|
||||
matched = False
|
||||
for k, v in in_kwargs.items():
|
||||
attr = getattr(i, k, None)
|
||||
# 如果属性或者value中是uuid,则转换成string
|
||||
if isinstance(v[0], uuid.UUID):
|
||||
v = [str(i) for i in v]
|
||||
if isinstance(attr, uuid.UUID):
|
||||
attr = str(attr)
|
||||
if attr in v:
|
||||
matched = True
|
||||
if matched:
|
||||
queryset.append(i)
|
||||
return AssetUserQuerySet(queryset)
|
||||
|
||||
def filter_equal(self, kwargs):
|
||||
def filter_it(obj):
|
||||
wanted = []
|
||||
real = []
|
||||
for k, v in kwargs.items():
|
||||
wanted.append(v)
|
||||
value = getattr(obj, k, None)
|
||||
if isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
real.append(value)
|
||||
return wanted == real
|
||||
kwargs = {k: v for k, v in kwargs.items() if k.find('__in') == -1}
|
||||
if len(kwargs) > 0:
|
||||
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
|
||||
else:
|
||||
queryset = self
|
||||
return queryset
|
||||
@abstractmethod
|
||||
def search(self, item):
|
||||
pass
|
||||
|
||||
def filter(self, **kwargs):
|
||||
queryset = self.filter_in(kwargs).filter_equal(kwargs)
|
||||
return queryset
|
||||
@abstractmethod
|
||||
def get_queryset(self):
|
||||
pass
|
||||
|
||||
def distinct(self):
|
||||
items = list(set(self))
|
||||
self[:] = items
|
||||
return self
|
||||
@abstractmethod
|
||||
def delete(self, union_id):
|
||||
pass
|
||||
|
||||
def __or__(self, other):
|
||||
self.extend(other)
|
||||
return self
|
||||
@staticmethod
|
||||
def qs_to_values(qs):
|
||||
values = qs.values(
|
||||
'hostname', 'ip', "asset_id",
|
||||
'username', 'password', 'private_key', 'public_key',
|
||||
'score', 'version',
|
||||
"asset_username", "union_id",
|
||||
'date_created', 'date_updated',
|
||||
'org_id', 'backend',
|
||||
)
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def make_assets_as_id(assets):
|
||||
if not assets:
|
||||
return []
|
||||
if isinstance(assets[0], Asset):
|
||||
assets = [a.id for a in assets]
|
||||
return assets
|
||||
|
@ -1,30 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import SystemUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class SystemUserBackend(AssetUserBackend):
|
||||
model = SystemUser
|
||||
backend = 'SystemUser'
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
queryset = cls._distinct_system_users_by_username(queryset)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 2.2.7 on 2020-01-06 07:34
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0046_auto_20191218_1705'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AssetUser',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('assets.authbook',),
|
||||
),
|
||||
]
|
@ -0,0 +1,35 @@
|
||||
# Generated by Django 2.2.7 on 2019-12-30 07:12
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0047_assetuser'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='authbook',
|
||||
name='is_active',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='username_same_with_user',
|
||||
field=models.BooleanField(default=False, verbose_name='Username same with user'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='users',
|
||||
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Users'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(blank=True, to='users.UserGroup',
|
||||
verbose_name='User groups'),
|
||||
),
|
||||
]
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.7 on 2020-01-19 07:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0048_auto_20191230_1512'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='sftp_root',
|
||||
field=models.CharField(default='tmp', max_length=128, verbose_name='SFTP Root'),
|
||||
),
|
||||
]
|
@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .authbook import AuthBook
|
||||
|
||||
|
||||
class AssetUser(AuthBook):
|
||||
hostname = ""
|
||||
ip = ""
|
||||
backend = ""
|
||||
union_id = ""
|
||||
asset_username = ""
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
@ -0,0 +1,212 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<style>
|
||||
.table.node_edit {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-detail' pk=system_user.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'assets:system-user-asset' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'Asset list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% if system_user.username_same_with_user %}
|
||||
<li class="active">
|
||||
<a href="{% url 'assets:system-user-user' pk=system_user.id %}" class="text-center">
|
||||
<i class="fa fa-bar-chart-o"></i> {% trans 'User list' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span style="float: left"><b>{{ system_user.name }} </b><span class="badge">{{ paginator.count }}</span></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table table-striped table-bordered table-hover" id="user_list_table" style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all">
|
||||
</th>
|
||||
<th class="text-center">{% trans 'User' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4" style="padding-left: 0;padding-right: 0">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info-circle"></i> {% trans 'Users' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table" id="add-users">
|
||||
<tbody>
|
||||
<form>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<select data-placeholder="{% trans 'Select users' %}" id="id_users" class="users-select2 select2" style="width: 100%" multiple="" tabindex="4">
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="no-borders">
|
||||
<button type="button" class="btn btn-info btn-sm" id="btn-add-users">{% trans 'Confirm' %}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var usersRelationUrl = "{% url 'api-assets:system-users-users-relation-list' %}?systemuser={{ system_user.id }}";
|
||||
var userTable = null;
|
||||
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#user_list_table'),
|
||||
toggle: true,
|
||||
ajax_url: usersRelationUrl,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 2, createdCell: function (td, cellData, rowData) {
|
||||
var removeBtn = '<button class="btn btn-xs btn-danger m-l-xs btn-remove" data-uid=UID>{% trans "Remove" %}</button>';
|
||||
removeBtn = removeBtn.replace("UID", rowData.user);
|
||||
$(td).html(removeBtn);
|
||||
},
|
||||
width: '70px'
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{data: "id"}, {data: "user_display"},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html(),
|
||||
};
|
||||
userTable = jumpserver.initServerSideDataTable(options);
|
||||
return userTable
|
||||
}
|
||||
|
||||
function addUsers(objectsId, success, fail) {
|
||||
if (!objectsId || objectsId.length === 0) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersRelationUrl;
|
||||
var body = [];
|
||||
objectsId.forEach(function (v) {
|
||||
var data = {systemuser: "{{ object.id }}"};
|
||||
data.user = v;
|
||||
body.push(data)
|
||||
});
|
||||
if (!success) {
|
||||
success = reloadPage
|
||||
}
|
||||
var option = {
|
||||
url: theUrl,
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
success: success,
|
||||
};
|
||||
if (fail) {
|
||||
option.error = fail;
|
||||
}
|
||||
requestApi(option)
|
||||
}
|
||||
|
||||
function removeUser(userId, success) {
|
||||
if (!userId) {
|
||||
return
|
||||
}
|
||||
var theUrl = usersRelationUrl;
|
||||
theUrl = setUrlParam(theUrl, 'user', userId);
|
||||
if (!success) {
|
||||
success = function () {
|
||||
userTable.ajax.reload();
|
||||
}
|
||||
}
|
||||
requestApi({
|
||||
url: theUrl,
|
||||
method: "DELETE",
|
||||
success: success,
|
||||
success_message: "{% trans "Remove success" %}"
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
initTable();
|
||||
usersSelect2Init('.users-select2');
|
||||
})
|
||||
.on('click', '.btn-remove', function() {
|
||||
var userId = $(this).data("uid");
|
||||
removeUser(userId);
|
||||
})
|
||||
.on('click', '#btn-add-users', function() {
|
||||
var usersId = $('.users-select2').val();
|
||||
var options = $(".users-select2").find(":selected");
|
||||
var failed = function(s, data) {
|
||||
var invalidIndex = [];
|
||||
var validIndex = [];
|
||||
$.each(data, function (k, v) {
|
||||
if (isEmptyObject(v)) {
|
||||
validIndex.push(k)
|
||||
} else {
|
||||
invalidIndex.push(k)
|
||||
}
|
||||
});
|
||||
var invalidLabel = [];
|
||||
$.each(invalidIndex, function (k, v) {
|
||||
invalidLabel.push(options[v].text)
|
||||
});
|
||||
var errorMsg = "{% trans 'Have existed: ' %}";
|
||||
errorMsg += invalidLabel.join(", ");
|
||||
toastr.error(errorMsg)
|
||||
};
|
||||
addUsers(usersId, null, failed);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .backends import *
|
||||
from .callback import *
|
@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django_cas_ng.backends import CASBackend as _CASBackend
|
||||
|
||||
|
||||
__all__ = ['CASBackend']
|
||||
|
||||
|
||||
class CASBackend(_CASBackend):
|
||||
def user_can_authenticate(self, user):
|
||||
return True
|
@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
def cas_callback(response):
|
||||
username = response['username']
|
||||
user, user_created = User.objects.get_or_create(username=username)
|
||||
profile, created = user.get_profile()
|
||||
|
||||
profile.role = response['attributes']['role']
|
||||
profile.birth_date = response['attributes']['birth_date']
|
||||
profile.save()
|
@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.urls import path
|
||||
import django_cas_ng.views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('login/', django_cas_ng.views.LoginView.as_view(), name='cas-login'),
|
||||
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'),
|
||||
]
|
@ -1,88 +1,32 @@
|
||||
{% extends '_base_only_content.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> {{ JMS_TITLE }} </title>
|
||||
<link rel="shortcut icon" href="{{ FAVICON_URL }}" type="image/x-icon">
|
||||
{% include '_head_css_js.html' %}
|
||||
<link href="{% static "css/jumpserver.css" %}" rel="stylesheet">
|
||||
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
|
||||
<script src="{% static "js/jumpserver.js" %}"></script>
|
||||
<script src="{% static "js/plugins/qrcode/qrcode.min.js" %}"></script>
|
||||
<style>
|
||||
.captcha {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="gray-bg">
|
||||
<div class="loginColumns animated fadeInDown">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 class="font-bold">{% trans 'Welcome to the Jumpserver open source fortress' %}</h2>
|
||||
<p>
|
||||
{% trans "The world's first fully open source fortress, using the GNU GPL v2.0 open source protocol, is a professional operation and maintenance audit system in compliance with 4A." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Developed using Python/Django, following the Web 2.0 specification and equipped with industry-leading Web Terminal solutions, with beautiful interactive interface and good user experience." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans 'Distributed architecture is adopted to support multi-machine room deployment across regions, central node provides API, and each machine room deploys login node, which can be extended horizontally and without concurrent access restrictions.' %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "Changes the world, starting with a little bit." %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ibox-content">
|
||||
<div>
|
||||
<img src="{% static 'img/logo.png' %}" width="60" height="60">
|
||||
<span class="font-bold text-center" style="font-size: 24px; font-family: inherit; margin-left: 20px">{% trans 'MFA certification' %}</span>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
|
||||
<div class="form-group">
|
||||
<p style="margin:30px auto;" class="text-center"><strong style="color:#000000">{% trans 'The account protection has been opened, please complete the following operations according to the prompts' %}</strong></p>
|
||||
<div class="text-center">
|
||||
<img src="{% static 'img/otp_auth.png' %}" alt="" width="72px" height="117">
|
||||
</div>
|
||||
<p style="margin: 30px auto"> {% trans 'Open Authenticator and enter the 6-bit dynamic code' %}</p>
|
||||
</div>
|
||||
{% block title %}
|
||||
{% trans 'MFA' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
|
||||
{% csrf_token %}
|
||||
{% if 'otp_code' in form.errors %}
|
||||
<p class="red-fonts">{{ form.otp_code.errors.as_text }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="{% trans 'Six figures' %}" required="">
|
||||
<select class="form-control">
|
||||
<option value="otp" selected>{% trans 'One-time password' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="otp_code" placeholder="" required="" autofocus="autofocus">
|
||||
<span class="help-block">
|
||||
{% trans 'Open Google Authenticator and enter the 6-bit dynamic code' %}
|
||||
</span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Next' %}</button>
|
||||
|
||||
<a href="#">
|
||||
<div>
|
||||
<small>{% trans "Can't provide security? Please contact the administrator!" %}</small>
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<p class="m-t">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% include '_copyright.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import socket
|
||||
import struct
|
||||
import random
|
||||
|
||||
|
||||
def random_datetime(date_start, date_end):
|
||||
random_delta = (date_end - date_start) * random.random()
|
||||
return date_start + random_delta
|
||||
|
||||
|
||||
def random_ip():
|
||||
return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
|
||||
|
||||
|
||||
|
||||
# def strTimeProp(start, end, prop, fmt):
|
||||
# time_start = time.mktime(time.strptime(start, fmt))
|
||||
# time_end = time.mktime(time.strptime(end, fmt))
|
||||
# ptime = time_start + prop * (time_end - time_start)
|
||||
# return int(ptime)
|
||||
#
|
||||
#
|
||||
# def randomTimestamp(start, end, fmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return strTimeProp(start, end, random.random(), fmt)
|
||||
#
|
||||
#
|
||||
# def randomDate(start, end, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return time.strftime(frmt, time.localtime(strTimeProp(start, end, random.random(), frmt)))
|
||||
#
|
||||
#
|
||||
# def randomTimestampList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return [randomTimestamp(start, end, frmt) for _ in range(n)]
|
||||
#
|
||||
#
|
||||
# def randomDateList(start, end, n, frmt='%Y-%m-%d %H:%M:%S'):
|
||||
# return [randomDate(start, end, frmt) for _ in range(n)]
|
||||
|
@ -1,182 +1,246 @@
|
||||
import datetime
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.views.generic import TemplateView
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, Max
|
||||
from django.shortcuts import redirect
|
||||
|
||||
|
||||
from users.models import User
|
||||
from assets.models import Asset
|
||||
from terminal.models import Session
|
||||
from orgs.utils import current_org
|
||||
from common.permissions import PermissionsMixin, IsValidUser
|
||||
from common.utils import timeit, lazyproperty
|
||||
|
||||
__all__ = ['IndexView']
|
||||
|
||||
|
||||
class IndexView(PermissionsMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
session_week = None
|
||||
session_month = None
|
||||
session_month_dates = []
|
||||
session_month_dates_archive = []
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
if request.user.is_common_user:
|
||||
return redirect('assets:user-asset-list')
|
||||
return super(IndexView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_user_count():
|
||||
return current_org.get_org_members().count()
|
||||
class MonthLoginMetricMixin:
|
||||
@lazyproperty
|
||||
def session_month(self):
|
||||
month_ago = timezone.now() - timezone.timedelta(days=30)
|
||||
session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
return session_month
|
||||
|
||||
@staticmethod
|
||||
def get_asset_count():
|
||||
return Asset.objects.all().count()
|
||||
@lazyproperty
|
||||
def session_month_dates(self):
|
||||
return self.session_month.dates('date_start', 'day')
|
||||
|
||||
@staticmethod
|
||||
def get_online_user_count():
|
||||
return len(set(Session.objects.filter(is_finished=False).values_list('user', flat=True)))
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [
|
||||
d.strftime('%m-%d') for d in self.session_month_dates
|
||||
] or ['0']
|
||||
return month_str
|
||||
|
||||
@staticmethod
|
||||
def get_online_session_count():
|
||||
return Session.objects.filter(is_finished=False).count()
|
||||
def get_cache_key(date, tp):
|
||||
date_str = date.strftime("%Y%m%d")
|
||||
key = "SESSION_MONTH_{}_{}".format(tp, date_str)
|
||||
return key
|
||||
|
||||
def __get_data_from_cache(self, date, tp):
|
||||
if date == timezone.now().date():
|
||||
return None
|
||||
cache_key = self.get_cache_key(date, tp)
|
||||
count = cache.get(cache_key)
|
||||
return count
|
||||
|
||||
def get_top5_user_a_week(self):
|
||||
return self.session_week.values('user').annotate(total=Count('user')).order_by('-total')[:5]
|
||||
def __set_data_to_cache(self, date, tp, count):
|
||||
cache_key = self.get_cache_key(date, tp)
|
||||
cache.set(cache_key, count, 3600*24*7)
|
||||
|
||||
def get_week_login_user_count(self):
|
||||
return self.session_week.values('user').distinct().count()
|
||||
@lazyproperty
|
||||
def user_disabled_total(self):
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.count()
|
||||
@lazyproperty
|
||||
def asset_disabled_total(self):
|
||||
return Asset.objects.filter(is_active=False).count()
|
||||
|
||||
def get_month_day_metrics(self):
|
||||
month_str = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
|
||||
return month_str
|
||||
def get_date_login_count(self, date):
|
||||
tp = "LOGIN"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date).count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_login_metrics(self):
|
||||
data = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(tzinfo=timezone.get_current_timezone())
|
||||
data.append(self.session_month.filter(date_start__range=(ds, de)).count())
|
||||
count = self.get_date_login_count(d)
|
||||
data.append(count)
|
||||
if len(data) == 0:
|
||||
data = [0]
|
||||
return data
|
||||
|
||||
def get_date_user_count(self, date):
|
||||
tp = "USER"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date)\
|
||||
.values('user').distinct().count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_active_user_metrics(self):
|
||||
if self.session_month_dates_archive:
|
||||
return [q.values('user').distinct().count()
|
||||
for q in self.session_month_dates_archive]
|
||||
else:
|
||||
return [0]
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
count = self.get_date_user_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
||||
def get_date_asset_count(self, date):
|
||||
tp = "ASSET"
|
||||
count = self.__get_data_from_cache(date, tp)
|
||||
if count is not None:
|
||||
return count
|
||||
count = Session.objects.filter(date_start__date=date) \
|
||||
.values('asset').distinct().count()
|
||||
self.__set_data_to_cache(date, tp, count)
|
||||
return count
|
||||
|
||||
def get_month_active_asset_metrics(self):
|
||||
if self.session_month_dates_archive:
|
||||
return [q.values('asset').distinct().count()
|
||||
for q in self.session_month_dates_archive]
|
||||
else:
|
||||
return [0]
|
||||
data = []
|
||||
for d in self.session_month_dates:
|
||||
count = self.get_date_asset_count(d)
|
||||
data.append(count)
|
||||
return data
|
||||
|
||||
def get_month_active_user_total(self):
|
||||
return self.session_month.values('user').distinct().count()
|
||||
@lazyproperty
|
||||
def month_active_user_total(self):
|
||||
count = self.session_month.values('user').distinct().count()
|
||||
return count
|
||||
|
||||
def get_month_inactive_user_total(self):
|
||||
count = current_org.get_org_members().count() - self.get_month_active_user_total()
|
||||
@lazyproperty
|
||||
def month_inactive_user_total(self):
|
||||
total = current_org.get_org_members().count()
|
||||
active = self.month_active_user_total
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
def get_month_active_asset_total(self):
|
||||
@lazyproperty
|
||||
def month_active_asset_total(self):
|
||||
return self.session_month.values('asset').distinct().count()
|
||||
|
||||
def get_month_inactive_asset_total(self):
|
||||
count = Asset.objects.all().count() - self.get_month_active_asset_total()
|
||||
@lazyproperty
|
||||
def month_inactive_asset_total(self):
|
||||
total = Asset.objects.all().count()
|
||||
active = self.month_active_asset_total
|
||||
count = total - active
|
||||
if count < 0:
|
||||
count = 0
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def get_user_disabled_total():
|
||||
return current_org.get_org_members().filter(is_active=False).count()
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'month_str': self.get_month_day_metrics(),
|
||||
'month_total_visit_count': self.get_month_login_metrics(),
|
||||
'month_user': self.get_month_active_user_metrics(),
|
||||
'mouth_asset': self.get_month_active_asset_metrics(),
|
||||
'month_user_active': self.month_active_user_total,
|
||||
'month_user_inactive': self.month_inactive_user_total,
|
||||
'month_user_disabled': self.user_disabled_total,
|
||||
'month_asset_active': self.month_active_asset_total,
|
||||
'month_asset_inactive': self.month_inactive_asset_total,
|
||||
'month_asset_disabled': self.asset_disabled_total,
|
||||
})
|
||||
return context
|
||||
|
||||
@staticmethod
|
||||
def get_asset_disabled_total():
|
||||
return Asset.objects.filter(is_active=False).count()
|
||||
|
||||
def get_week_top10_asset(self):
|
||||
assets = list(self.session_week.values('asset').annotate(total=Count('asset')).order_by('-total')[:10])
|
||||
for asset in assets:
|
||||
last_login = self.session_week.filter(asset=asset["asset"]).order_by('date_start').last()
|
||||
asset['last'] = last_login
|
||||
class WeekSessionMetricMixin:
|
||||
session_week = None
|
||||
|
||||
@lazyproperty
|
||||
def session_week(self):
|
||||
week_ago = timezone.now() - timezone.timedelta(weeks=1)
|
||||
session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
return session_week
|
||||
|
||||
def get_top5_user_a_week(self):
|
||||
users = self.session_week.values('user') \
|
||||
.annotate(total=Count('user')) \
|
||||
.order_by('-total')[:5]
|
||||
return users
|
||||
|
||||
def get_week_login_user_count(self):
|
||||
return self.session_week.values('user').distinct().count()
|
||||
|
||||
def get_week_login_asset_count(self):
|
||||
return self.session_week.count()
|
||||
|
||||
def get_week_top10_assets(self):
|
||||
assets = self.session_week.values("asset")\
|
||||
.annotate(total=Count("asset"))\
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
return assets
|
||||
|
||||
def get_week_top10_user(self):
|
||||
users = list(self.session_week.values('user').annotate(
|
||||
total=Count('asset')).order_by('-total')[:10])
|
||||
for user in users:
|
||||
last_login = self.session_week.filter(user=user["user"]).order_by('date_start').last()
|
||||
user['last'] = last_login
|
||||
def get_week_top10_users(self):
|
||||
users = self.session_week.values("user") \
|
||||
.annotate(total=Count("user")) \
|
||||
.annotate(last=Max("date_start")).order_by("-total")[:10]
|
||||
return users
|
||||
|
||||
def get_last10_sessions(self):
|
||||
sessions = self.session_week.order_by('-date_start')[:10]
|
||||
for session in sessions:
|
||||
try:
|
||||
session.avatar_url = User.objects.get(username=session.user).avatar_url()
|
||||
except User.DoesNotExist:
|
||||
session.avatar_url = User.objects.first().avatar_url()
|
||||
session.avatar_url = User.get_avatar_url("")
|
||||
return sessions
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
week_ago = timezone.now() - timezone.timedelta(weeks=1)
|
||||
month_ago = timezone.now() - timezone.timedelta(days=30)
|
||||
self.session_week = Session.objects.filter(date_start__gt=week_ago)
|
||||
self.session_month = Session.objects.filter(date_start__gt=month_ago)
|
||||
self.session_month_dates = self.session_month.dates('date_start', 'day')
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'user_visit_count_weekly': self.get_week_login_user_count(),
|
||||
'asset_visit_count_weekly': self.get_week_login_asset_count(),
|
||||
'user_visit_count_top_five': self.get_top5_user_a_week(),
|
||||
'last_login_ten': self.get_last10_sessions(),
|
||||
'week_asset_hot_ten': self.get_week_top10_assets(),
|
||||
'week_user_hot_ten': self.get_week_top10_users(),
|
||||
})
|
||||
return context
|
||||
|
||||
self.session_month_dates_archive = []
|
||||
time_min = datetime.datetime.min.time()
|
||||
time_max = datetime.datetime.max.time()
|
||||
|
||||
for d in self.session_month_dates:
|
||||
ds = datetime.datetime.combine(d, time_min).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
de = datetime.datetime.combine(d, time_max).replace(
|
||||
tzinfo=timezone.get_current_timezone())
|
||||
self.session_month_dates_archive.append(
|
||||
self.session_month.filter(date_start__range=(ds, de)))
|
||||
|
||||
context = {
|
||||
class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin, TemplateView):
|
||||
template_name = 'index.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
if request.user.is_common_user:
|
||||
return redirect('assets:user-asset-list')
|
||||
return super(IndexView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_user_count():
|
||||
return current_org.get_org_members().count()
|
||||
|
||||
@staticmethod
|
||||
def get_asset_count():
|
||||
return Asset.objects.all().count()
|
||||
|
||||
@staticmethod
|
||||
def get_online_user_count():
|
||||
count = Session.objects.filter(is_finished=False)\
|
||||
.values_list('user', flat=True).distinct().count()
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
def get_online_session_count():
|
||||
return Session.objects.filter(is_finished=False).count()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'assets_count': self.get_asset_count(),
|
||||
'users_count': self.get_user_count(),
|
||||
'online_user_count': self.get_online_user_count(),
|
||||
'online_asset_count': self.get_online_session_count(),
|
||||
'user_visit_count_weekly': self.get_week_login_user_count(),
|
||||
'asset_visit_count_weekly': self.get_week_login_asset_count(),
|
||||
'user_visit_count_top_five': self.get_top5_user_a_week(),
|
||||
'month_str': self.get_month_day_metrics(),
|
||||
'month_total_visit_count': self.get_month_login_metrics(),
|
||||
'month_user': self.get_month_active_user_metrics(),
|
||||
'mouth_asset': self.get_month_active_asset_metrics(),
|
||||
'month_user_active': self.get_month_active_user_total(),
|
||||
'month_user_inactive': self.get_month_inactive_user_total(),
|
||||
'month_user_disabled': self.get_user_disabled_total(),
|
||||
'month_asset_active': self.get_month_active_asset_total(),
|
||||
'month_asset_inactive': self.get_month_inactive_asset_total(),
|
||||
'month_asset_disabled': self.get_asset_disabled_total(),
|
||||
'week_asset_hot_ten': self.get_week_top10_asset(),
|
||||
'last_login_ten': self.get_last10_sessions(),
|
||||
'week_user_hot_ten': self.get_week_top10_user(),
|
||||
'app': _("Dashboard"),
|
||||
}
|
||||
|
||||
kwargs.update(context)
|
||||
return super(IndexView, self).get_context_data(**kwargs)
|
||||
})
|
||||
return context
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue