mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-21 11:33:03 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3533c01011 | ||
|
|
77ec2d5a86 | ||
|
|
8b707dc8f5 | ||
|
|
8dcc9c8d53 | ||
|
|
0ca0519ef1 | ||
|
|
8de50a0e83 | ||
|
|
64b7fb6a3d | ||
|
|
a114e173e0 | ||
|
|
402df81048 | ||
|
|
2a51eeef10 | ||
|
|
4b593e56f9 | ||
|
|
9f3c83b052 | ||
|
|
a864c38238 | ||
|
|
547408222d | ||
|
|
0ad26254ef | ||
|
|
b7e138f919 | ||
|
|
2ec415fd84 | ||
|
|
b3c12e8861 | ||
|
|
da8f30fc72 | ||
|
|
cddb0ce537 | ||
|
|
124e26fa32 | ||
|
|
59c689cd61 | ||
|
|
0cc36b1302 | ||
|
|
0ac9d19252 | ||
|
|
d9e497e8be | ||
|
|
e18ae7f82c | ||
|
|
e5286686b9 | ||
|
|
332316b0af | ||
|
|
8f666785d2 | ||
|
|
0ed4b84b63 | ||
|
|
97591e6f03 | ||
|
|
5d5d8ab32a | ||
|
|
5489f3ae36 | ||
|
|
ce7a3d1f33 |
@@ -1,9 +0,0 @@
|
||||
.git
|
||||
logs/*
|
||||
data/*
|
||||
.github
|
||||
tmp/*
|
||||
django.db
|
||||
celerybeat.pid
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
17
.github/ISSUE_TEMPLATE.md
vendored
17
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,17 +0,0 @@
|
||||
[简述你的问题]
|
||||
|
||||
|
||||
##### 使用版本
|
||||
[请提供你使用的JumpServer版本 如 2.0.1 注: 1.4及以下版本不再提供支持]
|
||||
|
||||
##### 问题复现步骤
|
||||
1. [步骤1]
|
||||
2. [步骤2]
|
||||
|
||||
##### 具体表现[截图可能会更好些,最好能截全]
|
||||
|
||||
|
||||
##### 其他
|
||||
|
||||
|
||||
[注:] 完成后请关闭 issue
|
||||
44
.github/release-config.yml
vendored
44
.github/release-config.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name-template: 'v$RESOLVED_VERSION'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
categories:
|
||||
- title: '🌱 新功能 Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- 'feat'
|
||||
- '新功能'
|
||||
- title: '🚀 性能优化 Optimization'
|
||||
labels:
|
||||
- 'perf'
|
||||
- 'opt'
|
||||
- 'refactor'
|
||||
- 'Optimization'
|
||||
- '优化'
|
||||
- title: '🐛 Bug修复 Bug Fixes'
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- title: '🧰 其它 Maintenance'
|
||||
labels:
|
||||
- 'chore'
|
||||
- 'docs'
|
||||
exclude-labels:
|
||||
- 'no'
|
||||
- '无需处理'
|
||||
- 'wontfix'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- 'major'
|
||||
minor:
|
||||
labels:
|
||||
- 'minor'
|
||||
patch:
|
||||
labels:
|
||||
- 'patch'
|
||||
default: patch
|
||||
template: |
|
||||
## 版本变化 What’s Changed
|
||||
$CHANGES
|
||||
46
.github/workflows/release-drafter.yml
vendored
46
.github/workflows/release-drafter.yml
vendored
@@ -1,46 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
name: Create Release And Upload assets
|
||||
|
||||
jobs:
|
||||
create-realese:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
TAG=$(basename ${GITHUB_REF})
|
||||
VERSION=${TAG/v/}
|
||||
echo "::set-output name=TAG::$TAG"
|
||||
echo "::set-output name=VERSION::$VERSION"
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
config-name: release-config.yml
|
||||
version: ${{ steps.get_version.outputs.TAG }}
|
||||
tag: ${{ steps.get_version.outputs.TAG }}
|
||||
|
||||
build-and-release:
|
||||
needs: create-realese
|
||||
name: Build and Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build it and upload
|
||||
uses: jumpserver/action-build-upload-assets@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-realese.outputs.upload_url }}
|
||||
74
.gitignore
vendored
74
.gitignore
vendored
@@ -1,40 +1,46 @@
|
||||
*.py[cod]
|
||||
.idea
|
||||
test.py
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.swp
|
||||
.env
|
||||
env
|
||||
env*
|
||||
venv
|
||||
dist
|
||||
build
|
||||
db.sqlite3
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
_mailinglist
|
||||
dump.rdb
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
lib
|
||||
lib64
|
||||
__pycache__
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
.cache/
|
||||
.idea/
|
||||
db.sqlite3
|
||||
config.py
|
||||
config.yml
|
||||
nosetests.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
||||
# Mr Developer
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
*.log
|
||||
host_rsa_key
|
||||
*.bat
|
||||
tags
|
||||
jumpserver.iml
|
||||
.python-version
|
||||
tmp/*
|
||||
sessions/*
|
||||
media
|
||||
celerybeat.pid
|
||||
django.db
|
||||
celerybeat-schedule.db
|
||||
data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
logs/*
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
release/*
|
||||
releashe
|
||||
keys/*
|
||||
jumpserver.conf
|
||||
nohup.out
|
||||
tmp/*
|
||||
|
||||
63
Dockerfile
63
Dockerfile
@@ -1,26 +1,37 @@
|
||||
FROM registry.fit2cloud.com/public/python:v3
|
||||
MAINTAINER Jumpserver Team <ibuler@qq.com>
|
||||
|
||||
WORKDIR /opt/jumpserver
|
||||
RUN useradd jumpserver
|
||||
|
||||
COPY ./requirements /tmp/requirements
|
||||
|
||||
RUN yum -y install epel-release && \
|
||||
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
|
||||
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
|
||||
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && pip install wheel && \
|
||||
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt || pip install -r requirements.txt
|
||||
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
RUN echo > config.yml
|
||||
VOLUME /opt/jumpserver/data
|
||||
VOLUME /opt/jumpserver/logs
|
||||
|
||||
ENV LANG=zh_CN.UTF-8
|
||||
ENV LC_ALL=zh_CN.UTF-8
|
||||
|
||||
EXPOSE 8070
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
FROM alpine
|
||||
MAINTAINER xRain <xrain@simcu.com>
|
||||
RUN apk add --update openssh sshpass python py-mysqldb py-psutil py-crypto && \
|
||||
rm -rf /var/cache/apk/*
|
||||
COPY . /jumpserver
|
||||
WORKDIR /jumpserver
|
||||
RUN python /jumpserver/install/docker/get-pip.py && \
|
||||
pip install -r /jumpserver/install/docker/piprequires.txt && \
|
||||
rm -rf /jumpserver/docs && \
|
||||
cp /jumpserver/install/docker/run.sh /run.sh && \
|
||||
rm -rf /etc/motd && chmod +x /run.sh && \
|
||||
rm -rf /jumpserver/keys && \
|
||||
rm -rf /jumpserver/logs && \
|
||||
rm -rf /home && \
|
||||
rm -rf /etc/ssh && \
|
||||
rm -rf /etc/shadow && \
|
||||
rm -rf /etc/passwd && \
|
||||
cp -r /jumpserver/install/docker/useradd /usr/sbin/useradd && \
|
||||
cp -r /jumpserver/install/docker/userdel /usr/sbin/userdel && \
|
||||
chmod +x /usr/sbin/useradd && \
|
||||
chmod +x /usr/sbin/userdel && \
|
||||
mkdir -p /data/home && \
|
||||
mkdir -p /data/logs && \
|
||||
mkdir -p /data/keys && \
|
||||
mkdir -p /data/ssh && \
|
||||
cp -r /jumpserver/install/docker/shadow /data/shadow && \
|
||||
cp -r /jumpserver/install/docker/passwd /data/passwd && \
|
||||
ln -s /data/logs /jumpserver/logs && \
|
||||
ln -s /data/keys /jumpserver/keys && \
|
||||
ln -s /data/home /home && \
|
||||
ln -s /data/ssh /etc/ssh && \
|
||||
ln -s /data/passwd /etc/passwd && \
|
||||
ln -s /data/shadow /etc/shadow && \
|
||||
chmod -R 777 /jumpserver
|
||||
VOLUME /data
|
||||
EXPOSE 80 22
|
||||
CMD /run.sh
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -1,4 +1,4 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
@@ -336,4 +336,4 @@ This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
Public License instead of this License.
|
||||
|
||||
262
README.md
262
README.md
@@ -1,234 +1,88 @@
|
||||
# JumpServer 多云环境下更好用的堡垒机
|
||||
## 写在前面
|
||||
- 版本号变更 2.0 -> 0.2版本 3.0 -> 0.3版本
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://hub.docker.com/u/jumpserver)
|
||||
#欢迎使用Jumpserver
|
||||
**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能。基于ssh协议来管理,客户端无需安装agent。
|
||||
支持常见系统:
|
||||
1. CentOS, RedHat, Fedora, Amazon Linux
|
||||
2. Debian
|
||||
3. SUSE, Ubuntu
|
||||
4. FreeBSD
|
||||
5. 其他ssh协议硬件设备
|
||||
|
||||
JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。
|
||||
###截图:
|
||||
|
||||
JumpServer 使用 Python / Django 为主进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 方案,交互界面美观、用户体验好。
|
||||
首页
|
||||
|
||||

|
||||
|
||||
JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
|
||||
WebTerminal:
|
||||
|
||||
改变世界,从一点点开始。
|
||||

|
||||
|
||||
> 注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
|
||||
Web批量执行命令
|
||||
|
||||
## 特色优势
|
||||

|
||||
|
||||
- 开源: 零门槛,线上快速获取和安装, 修复版本视情况而定;
|
||||
, 修复版本视情况而定- 分布式: 轻松支持大规模并发访问;
|
||||
- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验;
|
||||
- 多云支持: 一套系统,同时管理不同云上面的资产;
|
||||
- 云端存储: 审计录像云端存储,永不丢失;
|
||||
- 多租户: 一套系统,多个子公司和部门同时使用。
|
||||
录像回放
|
||||
|
||||
## 版本说明
|
||||

|
||||
|
||||
自 v2.0.0 发布后, JumpServer 版本号命名将变更为:v大版本.功能版本.Bug修复版本。比如:
|
||||
跳转和批量命令
|
||||
|
||||
```
|
||||
v2.0.1 是 v2.0.0 之后的Bug修复版本;
|
||||
v2.1.0 是 v2.0.0 之后的功能版本。
|
||||
```
|
||||

|
||||
|
||||
像其它优秀开源项目一样,JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如:
|
||||
命令统计
|
||||
|
||||
```
|
||||
在 v2.4 发布前,我们会同时维护 v2.1、v2.2、v2.3;
|
||||
在 v2.4 发布后,我们会同时维护 v2.2、v2.3、v2.4;v2.1 会停止维护。
|
||||
```
|
||||

|
||||
|
||||
## 功能列表
|
||||
### 文档
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td rowspan="8">身份认证<br>Authentication</td>
|
||||
<td rowspan="5">登录认证</td>
|
||||
<td>资源统一登录与认证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>LDAP/AD 认证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RADIUS 认证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OpenID 认证(实现单点登录)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CAS 认证 (实现单点登录)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">MFA认证</td>
|
||||
<td>MFA 二次认证(Google Authenticator)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>RADIUS 二次认证</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>登录复核(X-PACK)</td>
|
||||
<td>用户登录行为受管理员的监管与控制</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="11">账号管理<br>Account</td>
|
||||
<td rowspan="2">集中账号</td>
|
||||
<td>管理用户管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>系统用户管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">统一密码</td>
|
||||
<td>资产密码托管</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自动生成密码</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>自动推送密码</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>密码过期设置</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">批量改密(X-PACK)</td>
|
||||
<td>定期批量改密</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多种密码策略</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>多云纳管(X-PACK)</td>
|
||||
<td>对私有云、公有云资产自动统一纳管</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>收集用户(X-PACK)</td>
|
||||
<td>自定义任务定期收集主机用户</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>密码匣子(X-PACK)</td>
|
||||
<td>统一对资产主机的用户密码进行查看、更新、测试操作</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="15">授权控制<br>Authorization</td>
|
||||
<td>多维授权</td>
|
||||
<td>对用户、用户组、资产、资产节点、应用以及系统用户进行授权</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">资产授权</td>
|
||||
<td>资产以树状结构进行展示</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>资产和节点均可灵活授权</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>节点内资产自动继承授权</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>子节点自动继承父节点授权</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">应用授权</td>
|
||||
<td>实现更细粒度的应用级授权</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MySQL 数据库应用、RemoteApp 远程应用(X-PACK)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>动作授权</td>
|
||||
<td>实现对授权资产的文件上传、下载以及连接动作的控制</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>时间授权</td>
|
||||
<td>实现对授权资源使用时间段的限制</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>特权指令</td>
|
||||
<td>实现对特权指令的使用(支持黑白名单)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>命令过滤</td>
|
||||
<td>实现对授权系统用户所执行的命令进行控制</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文件传输</td>
|
||||
<td>SFTP 文件上传/下载</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文件管理</td>
|
||||
<td>实现 Web SFTP 文件管理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>工单管理(X-PACK)</td>
|
||||
<td>支持对用户登录请求行为进行控制</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>组织管理(X-PACK)</td>
|
||||
<td>实现多租户管理与权限隔离</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="7">安全审计<br>Audit</td>
|
||||
<td>操作审计</td>
|
||||
<td>用户操作行为审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">会话审计</td>
|
||||
<td>在线会话内容审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>历史会话内容审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">录像审计</td>
|
||||
<td>支持对 Linux、Windows 等资产操作的录像进行回放审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>支持对 RemoteApp(X-PACK)、MySQL 等应用操作的录像进行回放审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>指令审计</td>
|
||||
<td>支持对资产和应用等操作的命令进行审计</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文件传输</td>
|
||||
<td>可对文件的上传、下载记录进行审计</td>
|
||||
</tr>
|
||||
</table>
|
||||
* [访问wiki](https://github.com/jumpserver/jumpserver/wiki)
|
||||
* [概览](https://github.com/jumpserver/jumpserver/wiki/%E6%A6%82%E8%A7%88)
|
||||
* [名词解释](https://github.com/jumpserver/jumpserver/wiki/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A)
|
||||
* [常见问题](https://github.com/jumpserver/jumpserver/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
|
||||
* 安装基于:[RedHat 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-RedHat-%E7%9A%84%E7%B3%BB%E7%BB%9F),[Debian 的系统](https://github.com/jumpserver/jumpserver/wiki/%E5%9F%BA%E4%BA%8E-Debian-%E7%9A%84%E7%B3%BB%E7%BB%9F)
|
||||
* [快速开始](https://github.com/jumpserver/jumpserver/wiki/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)
|
||||
* [安装图解](https://github.com/jumpserver/jumpserver/wiki/%E5%AE%89%E8%A3%85%E5%9B%BE%E8%A7%A3)
|
||||
* [应用图解](https://github.com/jumpserver/jumpserver/wiki/%E5%BA%94%E7%94%A8%E5%9B%BE%E8%A7%A3)
|
||||
|
||||
## 快速开始
|
||||
### 特点
|
||||
|
||||
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
|
||||
- [完整文档](https://docs.jumpserver.org)
|
||||
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
|
||||
* 完全开源,GPL授权
|
||||
* Python编写,容易再次开发
|
||||
* 实现了跳板机基本功能,认证、授权、审计
|
||||
* 集成了Ansible,批量命令等
|
||||
* 支持WebTerminal
|
||||
* Bootstrap编写,界面美观
|
||||
* 自动收集硬件信息
|
||||
* 录像回放
|
||||
* 命令搜索
|
||||
* 实时监控
|
||||
* 批量上传下载
|
||||
|
||||
## 案例研究
|
||||
### 其它
|
||||
|
||||
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147);
|
||||
- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882);
|
||||
- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851);
|
||||
- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516);
|
||||
- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732);
|
||||
- [中通快递:JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708);
|
||||
- [东方明珠:JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687);
|
||||
- [江苏农信:JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。
|
||||
[Jumpserver官网](http://www.jumpserver.org)
|
||||
|
||||
## 安全说明
|
||||
[论坛](http://bbs.jumpserver.org)
|
||||
|
||||
JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/) 部署安装.
|
||||
[demo站点](http://demo.jumpserver.org)
|
||||
|
||||
如果你发现安全问题,可以直接联系我们:
|
||||
交流群: 552054376
|
||||
|
||||
- ibuler@fit2cloud.com
|
||||
- support@fit2cloud.com
|
||||
- 400-052-0755
|
||||
### 团队
|
||||
|
||||
## License & Copyright
|
||||

|
||||
|
||||
Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
### License & Copyright
|
||||
|
||||
Copyright (c) 2014-2017 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
|
||||
60
README_EN.md
60
README_EN.md
@@ -1,60 +0,0 @@
|
||||
## Jumpserver
|
||||
|
||||

|
||||

|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
|
||||
----
|
||||
|
||||
- [中文版](https://github.com/jumpserver/jumpserver/blob/master/README_EN.md)
|
||||
|
||||
Jumpserver is the first fully open source bastion in the world, based on the GNU GPL v2.0 open source protocol. Jumpserver is a professional operation and maintenance audit system conforms to 4A specifications.
|
||||
|
||||
Jumpserver is developed using Python / Django, conforms to the Web 2.0 specification, and is equipped with the industry-leading Web Terminal solution which have beautiful interface and great user experience.
|
||||
|
||||
Jumpserver adopts a distributed architecture to support multi-branch deployment across multiple areas. The central node provides APIs, and login nodes are deployed in each branch. It can be scaled horizontally without concurrency restrictions.
|
||||
|
||||
Change the world, starting from little things.
|
||||
|
||||
----
|
||||
|
||||
### Features
|
||||
|
||||

|
||||
|
||||
### Start
|
||||
|
||||
Quick start [Docker Install](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
|
||||
Step by Step deployment. [Docs](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
Full documentation [Docs](http://docs.jumpserver.org)
|
||||
|
||||
### Demo、Video 和 Snapshot
|
||||
|
||||
We provide online demo, demo video and screenshots to get you started quickly.
|
||||
|
||||
[Demo](https://demo.jumpserver.org/auth/login/?next=/)
|
||||
[Video](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
|
||||
[Snapshot](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
|
||||
We provide the SDK for your other systems to quickly interact with the Jumpserver API.
|
||||
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver other components use this SDK to complete the interaction.
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK thanks to 恺珺 for provide Java SDK
|
||||
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
56
Vagrantfile
vendored
56
Vagrantfile
vendored
@@ -1,56 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
# Every Vagrant development environment requires a box. You can search for
|
||||
# boxes at https://vagrantcloud.com/search.
|
||||
config.vm.box_check_update = false
|
||||
config.vm.box = "centos/7"
|
||||
config.vm.hostname = "jumpserver"
|
||||
config.vm.network "private_network", ip: "172.17.8.101"
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = "4096"
|
||||
vb.cpus = 2
|
||||
vb.name = "jumpserver"
|
||||
end
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant", type: "rsync",
|
||||
rsync__verbose: true,
|
||||
rsync__exclude: ['.git*', 'node_modules*','*.log','*.box','Vagrantfile']
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
## 设置yum的阿里云源
|
||||
sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
|
||||
sudo sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
|
||||
sudo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
|
||||
sudo yum makecache
|
||||
|
||||
## 安装依赖包
|
||||
sudo yum install -y python36 python36-devel python36-pip \
|
||||
libtiff-devel libjpeg-devel libzip-devel freetype-devel \
|
||||
lcms2-devel libwebp-devel tcl-devel tk-devel sshpass \
|
||||
openldap-devel mariadb-devel mysql-devel libffi-devel \
|
||||
openssh-clients telnet openldap-clients gcc
|
||||
|
||||
## 配置pip阿里云源
|
||||
mkdir /home/vagrant/.pip
|
||||
cat << EOF | sudo tee -a /home/vagrant/.pip/pip.conf
|
||||
[global]
|
||||
timeout = 6000
|
||||
index-url = https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
[install]
|
||||
use-mirrors = true
|
||||
mirrors = https://mirrors.aliyun.com/pypi/simple/
|
||||
trusted-host=mirrors.aliyun.com
|
||||
EOF
|
||||
|
||||
python3.6 -m venv /home/vagrant/venv
|
||||
source /home/vagrant/venv/bin/activate
|
||||
echo 'source /home/vagrant/venv/bin/activate' >> /home/vagrant/.bash_profile
|
||||
SHELL
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
@@ -1,2 +0,0 @@
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
@@ -1,20 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
|
||||
from .. import models
|
||||
from .. import serializers
|
||||
from ..hands import IsOrgAdminOrAppUser
|
||||
|
||||
__all__ = [
|
||||
'DatabaseAppViewSet',
|
||||
]
|
||||
|
||||
|
||||
class DatabaseAppViewSet(OrgBulkModelViewSet):
|
||||
model = models.DatabaseApp
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.DatabaseAppSerializer
|
||||
@@ -1,27 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from ..hands import IsOrgAdmin, IsAppUser
|
||||
from ..models import RemoteApp
|
||||
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppViewSet(OrgBulkModelViewSet):
|
||||
model = RemoteApp
|
||||
filter_fields = ('name', 'type', 'comment')
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = RemoteAppSerializer
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
||||
model = RemoteApp
|
||||
permission_classes = (IsAppUser, )
|
||||
serializer_class = RemoteAppConnectionInfoSerializer
|
||||
@@ -1,7 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApplicationsConfig(AppConfig):
|
||||
name = 'applications'
|
||||
@@ -1,63 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
# RemoteApp
|
||||
|
||||
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
|
||||
|
||||
REMOTE_APP_TYPE_CHROME = 'chrome'
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
|
||||
REMOTE_APP_TYPE_CUSTOM = 'custom'
|
||||
|
||||
# Fields attribute write_only default => False
|
||||
|
||||
REMOTE_APP_TYPE_CHROME_FIELDS = [
|
||||
{'name': 'chrome_target'},
|
||||
{'name': 'chrome_username'},
|
||||
{'name': 'chrome_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
|
||||
{'name': 'mysql_workbench_ip'},
|
||||
{'name': 'mysql_workbench_name'},
|
||||
{'name': 'mysql_workbench_username'},
|
||||
{'name': 'mysql_workbench_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
|
||||
{'name': 'vmware_target'},
|
||||
{'name': 'vmware_username'},
|
||||
{'name': 'vmware_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_CUSTOM_FIELDS = [
|
||||
{'name': 'custom_cmdline'},
|
||||
{'name': 'custom_target'},
|
||||
{'name': 'custom_username'},
|
||||
{'name': 'custom_password', 'write_only': True}
|
||||
]
|
||||
|
||||
REMOTE_APP_TYPE_FIELDS_MAP = {
|
||||
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
|
||||
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
|
||||
}
|
||||
|
||||
REMOTE_APP_TYPE_CHOICES = (
|
||||
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
|
||||
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
|
||||
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'),
|
||||
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
|
||||
)
|
||||
|
||||
|
||||
# DatabaseApp
|
||||
|
||||
|
||||
DATABASE_APP_TYPE_MYSQL = 'mysql'
|
||||
|
||||
DATABASE_APP_TYPE_CHOICES = (
|
||||
(DATABASE_APP_TYPE_MYSQL, 'MySQL'),
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
jumpserver.__app__.hands.py
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This app depends other apps api, function .. should be import or write mack here.
|
||||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by JumpServer Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
@@ -1,42 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-20 11:04
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RemoteApp',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
|
||||
('path', models.CharField(max_length=128, verbose_name='App path')),
|
||||
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'RemoteApp',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='remoteapp',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-09-09 09:57
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('applications', '0001_initial'),
|
||||
('perms', '0009_remoteapppermission_system_users'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='remoteapp',
|
||||
name='system_user',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.11 on 2019-12-10 08:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('applications', '0002_remove_remoteapp_system_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='remoteapp',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type'),
|
||||
),
|
||||
]
|
||||
@@ -1,38 +0,0 @@
|
||||
# Generated by Django 2.1.11 on 2019-12-18 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('applications', '0003_auto_20191210_1659'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DatabaseApp',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=128, verbose_name='Type')),
|
||||
('host', models.CharField(db_index=True, max_length=128, verbose_name='Host')),
|
||||
('port', models.IntegerField(default=3306, verbose_name='Port')),
|
||||
('database', models.CharField(blank=True, db_index=True, max_length=128, null=True, verbose_name='Database')),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'DatabaseApp',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='databaseapp',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
]
|
||||
@@ -1,2 +0,0 @@
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
@@ -1,42 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.mixins import CommonModelMixin
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = ['DatabaseApp']
|
||||
|
||||
|
||||
class DatabaseApp(CommonModelMixin, OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
type = models.CharField(
|
||||
default=const.DATABASE_APP_TYPE_MYSQL,
|
||||
choices=const.DATABASE_APP_TYPE_CHOICES,
|
||||
max_length=128, verbose_name=_('Type')
|
||||
)
|
||||
host = models.CharField(
|
||||
max_length=128, verbose_name=_('Host'), db_index=True
|
||||
)
|
||||
port = models.IntegerField(default=3306, verbose_name=_('Port'))
|
||||
database = models.CharField(
|
||||
max_length=128, blank=True, null=True, verbose_name=_('Database'),
|
||||
db_index=True
|
||||
)
|
||||
comment = models.TextField(
|
||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'name'), ]
|
||||
verbose_name = _("DatabaseApp")
|
||||
ordering = ('name', )
|
||||
@@ -1,78 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from common.fields.model import EncryptJsonDictTextField
|
||||
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteApp',
|
||||
]
|
||||
|
||||
|
||||
class RemoteApp(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
asset = models.ForeignKey(
|
||||
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
)
|
||||
type = models.CharField(
|
||||
default=const.REMOTE_APP_TYPE_CHROME,
|
||||
choices=const.REMOTE_APP_TYPE_CHOICES,
|
||||
max_length=128, verbose_name=_('App type')
|
||||
)
|
||||
path = models.CharField(
|
||||
max_length=128, blank=False, null=False,
|
||||
verbose_name=_('App path')
|
||||
)
|
||||
params = EncryptJsonDictTextField(
|
||||
max_length=4096, default={}, blank=True, null=True,
|
||||
verbose_name=_('Parameters')
|
||||
)
|
||||
created_by = models.CharField(
|
||||
max_length=32, null=True, blank=True, verbose_name=_('Created by')
|
||||
)
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||
)
|
||||
comment = models.TextField(
|
||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("RemoteApp")
|
||||
unique_together = [('org_id', 'name')]
|
||||
ordering = ('name', )
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
"""
|
||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||
"""
|
||||
_parameters = list()
|
||||
_parameters.append(self.type)
|
||||
path = '\"%s\"' % self.path
|
||||
_parameters.append(path)
|
||||
for field in const.REMOTE_APP_TYPE_FIELDS_MAP[self.type]:
|
||||
value = self.params.get(field['name'])
|
||||
if value is None:
|
||||
continue
|
||||
_parameters.append(value)
|
||||
_parameters = ' '.join(_parameters)
|
||||
return _parameters
|
||||
|
||||
@property
|
||||
def asset_info(self):
|
||||
return {
|
||||
'id': self.asset.id,
|
||||
'hostname': self.asset.hostname
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
from .remote_app import *
|
||||
from .database_app import *
|
||||
@@ -1,26 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from .. import models
|
||||
|
||||
__all__ = [
|
||||
'DatabaseAppSerializer',
|
||||
]
|
||||
|
||||
|
||||
class DatabaseAppSerializer(BulkOrgResourceModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.DatabaseApp
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'type', 'get_type_display', 'host', 'port',
|
||||
'database', 'comment', 'created_by', 'date_created', 'date_updated',
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'date_updated'
|
||||
'get_type_display',
|
||||
]
|
||||
@@ -1,86 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
import copy
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from common.fields.serializer import CustomMetaDictField
|
||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||
|
||||
from .. import const
|
||||
from ..models import RemoteApp
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppParamsDictField(CustomMetaDictField):
|
||||
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
|
||||
default_type = const.REMOTE_APP_TYPE_CHROME
|
||||
convert_key_remove_type_prefix = False
|
||||
convert_key_to_upper = False
|
||||
|
||||
|
||||
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
||||
params = RemoteAppParamsDictField()
|
||||
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'asset_info', 'type', 'get_type_display',
|
||||
'path', 'params', 'date_created', 'created_by', 'comment',
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'asset_info',
|
||||
'get_type_display'
|
||||
]
|
||||
|
||||
def process_params(self, instance, validated_data):
|
||||
new_params = copy.deepcopy(validated_data.get('params', {}))
|
||||
tp = validated_data.get('type', '')
|
||||
|
||||
if tp != instance.type:
|
||||
return new_params
|
||||
|
||||
old_params = instance.params
|
||||
fields = self.type_fields_map.get(instance.type, [])
|
||||
for field in fields:
|
||||
if not field.get('write_only', False):
|
||||
continue
|
||||
field_name = field['name']
|
||||
new_value = new_params.get(field_name, '')
|
||||
old_value = old_params.get(field_name, '')
|
||||
field_value = new_value if new_value else old_value
|
||||
new_params[field_name] = field_value
|
||||
|
||||
return new_params
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
params = self.process_params(instance, validated_data)
|
||||
validated_data['params'] = params
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||
parameter_remote_app = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'parameter_remote_app',
|
||||
]
|
||||
read_only_fields = ['parameter_remote_app']
|
||||
|
||||
@staticmethod
|
||||
def get_parameter_remote_app(obj):
|
||||
parameter = {
|
||||
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
|
||||
'working_directory': '',
|
||||
'parameters': obj.parameters,
|
||||
}
|
||||
return parameter
|
||||
@@ -1,7 +0,0 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from common import api as capi
|
||||
from .. import api
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
|
||||
router.register(r'database-apps', api.DatabaseAppViewSet, 'database-app')
|
||||
|
||||
urlpatterns = [
|
||||
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
|
||||
]
|
||||
|
||||
old_version_urlpatterns = [
|
||||
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)
|
||||
]
|
||||
|
||||
urlpatterns += router.urls + old_version_urlpatterns
|
||||
@@ -1,7 +0,0 @@
|
||||
# coding:utf-8
|
||||
from django.urls import path
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
urlpatterns = [
|
||||
]
|
||||
@@ -1,11 +0,0 @@
|
||||
from .admin_user import *
|
||||
from .asset import *
|
||||
from .label import *
|
||||
from .system_user import *
|
||||
from .system_user_relation import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .asset_user import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
@@ -1,106 +0,0 @@
|
||||
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import test_admin_user_connectivity_manual
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AdminUserViewSet', 'ReplaceNodesAdminUserApi',
|
||||
'AdminUserTestConnectiveApi', 'AdminUserAuthApi',
|
||||
'AdminUserAssetsListView',
|
||||
]
|
||||
|
||||
|
||||
class AdminUserViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
model = AdminUser
|
||||
filter_fields = ("name", "username")
|
||||
search_fields = filter_fields
|
||||
serializer_class = serializers.AdminUserSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(assets_amount=Count('assets'))
|
||||
return queryset
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
has_related_asset = instance.assets.exists()
|
||||
if has_related_asset:
|
||||
data = {'msg': _('Deleted failed, There are related assets')}
|
||||
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
class AdminUserAuthApi(generics.UpdateAPIView):
|
||||
model = AdminUser
|
||||
serializer_class = serializers.AdminUserAuthSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class ReplaceNodesAdminUserApi(generics.UpdateAPIView):
|
||||
model = AdminUser
|
||||
serializer_class = serializers.ReplaceNodeAdminUserSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
serializer = self.serializer_class(data=request.data)
|
||||
if serializer.is_valid():
|
||||
nodes = serializer.validated_data['nodes']
|
||||
assets = []
|
||||
for node in nodes:
|
||||
assets.extend([asset.id for asset in node.get_all_assets()])
|
||||
|
||||
with transaction.atomic():
|
||||
Asset.objects.filter(id__in=assets).update(admin_user=admin_user)
|
||||
|
||||
return Response({"msg": "ok"})
|
||||
else:
|
||||
return Response({'error': serializer.errors}, status=400)
|
||||
|
||||
|
||||
class AdminUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset admin user assets_connectivity
|
||||
"""
|
||||
model = AdminUser
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
admin_user = self.get_object()
|
||||
task = test_admin_user_connectivity_manual.delay(admin_user)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AdminUserAssetsListView(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSimpleSerializer
|
||||
filter_fields = ("hostname", "ip")
|
||||
http_method_names = ['get']
|
||||
search_fields = filter_fields
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(AdminUser, pk=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
admin_user = self.get_object()
|
||||
return admin_user.get_related_assets()
|
||||
@@ -1,126 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.generics import RetrieveAPIView
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from ..models import Asset, Node, Platform
|
||||
from .. import serializers
|
||||
from ..tasks import (
|
||||
update_asset_hardware_info_manual, test_asset_connectivity_manual
|
||||
)
|
||||
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'AssetPlatformRetrieveApi',
|
||||
'AssetGatewayListApi', 'AssetPlatformViewSet',
|
||||
'AssetTaskCreateApi',
|
||||
]
|
||||
|
||||
|
||||
class AssetViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
model = Asset
|
||||
filter_fields = (
|
||||
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
|
||||
"is_active"
|
||||
)
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetSerializer,
|
||||
'display': serializers.AssetDisplaySerializer,
|
||||
}
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
|
||||
|
||||
def set_assets_node(self, assets):
|
||||
if not isinstance(assets, list):
|
||||
assets = [assets]
|
||||
node_id = self.request.query_params.get('node_id')
|
||||
if not node_id:
|
||||
return
|
||||
node = get_object_or_none(Node, pk=node_id)
|
||||
if not node:
|
||||
return
|
||||
node.assets.add(*assets)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
assets = serializer.save()
|
||||
self.set_assets_node(assets)
|
||||
|
||||
|
||||
class AssetPlatformRetrieveApi(RetrieveAPIView):
|
||||
queryset = Platform.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
|
||||
def get_object(self):
|
||||
asset_pk = self.kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_pk)
|
||||
return asset.platform
|
||||
|
||||
|
||||
class AssetPlatformViewSet(ModelViewSet):
|
||||
queryset = Platform.objects.all()
|
||||
permission_classes = (IsSuperUser,)
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
filter_fields = ['name', 'base']
|
||||
search_fields = ['name']
|
||||
|
||||
def get_permissions(self):
|
||||
if self.request.method.lower() in ['get', 'options']:
|
||||
self.permission_classes = (IsOrgAdmin,)
|
||||
return super().get_permissions()
|
||||
|
||||
def check_object_permissions(self, request, obj):
|
||||
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
|
||||
self.permission_denied(
|
||||
request, message={"detail": "Internal platform"}
|
||||
)
|
||||
return super().check_object_permissions(request, obj)
|
||||
|
||||
|
||||
class AssetTaskCreateApi(generics.CreateAPIView):
|
||||
model = Asset
|
||||
serializer_class = serializers.AssetTaskSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
instance = get_object_or_404(Asset, pk=pk)
|
||||
return instance
|
||||
|
||||
def perform_create(self, serializer):
|
||||
asset = self.get_object()
|
||||
action = serializer.validated_data["action"]
|
||||
if action == "refresh":
|
||||
task = update_asset_hardware_info_manual.delay(asset)
|
||||
else:
|
||||
task = test_asset_connectivity_manual.delay(asset)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
|
||||
|
||||
class AssetGatewayListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.GatewayWithAuthSerializer
|
||||
model = Asset
|
||||
|
||||
def get_queryset(self):
|
||||
asset_id = self.kwargs.get('pk')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
if not asset.domain:
|
||||
return []
|
||||
queryset = asset.domain.gateways.filter(protocol='ssh')
|
||||
return queryset
|
||||
@@ -1,157 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import coreapi
|
||||
from django.conf import settings
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import generics, filters
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.mixins import CommonApiMixin
|
||||
from ..backends import AssetUserManager
|
||||
from ..models import Asset, Node, SystemUser
|
||||
from .. import serializers
|
||||
from ..tasks import (
|
||||
test_asset_users_connectivity_manual, push_system_user_a_asset_manual
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoViewSet', 'AssetUserTaskCreateAPI',
|
||||
]
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AssetUserFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
kwargs = {}
|
||||
for field in view.filter_fields:
|
||||
value = request.GET.get(field)
|
||||
if not value:
|
||||
continue
|
||||
if field == "node_id":
|
||||
value = get_object_or_none(Node, pk=value)
|
||||
kwargs["node"] = value
|
||||
continue
|
||||
elif field == "asset_id":
|
||||
field = "asset"
|
||||
kwargs[field] = value
|
||||
if kwargs:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
logger.debug("Filter {}".format(kwargs))
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
value = request.GET.get('search')
|
||||
if not value:
|
||||
return queryset
|
||||
queryset = queryset.search(value)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserLatestFilterBackend(filters.BaseFilterBackend):
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name='latest', location='query', required=False,
|
||||
type='string', example='1',
|
||||
description='Only the latest version'
|
||||
)
|
||||
]
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
latest = request.GET.get('latest') == '1'
|
||||
if latest:
|
||||
queryset = queryset.distinct()
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
|
||||
serializer_classes = {
|
||||
'default': serializers.AssetUserWriteSerializer,
|
||||
'display': serializers.AssetUserReadSerializer,
|
||||
'retrieve': serializers.AssetUserReadSerializer,
|
||||
}
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username",
|
||||
"asset_id", "node_id",
|
||||
"prefer", "prefer_id",
|
||||
]
|
||||
search_fields = ["ip", "hostname", "username"]
|
||||
filter_backends = [
|
||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||
AssetUserLatestFilterBackend,
|
||||
]
|
||||
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
return False
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
if pk is None:
|
||||
return
|
||||
queryset = self.get_queryset()
|
||||
obj = queryset.get(id=pk)
|
||||
return obj
|
||||
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
logger.error(e, exc_info=True)
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
manager = AssetUserManager()
|
||||
manager.delete(instance)
|
||||
|
||||
def get_queryset(self):
|
||||
manager = AssetUserManager()
|
||||
queryset = manager.all()
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserAuthInfoViewSet(AssetUserViewSet):
|
||||
serializer_classes = {"default": serializers.AssetUserAuthInfoSerializer}
|
||||
http_method_names = ['get', 'post']
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
|
||||
def get_permissions(self):
|
||||
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
|
||||
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
return super().get_permissions()
|
||||
|
||||
|
||||
class AssetUserTaskCreateAPI(generics.CreateAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.AssetUserTaskSerializer
|
||||
filter_backends = AssetUserViewSet.filter_backends
|
||||
filter_fields = AssetUserViewSet.filter_fields
|
||||
|
||||
def get_asset_users(self):
|
||||
manager = AssetUserManager()
|
||||
queryset = manager.all()
|
||||
for cls in self.filter_backends:
|
||||
queryset = cls().filter_queryset(self.request, queryset, self)
|
||||
return list(queryset)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
asset_users = self.get_asset_users()
|
||||
# action = serializer.validated_data["action"]
|
||||
# only this
|
||||
# if action == "test":
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
return task
|
||||
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
@@ -1,37 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
from .. import serializers
|
||||
|
||||
|
||||
__all__ = ['CommandFilterViewSet', 'CommandFilterRuleViewSet']
|
||||
|
||||
|
||||
class CommandFilterViewSet(OrgBulkModelViewSet):
|
||||
model = CommandFilter
|
||||
filter_fields = ("name",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.CommandFilterSerializer
|
||||
|
||||
|
||||
class CommandFilterRuleViewSet(OrgBulkModelViewSet):
|
||||
model = CommandFilterRule
|
||||
filter_fields = ("content",)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.CommandFilterRuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
fpk = self.kwargs.get('filter_pk')
|
||||
if not fpk:
|
||||
return CommandFilterRule.objects.none()
|
||||
cmd_filter = get_object_or_404(CommandFilter, pk=fpk)
|
||||
return cmd_filter.rules.all()
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from rest_framework.views import APIView, Response
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from ..models import Domain, Gateway
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['DomainViewSet', 'GatewayViewSet', "GatewayTestConnectionApi"]
|
||||
|
||||
|
||||
class DomainViewSet(OrgBulkModelViewSet):
|
||||
model = Domain
|
||||
filter_fields = ("name", )
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.DomainSerializer
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.query_params.get('gateway'):
|
||||
return serializers.DomainWithGatewaySerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
|
||||
class GatewayViewSet(OrgBulkModelViewSet):
|
||||
model = Gateway
|
||||
filter_fields = ("domain__name", "name", "username", "ip", "domain")
|
||||
search_fields = ("domain__name", "name", "username", "ip")
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.GatewaySerializer
|
||||
|
||||
|
||||
class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
object = None
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(Gateway.objects.all())
|
||||
local_port = self.request.data.get('port') or self.object.port
|
||||
ok, e = self.object.test_connective(local_port=local_port)
|
||||
if ok:
|
||||
return Response("ok")
|
||||
else:
|
||||
return Response({"error": e}, status=400)
|
||||
@@ -1,27 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from common.permissions import IsValidUser
|
||||
from orgs.utils import tmp_to_root_org
|
||||
from ..models import FavoriteAsset
|
||||
from ..serializers import FavoriteAssetSerializer
|
||||
|
||||
__all__ = ['FavoriteAssetViewSet']
|
||||
|
||||
|
||||
class FavoriteAssetViewSet(BulkModelViewSet):
|
||||
serializer_class = FavoriteAssetSerializer
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['asset']
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
with tmp_to_root_org():
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = FavoriteAsset.objects.filter(user=self.request.user)
|
||||
return queryset
|
||||
|
||||
def allow_bulk_destroy(self, qs, filtered):
|
||||
return filtered.count() == 1
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from orgs.mixins.api import OrgModelViewSet
|
||||
from assets.models import GatheredUser
|
||||
from common.permissions import IsOrgAdmin
|
||||
|
||||
from ..serializers import GatheredUserSerializer
|
||||
from ..filters import AssetRelatedByNodeFilterBackend
|
||||
|
||||
|
||||
__all__ = ['GatheredUserViewSet']
|
||||
|
||||
|
||||
class GatheredUserViewSet(OrgModelViewSet):
|
||||
model = GatheredUser
|
||||
serializer_class = GatheredUserSerializer
|
||||
permission_classes = [IsOrgAdmin]
|
||||
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
|
||||
|
||||
filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']
|
||||
search_fields = ['username', 'asset__ip', 'asset__hostname']
|
||||
@@ -1,44 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the GNU General Public License v2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.gnu.org/licenses/gpl-2.0.html
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(OrgBulkModelViewSet):
|
||||
model = Label
|
||||
filter_fields = ("name", "value")
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.LabelSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if request.query_params.get("distinct"):
|
||||
self.serializer_class = serializers.LabelDistinctSerializer
|
||||
self.queryset = self.queryset.values("name").distinct()
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = Label.objects.annotate(asset_count=Count("assets"))
|
||||
return self.queryset
|
||||
@@ -1,289 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
|
||||
from collections import namedtuple
|
||||
from rest_framework import status
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.response import Response
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404, Http404
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.tree import TreeNodeSerializer
|
||||
from orgs.mixins.api import OrgModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import (
|
||||
update_node_assets_hardware_info_manual,
|
||||
test_node_assets_connectivity_manual,
|
||||
)
|
||||
from .. import serializers
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
|
||||
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
|
||||
'NodeAddChildrenApi', 'NodeListAsTreeApi',
|
||||
'NodeChildrenAsTreeApi',
|
||||
'NodeTaskCreateApi',
|
||||
]
|
||||
|
||||
|
||||
class NodeViewSet(OrgModelViewSet):
|
||||
model = Node
|
||||
filter_fields = ('value', 'key', 'id')
|
||||
search_fields = ('value', )
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
|
||||
# 仅支持根节点指直接创建,子节点下的节点需要通过children接口创建
|
||||
def perform_create(self, serializer):
|
||||
child_key = Node.org_root().get_next_child_key()
|
||||
serializer.validated_data["key"] = child_key
|
||||
serializer.save()
|
||||
|
||||
def perform_update(self, serializer):
|
||||
node = self.get_object()
|
||||
if node.is_org_root() and node.value != serializer.validated_data['value']:
|
||||
msg = _("You can't update the root node name")
|
||||
raise ValidationError({"error": msg})
|
||||
return super().perform_update(serializer)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
node = self.get_object()
|
||||
if node.has_children_or_has_assets():
|
||||
error = _("Deletion failed and the node contains children or assets")
|
||||
return Response(data={'error': error}, status=status.HTTP_403_FORBIDDEN)
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeListAsTreeApi(generics.ListAPIView):
|
||||
"""
|
||||
获取节点列表树
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
"""
|
||||
model = Node
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = TreeNodeSerializer
|
||||
|
||||
@staticmethod
|
||||
def to_tree_queryset(queryset):
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = super().filter_queryset(queryset)
|
||||
queryset = self.to_tree_queryset(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenApi(generics.ListCreateAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
instance = None
|
||||
is_initial = False
|
||||
|
||||
def initial(self, request, *args, **kwargs):
|
||||
self.instance = self.get_object()
|
||||
return super().initial(request, *args, **kwargs)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
data = serializer.validated_data
|
||||
_id = data.get("id")
|
||||
value = data.get("value")
|
||||
if not value:
|
||||
value = self.instance.get_next_child_preset_name()
|
||||
node = self.instance.create_child(value=value, _id=_id)
|
||||
# 避免查询 full value
|
||||
node._full_value = node.value
|
||||
serializer.instance = node
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk') or self.request.query_params.get('id')
|
||||
key = self.request.query_params.get("key")
|
||||
if not pk and not key:
|
||||
node = Node.org_root()
|
||||
self.is_initial = True
|
||||
return node
|
||||
if pk:
|
||||
node = get_object_or_404(Node, pk=pk)
|
||||
else:
|
||||
node = get_object_or_404(Node, key=key)
|
||||
return node
|
||||
|
||||
def get_queryset(self):
|
||||
query_all = self.request.query_params.get("all", "0") == "all"
|
||||
if not self.instance:
|
||||
return Node.objects.none()
|
||||
|
||||
if self.is_initial:
|
||||
with_self = True
|
||||
else:
|
||||
with_self = False
|
||||
|
||||
if query_all:
|
||||
queryset = self.instance.get_all_children(with_self=with_self)
|
||||
else:
|
||||
queryset = self.instance.get_children(with_self=with_self)
|
||||
return queryset
|
||||
|
||||
|
||||
class NodeChildrenAsTreeApi(NodeChildrenApi):
|
||||
"""
|
||||
节点子节点作为树返回,
|
||||
[
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"pId": "",
|
||||
"meta": ""
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
model = Node
|
||||
serializer_class = TreeNodeSerializer
|
||||
http_method_names = ['get']
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
queryset = self.add_assets_if_need(queryset)
|
||||
queryset = sorted(queryset)
|
||||
return queryset
|
||||
|
||||
def add_assets_if_need(self, queryset):
|
||||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.instance.get_assets().only(
|
||||
"id", "hostname", "ip", "os",
|
||||
"org_id", "protocols",
|
||||
)
|
||||
for asset in assets:
|
||||
queryset.append(asset.as_tree_node(self.instance))
|
||||
return queryset
|
||||
|
||||
def check_need_refresh_nodes(self):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
Node.refresh_nodes()
|
||||
|
||||
|
||||
class NodeAssetsApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.AssetSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
node_id = self.kwargs.get('pk')
|
||||
query_all = self.request.query_params.get('all')
|
||||
instance = get_object_or_404(Node, pk=node_id)
|
||||
if query_all:
|
||||
return instance.get_all_assets()
|
||||
else:
|
||||
return instance.get_assets()
|
||||
|
||||
|
||||
class NodeAddChildrenApi(generics.UpdateAPIView):
|
||||
model = Node
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeAddChildrenSerializer
|
||||
instance = None
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
nodes_id = request.data.get("nodes")
|
||||
children = [get_object_or_none(Node, id=pk) for pk in nodes_id]
|
||||
for node in children:
|
||||
if not node:
|
||||
continue
|
||||
node.parent = instance
|
||||
return Response("OK")
|
||||
|
||||
|
||||
class NodeAddAssetsApi(generics.UpdateAPIView):
|
||||
model = Node
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
instance.assets.add(*tuple(assets))
|
||||
|
||||
|
||||
class NodeRemoveAssetsApi(generics.UpdateAPIView):
|
||||
model = Node
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
if instance != Node.org_root():
|
||||
instance.assets.remove(*tuple(assets))
|
||||
else:
|
||||
assets = [asset for asset in assets if asset.nodes.count() > 1]
|
||||
instance.assets.remove(*tuple(assets))
|
||||
|
||||
|
||||
class NodeReplaceAssetsApi(generics.UpdateAPIView):
|
||||
model = Node
|
||||
serializer_class = serializers.NodeAssetsSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
instance = None
|
||||
|
||||
def perform_update(self, serializer):
|
||||
assets = serializer.validated_data.get('assets')
|
||||
instance = self.get_object()
|
||||
for asset in assets:
|
||||
asset.nodes.set([instance])
|
||||
|
||||
|
||||
class NodeTaskCreateApi(generics.CreateAPIView):
|
||||
model = Node
|
||||
serializer_class = serializers.NodeTaskSerializer
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def get_object(self):
|
||||
node_id = self.kwargs.get('pk')
|
||||
node = get_object_or_none(self.model, id=node_id)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
def set_serializer_data(s, task):
|
||||
data = getattr(s, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(s, '_data', data)
|
||||
|
||||
@staticmethod
|
||||
def refresh_nodes_cache():
|
||||
Node.refresh_nodes()
|
||||
Task = namedtuple('Task', ['id'])
|
||||
task = Task(id="0")
|
||||
return task
|
||||
|
||||
def perform_create(self, serializer):
|
||||
action = serializer.validated_data["action"]
|
||||
node = self.get_object()
|
||||
if action == "refresh_cache" and node is None:
|
||||
task = self.refresh_nodes_cache()
|
||||
self.set_serializer_data(serializer, task)
|
||||
return
|
||||
if node is None:
|
||||
raise Http404()
|
||||
if action == "refresh":
|
||||
task = update_node_assets_hardware_info_manual.delay(node)
|
||||
else:
|
||||
task = test_node_assets_connectivity_manual.delay(node)
|
||||
self.set_serializer_data(serializer, task)
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# ~*~ coding: utf-8 ~*~
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.response import Response
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsAppUser
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.mixins import generics
|
||||
from orgs.utils import tmp_to_org
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..serializers import SystemUserWithAuthInfoSerializer
|
||||
from ..tasks import (
|
||||
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
|
||||
push_system_user_a_asset_manual,
|
||||
)
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
|
||||
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi',
|
||||
]
|
||||
|
||||
|
||||
class SystemUserViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
model = SystemUser
|
||||
filter_fields = ("name", "username", "protocol")
|
||||
search_fields = filter_fields
|
||||
serializer_class = serializers.SystemUserSerializer
|
||||
serializer_classes = {
|
||||
'default': serializers.SystemUserSerializer,
|
||||
'list': serializers.SystemUserListSerializer,
|
||||
}
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
|
||||
class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
Get system user auth info
|
||||
"""
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = SystemUserWithAuthInfoSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.clear_auth()
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Get system user with asset auth info
|
||||
"""
|
||||
model = SystemUser
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = SystemUserWithAuthInfoSerializer
|
||||
|
||||
def get_exception_handler(self):
|
||||
def handler(e, context):
|
||||
return Response({"error": str(e)}, status=400)
|
||||
return handler
|
||||
|
||||
def get_object(self):
|
||||
instance = super().get_object()
|
||||
username = instance.username
|
||||
if instance.username_same_with_user:
|
||||
username = self.request.query_params.get("username")
|
||||
asset_id = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
with tmp_to_org(asset.org_id):
|
||||
instance.load_asset_special_auth(asset=asset, username=username)
|
||||
return instance
|
||||
|
||||
|
||||
class SystemUserTaskApi(generics.CreateAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.SystemUserTaskSerializer
|
||||
|
||||
def do_push(self, system_user, asset=None):
|
||||
if asset is None:
|
||||
task = push_system_user_to_assets_manual.delay(system_user)
|
||||
else:
|
||||
username = self.request.query_params.get('username')
|
||||
task = push_system_user_a_asset_manual.delay(
|
||||
system_user, asset, username=username
|
||||
)
|
||||
return task
|
||||
|
||||
@staticmethod
|
||||
def do_test(system_user, asset=None):
|
||||
task = test_system_user_connectivity_manual.delay(system_user)
|
||||
return task
|
||||
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
return get_object_or_404(SystemUser, pk=pk)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
action = serializer.validated_data["action"]
|
||||
asset = serializer.validated_data.get('asset')
|
||||
system_user = self.get_object()
|
||||
if action == 'push':
|
||||
task = self.do_push(system_user, asset)
|
||||
else:
|
||||
task = self.do_test(system_user, asset)
|
||||
data = getattr(serializer, '_data', {})
|
||||
data["task"] = task.id
|
||||
setattr(serializer, '_data', data)
|
||||
|
||||
|
||||
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
from ..serializers import CommandFilterRuleSerializer
|
||||
return CommandFilterRuleSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
pk = self.kwargs.get('pk', None)
|
||||
system_user = get_object_or_404(SystemUser, pk=pk)
|
||||
return system_user.cmd_filter_rules
|
||||
@@ -1,136 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from collections import defaultdict
|
||||
from django.db.models import F, Value
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
from common.permissions import IsOrgAdmin
|
||||
from orgs.mixins.api import OrgBulkModelViewSet
|
||||
from orgs.utils import current_org
|
||||
from .. import models, serializers
|
||||
|
||||
__all__ = [
|
||||
'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet',
|
||||
'SystemUserUserRelationViewSet',
|
||||
]
|
||||
|
||||
|
||||
class RelationMixin:
|
||||
def get_queryset(self):
|
||||
queryset = self.model.objects.all()
|
||||
org_id = current_org.org_id()
|
||||
if org_id is not None:
|
||||
queryset = queryset.filter(systemuser__org_id=org_id)
|
||||
queryset = queryset.annotate(systemuser_display=Concat(
|
||||
F('systemuser__name'), Value('('), F('systemuser__username'),
|
||||
Value(')')
|
||||
))
|
||||
return queryset
|
||||
|
||||
def send_post_add_signal(self, instance):
|
||||
if not isinstance(instance, list):
|
||||
instance = [instance]
|
||||
|
||||
system_users_objects_map = defaultdict(list)
|
||||
model, object_field = self.get_objects_attr()
|
||||
|
||||
for i in instance:
|
||||
_id = getattr(i, object_field).id
|
||||
system_users_objects_map[i.systemuser].append(_id)
|
||||
|
||||
sender = self.get_sender()
|
||||
for system_user, objects in system_users_objects_map.items():
|
||||
m2m_changed.send(
|
||||
sender=sender, instance=system_user, action='post_add',
|
||||
reverse=False, model=model, pk_set=objects
|
||||
)
|
||||
|
||||
def get_sender(self):
|
||||
return self.model
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Asset, 'asset'
|
||||
|
||||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
self.send_post_add_signal(instance)
|
||||
|
||||
|
||||
class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserAssetRelationSerializer
|
||||
model = models.SystemUser.assets.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', 'asset', 'systemuser',
|
||||
]
|
||||
search_fields = [
|
||||
"id", "asset__hostname", "asset__ip",
|
||||
"systemuser__name", "systemuser__username"
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Asset, 'asset'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(
|
||||
asset_display=Concat(
|
||||
F('asset__hostname'), Value('('),
|
||||
F('asset__ip'), Value(')')
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserNodeRelationSerializer
|
||||
model = models.SystemUser.nodes.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', 'node', 'systemuser',
|
||||
]
|
||||
search_fields = [
|
||||
"node__value", "systemuser__name", "systemuser_username"
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
return models.Node, 'node'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset \
|
||||
.annotate(node_key=F('node__key'))
|
||||
return queryset
|
||||
|
||||
|
||||
class SystemUserUserRelationViewSet(BaseRelationViewSet):
|
||||
serializer_class = serializers.SystemUserUserRelationSerializer
|
||||
model = models.SystemUser.users.through
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
filter_fields = [
|
||||
'id', 'user', 'systemuser',
|
||||
]
|
||||
search_fields = [
|
||||
"user__username", "user__name",
|
||||
"systemuser__name", "systemuser__username",
|
||||
]
|
||||
|
||||
def get_objects_attr(self):
|
||||
from users.models import User
|
||||
return User, 'user'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
queryset = queryset.annotate(
|
||||
user_display=Concat(
|
||||
F('user__name'), Value('('),
|
||||
F('user__username'), Value(')')
|
||||
)
|
||||
)
|
||||
return queryset
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.signals import post_migrate
|
||||
|
||||
|
||||
def initial_some_nodes():
|
||||
from .models import Node
|
||||
Node.initial_some_nodes()
|
||||
|
||||
|
||||
def initial_some_nodes_callback(sender, **kwargs):
|
||||
initial_some_nodes()
|
||||
|
||||
|
||||
class AssetsConfig(AppConfig):
|
||||
name = 'assets'
|
||||
|
||||
def ready(self):
|
||||
super().ready()
|
||||
from . import signals_handler
|
||||
try:
|
||||
initial_some_nodes()
|
||||
except Exception:
|
||||
post_migrate.connect(initial_some_nodes_callback, sender=self)
|
||||
@@ -1 +0,0 @@
|
||||
from .manager import AssetUserManager
|
||||
@@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from abc import abstractmethod
|
||||
|
||||
from ..models import Asset
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
@abstractmethod
|
||||
def all(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def filter(self, username=None, hostname=None, ip=None, assets=None,
|
||||
node=None, prefer_id=None, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def search(self, item):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_queryset(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, union_id):
|
||||
pass
|
||||
|
||||
@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,318 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext as _
|
||||
from functools import reduce
|
||||
from django.db.models import F, CharField, Value, IntegerField, Q, Count
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
from common.utils import get_object_or_none
|
||||
from orgs.utils import current_org
|
||||
from ..models import AuthBook, SystemUser, Asset, AdminUser
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class DBBackend(BaseBackend):
|
||||
union_id_length = 2
|
||||
|
||||
def __init__(self, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.all()
|
||||
self.queryset = queryset
|
||||
|
||||
def _clone(self):
|
||||
return self.__class__(self.queryset)
|
||||
|
||||
def all(self):
|
||||
return AuthBook.objects.none()
|
||||
|
||||
def count(self):
|
||||
return self.queryset.count()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset
|
||||
|
||||
def delete(self, union_id):
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
return
|
||||
return self._perform_delete_by_union_id(cleaned_union_id)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
pass
|
||||
|
||||
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
|
||||
union_id=None, id__in=None, **kwargs):
|
||||
clone = self._clone()
|
||||
clone._filter_union_id(union_id)
|
||||
clone._filter_prefer(prefer, prefer_id)
|
||||
clone._filter_node(node)
|
||||
clone._filter_assets(assets)
|
||||
clone._filter_other(kwargs)
|
||||
clone._filter_id_in(id__in)
|
||||
return clone
|
||||
|
||||
def _filter_union_id(self, union_id):
|
||||
if not union_id:
|
||||
return
|
||||
cleaned_union_id = union_id.split('_')
|
||||
# 如果union_id通不过本检查,代表可能不是本backend, 应该返回空
|
||||
if not self._check_union_id(union_id, cleaned_union_id):
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
return self._perform_filter_union_id(union_id, cleaned_union_id)
|
||||
|
||||
def _check_union_id(self, union_id, cleaned_union_id):
|
||||
return union_id and len(cleaned_union_id) == self.union_id_length
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
self.queryset = self.queryset.filter(union_id=union_id)
|
||||
|
||||
def _filter_assets(self, assets):
|
||||
assets_id = self.make_assets_as_id(assets)
|
||||
if assets_id:
|
||||
self.queryset = self.queryset.filter(asset_id__in=assets_id)
|
||||
|
||||
def _filter_node(self, node):
|
||||
pass
|
||||
|
||||
def _filter_id_in(self, ids):
|
||||
if ids and isinstance(ids, list):
|
||||
self.queryset = self.queryset.filter(union_id__in=ids)
|
||||
|
||||
@staticmethod
|
||||
def clean_kwargs(kwargs):
|
||||
return {k: v for k, v in kwargs.items() if v}
|
||||
|
||||
def _filter_other(self, kwargs):
|
||||
kwargs = self.clean_kwargs(kwargs)
|
||||
if kwargs:
|
||||
self.queryset = self.queryset.filter(**kwargs)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
pass
|
||||
|
||||
def search(self, item):
|
||||
qs = []
|
||||
for i in ['hostname', 'ip', 'username']:
|
||||
kwargs = {i + '__startswith': item}
|
||||
qs.append(Q(**kwargs))
|
||||
q = reduce(lambda x, y: x | y, qs)
|
||||
clone = self._clone()
|
||||
clone.queryset = clone.queryset.filter(q).distinct()
|
||||
return clone
|
||||
|
||||
|
||||
class SystemUserBackend(DBBackend):
|
||||
model = SystemUser.assets.through
|
||||
backend = 'system_user'
|
||||
prefer = backend
|
||||
base_score = 0
|
||||
union_id_length = 2
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.prefer:
|
||||
self.queryset = self.queryset.none()
|
||||
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser__id=system_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
if all((system_user, asset)):
|
||||
system_user.assets.remove(asset)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = dict(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
username=F("systemuser__username"),
|
||||
password=F("systemuser__password"),
|
||||
private_key=F("systemuser__private_key"),
|
||||
public_key=F("systemuser__public_key"),
|
||||
score=F("systemuser__priority") + self.base_score,
|
||||
version=Value(0, IntegerField()),
|
||||
date_created=F("systemuser__date_created"),
|
||||
date_updated=F("systemuser__date_updated"),
|
||||
asset_username=Concat(F("asset__id"), Value("_"),
|
||||
F("systemuser__username"),
|
||||
output_field=CharField()),
|
||||
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
output_field=CharField()),
|
||||
org_id=F("asset__org_id"),
|
||||
backend=Value(self.backend, CharField())
|
||||
)
|
||||
return kwargs
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
systemuser__username_same_with_user=False,
|
||||
)
|
||||
|
||||
def all(self):
|
||||
kwargs = self.get_annotate()
|
||||
filters = self.get_filter()
|
||||
qs = self.model.objects.all().annotate(**kwargs)
|
||||
if current_org.org_id() is not None:
|
||||
filters['org_id'] = current_org.org_id()
|
||||
qs = qs.filter(**filters)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class DynamicSystemUserBackend(SystemUserBackend):
|
||||
backend = 'system_user_dynamic'
|
||||
prefer = 'system_user'
|
||||
union_id_length = 3
|
||||
|
||||
def get_annotate(self):
|
||||
kwargs = super().get_annotate()
|
||||
kwargs.update(dict(
|
||||
username=F("systemuser__users__username"),
|
||||
asset_username=Concat(
|
||||
F("asset__id"), Value("_"),
|
||||
F("systemuser__users__username"),
|
||||
output_field=CharField()
|
||||
),
|
||||
union_id=Concat(
|
||||
F("systemuser_id"), Value("_"), F("asset_id"),
|
||||
Value("_"), F("systemuser__users__id"),
|
||||
output_field=CharField()
|
||||
),
|
||||
users_count=Count('systemuser__users'),
|
||||
))
|
||||
return kwargs
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
asset_id=asset_id, systemuser_id=system_user_id,
|
||||
union_id=union_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
system_user_id, asset_id, user_id = union_id_cleaned
|
||||
system_user = get_object_or_none(SystemUser, pk=system_user_id)
|
||||
if not system_user:
|
||||
return
|
||||
system_user.users.remove(user_id)
|
||||
if system_user.users.count() == 0:
|
||||
system_user.assets.remove(asset_id)
|
||||
|
||||
def get_filter(self):
|
||||
return dict(
|
||||
users_count__gt=0,
|
||||
systemuser__username_same_with_user=True
|
||||
)
|
||||
|
||||
|
||||
class AdminUserBackend(DBBackend):
|
||||
model = Asset
|
||||
backend = 'admin_user'
|
||||
prefer = backend
|
||||
base_score = 200
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if prefer and prefer != self.backend:
|
||||
self.queryset = self.queryset.none()
|
||||
if prefer_id:
|
||||
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(nodes__id=node.id)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
admin_user_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=asset_id, admin_user_id=admin_user_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
raise PermissionError(_("Could not remove asset admin user"))
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
asset_id=F("id"),
|
||||
username=F("admin_user__username"),
|
||||
password=F("admin_user__password"),
|
||||
private_key=F("admin_user__private_key"),
|
||||
public_key=F("admin_user__public_key"),
|
||||
score=Value(self.base_score, IntegerField()),
|
||||
version=Value(0, IntegerField()),
|
||||
date_updated=F("admin_user__date_updated"),
|
||||
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
|
||||
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
|
||||
|
||||
class AuthbookBackend(DBBackend):
|
||||
model = AuthBook
|
||||
backend = 'db'
|
||||
prefer = backend
|
||||
base_score = 400
|
||||
|
||||
def _filter_node(self, node):
|
||||
if node:
|
||||
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
|
||||
|
||||
def _filter_prefer(self, prefer, prefer_id):
|
||||
if not prefer or not prefer_id:
|
||||
return
|
||||
if prefer.lower() == "admin_user":
|
||||
model = AdminUser
|
||||
elif prefer.lower() == "system_user":
|
||||
model = SystemUser
|
||||
else:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
obj = get_object_or_none(model, pk=prefer_id)
|
||||
if obj is None:
|
||||
self.queryset = self.queryset.none()
|
||||
return
|
||||
username = obj.get_username()
|
||||
if isinstance(username, str):
|
||||
self.queryset = self.queryset.filter(username=username)
|
||||
# dynamic system user return more username
|
||||
else:
|
||||
self.queryset = self.queryset.filter(username__in=username)
|
||||
|
||||
def _perform_filter_union_id(self, union_id, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
self.queryset = self.queryset.filter(
|
||||
id=authbook_id, asset_id=asset_id,
|
||||
)
|
||||
|
||||
def _perform_delete_by_union_id(self, union_id_cleaned):
|
||||
authbook_id, asset_id = union_id_cleaned
|
||||
authbook = get_object_or_none(AuthBook, pk=authbook_id)
|
||||
if authbook.is_latest:
|
||||
raise PermissionError(_("Latest version could not be delete"))
|
||||
AuthBook.objects.filter(id=authbook_id).delete()
|
||||
|
||||
def all(self):
|
||||
qs = self.model.objects.all().annotate(
|
||||
hostname=F("asset__hostname"),
|
||||
ip=F("asset__ip"),
|
||||
score=F('version') + self.base_score,
|
||||
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
|
||||
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
|
||||
backend=Value(self.backend, CharField()),
|
||||
)
|
||||
qs = self.qs_to_values(qs)
|
||||
return qs
|
||||
@@ -1,162 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from itertools import chain, groupby
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
|
||||
from orgs.utils import current_org
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from common.struct import QuerySetChain
|
||||
|
||||
from ..models import AssetUser, AuthBook
|
||||
from .db import (
|
||||
AuthbookBackend, SystemUserBackend, AdminUserBackend,
|
||||
DynamicSystemUserBackend
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserQueryset:
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
|
||||
def __init__(self, backends=()):
|
||||
self.backends = backends
|
||||
self._distinct_queryset = None
|
||||
|
||||
def backends_queryset(self):
|
||||
return [b.get_queryset() for b in self.backends]
|
||||
|
||||
@lazyproperty
|
||||
def backends_counts(self):
|
||||
return [b.count() for b in self.backends]
|
||||
|
||||
def filter(self, hostname=None, ip=None, username=None,
|
||||
assets=None, asset=None, node=None,
|
||||
id=None, prefer_id=None, prefer=None, id__in=None):
|
||||
if not assets and asset:
|
||||
assets = [asset]
|
||||
|
||||
kwargs = dict(
|
||||
hostname=hostname, ip=ip, username=username,
|
||||
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
|
||||
id__in=id__in, union_id=id,
|
||||
)
|
||||
logger.debug("Filter: {}".format(kwargs))
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
clone = backend.filter(**kwargs)
|
||||
backends.append(clone)
|
||||
return self._clone(backends)
|
||||
|
||||
def _clone(self, backends=None):
|
||||
if backends is None:
|
||||
backends = self.backends
|
||||
return self.__class__(backends)
|
||||
|
||||
def search(self, item):
|
||||
backends = []
|
||||
for backend in self.backends:
|
||||
new = backend.search(item)
|
||||
backends.append(new)
|
||||
return self._clone(backends)
|
||||
|
||||
def distinct(self):
|
||||
logger.debug("Distinct asset user queryset")
|
||||
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
|
||||
queryset_sorted = sorted(
|
||||
queryset_chain,
|
||||
key=lambda item: (item["asset_username"], item["score"]),
|
||||
reverse=True,
|
||||
)
|
||||
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
|
||||
final = [next(result[1]) for result in results]
|
||||
self._distinct_queryset = final
|
||||
return self
|
||||
|
||||
def get(self, latest=False, **kwargs):
|
||||
queryset = self.filter(**kwargs)
|
||||
if latest:
|
||||
queryset = queryset.distinct()
|
||||
queryset = list(queryset)
|
||||
count = len(queryset)
|
||||
if count == 1:
|
||||
data = queryset[0]
|
||||
return data
|
||||
elif count > 1:
|
||||
msg = 'Should return 1 record, but get {}'.format(count)
|
||||
raise MultipleObjectsReturned(msg)
|
||||
else:
|
||||
msg = 'No record found(org is {})'.format(current_org.name)
|
||||
raise ObjectDoesNotExist(msg)
|
||||
|
||||
def get_latest(self, **kwargs):
|
||||
return self.get(latest=True, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def to_asset_user(data):
|
||||
obj = AssetUser()
|
||||
for k, v in data.items():
|
||||
setattr(obj, k, v)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def queryset(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return self._distinct_queryset
|
||||
return QuerySetChain(self.backends_queryset())
|
||||
|
||||
def count(self):
|
||||
if self._distinct_queryset is not None:
|
||||
return len(self._distinct_queryset)
|
||||
else:
|
||||
return sum(self.backends_counts)
|
||||
|
||||
def __getitem__(self, ndx):
|
||||
return self.queryset.__getitem__(ndx)
|
||||
|
||||
def __iter__(self):
|
||||
self._data = iter(self.queryset)
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.to_asset_user(next(self._data))
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
support_backends = (
|
||||
('db', AuthbookBackend),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
('system_user_dynamic', DynamicSystemUserBackend),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.backends = [backend() for name, backend in self.support_backends]
|
||||
self._queryset = AssetUserQueryset(self.backends)
|
||||
|
||||
def all(self):
|
||||
return self._queryset
|
||||
|
||||
def delete(self, obj):
|
||||
name_backends_map = dict(self.support_backends)
|
||||
backend_name = obj.backend
|
||||
backend_cls = name_backends_map.get(backend_name)
|
||||
union_id = obj.union_id
|
||||
if backend_cls:
|
||||
backend_cls().delete(union_id)
|
||||
else:
|
||||
raise ObjectDoesNotExist("Not backend found")
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
# 使用create方法创建AuthBook对象,解决并发创建问题(添加锁机制)
|
||||
authbook = AuthBook.create(**kwargs)
|
||||
return authbook
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self._queryset, item)
|
||||
@@ -1,7 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
# from django.conf import settings
|
||||
|
||||
# from .vault import VaultBackend
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
@@ -1,119 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import coreapi
|
||||
from rest_framework import filters
|
||||
from django.db.models import Q
|
||||
|
||||
from common.utils import dict_get_any, is_uuid, get_object_or_none
|
||||
from .models import Node, Label
|
||||
|
||||
|
||||
class AssetByNodeFilterBackend(filters.BaseFilterBackend):
|
||||
fields = ['node', 'all']
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name=field, location='query', required=False,
|
||||
type='string', example='', description='', schema=None,
|
||||
)
|
||||
for field in self.fields
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def is_query_all(request):
|
||||
query_all_arg = request.query_params.get('all')
|
||||
show_current_asset_arg = request.query_params.get('show_current_asset')
|
||||
|
||||
query_all = query_all_arg == '1'
|
||||
if show_current_asset_arg is not None:
|
||||
query_all = show_current_asset_arg != '1'
|
||||
return query_all
|
||||
|
||||
@staticmethod
|
||||
def get_query_node(request):
|
||||
node_id = dict_get_any(request.query_params, ['node', 'node_id'])
|
||||
if not node_id:
|
||||
return None, False
|
||||
|
||||
if is_uuid(node_id):
|
||||
node = get_object_or_none(Node, id=node_id)
|
||||
else:
|
||||
node = get_object_or_none(Node, key=node_id)
|
||||
return node, True
|
||||
|
||||
@staticmethod
|
||||
def perform_query(pattern, queryset):
|
||||
return queryset.filter(nodes__key__regex=pattern).distinct()
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
node, has_query_arg = self.get_query_node(request)
|
||||
if not has_query_arg:
|
||||
return queryset
|
||||
|
||||
if node is None:
|
||||
return queryset
|
||||
query_all = self.is_query_all(request)
|
||||
if query_all:
|
||||
pattern = node.get_all_children_pattern(with_self=True)
|
||||
else:
|
||||
# pattern = node.get_children_key_pattern(with_self=True)
|
||||
# 只显示当前节点下资产
|
||||
pattern = r"^{}$".format(node.key)
|
||||
return self.perform_query(pattern, queryset)
|
||||
|
||||
|
||||
class LabelFilterBackend(filters.BaseFilterBackend):
|
||||
sep = ':'
|
||||
query_arg = 'label'
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
example = self.sep.join(['os', 'linux'])
|
||||
return [
|
||||
coreapi.Field(
|
||||
name=self.query_arg, location='query', required=False,
|
||||
type='string', example=example, description=''
|
||||
)
|
||||
]
|
||||
|
||||
def get_query_labels(self, request):
|
||||
labels_query = request.query_params.getlist(self.query_arg)
|
||||
if not labels_query:
|
||||
return None
|
||||
|
||||
q = None
|
||||
for kv in labels_query:
|
||||
if '#' in kv:
|
||||
self.sep = '#'
|
||||
if self.sep not in kv:
|
||||
continue
|
||||
key, value = kv.strip().split(self.sep)[:2]
|
||||
if not all([key, value]):
|
||||
continue
|
||||
if q:
|
||||
q |= Q(name=key, value=value)
|
||||
else:
|
||||
q = Q(name=key, value=value)
|
||||
if not q:
|
||||
return []
|
||||
labels = Label.objects.filter(q, is_active=True)\
|
||||
.values_list('id', flat=True)
|
||||
return labels
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
labels = self.get_query_labels(request)
|
||||
if labels is None:
|
||||
return queryset
|
||||
if len(labels) == 0:
|
||||
return queryset.none()
|
||||
for label in labels:
|
||||
queryset = queryset.filter(labels=label)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
|
||||
@staticmethod
|
||||
def perform_query(pattern, queryset):
|
||||
return queryset.filter(asset__nodes__key__regex=pattern).distinct()
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
jumpserver.__app__.hands.py
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This app depends other apps api, function .. should be import or write mack here.
|
||||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by JumpServer Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
@@ -1,168 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-12-21 16:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.utils
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
def add_default_group(apps, schema_editor):
|
||||
group_model = apps.get_model("assets", "AssetGroup")
|
||||
db_alias = schema_editor.connection.alias
|
||||
group_model.objects.using(db_alias).create(
|
||||
name="Default"
|
||||
)
|
||||
|
||||
|
||||
def add_default_cluster(apps, schema_editor):
|
||||
cluster_model = apps.get_model("assets", "Cluster")
|
||||
db_alias = schema_editor.connection.alias
|
||||
cluster_model.objects.using(db_alias).create(
|
||||
name="Default"
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AdminUser',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(max_length=16, verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
|
||||
('become', models.BooleanField(default=True)),
|
||||
('become_method', models.CharField(choices=[('sudo', 'sudo'), ('su', 'su')], default='sudo', max_length=4)),
|
||||
('become_user', models.CharField(default='root', max_length=64)),
|
||||
('_become_pass', models.CharField(default='', max_length=128)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Asset',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('hostname', models.CharField(max_length=128, unique=True, verbose_name='Hostname')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('type', models.CharField(blank=True, choices=[('Server', 'Server'), ('VM', 'VM'), ('Switch', 'Switch'), ('Router', 'Router'), ('Firewall', 'Firewall'), ('Storage', 'Storage')], default='Server', max_length=16, null=True, verbose_name='Asset type')),
|
||||
('env', models.CharField(blank=True, choices=[('Prod', 'Production'), ('Dev', 'Development'), ('Test', 'Testing')], default='Prod', max_length=8, null=True, verbose_name='Asset environment')),
|
||||
('status', models.CharField(blank=True, choices=[('In use', 'In use'), ('Out of use', 'Out of use')], default='In use', max_length=12, null=True, verbose_name='Asset status')),
|
||||
('public_ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='Public IP')),
|
||||
('remote_card_ip', models.CharField(blank=True, max_length=16, null=True, verbose_name='Remote control card IP')),
|
||||
('cabinet_no', models.CharField(blank=True, max_length=32, null=True, verbose_name='Cabinet number')),
|
||||
('cabinet_pos', models.IntegerField(blank=True, null=True, verbose_name='Cabinet position')),
|
||||
('number', models.CharField(blank=True, max_length=32, null=True, verbose_name='Asset number')),
|
||||
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
|
||||
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
|
||||
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
|
||||
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
|
||||
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
|
||||
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
|
||||
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
|
||||
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
|
||||
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
|
||||
('platform', models.CharField(blank=True, max_length=128, null=True, verbose_name='Platform')),
|
||||
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
|
||||
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
|
||||
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
|
||||
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AssetGroup',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64, unique=True, verbose_name='Name')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Cluster',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=32, verbose_name='Name')),
|
||||
('bandwidth', models.CharField(blank=True, max_length=32, verbose_name='Bandwidth')),
|
||||
('contact', models.CharField(blank=True, max_length=128, verbose_name='Contact')),
|
||||
('phone', models.CharField(blank=True, max_length=32, verbose_name='Phone')),
|
||||
('address', models.CharField(blank=True, max_length=128, verbose_name='Address')),
|
||||
('intranet', models.TextField(blank=True, verbose_name='Intranet')),
|
||||
('extranet', models.TextField(blank=True, verbose_name='Extranet')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('operator', models.CharField(blank=True, max_length=32, verbose_name='Operator')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, verbose_name='Created by')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('admin_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='assets.AdminUser', verbose_name='Admin user')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemUser',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(max_length=16, verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=32, null=True, verbose_name='Created by')),
|
||||
('priority', models.IntegerField(default=10, verbose_name='Priority')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('auto_push', models.BooleanField(default=True, verbose_name='Auto push')),
|
||||
('sudo', models.TextField(default='/sbin/ifconfig', verbose_name='Sudo')),
|
||||
('shell', models.CharField(default='/bin/bash', max_length=64, verbose_name='Shell')),
|
||||
('cluster', models.ManyToManyField(blank=True, to='assets.Cluster', verbose_name='Cluster')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.AssetGroup', verbose_name='Asset groups'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together=set([('ip', 'port')]),
|
||||
),
|
||||
|
||||
migrations.RunPython(add_default_cluster),
|
||||
migrations.RunPython(add_default_group),
|
||||
]
|
||||
@@ -1,158 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,220 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# assets.migrations.0017_auto_20180702_1415
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_win_to_ssh_protocol,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
||||
@@ -1,48 +0,0 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-16 08:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0019_auto_20180816_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 2.1 on 2018-09-03 03:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0020_auto_20180816_1652'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='domain',
|
||||
options={'verbose_name': 'Domain'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='gateway',
|
||||
options={'verbose_name': 'Gateway'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='node',
|
||||
options={'verbose_name': 'Node'},
|
||||
),
|
||||
]
|
||||
@@ -1,56 +0,0 @@
|
||||
# Generated by Django 2.1.1 on 2018-10-12 09:17
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0021_auto_20180903_1132'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CommandFilter',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64, verbose_name='Name')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, default='', verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CommandFilterRule',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('type', models.CharField(choices=[('regex', 'Regex'), ('command', 'Command')], default='command', max_length=16, verbose_name='Type')),
|
||||
('priority', models.IntegerField(default=50, help_text='1-100, the lower will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority')),
|
||||
('content', models.TextField(help_text='One line one command', max_length=1024, verbose_name='Content')),
|
||||
('action', models.IntegerField(choices=[(0, 'Deny'), (1, 'Allow')], default=0, verbose_name='Action')),
|
||||
('comment', models.CharField(blank=True, default='', max_length=64, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(blank=True, default='', max_length=128, verbose_name='Created by')),
|
||||
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='assets.CommandFilter', verbose_name='Filter')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('priority', 'action'),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='cmd_filters',
|
||||
field=models.ManyToManyField(blank=True, related_name='system_users', to='assets.CommandFilter', verbose_name='Command filter'),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 2.1.1 on 2018-10-16 08:50
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0022_auto_20181012_1717'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilterrule',
|
||||
options={'ordering': ('-priority', 'action')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='commandfilterrule',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=50, help_text='1-100, the higher will be match first', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name='Priority'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.1.4 on 2018-12-19 08:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0023_auto_20181016_1650'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 11:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0024_auto_20181219_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilter',
|
||||
options={'verbose_name': 'Command filter'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilterrule',
|
||||
options={'ordering': ('-priority', 'action'), 'verbose_name': 'Command filter rule'},
|
||||
),
|
||||
]
|
||||
@@ -1,43 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-25 12:35
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0025_auto_20190221_1902'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuthBook',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
|
||||
('version', models.IntegerField(default=1, verbose_name='Version')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'AuthBook',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='node',
|
||||
options={'ordering': ['key'], 'verbose_name': 'Node'},
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-21 09:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='public_ip',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-22 02:58
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0027_auto_20190521_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Protocol',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
|
||||
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=models.ManyToManyField(to='assets.Protocol',
|
||||
verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-22 03:14
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0028_protocol'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-19 03:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0029_auto_20190522_1114'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
]
|
||||
@@ -1,53 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-21 05:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0030_auto_20190619_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
]
|
||||
@@ -1,75 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
import assets.models.utils
|
||||
import common.fields.model
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0031_auto_20190621_1332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
]
|
||||
@@ -1,73 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0032_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-05 05:48
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
from django.db.models import CharField, Value as V
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
|
||||
def migrate_assets_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
assets = asset_model.objects.using(db_alias).all().annotate(
|
||||
protocols_new=Concat(
|
||||
'protocol', V('/'), 'port',
|
||||
output_field=CharField(),
|
||||
),
|
||||
)
|
||||
assets.update(protocols=F('protocols_new'))
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0033_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
|
||||
),
|
||||
migrations.RunPython(migrate_assets_protocol),
|
||||
migrations.DeleteModel(name='Protocol'),
|
||||
]
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-11 12:18
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0034_auto_20190705_1348'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-16 07:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0035_auto_20190711_2018'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='commandfilter',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-24 12:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0036_auto_20190716_1535'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_become_pass',
|
||||
field=models.CharField(blank=True, default='', max_length=128),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-09-11 08:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0037_auto_20190724_2002'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-09-17 12:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0038_auto_20190911_1634'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='authbook',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True, verbose_name='Is active'),
|
||||
),
|
||||
]
|
||||
@@ -1,36 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-09-17 12:56
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0039_authbook_is_active'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 2.1.7 on 2019-09-18 04:10
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0040_auto_20190917_2056'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GatheredUser',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
|
||||
('present', models.BooleanField(default=True, verbose_name='Present')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
],
|
||||
options={'ordering': ['asset'], 'verbose_name': 'GatherUser'},
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-10-16 08:38
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('assets', '0041_gathereduser'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FavoriteAsset',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'asset')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 2.2.5 on 2019-11-14 03:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0042_favoriteasset'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='gathereduser',
|
||||
name='date_last_login',
|
||||
field=models.DateTimeField(null=True, verbose_name='Date last login'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gathereduser',
|
||||
name='ip_last_login',
|
||||
field=models.CharField(default='', max_length=39, verbose_name='IP last login'),
|
||||
),
|
||||
]
|
||||
@@ -1,48 +0,0 @@
|
||||
# Generated by Django 2.2.7 on 2019-12-06 07:26
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def create_internal_platform(apps, schema_editor):
|
||||
model = apps.get_model("assets", "Platform")
|
||||
db_alias = schema_editor.connection.alias
|
||||
type_platforms = (
|
||||
('Linux', 'Linux', None),
|
||||
('Unix', 'Unix', None),
|
||||
('MacOS', 'MacOS', None),
|
||||
('BSD', 'BSD', None),
|
||||
('Windows', 'Windows', None),
|
||||
('Windows2016', 'Windows', {'security': 'tls'}),
|
||||
('Other', 'Other', None),
|
||||
)
|
||||
for name, base, meta in type_platforms:
|
||||
model.objects.using(db_alias).create(
|
||||
name=name, base=base, internal=True, meta=meta
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0043_auto_20191114_1111'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Platform',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
|
||||
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
|
||||
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
|
||||
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
|
||||
('internal', models.BooleanField(default=False, verbose_name='Internal')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Platform'
|
||||
}
|
||||
),
|
||||
migrations.RunPython(create_internal_platform)
|
||||
]
|
||||
@@ -1,47 +0,0 @@
|
||||
# Generated by Django 2.2.7 on 2019-12-06 08:07
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_platform_to_asset_type(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
platform_model = apps.get_model("assets", "Platform")
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
platforms = platform_model.objects.using(db_alias).all()
|
||||
platforms_map = {p.name: p for p in platforms}
|
||||
for name, p in platforms_map.items():
|
||||
asset_model.objects.using(db_alias)\
|
||||
.filter(_platform=name)\
|
||||
.update(platform=p)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0044_platform'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='asset',
|
||||
old_name='platform',
|
||||
new_name='_platform',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.ForeignKey(
|
||||
default=assets.models.asset.Platform.default,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='assets', to='assets.Platform',
|
||||
verbose_name='Platform'),
|
||||
),
|
||||
migrations.RunPython(migrate_platform_to_asset_type),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='_platform',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 2.1.11 on 2019-12-18 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0045_auto_20191206_1607'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc'), ('mysql', 'mysql')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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',),
|
||||
),
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
from .base import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
from .asset_user import *
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .cmd_filter import *
|
||||
from .authbook import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
from .gathered_user import *
|
||||
from .favorite_asset import *
|
||||
@@ -1,389 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.fields.model import JsonDictTextField
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from .base import ConnectivityMixin
|
||||
from .utils import Connectivity
|
||||
|
||||
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def default_cluster():
|
||||
from .cluster import Cluster
|
||||
name = "Default"
|
||||
defaults = {"name": name}
|
||||
cluster, created = Cluster.objects.get_or_create(
|
||||
defaults=defaults, name=name
|
||||
)
|
||||
return cluster.id
|
||||
|
||||
|
||||
def default_node():
|
||||
try:
|
||||
from .node import Node
|
||||
root = Node.org_root()
|
||||
return root
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
class AssetManager(OrgManager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().annotate(
|
||||
platform_base=models.F('platform__base')
|
||||
)
|
||||
|
||||
|
||||
class AssetQuerySet(models.QuerySet):
|
||||
def active(self):
|
||||
return self.filter(is_active=True)
|
||||
|
||||
def valid(self):
|
||||
return self.active()
|
||||
|
||||
def has_protocol(self, name):
|
||||
return self.filter(protocols__contains=name)
|
||||
|
||||
|
||||
class ProtocolsMixin:
|
||||
protocols = ''
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
|
||||
@property
|
||||
def protocols_as_list(self):
|
||||
if not self.protocols:
|
||||
return []
|
||||
return self.protocols.split(' ')
|
||||
|
||||
@property
|
||||
def protocols_as_dict(self):
|
||||
d = OrderedDict()
|
||||
protocols = self.protocols_as_list
|
||||
for i in protocols:
|
||||
if '/' not in i:
|
||||
continue
|
||||
name, port = i.split('/')[:2]
|
||||
if not all([name, port]):
|
||||
continue
|
||||
d[name] = int(port)
|
||||
return d
|
||||
|
||||
@property
|
||||
def protocols_as_json(self):
|
||||
return [
|
||||
{"name": name, "port": port}
|
||||
for name, port in self.protocols_as_dict.items()
|
||||
]
|
||||
|
||||
def has_protocol(self, name):
|
||||
return name in self.protocols_as_dict
|
||||
|
||||
@property
|
||||
def ssh_port(self):
|
||||
return self.protocols_as_dict.get("ssh", 22)
|
||||
|
||||
|
||||
class NodesRelationMixin:
|
||||
NODES_CACHE_KEY = 'ASSET_NODES_{}'
|
||||
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
|
||||
CACHE_TIME = 3600 * 24 * 7
|
||||
id = ""
|
||||
_all_nodes_keys = None
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
nodes = self.nodes.all()
|
||||
if not nodes:
|
||||
nodes = Node.objects.filter(id=Node.org_root().id)
|
||||
return nodes
|
||||
|
||||
def get_all_nodes(self, flat=False):
|
||||
nodes = []
|
||||
for node in self.get_nodes():
|
||||
_nodes = node.get_ancestors(with_self=True)
|
||||
nodes.append(_nodes)
|
||||
if flat:
|
||||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
|
||||
class Platform(models.Model):
|
||||
CHARSET_CHOICES = (
|
||||
('utf8', 'UTF-8'),
|
||||
('gbk', 'GBK'),
|
||||
)
|
||||
BASE_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
|
||||
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
|
||||
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
|
||||
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
|
||||
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
linux, created = cls.objects.get_or_create(
|
||||
defaults={'name': 'Linux'}, name='Linux'
|
||||
)
|
||||
return linux.id
|
||||
|
||||
def is_windows(self):
|
||||
return self.base.lower() in ('windows',)
|
||||
|
||||
def is_unixlike(self):
|
||||
return self.base.lower() in ("linux", "unix", "macos", "bsd")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Platform")
|
||||
# ordering = ('name',)
|
||||
|
||||
|
||||
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=ProtocolsMixin.PROTOCOL_SSH,
|
||||
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
||||
verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
||||
|
||||
# Some information
|
||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
|
||||
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
|
||||
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
|
||||
|
||||
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
|
||||
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
|
||||
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
|
||||
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
|
||||
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
|
||||
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
|
||||
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
|
||||
|
||||
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
|
||||
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
|
||||
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
|
||||
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
|
||||
|
||||
labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels"))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = AssetManager.from_queryset(AssetQuerySet)()
|
||||
_connectivity = None
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
warning = ''
|
||||
if not self.is_active:
|
||||
warning += ' inactive'
|
||||
if warning:
|
||||
return False, warning
|
||||
return True, warning
|
||||
|
||||
@lazyproperty
|
||||
def platform_base(self):
|
||||
return self.platform.base
|
||||
|
||||
@lazyproperty
|
||||
def admin_user_display(self):
|
||||
return self.admin_user.name
|
||||
|
||||
@lazyproperty
|
||||
def admin_user_username(self):
|
||||
"""求可连接性时,直接用用户名去取,避免再查一次admin user
|
||||
serializer 中直接通过annotate方式返回了这个
|
||||
"""
|
||||
return self.admin_user.username
|
||||
|
||||
def is_windows(self):
|
||||
return self.platform.is_windows()
|
||||
|
||||
def is_unixlike(self):
|
||||
return self.platform.is_unixlike()
|
||||
|
||||
def is_support_ansible(self):
|
||||
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
|
||||
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
if self.cpu_model:
|
||||
info += self.cpu_model
|
||||
if self.cpu_count and self.cpu_cores:
|
||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||
return info
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
if self.cpu_count:
|
||||
return '{} Core {} {}'.format(
|
||||
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
|
||||
self.memory, self.disk_total
|
||||
)
|
||||
else:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
if not self.admin_user_username:
|
||||
return Connectivity.unknown()
|
||||
connectivity = ConnectivityMixin.get_asset_username_connectivity(
|
||||
self, self.admin_user_username
|
||||
)
|
||||
return connectivity
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
if not self.admin_user:
|
||||
return
|
||||
self.admin_user.set_asset_connectivity(self, value)
|
||||
|
||||
def get_auth_info(self):
|
||||
if not self.admin_user:
|
||||
return {}
|
||||
|
||||
self.admin_user.load_asset_special_auth(self)
|
||||
info = {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
}
|
||||
return info
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
fake_node = Node()
|
||||
fake_node.id = self.id
|
||||
fake_node.key = self.id
|
||||
fake_node.value = self.hostname
|
||||
fake_node.asset = self
|
||||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
if self.platform_base.lower() == 'windows':
|
||||
icon_skin = 'windows'
|
||||
elif self.platform_base.lower() == 'linux':
|
||||
icon_skin = 'linux'
|
||||
data = {
|
||||
'id': str(self.id),
|
||||
'name': self.hostname,
|
||||
'title': self.ip,
|
||||
'pId': parent_node.key,
|
||||
'isParent': False,
|
||||
'open': False,
|
||||
'iconSkin': icon_skin,
|
||||
'meta': {
|
||||
'type': 'asset',
|
||||
'asset': {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'protocols': self.protocols_as_list,
|
||||
'platform': self.platform_base,
|
||||
}
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
class Meta:
|
||||
unique_together = [('org_id', 'hostname')]
|
||||
verbose_name = _("Asset")
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from .user import AdminUser, SystemUser
|
||||
from random import seed, choice
|
||||
from django.db import IntegrityError
|
||||
from .node import Node
|
||||
from orgs.utils import get_current_org
|
||||
from orgs.models import Organization
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
|
||||
nodes = list(Node.objects.all())
|
||||
seed()
|
||||
for i in range(count):
|
||||
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||
asset = cls(ip='.'.join(ip),
|
||||
hostname='.'.join(ip),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
asset.protocols = 'ssh/22'
|
||||
if nodes and len(nodes) > 3:
|
||||
_nodes = random.sample(nodes, 3)
|
||||
else:
|
||||
_nodes = [Node.default_node()]
|
||||
asset.nodes.set(_nodes)
|
||||
logger.debug('Generate fake asset : %s' % asset.ip)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .authbook import AuthBook
|
||||
|
||||
|
||||
class AssetUser(AuthBook):
|
||||
hostname = ""
|
||||
ip = ""
|
||||
backend = ""
|
||||
union_id = ""
|
||||
asset_username = ""
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
@@ -1,94 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Max
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgManager
|
||||
from .base import BaseUser
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
||||
class AuthBookQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
if self.count() > 1:
|
||||
raise PermissionError(_("Bulk delete deny"))
|
||||
return super().delete()
|
||||
|
||||
|
||||
class AuthBookManager(OrgManager):
|
||||
pass
|
||||
|
||||
|
||||
class AuthBook(BaseUser):
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
|
||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
backend = "db"
|
||||
# 用于system user和admin_user的动态设置
|
||||
_connectivity = None
|
||||
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
||||
def get_related_assets(self):
|
||||
return [self.asset]
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
return self.id
|
||||
|
||||
@classmethod
|
||||
def get_max_version(cls, username, asset):
|
||||
version_max = cls.objects.filter(username=username, asset=asset) \
|
||||
.aggregate(Max('version'))
|
||||
version_max = version_max['version__max'] or 0
|
||||
return version_max
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
"""
|
||||
使用并发锁机制创建AuthBook对象, (主要针对并发创建 username, asset 相同的对象时)
|
||||
并更新其他对象的 is_latest=False (其他对象: 与当前对象的 username, asset 相同)
|
||||
同时设置自己的 is_latest=True, version=max_version + 1
|
||||
"""
|
||||
username = kwargs['username']
|
||||
asset = kwargs['asset']
|
||||
key_lock = 'KEY_LOCK_CREATE_AUTH_BOOK_{}_{}'.format(username, asset.id)
|
||||
with cache.lock(key_lock):
|
||||
with transaction.atomic():
|
||||
cls.objects.filter(
|
||||
username=username, asset=asset, is_latest=True
|
||||
).update(is_latest=False)
|
||||
max_version = cls.get_max_version(username, asset)
|
||||
kwargs.update({
|
||||
'version': max_version + 1,
|
||||
'is_latest': True
|
||||
})
|
||||
obj = cls.objects.create(**kwargs)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
return self.get_asset_connectivity(self.asset)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
return '{}_#_{}'.format(self.username, str(self.asset.id))
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.asset.hostname
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.asset.ip
|
||||
|
||||
def __str__(self):
|
||||
return '{}@{}'.format(self.username, self.asset)
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import io
|
||||
import os
|
||||
import uuid
|
||||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import (
|
||||
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
|
||||
)
|
||||
from common.validators import alphanumeric
|
||||
from common import fields
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .utils import Connectivity
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class ConnectivityMixin:
|
||||
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
|
||||
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 3600 * 24
|
||||
id = ''
|
||||
username = ''
|
||||
|
||||
@property
|
||||
def part_id(self):
|
||||
i = '-'.join(str(self.id).split('-')[:3])
|
||||
return i
|
||||
|
||||
def set_connectivity(self, summary):
|
||||
unreachable = summary.get('dark', {}).keys()
|
||||
reachable = summary.get('contacted', {}).keys()
|
||||
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
for asset in assets:
|
||||
if asset.hostname in unreachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.unreachable())
|
||||
elif asset.hostname in reachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.reachable())
|
||||
else:
|
||||
self.set_asset_connectivity(asset, Connectivity.unknown())
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
'unknown': [],
|
||||
}
|
||||
for asset in assets:
|
||||
connectivity = self.get_asset_connectivity(asset)
|
||||
if connectivity.is_reachable():
|
||||
data["reachable"].append(asset.hostname)
|
||||
elif connectivity.is_unreachable():
|
||||
data["unreachable"].append(asset.hostname)
|
||||
else:
|
||||
data["unknown"].append(asset.hostname)
|
||||
return data
|
||||
|
||||
@property
|
||||
def connectivity_amount(self):
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
amount = cache.get(cache_key)
|
||||
if not amount:
|
||||
amount = {k: len(v) for k, v in self.connectivity.items()}
|
||||
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
|
||||
return amount
|
||||
|
||||
@classmethod
|
||||
def get_asset_username_connectivity(cls, asset, username):
|
||||
key = cls.CONNECTIVITY_ASSET_CACHE_KEY.format(username, asset.id)
|
||||
return Connectivity.get(key)
|
||||
|
||||
def get_asset_connectivity(self, asset):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
return Connectivity.get(key)
|
||||
|
||||
def get_asset_connectivity_key(self, asset):
|
||||
return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)
|
||||
|
||||
def set_asset_connectivity(self, asset, c):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
Connectivity.set(key, c)
|
||||
|
||||
|
||||
class AuthMixin:
|
||||
private_key = ''
|
||||
password = ''
|
||||
public_key = ''
|
||||
username = ''
|
||||
_prefer = 'system_user'
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self.private_key:
|
||||
key_obj = ssh_key_string_to_obj(self.private_key, password=self.password)
|
||||
return key_obj
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def private_key_file(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
def get_private_key(self):
|
||||
if not self.private_key_obj:
|
||||
return None
|
||||
string_io = io.StringIO()
|
||||
self.private_key_obj.write_private_key(string_io)
|
||||
private_key = string_io.getvalue()
|
||||
return private_key
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
try:
|
||||
return sshpubkeys.SSHKey(self.public_key)
|
||||
except TabError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self.password = password
|
||||
update_fields.append('password')
|
||||
if private_key:
|
||||
self.private_key = private_key
|
||||
update_fields.append('private_key')
|
||||
if public_key:
|
||||
self.public_key = public_key
|
||||
update_fields.append('public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def has_special_auth(self, asset=None):
|
||||
from .authbook import AuthBook
|
||||
queryset = AuthBook.objects.filter(username=self.username)
|
||||
if asset:
|
||||
queryset = queryset.filter(asset=asset)
|
||||
return queryset.exists()
|
||||
|
||||
def get_asset_user(self, asset, username=None):
|
||||
from ..backends import AssetUserManager
|
||||
if username is None:
|
||||
username = self.username
|
||||
try:
|
||||
manager = AssetUserManager()
|
||||
other = manager.get_latest(
|
||||
username=username, asset=asset,
|
||||
prefer_id=self.id, prefer=self._prefer,
|
||||
)
|
||||
return other
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
||||
def load_asset_special_auth(self, asset=None, username=None):
|
||||
if not asset:
|
||||
return self
|
||||
|
||||
instance = self.get_asset_user(asset, username=username)
|
||||
if instance:
|
||||
self._merge_auth(instance)
|
||||
|
||||
def _merge_auth(self, other):
|
||||
if other.password:
|
||||
self.password = other.password
|
||||
if other.public_key or other.private_key:
|
||||
self.private_key = other.private_key
|
||||
self.public_key = other.public_key
|
||||
|
||||
def clear_auth(self):
|
||||
self.password = ''
|
||||
self.private_key = ''
|
||||
self.public_key = ''
|
||||
self.save()
|
||||
|
||||
@staticmethod
|
||||
def gen_password():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@staticmethod
|
||||
def gen_key(username):
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=username
|
||||
)
|
||||
return private_key, public_key
|
||||
|
||||
def auto_gen_auth(self, password=True, key=True):
|
||||
_password = None
|
||||
_private_key = None
|
||||
_public_key = None
|
||||
|
||||
if password:
|
||||
_password = self.gen_password()
|
||||
if key:
|
||||
_private_key, _public_key = self.gen_key(self.username)
|
||||
self.set_auth(
|
||||
password=_password, private_key=_private_key,
|
||||
public_key=_public_key
|
||||
)
|
||||
|
||||
|
||||
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 600
|
||||
|
||||
_prefer = "system_user"
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.filter(org_id=self.org_id)
|
||||
return assets
|
||||
|
||||
def get_username(self):
|
||||
return self.username
|
||||
|
||||
@lazyproperty
|
||||
def assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cached = cache.get(cache_key)
|
||||
if not cached:
|
||||
cached = self.get_related_assets().count()
|
||||
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
|
||||
return cached
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
return {
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'password': self.password,
|
||||
'public_key': self.public_key,
|
||||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ['Cluster']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cluster(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=32, verbose_name=_('Name'))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', null=True, blank=True, on_delete=models.SET_NULL, verbose_name=_("Admin user"))
|
||||
bandwidth = models.CharField(max_length=32, blank=True, verbose_name=_('Bandwidth'))
|
||||
contact = models.CharField(max_length=128, blank=True, verbose_name=_('Contact'))
|
||||
phone = models.CharField(max_length=32, blank=True, verbose_name=_('Phone'))
|
||||
address = models.CharField(max_length=128, blank=True, verbose_name=_("Address"))
|
||||
intranet = models.TextField(blank=True, verbose_name=_('Intranet'))
|
||||
extranet = models.TextField(blank=True, verbose_name=_('Extranet'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||
operator = models.CharField(max_length=32, blank=True, verbose_name=_('Operator'))
|
||||
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
return cls.objects.get_or_create(name=_('Default'), created_by=_('System'), comment=_('Default Cluster'))[0]
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = _("Cluster")
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=5):
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
cluster = cls(name=forgery_py.name.full_name(),
|
||||
bandwidth='200M',
|
||||
contact=forgery_py.name.full_name(),
|
||||
phone=forgery_py.address.phone(),
|
||||
address=forgery_py.address.city() + forgery_py.address.street_address(),
|
||||
# operator=choice(['北京联通', '北京电信', 'BGP全网通']),
|
||||
operator=choice([_('Beijing unicom'), _('Beijing telecom'), _('BGP full netcom')]),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
cluster.save()
|
||||
logger.debug('Generate fake asset group: %s' % cluster.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
@@ -1,97 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from django.db import models
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CommandFilter', 'CommandFilterRule'
|
||||
]
|
||||
|
||||
|
||||
class CommandFilter(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_("Name"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Command filter")
|
||||
|
||||
|
||||
class CommandFilterRule(OrgModelMixin):
|
||||
TYPE_REGEX = 'regex'
|
||||
TYPE_COMMAND = 'command'
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_REGEX, _('Regex')),
|
||||
(TYPE_COMMAND, _('Command')),
|
||||
)
|
||||
|
||||
ACTION_DENY, ACTION_ALLOW, ACTION_UNKNOWN = range(3)
|
||||
ACTION_CHOICES = (
|
||||
(ACTION_DENY, _('Deny')),
|
||||
(ACTION_ALLOW, _('Allow')),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
filter = models.ForeignKey('CommandFilter', on_delete=models.CASCADE, verbose_name=_("Filter"), related_name='rules')
|
||||
type = models.CharField(max_length=16, default=TYPE_COMMAND, choices=TYPE_CHOICES, verbose_name=_("Type"))
|
||||
priority = models.IntegerField(default=50, verbose_name=_("Priority"), help_text=_("1-100, the higher will be match first"),
|
||||
validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
content = models.TextField(max_length=1024, verbose_name=_("Content"), help_text=_("One line one command"))
|
||||
action = models.IntegerField(default=ACTION_DENY, choices=ACTION_CHOICES, verbose_name=_("Action"))
|
||||
comment = models.CharField(max_length=64, blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
created_by = models.CharField(max_length=128, blank=True, default='', verbose_name=_('Created by'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('-priority', 'action')
|
||||
verbose_name = _("Command filter rule")
|
||||
|
||||
@lazyproperty
|
||||
def _pattern(self):
|
||||
if self.type == 'command':
|
||||
regex = []
|
||||
content = self.content.replace('\r\n', '\n')
|
||||
for cmd in content.split('\n'):
|
||||
cmd = re.escape(cmd)
|
||||
cmd = cmd.replace('\\ ', '\s+')
|
||||
if cmd[-1].isalpha():
|
||||
regex.append(r'\b{0}\b'.format(cmd))
|
||||
else:
|
||||
regex.append(r'\b{0}'.format(cmd))
|
||||
s = r'{}'.format('|'.join(regex))
|
||||
else:
|
||||
s = r'{0}'.format(self.content)
|
||||
try:
|
||||
_pattern = re.compile(s)
|
||||
except:
|
||||
_pattern = ''
|
||||
return _pattern
|
||||
|
||||
def match(self, data):
|
||||
found = self._pattern.search(data)
|
||||
if not found:
|
||||
return self.ACTION_UNKNOWN, ''
|
||||
|
||||
if self.action == self.ACTION_ALLOW:
|
||||
return self.ACTION_ALLOW, found.group()
|
||||
else:
|
||||
return self.ACTION_DENY, found.group()
|
||||
|
||||
def __str__(self):
|
||||
return '{} % {}'.format(self.type, self.content)
|
||||
@@ -1,100 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
import random
|
||||
import re
|
||||
|
||||
import paramiko
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
from .base import BaseUser
|
||||
|
||||
__all__ = ['Domain', 'Gateway']
|
||||
|
||||
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
verbose_name=_('Date created'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Domain")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def has_gateway(self):
|
||||
return self.gateway_set.filter(is_active=True).exists()
|
||||
|
||||
@property
|
||||
def gateways(self):
|
||||
return self.gateway_set.filter(is_active=True)
|
||||
|
||||
def random_gateway(self):
|
||||
return random.choice(self.gateways)
|
||||
|
||||
|
||||
class Gateway(BaseUser):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
protocol = models.CharField(choices=PROTOCOL_CHOICES, max_length=16, default=PROTOCOL_SSH, verbose_name=_("Protocol"))
|
||||
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
|
||||
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Gateway")
|
||||
|
||||
def test_connective(self, local_port=None):
|
||||
if local_port is None:
|
||||
local_port = self.port
|
||||
if self.password and not re.match(r'\w+$', self.password):
|
||||
return False, _("Password should not contain special characters")
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
proxy = paramiko.SSHClient()
|
||||
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
try:
|
||||
proxy.connect(self.ip, port=self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
pkey=self.private_key_obj)
|
||||
except(paramiko.AuthenticationException,
|
||||
paramiko.BadAuthenticationType,
|
||||
paramiko.SSHException,
|
||||
paramiko.ssh_exception.NoValidConnectionsError) as e:
|
||||
return False, str(e)
|
||||
|
||||
try:
|
||||
sock = proxy.get_transport().open_channel(
|
||||
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
|
||||
)
|
||||
client.connect("127.0.0.1", port=local_port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
key_filename=self.private_key_file,
|
||||
sock=sock,
|
||||
timeout=5)
|
||||
except (paramiko.SSHException, paramiko.ssh_exception.SSHException,
|
||||
paramiko.AuthenticationException, TimeoutError) as e:
|
||||
return False, str(e)
|
||||
finally:
|
||||
client.close()
|
||||
return True, None
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.db import models
|
||||
|
||||
from common.mixins.models import CommonModelMixin
|
||||
|
||||
|
||||
__all__ = ['FavoriteAsset']
|
||||
|
||||
|
||||
class FavoriteAsset(CommonModelMixin):
|
||||
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'asset')
|
||||
|
||||
@classmethod
|
||||
def get_user_favorite_assets_id(cls, user):
|
||||
return cls.objects.filter(user=user).values_list('asset', flat=True)
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
__all__ = ['GatheredUser']
|
||||
|
||||
|
||||
class GatheredUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
|
||||
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
|
||||
present = models.BooleanField(default=True, verbose_name=_("Present"))
|
||||
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
|
||||
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.asset.hostname
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.asset.ip
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('GatherUser')
|
||||
ordering = ['asset']
|
||||
|
||||
def __str__(self):
|
||||
return '{}: {}'.format(self.asset.hostname, self.username)
|
||||
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
import logging
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
__all__ = ['AssetGroup']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssetGroup(models.Model):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||
created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
verbose_name = _("Asset group")
|
||||
|
||||
@classmethod
|
||||
def initial(cls):
|
||||
asset_group = cls(name=_('Default'), comment=_('Default asset group'))
|
||||
asset_group.save()
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
group = cls(name=forgery_py.name.full_name(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
group.save()
|
||||
logger.debug('Generate fake asset group: %s' % group.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from orgs.mixins.models import OrgModelMixin
|
||||
|
||||
|
||||
class Label(OrgModelMixin):
|
||||
SYSTEM_CATEGORY = "S"
|
||||
USER_CATEGORY = "U"
|
||||
CATEGORY_CHOICES = (
|
||||
("S", _("System")),
|
||||
("U", _("User"))
|
||||
)
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_("Name"))
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES,
|
||||
default=USER_CATEGORY, verbose_name=_("Category"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
|
||||
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_queryset_group_by_name(cls):
|
||||
names = cls.objects.values_list('name', flat=True)
|
||||
for name in names:
|
||||
yield name, cls.objects.filter(name=name)
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}".format(self.name, self.value)
|
||||
|
||||
class Meta:
|
||||
db_table = "assets_label"
|
||||
unique_together = [('name', 'value', 'org_id')]
|
||||
@@ -1,586 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import re
|
||||
import time
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.db.utils import IntegrityError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext
|
||||
from django.core.cache import cache
|
||||
|
||||
from common.utils import get_logger, lazyproperty
|
||||
from orgs.mixins.models import OrgModelMixin, OrgManager
|
||||
from orgs.utils import get_current_org, tmp_to_org, current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
|
||||
__all__ = ['Node']
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class NodeQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
raise PermissionError("Bulk delete node deny")
|
||||
|
||||
|
||||
class TreeCache:
|
||||
updated_time_cache_key = 'NODE_TREE_UPDATED_AT_{}'
|
||||
cache_time = 3600
|
||||
assets_updated_time_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT_{}'
|
||||
|
||||
def __init__(self, tree, org_id):
|
||||
now = time.time()
|
||||
self.created_time = now
|
||||
self.assets_created_time = now
|
||||
self.tree = tree
|
||||
self.org_id = org_id
|
||||
|
||||
def _has_changed(self, tp="tree"):
|
||||
if tp == "assets":
|
||||
key = self.assets_updated_time_cache_key.format(self.org_id)
|
||||
else:
|
||||
key = self.updated_time_cache_key.format(self.org_id)
|
||||
updated_time = cache.get(key, 0)
|
||||
if updated_time > self.created_time:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def set_changed(cls, tp="tree", t=None, org_id=None):
|
||||
if org_id is None:
|
||||
org_id = current_org.id
|
||||
if tp == "assets":
|
||||
key = cls.assets_updated_time_cache_key.format(org_id)
|
||||
else:
|
||||
key = cls.updated_time_cache_key.format(org_id)
|
||||
ttl = cls.cache_time
|
||||
if not t:
|
||||
t = time.time()
|
||||
cache.set(key, t, ttl)
|
||||
|
||||
def tree_has_changed(self):
|
||||
return self._has_changed("tree")
|
||||
|
||||
def set_tree_changed(self, t=None):
|
||||
logger.debug("Set tree tree changed")
|
||||
self.__class__.set_changed(t=t, tp="tree")
|
||||
|
||||
def assets_has_changed(self):
|
||||
return self._has_changed("assets")
|
||||
|
||||
def set_tree_assets_changed(self, t=None):
|
||||
logger.debug("Set tree assets changed")
|
||||
self.__class__.set_changed(t=t, tp="assets")
|
||||
|
||||
def get(self):
|
||||
if self.tree_has_changed():
|
||||
self.renew()
|
||||
return self.tree
|
||||
if self.assets_has_changed():
|
||||
self.tree.init_assets()
|
||||
return self.tree
|
||||
|
||||
def renew(self):
|
||||
new_obj = self.__class__.new(self.org_id)
|
||||
self.tree = new_obj.tree
|
||||
self.created_time = new_obj.created_time
|
||||
self.assets_created_time = new_obj.assets_created_time
|
||||
|
||||
@classmethod
|
||||
def new(cls, org_id=None):
|
||||
from ..utils import TreeService
|
||||
logger.debug("Create node tree")
|
||||
if not org_id:
|
||||
org_id = current_org.id
|
||||
with tmp_to_org(org_id):
|
||||
tree = TreeService.new()
|
||||
obj = cls(tree, org_id)
|
||||
obj.tree = tree
|
||||
return obj
|
||||
|
||||
|
||||
class TreeMixin:
|
||||
_org_tree_map = {}
|
||||
|
||||
@classmethod
|
||||
def tree(cls):
|
||||
org_id = current_org.org_id()
|
||||
t = cls.get_local_tree_cache(org_id)
|
||||
|
||||
if t is None:
|
||||
t = TreeCache.new()
|
||||
cls._org_tree_map[org_id] = t
|
||||
return t.get()
|
||||
|
||||
@classmethod
|
||||
def get_local_tree_cache(cls, org_id=None):
|
||||
t = cls._org_tree_map.get(org_id)
|
||||
return t
|
||||
|
||||
@classmethod
|
||||
def refresh_tree(cls, t=None):
|
||||
TreeCache.set_changed(tp="tree", t=t, org_id=current_org.id)
|
||||
|
||||
@classmethod
|
||||
def refresh_node_assets(cls, t=None):
|
||||
TreeCache.set_changed(tp="assets", t=t, org_id=current_org.id)
|
||||
|
||||
|
||||
class FamilyMixin:
|
||||
__parents = None
|
||||
__children = None
|
||||
__all_children = None
|
||||
is_node = True
|
||||
|
||||
@staticmethod
|
||||
def clean_children_keys(nodes_keys):
|
||||
nodes_keys = sorted(list(nodes_keys), key=lambda x: (len(x), x))
|
||||
nodes_keys_clean = []
|
||||
for key in nodes_keys[::-1]:
|
||||
found = False
|
||||
for k in nodes_keys:
|
||||
if key.startswith(k + ':'):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
nodes_keys_clean.append(key)
|
||||
return nodes_keys_clean
|
||||
|
||||
@classmethod
|
||||
def get_node_all_children_key_pattern(cls, key, with_self=True):
|
||||
pattern = r'^{0}:'.format(key)
|
||||
if with_self:
|
||||
pattern += r'|^{0}$'.format(key)
|
||||
return pattern
|
||||
|
||||
@classmethod
|
||||
def get_node_children_key_pattern(cls, key, with_self=True):
|
||||
pattern = r'^{0}:[0-9]+$'.format(key)
|
||||
if with_self:
|
||||
pattern += r'|^{0}$'.format(key)
|
||||
return pattern
|
||||
|
||||
def get_children_key_pattern(self, with_self=False):
|
||||
return self.get_node_children_key_pattern(self.key, with_self=with_self)
|
||||
|
||||
def get_all_children_pattern(self, with_self=False):
|
||||
return self.get_node_all_children_key_pattern(self.key, with_self=with_self)
|
||||
|
||||
def is_children(self, other):
|
||||
children_pattern = other.get_children_key_pattern(with_self=False)
|
||||
return re.match(children_pattern, self.key)
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
pattern = self.get_children_key_pattern(with_self=with_self)
|
||||
return Node.objects.filter(key__regex=pattern)
|
||||
|
||||
def get_all_children(self, with_self=False):
|
||||
pattern = self.get_all_children_pattern(with_self=with_self)
|
||||
children = Node.objects.filter(key__regex=pattern)
|
||||
return children
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self.get_children(with_self=False)
|
||||
|
||||
@property
|
||||
def all_children(self):
|
||||
return self.get_all_children(with_self=False)
|
||||
|
||||
def create_child(self, value, _id=None):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(
|
||||
id=_id, key=child_key, value=value
|
||||
)
|
||||
return child
|
||||
|
||||
def get_next_child_key(self):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
self.save()
|
||||
return "{}:{}".format(self.key, mark)
|
||||
|
||||
def get_next_child_preset_name(self):
|
||||
name = ugettext("New node")
|
||||
values = [
|
||||
child.value[child.value.rfind(' '):]
|
||||
for child in self.get_children()
|
||||
if child.value.startswith(name)
|
||||
]
|
||||
values = [int(value) for value in values if value.strip().isdigit()]
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
# Parents
|
||||
@classmethod
|
||||
def get_node_ancestor_keys(cls, key, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
parent_keys.append(":".join(key_list))
|
||||
key_list.pop()
|
||||
return parent_keys
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
return self.get_node_ancestor_keys(
|
||||
self.key, with_self=with_self
|
||||
)
|
||||
|
||||
@property
|
||||
def ancestors(self):
|
||||
return self.get_ancestors(with_self=False)
|
||||
|
||||
def get_ancestors(self, with_self=False):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
|
||||
return self.__class__.objects.filter(key__in=ancestor_keys)
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
def is_parent(self, other):
|
||||
return other.is_children(self)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.is_org_root():
|
||||
return self
|
||||
parent_key = self.parent_key
|
||||
return Node.objects.get(key=parent_key)
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
self.save()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
|
||||
def get_siblings(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = Node.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
ancestors = self.get_ancestors()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestors), self, *tuple(children)]
|
||||
|
||||
|
||||
class FullValueMixin:
|
||||
key = ''
|
||||
|
||||
@lazyproperty
|
||||
def full_value(self):
|
||||
if self.is_org_root():
|
||||
return self.value
|
||||
value = self.tree().get_node_full_tag(self.key)
|
||||
return value
|
||||
|
||||
|
||||
class NodeAssetsMixin:
|
||||
key = ''
|
||||
id = None
|
||||
|
||||
@lazyproperty
|
||||
def assets_amount(self):
|
||||
amount = self.tree().assets_amount(self.key)
|
||||
return amount
|
||||
|
||||
def get_all_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_org_root():
|
||||
return Asset.objects.filter(org_id=self.org_id)
|
||||
pattern = '^{0}$|^{0}:'.format(self.key)
|
||||
return Asset.objects.filter(nodes__key__regex=pattern).distinct()
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_org_root():
|
||||
assets = Asset.objects.filter(Q(nodes=self) | Q(nodes__isnull=True))
|
||||
else:
|
||||
assets = Asset.objects.filter(nodes=self)
|
||||
return assets.distinct()
|
||||
|
||||
def get_valid_assets(self):
|
||||
return self.get_assets().valid()
|
||||
|
||||
def get_all_valid_assets(self):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
@classmethod
|
||||
def _get_nodes_all_assets(cls, nodes_keys):
|
||||
"""
|
||||
当节点比较多的时候,这种正则方式性能差极了
|
||||
:param nodes_keys:
|
||||
:return:
|
||||
"""
|
||||
from .asset import Asset
|
||||
nodes_keys = cls.clean_children_keys(nodes_keys)
|
||||
nodes_children_pattern = set()
|
||||
for key in nodes_keys:
|
||||
children_pattern = cls.get_node_all_children_key_pattern(key)
|
||||
nodes_children_pattern.add(children_pattern)
|
||||
pattern = '|'.join(nodes_children_pattern)
|
||||
return Asset.objects.filter(nodes__key__regex=pattern).distinct()
|
||||
|
||||
@classmethod
|
||||
def get_nodes_all_assets_ids(cls, nodes_keys):
|
||||
nodes_keys = cls.clean_children_keys(nodes_keys)
|
||||
assets_ids = set()
|
||||
for key in nodes_keys:
|
||||
node_assets_ids = cls.tree().all_assets(key)
|
||||
assets_ids.update(set(node_assets_ids))
|
||||
return assets_ids
|
||||
|
||||
@classmethod
|
||||
def get_nodes_all_assets(cls, nodes_keys, extra_assets_ids=None):
|
||||
from .asset import Asset
|
||||
nodes_keys = cls.clean_children_keys(nodes_keys)
|
||||
assets_ids = cls.get_nodes_all_assets_ids(nodes_keys)
|
||||
if extra_assets_ids:
|
||||
assets_ids.update(set(extra_assets_ids))
|
||||
return Asset.objects.filter(id__in=assets_ids)
|
||||
|
||||
|
||||
class SomeNodesMixin:
|
||||
key = ''
|
||||
default_key = '1'
|
||||
default_value = 'Default'
|
||||
ungrouped_key = '-10'
|
||||
ungrouped_value = _('ungrouped')
|
||||
empty_key = '-11'
|
||||
empty_value = _("empty")
|
||||
favorite_key = '-12'
|
||||
favorite_value = _("favorite")
|
||||
|
||||
def is_default_node(self):
|
||||
return self.key == self.default_key
|
||||
|
||||
def is_org_root(self):
|
||||
if self.key.isdigit():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_next_org_root_node_key(cls):
|
||||
with tmp_to_org(Organization.root()):
|
||||
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
|
||||
if not org_nodes_roots_keys:
|
||||
org_nodes_roots_keys = ['1']
|
||||
max_key = max([int(k) for k in org_nodes_roots_keys])
|
||||
key = str(max_key + 1) if max_key != 0 else '2'
|
||||
return key
|
||||
|
||||
@classmethod
|
||||
def create_org_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
ori_org = get_current_org()
|
||||
with transaction.atomic():
|
||||
if not ori_org.is_real():
|
||||
return cls.default_node()
|
||||
key = cls.get_next_org_root_node_key()
|
||||
root = cls.objects.create(key=key, value=ori_org.name)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def org_root(cls):
|
||||
root = cls.objects.filter(key__regex=r'^[0-9]+$')
|
||||
if root:
|
||||
return root[0]
|
||||
else:
|
||||
return cls.create_org_root_node()
|
||||
|
||||
@classmethod
|
||||
def ungrouped_node(cls):
|
||||
with tmp_to_org(Organization.system()):
|
||||
defaults = {'value': cls.ungrouped_value}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.ungrouped_key
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
with tmp_to_org(Organization.default()):
|
||||
defaults = {'value': cls.default_value}
|
||||
try:
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.default_key,
|
||||
)
|
||||
except IntegrityError as e:
|
||||
logger.error("Create default node failed: {}".format(e))
|
||||
cls.modify_other_org_root_node_key()
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.default_key,
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def favorite_node(cls):
|
||||
with tmp_to_org(Organization.system()):
|
||||
defaults = {'value': cls.favorite_value}
|
||||
obj, created = cls.objects.get_or_create(
|
||||
defaults=defaults, key=cls.favorite_key
|
||||
)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def initial_some_nodes(cls):
|
||||
cls.default_node()
|
||||
cls.ungrouped_node()
|
||||
cls.favorite_node()
|
||||
|
||||
@classmethod
|
||||
def modify_other_org_root_node_key(cls):
|
||||
"""
|
||||
解决创建 default 节点失败的问题,
|
||||
因为在其他组织下存在 default 节点,故在 DEFAULT 组织下 get 不到 create 失败
|
||||
"""
|
||||
logger.info("Modify other org root node key")
|
||||
|
||||
with tmp_to_org(Organization.root()):
|
||||
node_key1 = cls.objects.filter(key='1').first()
|
||||
if not node_key1:
|
||||
logger.info("Not found node that `key` = 1")
|
||||
return
|
||||
if not node_key1.org.is_real():
|
||||
logger.info("Org is not real for node that `key` = 1")
|
||||
return
|
||||
|
||||
with transaction.atomic():
|
||||
with tmp_to_org(node_key1.org):
|
||||
org_root_node_new_key = cls.get_next_org_root_node_key()
|
||||
for n in cls.objects.all():
|
||||
old_key = n.key
|
||||
key_list = n.key.split(':')
|
||||
key_list[0] = org_root_node_new_key
|
||||
new_key = ':'.join(key_list)
|
||||
n.key = new_key
|
||||
n.save()
|
||||
logger.info('Modify key ( {} > {} )'.format(old_key, new_key))
|
||||
|
||||
|
||||
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OrgManager.from_queryset(NodeQuerySet)()
|
||||
is_node = True
|
||||
_parents = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
ordering = ['key']
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
# def __eq__(self, other):
|
||||
# if not other:
|
||||
# return False
|
||||
# return self.id == other.id
|
||||
#
|
||||
def __gt__(self, other):
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
self_parent_key = self_key[:-1]
|
||||
other_parent_key = other_key[:-1]
|
||||
|
||||
if self_parent_key and self_parent_key == other_parent_key:
|
||||
return self.value > other.value
|
||||
return self_key > other_key
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
return len(self.key.split(':'))
|
||||
|
||||
@classmethod
|
||||
def refresh_nodes(cls):
|
||||
cls.refresh_tree()
|
||||
|
||||
@classmethod
|
||||
def refresh_assets(cls):
|
||||
cls.refresh_node_assets()
|
||||
|
||||
def as_tree_node(self):
|
||||
from common.tree import TreeNode
|
||||
name = '{} ({})'.format(self.value, self.assets_amount)
|
||||
data = {
|
||||
'id': self.key,
|
||||
'name': name,
|
||||
'title': name,
|
||||
'pId': self.parent_key,
|
||||
'isParent': True,
|
||||
'open': self.is_org_root(),
|
||||
'meta': {
|
||||
'node': {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"value": self.value,
|
||||
"key": self.key,
|
||||
"assets_amount": self.assets_amount,
|
||||
},
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
def has_children_or_has_assets(self):
|
||||
if self.children or self.get_assets().exists():
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.has_children_or_has_assets():
|
||||
return
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
nodes = list(cls.objects.all())
|
||||
if count > 100:
|
||||
length = 100
|
||||
else:
|
||||
length = count
|
||||
|
||||
for i in range(length):
|
||||
node = random.choice(nodes)
|
||||
child = node.create_child('Node {}'.format(i))
|
||||
print("{}. {}".format(i, child))
|
||||
@@ -1,215 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import signer
|
||||
from .base import BaseUser
|
||||
from .asset import Asset
|
||||
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminUser(BaseUser):
|
||||
"""
|
||||
A privileged user that ansible can use it to push system user and so on
|
||||
"""
|
||||
BECOME_METHOD_CHOICES = (
|
||||
('sudo', 'sudo'),
|
||||
('su', 'su'),
|
||||
)
|
||||
become = models.BooleanField(default=True)
|
||||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', blank=True, max_length=128)
|
||||
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
|
||||
_prefer = "admin_user"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def become_pass(self):
|
||||
password = signer.unsign(self._become_pass)
|
||||
if password:
|
||||
return password
|
||||
else:
|
||||
return ""
|
||||
|
||||
@become_pass.setter
|
||||
def become_pass(self, password):
|
||||
self._become_pass = signer.sign(password)
|
||||
|
||||
@property
|
||||
def become_info(self):
|
||||
if self.become:
|
||||
info = {
|
||||
"method": self.become_method,
|
||||
"user": self.become_user,
|
||||
"pass": self.become_pass,
|
||||
}
|
||||
else:
|
||||
info = None
|
||||
return info
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Admin user")
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=10):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
obj = cls(name=forgery_py.name.full_name(),
|
||||
username=forgery_py.internet.user_name(),
|
||||
password=forgery_py.lorem_ipsum.word(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
obj.save()
|
||||
logger.debug('Generate fake asset group: %s' % obj.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
|
||||
|
||||
class SystemUser(BaseUser):
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
PROTOCOL_VNC = 'vnc'
|
||||
PROTOCOL_MYSQL = 'mysql'
|
||||
PROTOCOL_CHOICES = (
|
||||
(PROTOCOL_SSH, 'ssh'),
|
||||
(PROTOCOL_RDP, 'rdp'),
|
||||
(PROTOCOL_TELNET, 'telnet'),
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
(PROTOCOL_MYSQL, 'mysql'),
|
||||
)
|
||||
|
||||
LOGIN_AUTO = 'auto'
|
||||
LOGIN_MANUAL = 'manual'
|
||||
LOGIN_MODE_CHOICES = (
|
||||
(LOGIN_AUTO, _('Automatic login')),
|
||||
(LOGIN_MANUAL, _('Manually login'))
|
||||
)
|
||||
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
|
||||
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
|
||||
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
|
||||
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
|
||||
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
|
||||
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
|
||||
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
|
||||
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
|
||||
sudo = models.TextField(default='/bin/whoami', verbose_name=_('Sudo'))
|
||||
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
|
||||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
|
||||
_prefer = 'system_user'
|
||||
|
||||
def __str__(self):
|
||||
username = self.username
|
||||
if self.username_same_with_user:
|
||||
username = 'dynamic'
|
||||
return '{0.name}({1})'.format(self, username)
|
||||
|
||||
def get_username(self):
|
||||
if self.username_same_with_user:
|
||||
return list(self.users.values_list('username', flat=True))
|
||||
else:
|
||||
return self.username
|
||||
|
||||
@property
|
||||
def nodes_amount(self):
|
||||
return self.nodes.all().count()
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_need_cmd_filter(self):
|
||||
return self.protocol not in [self.PROTOCOL_RDP, self.PROTOCOL_VNC]
|
||||
|
||||
@property
|
||||
def is_need_test_asset_connective(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
|
||||
@property
|
||||
def can_perm_to_asset(self):
|
||||
return self.protocol not in [self.PROTOCOL_MYSQL]
|
||||
|
||||
def _merge_auth(self, other):
|
||||
super()._merge_auth(other)
|
||||
if self.username_same_with_user:
|
||||
self.username = other.username
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
rules = CommandFilterRule.objects.filter(
|
||||
filter__in=self.cmd_filters.all()
|
||||
).distinct()
|
||||
return rules
|
||||
|
||||
def is_command_can_run(self, command):
|
||||
for rule in self.cmd_filter_rules:
|
||||
action, matched_cmd = rule.match(command)
|
||||
if action == rule.ACTION_ALLOW:
|
||||
return True, None
|
||||
elif action == rule.ACTION_DENY:
|
||||
return False, matched_cmd
|
||||
return True, None
|
||||
|
||||
def get_all_assets(self):
|
||||
from assets.models import Node
|
||||
nodes_keys = self.nodes.all().values_list('key', flat=True)
|
||||
assets_ids = set(self.assets.all().values_list('id', flat=True))
|
||||
nodes_assets_ids = Node.get_nodes_all_assets_ids(nodes_keys)
|
||||
assets_ids.update(nodes_assets_ids)
|
||||
assets = Asset.objects.filter(id__in=assets_ids)
|
||||
return assets
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("System user")
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=10):
|
||||
from random import seed
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
|
||||
seed()
|
||||
for i in range(count):
|
||||
obj = cls(name=forgery_py.name.full_name(),
|
||||
username=forgery_py.internet.user_name(),
|
||||
password=forgery_py.lorem_ipsum.word(),
|
||||
comment=forgery_py.lorem_ipsum.sentence(),
|
||||
created_by='Fake')
|
||||
try:
|
||||
obj.save()
|
||||
logger.debug('Generate fake asset group: %s' % obj.name)
|
||||
except IntegrityError:
|
||||
print('Error continue')
|
||||
continue
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import validate_ssh_private_key
|
||||
|
||||
|
||||
__all__ = [
|
||||
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
|
||||
]
|
||||
|
||||
|
||||
def init_model():
|
||||
from . import SystemUser, AdminUser, Asset
|
||||
for cls in [SystemUser, AdminUser, Asset]:
|
||||
if hasattr(cls, 'initial'):
|
||||
cls.initial()
|
||||
|
||||
|
||||
def generate_fake():
|
||||
from . import SystemUser, AdminUser, Asset
|
||||
for cls in [SystemUser, AdminUser, Asset]:
|
||||
if hasattr(cls, 'generate_fake'):
|
||||
cls.generate_fake()
|
||||
|
||||
|
||||
def private_key_validator(value):
|
||||
if not validate_ssh_private_key(value):
|
||||
raise ValidationError(
|
||||
_('%(value)s is not an even number'),
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
|
||||
class Connectivity:
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
status = UNKNOWN
|
||||
datetime = timezone.now()
|
||||
|
||||
def __init__(self, status, datetime):
|
||||
self.status = status
|
||||
self.datetime = datetime
|
||||
|
||||
def display(self):
|
||||
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
|
||||
|
||||
def is_reachable(self):
|
||||
return self.status == self.REACHABLE
|
||||
|
||||
def is_unreachable(self):
|
||||
return self.status == self.UNREACHABLE
|
||||
|
||||
def is_unknown(self):
|
||||
return self.status == self.UNKNOWN
|
||||
|
||||
@classmethod
|
||||
def unreachable(cls):
|
||||
return cls(cls.UNREACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def reachable(cls):
|
||||
return cls(cls.REACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def unknown(cls):
|
||||
return cls(cls.UNKNOWN, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, value, ttl=None):
|
||||
cache.set(key, value, ttl)
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
value = cache.get(key, cls.unknown())
|
||||
if not isinstance(value, cls):
|
||||
value = cls.unknown()
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def set_unreachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.unreachable(), ttl)
|
||||
|
||||
@classmethod
|
||||
def set_reachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.reachable(), ttl)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.status == other.status
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.status > other.status
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __str__(self):
|
||||
return self.display()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user