Compare commits

...

562 Commits

Author SHA1 Message Date
BaiJiangJie
05818671a7 V52 (#3158)
* [Update] 优化切换组织时重定向页面逻辑 (#3133)

* [Update] 优化切换组织时重定向页面逻辑

* [Update] 优化切换组织时重定向页面逻辑 2

* [Update] WebTerminal 跳转添加时间戳

* [Update] 修复创建授权规则授权节点时,系统用户不自动推送的问题
2019-08-26 11:58:06 +08:00
老广
7d4e9c5669 Merge pull request #3131 from jumpserver/dev
Dev
2019-08-16 10:48:16 +08:00
BaiJiangJie
088f815a30 Merge pull request #3129 from jumpserver/v52_remoteapp
V52 remoteapp
2019-08-15 19:10:18 +08:00
BaiJiangJie
50f50a4f28 [Bugfix] 修复remote-app应用创建/更新时,参数设置不成功的bug 2019-08-15 19:08:04 +08:00
BaiJiangJie
820dcaaae7 Merge pull request #3127 from jumpserver/v52_manager
V52 manager
2019-08-15 15:30:06 +08:00
BaiJiangJie
10de4f4ef8 Merge pull request #3126 from jumpserver/v52_manager
[Update] 修复资产用户管理器获取 __name__ 的问题
2019-08-15 15:28:40 +08:00
BaiJiangJie
214b4026ec [Update] 修复资产用户管理器获取 __name__ 的问题 2019-08-15 15:26:14 +08:00
老广
a5e59e6787 Merge pull request #3121 from jumpserver/dev
Dev
2019-08-14 20:26:46 +08:00
ibuler
907d93bcad [Update] 还原一些 2019-08-14 20:26:05 +08:00
ibuler
c84df42ff4 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-08-14 20:23:26 +08:00
ibuler
8fb0d6315d [Update] 修改导入bug 2019-08-14 20:23:02 +08:00
BaiJiangJie
72e746181f Merge pull request #3118 from jumpserver/dev_perms
[Update] 优化资产授权详情页面,添加系统用户后自动reload页面
2019-08-14 18:00:14 +08:00
BaiJiangJie
a10fa3c55b [Update] 优化资产授权详情页面,添加系统用户后自动reload页面 2019-08-14 17:59:11 +08:00
老广
d0633adc38 Merge pull request #3109 from KubeOperator/patch-1
Update README.md
2019-08-12 19:15:04 +08:00
老广
d8ee6d6092 Merge pull request #3110 from jumpserver/dev
[Update] 修改nodes
2019-08-12 19:14:08 +08:00
ibuler
a315df29ca [Update] 暂时不更改这里逻辑 2019-08-12 19:11:32 +08:00
ibuler
2e9711ae8a [Update] 修改nodes 2019-08-12 19:03:10 +08:00
KubeOperator
75f66a3809 Update README.md 2019-08-12 19:01:18 +08:00
老广
fff9f31913 Merge pull request #3108 from jumpserver/dev
Dev
2019-08-12 17:45:34 +08:00
老广
ef73469d5d Merge pull request #3107 from jumpserver/bugfix
[Update] 修改避免每次刷新asset user的可连接性
2019-08-12 17:42:35 +08:00
ibuler
05e4e7301c [Update] 修改避免每次刷新asset user的可连接性 2019-08-12 17:41:18 +08:00
老广
2d7d9d4de7 Merge pull request #3105 from jumpserver/bugfix
Bugfix
2019-08-12 17:14:50 +08:00
ibuler
fdef282c57 [Bugfix] 修复批量更新用户的bug, 导出字段添加 * 2019-08-12 17:05:01 +08:00
老广
39528a935b Merge pull request #3100 from jumpserver/elfinder_theme
fix selected item display bug
2019-08-09 16:40:18 +08:00
Eric
22b0b77b83 fix selected item display bug 2019-08-09 16:13:31 +08:00
ibuler
a188968e5d [Update] 修复Model过长的bug 2019-08-08 19:04:50 +08:00
ibuler
3c9814191d [Update] 修改缩进 2019-08-08 18:33:48 +08:00
ibuler
785b375f4a [Bugfix] 修复授权列表用户无法搜索的bug 2019-08-08 18:32:44 +08:00
ibuler
fbfbfcc274 [Update] 修复磁盘显示单位问题 2019-08-08 17:59:51 +08:00
ibuler
42ff5a382a [Update] 修改设置session的时间,避免sdk等报错 2019-08-08 15:14:08 +08:00
老广
acc03f14dd Merge pull request #3094 from jumpserver/dev
Dev
2019-08-08 14:27:45 +08:00
ibuler
0ee5d3b79f [Update] Add to english readme 2019-08-08 14:26:01 +08:00
ibuler
53a5c53857 [Update] 添加访问数量 2019-08-08 14:24:37 +08:00
老广
e14bbb2698 Merge pull request #3093 from jumpserver/allow_show_all_nodes
[Update] 控制是否显示未分组节点
2019-08-08 13:38:19 +08:00
ibuler
6b56c43a3a [Update] 控制是否显示未分组节点 2019-08-08 13:03:03 +08:00
老广
d222376b1f Merge pull request #3075 from jumpserver/dev
Dev
2019-08-05 10:15:20 +08:00
ibuler
c3a206b291 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-08-05 10:01:33 +08:00
ibuler
afffb50b94 [UPdate] 修改手动模式问题 2019-08-05 10:01:03 +08:00
老广
45e06d215e Merge pull request #3071 from jumpserver/i18n
修改elfinder中文翻译
2019-08-02 19:51:42 +08:00
Eric
2208320049 修改elfinder中文翻译 2019-08-02 19:49:25 +08:00
BaiJiangJie
af2b6742cf Merge pull request #3066 from jumpserver/dev
Dev
2019-08-01 17:13:53 +08:00
BaiJiangJie
4254775149 Bugfix (#3065)
* [Update] 修复浏览器关闭后session不失效的问题

* [Update] 修改一些内容

* [Update] 解决命令执行找不到对象的问题

* [Update] 修改Permission判断

* [Update] 修改session

* [Update] 修改创建系统用户时没有public key
2019-08-01 17:10:02 +08:00
BaiJiangJie
44d41e86c9 Merge pull request #3059 from jumpserver/dev_users
[Update] 添加用户过期邮件提醒
2019-07-31 17:07:27 +08:00
BaiJiangJie
586d6e6abb [Update] 添加用户过期邮件提醒 2019-07-31 16:57:21 +08:00
BaiJiangJie
9ab6c58676 Merge pull request #3055 from jumpserver/dev_login
[Update] 用户登录失败次数提示
2019-07-30 19:12:39 +08:00
BaiJiangJie
3d9c0a212e [Update] 用户登录失败次数提示 2019-07-30 19:10:06 +08:00
老广
f51c6efddc Merge pull request #3054 from jumpserver/dev
获取子节点资产时 使用 in
2019-07-30 17:48:24 +08:00
老广
bb235f3e88 [Update] 修改获取资产时使用in (#3053) 2019-07-30 17:46:27 +08:00
ibuler
c706a410a2 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-30 16:50:05 +08:00
ibuler
d0c74a7704 [Bugfix] 修复只设置公钥不推送的问题 2019-07-30 16:49:54 +08:00
BaiJiangJie
32dacecd24 Merge pull request #3052 from jumpserver/dev_bai
[Update] htmlEscape
2019-07-30 15:11:02 +08:00
BaiJiangJie
a7066a5c85 [Update] htmlEscape 2019-07-30 15:10:14 +08:00
BaiJiangJie
4fe74e9e25 Merge pull request #3051 from jumpserver/dev_bai
[Update] 密码过期邮件发送(密码未过期用户&有效用户)
2019-07-30 12:57:55 +08:00
BaiJiangJie
637fc91413 [Update] 密码过期邮件发送(密码未过期用户&有效用户) 2019-07-30 12:52:45 +08:00
BaiJiangJie
b5aa69db61 Merge pull request #3045 from jumpserver/dev_bai
[Update] 修改翻译(Cloud)
2019-07-29 18:44:30 +08:00
BaiJiangJie
89a98be6ce [Update] 修改翻译(Cloud) 2019-07-29 18:24:43 +08:00
老广
c6ec00e84d [Update] 修改collect static file机制 (#3044) 2019-07-29 17:01:27 +08:00
AnJia
99ce688a70 added Vagrantfile to support windows dev (#3036) 2019-07-29 17:00:31 +08:00
BaiJiangJie
3cdabaf883 [Update] 系统用户资产管理页面获取相关的所有资产 (#3038)
* [Update] 系统用户资产管理页面获取相关的所有资产

* [Update] 系统用户资产管理页面获取相关的所有资产2
2019-07-26 16:16:06 +08:00
BaiJiangJie
be59bff3d0 Merge pull request #3035 from jumpserver/dev
Dev
2019-07-26 10:50:30 +08:00
BaiJiangJie
28f0302e4d Merge pull request #3034 from jumpserver/dev_bai
[Update] 是否允许用户更改/使用密钥登录,根据settings配置进行控制
2019-07-26 10:49:46 +08:00
BaiJiangJie
947f16e371 [Update] 是否允许用户更改/使用密钥登录,根据settings配置进行控制 2019-07-26 10:46:43 +08:00
BaiJiangJie
7e8f734301 Merge pull request #3033 from jumpserver/dev
Dev
2019-07-25 16:58:27 +08:00
BaiJiangJie
69841a4198 [Feature] 资产授权规则页面添加刷新授权缓存按钮 (#3032)
* [Feature] 资产授权规则页面添加刷新授权缓存按钮

* [Feature] 添加注释
2019-07-25 16:25:12 +08:00
BaiJiangJie
4f7daa96a7 Merge pull request #3026 from jumpserver/dev_bai
[Update] 授权资产变动过期所有缓存(cache_key和cache_meta_key)
2019-07-24 17:48:19 +08:00
BaiJiangJie
31c220605e [Update] 授权资产变动过期所有缓存(cache_key和cache_meta_key) 2019-07-24 17:45:38 +08:00
BaiJiangJie
2f439fd417 Merge pull request #3024 from jumpserver/dev_bai
[Bugfix] 解决用户登录日志记录错误的问题(coco端登录记录类型为Web)
2019-07-24 15:53:27 +08:00
BaiJiangJie
f4c835d47a [Bugfix] 解决用户登录日志记录错误的问题(coco端登录记录类型为Web) 2019-07-24 15:49:37 +08:00
BaiJiangJie
c929c1a87e [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时) (#3017)
* [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时)

* [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时)2

* [Bugfix] 解决下载导入模版时KeyError的问题(数据为空时)3

* [Update] 解决LDAP用户禁用后,终端还可以登录成功一次的问题

* [Update] 解决LDAP用户禁用后,终端还可以登录成功一次的问题2

* [Update] LDAP AD Server可以通过UserAccountControl映射is_active字段

* [Update] 限制只有local用户可以更新ssh key

* [Update] 限制只有local用户可以更新ssh key 2
2019-07-24 12:50:39 +08:00
老广
b7915ccdeb Merge pull request #3015 from jumpserver/dev
Bugfix (#3014)
2019-07-23 14:49:21 +08:00
老广
421e98696c Bugfix (#3014)
* [Bugfix] 修复英文时间时期的bug

* [bugfix] 修复session时间日期的问题

* [Bugfix] 修改时间日志格式化错误

* [Update] 修改函数名称

* [Update] 修改函数顺序

* [Bugfix] 修复节点刷新菜单一直append的bug
2019-07-23 14:48:37 +08:00
老广
00e1bf6cff Merge pull request #3013 from jumpserver/dev
Dev
2019-07-23 14:41:36 +08:00
老广
9454854f6c Merge pull request #2684 from yubinhong/master
add gcc libc-dev linux-headers make autoconf
2019-07-23 12:01:42 +08:00
BaiJiangJie
74ae8636a8 Merge pull request #3011 from jumpserver/dev_bai
[Bugfix] 解决邮件设置不成功的问题(提交前点击测试按钮)
2019-07-22 18:55:26 +08:00
BaiJiangJie
d93305f6a4 [Bugfix] 解决邮件设置不成功的问题(提交前点击测试按钮) 2019-07-22 18:53:51 +08:00
老广
332d75b62a Merge pull request #3009 from jumpserver/dev
Bugfix (#3007)
2019-07-22 17:16:29 +08:00
老广
5db704f793 Bugfix (#3007)
* [Bugfix] 修复英文时间时期的bug

* [bugfix] 修复session时间日期的问题

* [Bugfix] 修改时间日志格式化错误

* [Update] 修改函数名称

* [Update] 修改函数顺序

* [Update] 注释部分迁移文件内容
2019-07-22 17:15:24 +08:00
老广
37be53f334 Merge pull request #3004 from jumpserver/dev
修改显示节点下的所有资产
2019-07-22 11:39:50 +08:00
老广
5f59c729b7 Perm for assets (#3003)
* [Update] 授权显示节点下所有的资产

* [Update] 修改返回资产的comment

* [Update] 修改节点返回字段
2019-07-22 11:38:09 +08:00
老广
a8af4e8804 Perm for assets (#3002)
* [Update] 授权显示节点下所有的资产

* [Update] 修改返回资产的comment
2019-07-22 11:25:04 +08:00
老广
8f5e91bc34 [Update] 授权显示节点下所有的资产 (#3001) 2019-07-22 11:12:38 +08:00
BaiJiangJie
277bbc0798 Merge pull request #2996 from jumpserver/dev
Dev
2019-07-19 13:13:01 +08:00
BaiJiangJie
d97654b851 Merge pull request #2995 from jumpserver/bugfix
Bugfix
2019-07-19 13:12:32 +08:00
BaiJiangJie
7ec82cbc60 [Update] 修改列表日期2 2019-07-19 13:10:34 +08:00
BaiJiangJie
63c2343d2c [Update] 修改列表日期 2019-07-19 12:55:43 +08:00
ibuler
d93fe11373 [Bugfix] 修复date format错误 2019-07-19 12:01:17 +08:00
BaiJiangJie
fedfbba30c Merge pull request #2991 from jumpserver/dev
Dev
2019-07-18 18:02:48 +08:00
ibuler
49ccba85a2 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-18 16:33:18 +08:00
ibuler
9903adf71b [Update] 修改cache policy 开关 2019-07-18 16:32:46 +08:00
BaiJiangJie
48cfc4d4ce Merge pull request #2990 from jumpserver/dev
Dev
2019-07-18 13:58:47 +08:00
BaiJiangJie
1f5cc1eef1 Merge pull request #2989 from jumpserver/dev_bai
[Update] 解决授权资产页面资产重复的问题
2019-07-18 13:57:05 +08:00
BaiJiangJie
ce1bb66f23 [Update] 解决授权资产页面资产重复的问题 2019-07-18 13:56:08 +08:00
BaiJiangJie
6f9528d78d Merge pull request #2987 from jumpserver/dev
Dev
2019-07-18 13:42:37 +08:00
BaiJiangJie
fc01b952b3 Merge pull request #2986 from jumpserver/dev_trans
[Update] 修改翻译
2019-07-18 13:23:40 +08:00
BaiJiangJie
51f4c1cde0 [Update] 修改翻译 2019-07-18 13:21:10 +08:00
ibuler
8bb82d168b Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-18 13:01:38 +08:00
ibuler
1ef16d55ef [Update] 优化详情页获取nodes 2019-07-18 13:01:07 +08:00
BaiJiangJie
a242bdd3b1 Merge pull request #2983 from jumpserver/dev
Dev
2019-07-18 11:20:09 +08:00
BaiJiangJie
97bc3d5e7c Merge pull request #2984 from jumpserver/gateway_conn_bug
[Bugfix] 修复网关测试连接失败未捕获导致报500的bug
2019-07-18 11:16:36 +08:00
jym503558564
dbb27a7771 [Bugfix] 修复网关测试连接失败未捕获导致报500的bug 2019-07-18 11:13:58 +08:00
八千流
ea11aa4abb [Bugfix] 修改排序,去掉标签 (#2982)
* [Update] 修改排序,去掉标签

* [Bugfix] 修改排序

* [Bugfix] 修改小问题

* [Bugfix] 修改小问题
2019-07-18 11:01:28 +08:00
ibuler
1af40b6ca0 [Update] 支持etag 2019-07-17 22:14:39 +08:00
老广
1b220326bf Merge pull request #2981 from jumpserver/dev
Bugfix (#2980)
2019-07-17 19:55:29 +08:00
老广
8ed3bb8586 Bugfix (#2980)
* [Update] 修改排序,去掉标签

* [Update] 修改批量更新后刷新页面

* [Update] 修改面包屑

* [Update] 修改用户排序

* [Update] 处理未登录主机显示为负数

* [Update] 支持单个资产的搜索
2019-07-17 19:52:33 +08:00
老广
887cda3287 Merge pull request #2978 from jumpserver/dev
Dev 修改private key校验
2019-07-17 13:46:04 +08:00
ibuler
40de7d64c6 [Update] 修改private创建 2019-07-17 13:41:26 +08:00
ibuler
fbc948fd7c Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-17 12:54:44 +08:00
ibuler
12b2f2f499 [Update] 刷新session有效期 2019-07-17 12:54:00 +08:00
BaiJiangJie
37c10c6cf5 Merge pull request #2976 from jumpserver/dev
[Update] 解决授权资产显示禁用资产的问题 (#2975)
2019-07-17 12:11:40 +08:00
BaiJiangJie
3451c2b418 [Update] 解决授权资产显示禁用资产的问题 (#2975)
* [Update] 解决授权资产显示禁用资产的问题

* [Bugfix] 解决资产更新页面取消激活失败的问题

* [Bugfix] 解决资产详情页面禁用资产时会清除协议的问题

* [Bugfix] 解决创建/更新管理用户、系统用户提交密钥错误时报错的问题
2019-07-17 12:10:14 +08:00
BaiJiangJie
158e2a9112 Merge pull request #2973 from jumpserver/dev
Merge pull request #2972 from jumpserver/dev_bai
2019-07-16 19:08:15 +08:00
BaiJiangJie
1c7a09c9e3 Merge pull request #2972 from jumpserver/dev_bai
[Update] 修改CommandFilter的system_users字段为不必填
2019-07-16 19:05:44 +08:00
BaiJiangJie
28b7768601 Merge pull request #2971 from jumpserver/dev_bai
[Update] 修改CommandFilter的system_users字段为不必填
2019-07-16 18:49:33 +08:00
BaiJiangJie
d21bb5207b [Update] 修改CommandFilter的system_users字段为不必填 2019-07-16 17:55:43 +08:00
BaiJiangJie
7a82c91bc8 Merge pull request #2970 from jumpserver/dev
Dev
2019-07-16 17:16:03 +08:00
BaiJiangJie
bf2ed21020 Merge pull request #2969 from jumpserver/dev_bai
[Update] 修改翻译
2019-07-16 17:14:11 +08:00
BaiJiangJie
560be610fe [Update] 修改翻译 2019-07-16 17:06:33 +08:00
BaiJiangJie
3d1d652cfb Merge pull request #2968 from jumpserver/dev
Dev
2019-07-16 17:00:41 +08:00
BaiJiangJie
f70567f44b Merge pull request #2966 from jumpserver/perms_detail_delete_bug
[Bugfix] 修复资产授权详情页 删除弹出框的权限名显示不对
2019-07-16 16:57:26 +08:00
BaiJiangJie
0b9308282f Merge pull request #2967 from jumpserver/dev_bai
[Update] 修改版本号
2019-07-16 16:54:43 +08:00
BaiJiangJie
2259bc5ea2 [Update] 修改版本号 2019-07-16 16:49:43 +08:00
BaiJiangJie
eb93e634e0 Merge pull request #2964 from jumpserver/dev
Dev
2019-07-16 15:42:23 +08:00
BaiJiangJie
e0802ef2c3 Merge pull request #2965 from jumpserver/dev_bai
[Update] 添加命令过滤器Model中字段name改为unique的迁移文件
2019-07-16 15:40:47 +08:00
jym503558564
1993dba2e0 [Bugfix] 修复资产授权详情页 删除弹出框的权限名显示不对 2019-07-16 15:40:41 +08:00
BaiJiangJie
018bf80930 [Update] 添加命令过滤器Model中字段name改为unique的迁移文件 2019-07-16 15:38:44 +08:00
BaiJiangJie
6eaa1bc3f9 Merge pull request #2963 from jumpserver/cmd_filter_detail_delete_bug
[Bugfix] 修复命令过滤器详情页 删除功能无效的bug
2019-07-16 15:33:08 +08:00
BaiJiangJie
d8d9174454 Merge pull request #2962 from jumpserver/domain_detail_delete_bug
[Bugfix] 修复网域详情页 删除功能无效的bug
2019-07-16 15:30:08 +08:00
BaiJiangJie
bd2f0168a1 Merge pull request #2961 from jumpserver/command_filter_model_bug
[Bugfix] 修复命令过滤器名称可重复问题
2019-07-16 15:27:00 +08:00
BaiJiangJie
a81601b0a1 Merge pull request #2959 from jumpserver/command_list_html_bug
[Bugfix] 修复命令记录页面的时间转换bug
2019-07-16 15:26:30 +08:00
jym503558564
3c166abd2d [Bugfix] 修复命令过滤器详情页 删除功能无效的bug 2019-07-16 15:25:49 +08:00
BaiJiangJie
25d40388da Merge pull request #2958 from jumpserver/cmd_detail_add_system_user_bug
[Bugfix] 修复命令过滤器详情添加不了系统用户的bug
2019-07-16 15:08:15 +08:00
jym503558564
74aa51578b [Bugfix] 修复网域详情页 删除功能无效的bug 2019-07-16 15:06:25 +08:00
jym503558564
48cde91dc1 [Bugfix] 修复命令过滤器名称可重复问题 2019-07-16 14:33:27 +08:00
BaiJiangJie
9455a1c3c0 Merge pull request #2960 from jumpserver/dev_bai
[Update] 获取用户授权资产时只返回资产协议支持的系统用户
2019-07-16 13:33:47 +08:00
BaiJiangJie
f19f28cf3e [Update] 获取用户授权资产时只返回资产协议支持的系统用户 2019-07-16 13:24:16 +08:00
jym503558564
59426d4aa9 [Bugfix] 修复命令记录页面的时间转换bug 2019-07-16 12:01:09 +08:00
jym503558564
e9e601fe45 [Bugfix] 修复命令过滤器详情添加不了系统用户的bug 2019-07-16 11:02:05 +08:00
ibuler
4d1da1d292 [Update] 给jumpserver.js添加版本号 2019-07-16 10:29:20 +08:00
老广
341b654078 Merge pull request #2957 from jumpserver/dev
Dev
2019-07-16 10:12:14 +08:00
ibuler
875a069a18 [Update] 修改校验规则 2019-07-16 10:09:36 +08:00
ibuler
106abc24f8 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-16 10:08:06 +08:00
八千流
ab520ee345 [Bugfix] 缺少引入翻译 (#2956) 2019-07-16 10:03:00 +08:00
BaiJiangJie
e212d413dc Merge pull request #2954 from jumpserver/dev_bai
[Bugfix] 修改资产列表数量显示的Bug
2019-07-15 19:04:14 +08:00
BaiJiangJie
6f2f025b04 [Bugfix] 修改资产列表数量显示的Bug 2019-07-15 19:02:00 +08:00
BaiJiangJie
a884d0d531 [Bugfix] 修改Bug (#2952)
* [Bugfix] 取消管理用户列表按资产数量字段排序

* [Bugfix] 修复后去用户资产权限动作的Bug
2019-07-15 18:47:17 +08:00
八千流
6cf11ab411 [Update] 优化mfa验证身份页面水平线在大屏幕无自动对其问题 (#2948)
* [Update] 优化mfa验证身份页面水平线在大屏幕无自动对其问题

* [Update] 修改小问题
2019-07-15 15:38:34 +08:00
老广
c8623c3b8c Merge pull request #2947 from jumpserver/dev
Dev
2019-07-15 15:11:30 +08:00
老广
9d54baac09 Secure (#2946)
* [Update] 修改assets

* [Update] 添加关闭terminal注册的设置

* [Update] 去掉sql打印
2019-07-15 15:11:01 +08:00
BaiJiangJie
382bb89e8e Merge pull request #2945 from jumpserver/dev_bai
[Update] 优化创建RemoteApp前端逻辑
2019-07-15 15:02:36 +08:00
BaiJiangJie
92aeecbc3e [Update] 优化创建RemoteApp前端逻辑 2019-07-15 14:57:57 +08:00
八千流
9219786f2d [Update] 创建/更新 远程应用 使用api (#2940)
* [Update] 创建/更新 远程应用 使用api

* [Update] 优化 params

* [Update] 修改小问题
2019-07-15 14:42:54 +08:00
ibuler
d2516cc328 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-15 14:37:18 +08:00
老广
ab9744d529 Bugfix (#2944)
* [Update] 修改assets

* [Update] 修改nodes
2019-07-15 14:36:11 +08:00
ibuler
3ac3581622 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-15 14:35:34 +08:00
BaiJiangJie
13ca70b328 Merge pull request #2942 from jumpserver/remote_app_perms_api
[Update] 创建/更新 远程应用授权 使用api
2019-07-15 14:25:06 +08:00
BaiJiangJie
911abeddd0 Merge pull request #2943 from jumpserver/dev_bai
[Bugfix] 修改授权规则详情用户组数量显示错误
2019-07-15 14:20:49 +08:00
BaiJiangJie
4ba306f597 [Bugfix] 修改授权规则详情用户组数量显示错误 2019-07-15 14:14:23 +08:00
jym503558564
cc7ce04402 [Update] 创建/更新 远程应用授权 使用api 2019-07-15 09:53:22 +08:00
ibuler
dfcf0995cd Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-12 18:33:17 +08:00
老广
55b049c86a Bugfix (#2938)
* [Update] 修改assets

* [Update] 修复用户组页面权限

* [Update] 统一授权资产页面,修改apiUpdateAttr函数名称
2019-07-12 18:31:55 +08:00
BaiJiangJie
e5bdceed58 [Update] 优化授权规则资产列表页面 (#2937)
* [Update] 优化授权规则资产列表页面

* [Update] 优化授权规则资产列表页面2

* [Update] 优化授权规则资产列表页面3

* [Update] 优化授权规则资产列表页面4

* [Update] 优化授权规则资产列表页面5

* [Update] 优化授权规则资产列表页面6
2019-07-12 18:31:14 +08:00
八千流
7bda48bd9f [Bugfix] 修复创建用户 数据库报1062 Duplicate entry .. for key PRIMARY 错误 (#2934)
* [Bugfix] 修复创建用户 数据报1062 Duplicate entry .. for key PRIMARY 错误

* [Bugfix] 修改小问题
2019-07-12 15:43:36 +08:00
八千流
63a502ba62 [Update] 创建/更新 用户 使用api (#2918)
* [Update] 创建/更新 用户 使用api

* [Update] 修改小问题

* [Update] 修改小问题
2019-07-11 18:54:30 +08:00
八千流
e0d492f599 [Update] 创建/更新 过滤器规则 使用api (#2925)
* [Update] 创建/更新 过滤器规则 使用api

* [Update] 修改小问题
2019-07-11 18:50:22 +08:00
八千流
48e74ed0ea [Bugfix] 创建/更新 系统用户使用api 在选择命令过滤器时的bug (#2924)
* [Bugfix] 创建/更新 系统用户使用api 在选择命令过滤器时的bug

* [Update] 修改小问题
2019-07-11 18:45:18 +08:00
八千流
95f1a19a0a [Update] 创建/更新 标签 使用api (#2919)
* [Update] 创建/更新 标签 使用api

* [Update] 修改小问题

* [Update] 修改小问题
2019-07-11 18:44:16 +08:00
八千流
63f3fa98db [Update] 创建/更新 网域使用api (#2915)
* [Update] 创建/更新 网域使用api

* [Update] 修改小问题

* [Update] 修改小问题
2019-07-11 18:21:19 +08:00
ibuler
fdfd2f97d2 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-11 18:18:04 +08:00
八千流
4e3e166132 [Update] 创建/更新 用户组使用api (#2914)
* [Update] 创建/更新 用户组使用api

* [Update] 修改小问题
2019-07-11 18:17:47 +08:00
ibuler
21ffa8b28a Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-11 18:17:34 +08:00
BaiJiangJie
5f9f970abd Perf (#2929)
* [Update] 优化性能

* [Update] 修改assets

* [Update] 优化处理

* [Update] Youhua

* [Update] 修改ungroup

* [Update] 修改perms的api地址,去掉sysuser adminuser的可连接性

* [Update] 修改perms urls兼容

* [Update] 修改分类

* [Update] 修改信号

* [Update] 优化获取授权资产

* [Update] 添加注释 (#2928)

* [update] 去掉nodes 的部分字段

* [Update] 删除不用的代码

* [Update] 拆分users

* [Update] 修改用户的属性
2019-07-11 18:12:14 +08:00
八千流
ff85e2ef57 [Update] 创建/更新 命令过滤器 使用api (#2926) 2019-07-11 12:02:51 +08:00
ibuler
d40d231e9a [Update] 修改assets 2019-07-09 19:58:16 +08:00
八千流
6e86e3f118 [Update] 创建/更新 网关使用api (#2911)
* [Update] 创建/更新 网关使用api

* [Update] 修改小问题

* [Update] 修改小问题

* [Update] 修改网关一些信息可读
2019-07-09 17:30:53 +08:00
老广
cedf0e4532 Merge pull request #2910 from jumpserver/bugfix
[Update] 修改日期显示差8小时
2019-07-09 12:06:18 +08:00
ibuler
05438baad9 [Update] 修改日期显示差8小时 2019-07-09 12:05:31 +08:00
老广
e603730320 Merge pull request #2908 from jumpserver/dev
[Update] 修复创建资产是协议bug
2019-07-08 19:04:31 +08:00
ibuler
c0cff6d1fe [Update] 修复创建资产是协议bug 2019-07-08 19:03:45 +08:00
老广
65060a9416 Merge pull request #2907 from jumpserver/dev
[Update] 修改系统用户校验用户名
2019-07-08 18:31:26 +08:00
ibuler
e2f1754a8e [Update] 修改系统用户校验用户名 2019-07-08 18:30:18 +08:00
老广
cf10a8898f Merge pull request #2906 from jumpserver/dev
[Update] 修改授权页面不显示资产的问题
2019-07-08 18:17:26 +08:00
ibuler
533f32ed48 [Update] 修改授权页面不显示资产的问题 2019-07-08 18:16:08 +08:00
老广
8918da48f8 Merge pull request #2904 from jumpserver/dev
Dev
2019-07-08 16:05:24 +08:00
老广
f3b5823cce Bugfix (#2903)
* [Update] 去掉session协议搜索

* [Update] 去掉adhoc wanging
2019-07-08 16:04:32 +08:00
老广
6b43ad60b3 [Update] 去掉session协议搜索 (#2902) 2019-07-08 15:59:55 +08:00
BaiJiangJie
1a60f38c19 Merge pull request #2898 from jumpserver/admin_user_template
[Update] 优化管理用户的资产列表页显示不友好问题
2019-07-08 15:54:08 +08:00
老广
6330f2d42d [Update] 修改获取禁用资产的问题 (#2901) 2019-07-08 15:52:03 +08:00
八千流
033b44f9d2 [Bugfix] 修复禁用的资产在luna页面显示的问题 (#2900) 2019-07-08 15:51:19 +08:00
老广
bf5acf7ef1 [Update] 修复系统用户管理用户提及重置密码的bug (#2899)
* [Update] 修复系统用户管理用户提及重置密码的bug

* [Update]  去掉forms

* [Update] 修改翻译

* [Update] 去掉debug信息
2019-07-08 15:35:20 +08:00
jym503558564
522d19a7e7 [Update] 优化管理用户的资产列表页显示不友好问题 2019-07-08 15:08:17 +08:00
八千流
4c34246750 [Bugfix] 修复批量执行命令没有选择资产bug (#2894)
* [Bugfix] 修复批量执行命令没有选择资产bug

* [Bugfix] 修复批量执行命令没有选择资产bug(2)
2019-07-08 10:44:53 +08:00
老广
52d528613e Merge pull request #2893 from jumpserver/dev
Dev
2019-07-06 16:01:34 +08:00
ibuler
0809916b01 [Update] Merge with master 2019-07-06 15:58:41 +08:00
ibuler
cc56c92562 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-07-06 15:15:27 +08:00
ibuler
447c3f3146 [Update] 修改一个bug 2019-07-06 15:14:55 +08:00
老广
eba8e54261 Merge pull request #2891 from jumpserver/dev_perms
[Update] 修复Action转化的bug
2019-07-06 15:10:16 +08:00
BaiJiangJie
a8c95093c4 [Update] 修复Action转化的bug 2019-07-06 14:46:01 +08:00
BaiJiangJie
0d72c4a7e0 Merge pull request #2890 from jumpserver/dev_bai
[Update] 资产model.__all__添加ProtocolsMixin
2019-07-06 14:07:21 +08:00
BaiJiangJie
963b0911cf [Update] 资产model.__all__添加ProtocolsMixin 2019-07-06 14:06:12 +08:00
BaiJiangJie
e8f68eb6c1 [Update] 解决授权规则actions字段为空列表或只填一个action的问题 (#2888)
* [Update] 解决授权规则actions字段为空列表或只填一个action的问题

* [Update] Action NO改为NULL

* [Update] Action NULL改为NONE
2019-07-06 14:02:56 +08:00
老广
59927ffcd9 Merge pull request #2889 from jumpserver/perf
[Update] 优化协议
2019-07-05 18:29:19 +08:00
ibuler
aac5eed9fc [Update] 去掉debug 2019-07-05 18:28:56 +08:00
ibuler
a412864c80 [Update] 去掉protocol model 2019-07-05 18:10:27 +08:00
ibuler
3c825440f0 [Update] 优化协议 2019-07-05 18:07:10 +08:00
BaiJiangJie
303cf41bb9 Merge pull request #2887 from jumpserver/dev_bugfix
[Update] 修复授权规则更新失败的bug
2019-07-05 16:41:17 +08:00
BaiJiangJie
0512988979 [Update] 修复授权规则更新失败的bug 2019-07-05 16:34:37 +08:00
BaiJiangJie
423a487bd1 Merge pull request #2886 from jumpserver/dev_users
[Update] 限制用户通过API删除自己
2019-07-05 14:00:52 +08:00
BaiJiangJie
e415ef8354 [Update] 限制用户通过API删除自己 2019-07-05 13:59:38 +08:00
BaiJiangJie
ece8f082fb Merge pull request #2885 from jumpserver/dev_perm
[Update] 用户授权资产树进行排序
2019-07-05 12:41:30 +08:00
BaiJiangJie
c52ea089da [Update] 用户授权资产树进行排序 2019-07-05 12:40:15 +08:00
ibuler
64e1e6191b [Update] 修改版本号 2019-07-04 21:25:05 +08:00
老广
58875d9a95 Bugfix (#2831)
* [Update] 修改小问题

* [Update] 添加重传guacamole的脚本

* [Update] 添加debug

* [Update] 优化可连接性

* [Update] 修改connectivity

* [Update] 更改查看认证需要的MFA时间间隔

* [Update] 修改表结构

* [Update] 修改users public_key等字段

* [Update] 修改用户表结构

* [Update] 修改assets users api

* [Update] 修改org mixin

* [Update] 解决连接windows资产出现幽灵会话的问题

* [Update] 优化树结构

* [Update] 修改Permission

* Stash

* [Update] 修改serializer

* [Update] 修改用户有权限的资产

* [Update] 修改upgrouped_node key的获取(解决操作日志中出现coco/gua的问题)

* [Update] 修改一些bug

* [Update] Debug cache

* [Bugfix] 修复用户页面不走cache的bug

* ipython

* [Update] 修改action

* [Bugfix] 修改校验系统用户资产动作权限的API逻辑

* [Update] 去掉原来批量的view

* [Bugfix] 会话/命令列表中获取用户列表排除app用户

* [Update] 修改用户授权资产API返回的queryset

* [Update] 修正migrations

* [Bugfix] 解决进入授权详情页的资产管理页面bug

* [Update] 修改Minxs

* [Update] 修改migrations

* [Update] 资产授权Model模块添加导入

* [Update] 优化命令记录列表

* [Update] 修改command列表

* [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug (#2874)

* [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug

* [Update] 如果用户授权节点为空,返回时添加空节点

* [Update] 修改command导出和搜索

* [Update] 修改session

* [Update] 修改Permission响应层缓存key

* [Update] 准备优化 asset user

* [Update] 修改去掉一些print

* [Bugfix] 修复initDataTable表格搜索栏位置错乱的问题,显示不友好问题 (#2880)

* [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则 (#2877)

* [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则

* [Bugfix]修复小问题

* [Update] 优化创建用户和更新用户密码的校验

* [Update] 优化用户表单校验password逻辑

* [Update] 小问题

* [Update] 修改command搜索

* [Update] 修改user group serialzier

* [Update] 优化资产

* [Update] 优化节点

* [Update] 优化用户组列表用户显示问题 (#2882)

* [Update] 解决select_for_update的错误

* [update] 修改Node无法被删除的bug

* [Update] 添加翻译

* [update] 修改资产导出的permssions

* [Bugfix] 修复删除节点bug (#2883)

* [update] 修改一些性能问题
2019-07-04 21:23:19 +08:00
ibuler
782bad916e Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-04 21:18:04 +08:00
ibuler
1472f0437f [update] 修改一些性能问题 2019-07-04 21:17:39 +08:00
八千流
1256944b96 [Bugfix] 修复删除节点bug (#2883) 2019-07-04 18:31:53 +08:00
ibuler
218e425333 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-04 17:29:32 +08:00
ibuler
5562a04f79 [update] 修改资产导出的permssions 2019-07-04 17:26:05 +08:00
BaiJiangJie
75409519a1 [Update] 添加翻译 2019-07-04 17:22:04 +08:00
ibuler
62689b240b Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-04 17:21:01 +08:00
ibuler
6f570bcbf1 [update] 修改Node无法被删除的bug 2019-07-04 17:20:42 +08:00
BaiJiangJie
b5962a098a [Update] 解决select_for_update的错误 2019-07-04 17:09:36 +08:00
BaiJiangJie
371aff3251 [Update] 优化用户组列表用户显示问题 (#2882) 2019-07-04 16:44:46 +08:00
ibuler
bab4326aeb [Update] 优化节点 2019-07-04 16:41:38 +08:00
ibuler
dc4ee2f0fa [Update] 优化资产 2019-07-04 16:41:25 +08:00
ibuler
bf9a2714d3 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-04 16:30:38 +08:00
ibuler
29824fb7d2 [Update] 修改user group serialzier 2019-07-04 16:30:26 +08:00
ibuler
5b1ee67820 [Update] 修改command搜索 2019-07-04 16:28:42 +08:00
八千流
3598bc79c3 [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则 (#2877)
* [Bugfix] 修复创建用户的View,使用密码创建用户时没有校验密码规则

* [Bugfix]修复小问题

* [Update] 优化创建用户和更新用户密码的校验

* [Update] 优化用户表单校验password逻辑

* [Update] 小问题
2019-07-04 16:14:00 +08:00
八千流
17e1fe2acb [Bugfix] 修复initDataTable表格搜索栏位置错乱的问题,显示不友好问题 (#2880) 2019-07-04 16:13:05 +08:00
ibuler
858c7df86b Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-04 16:09:14 +08:00
ibuler
2d07eeb16b [Update] 修改去掉一些print 2019-07-04 16:08:52 +08:00
ibuler
1448d23ca6 [Update] 准备优化 asset user 2019-07-04 15:36:57 +08:00
BaiJiangJie
2eb942a947 [Update] 修改Permission响应层缓存key 2019-07-04 11:31:31 +08:00
ibuler
cdbdc853ea Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-03 22:28:45 +08:00
ibuler
c3a54a8927 [Update] 修改session 2019-07-03 22:28:20 +08:00
ibuler
dfcbdb0c35 [Update] 修改command导出和搜索 2019-07-03 18:03:01 +08:00
BaiJiangJie
28ec1eb0ad [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug (#2874)
* [Update] 解决用户授权资产/节点为空时,前端构建资产授权树的bug

* [Update] 如果用户授权节点为空,返回时添加空节点
2019-07-03 16:33:21 +08:00
ibuler
5f6af8c07d [Update] 修改command列表 2019-07-03 16:29:39 +08:00
ibuler
e7d600ee50 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-02 22:09:12 +08:00
ibuler
b9f82fd0ac [Update] 优化命令记录列表 2019-07-02 22:08:50 +08:00
BaiJiangJie
186c22decd [Update] 资产授权Model模块添加导入 2019-07-02 19:30:59 +08:00
BaiJiangJie
9a20ec9c09 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-02 18:24:13 +08:00
ibuler
1b44172bc5 [Update] 修改migrations 2019-07-02 18:23:47 +08:00
BaiJiangJie
bbf5e28571 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-02 18:01:20 +08:00
ibuler
7bf1555c67 [Update] 修改Minxs 2019-07-02 17:51:50 +08:00
BaiJiangJie
003601bbdd [Bugfix] 解决进入授权详情页的资产管理页面bug 2019-07-02 17:48:29 +08:00
ibuler
58182712a2 Merge branch 'bugfix' of github.com:jumpserver/jumpserver into bugfix 2019-07-02 16:47:10 +08:00
ibuler
930eb1d2e1 [Update] 修正migrations 2019-07-02 16:45:26 +08:00
BaiJiangJie
98260b5b52 [Update] 修改用户授权资产API返回的queryset 2019-07-02 16:34:38 +08:00
BaiJiangJie
9a3065ad4c [Bugfix] 会话/命令列表中获取用户列表排除app用户 2019-07-02 16:01:45 +08:00
ibuler
79554b47d3 [Update] Merge 2019-07-02 14:19:45 +08:00
ibuler
31d2f2a799 [Update] 去掉原来批量的view 2019-07-02 14:17:56 +08:00
BaiJiangJie
1983533e76 [Bugfix] 修改校验系统用户资产动作权限的API逻辑 2019-07-02 12:44:53 +08:00
ibuler
e4880a247f [Update] 修改action 2019-07-02 12:08:25 +08:00
ibuler
e43da3d6e1 ipython 2019-07-02 10:51:48 +08:00
ibuler
b064be3ec0 [Bugfix] 修复用户页面不走cache的bug 2019-07-01 22:09:04 +08:00
ibuler
c33084421d [Update] Debug cache 2019-07-01 21:48:25 +08:00
ibuler
7f7853dbc9 Merge remote-tracking branch 'origin/bugfix' into bugfix 2019-07-01 20:02:17 +08:00
ibuler
4d3856975b [Update] 修改一些bug 2019-07-01 20:01:47 +08:00
BaiJiangJie
8905c27b86 [Update] 修改upgrouped_node key的获取(解决操作日志中出现coco/gua的问题) 2019-07-01 19:39:10 +08:00
ibuler
034fee0f75 [Update] 修改用户有权限的资产 2019-07-01 19:30:18 +08:00
ibuler
d41d58e30f [Update] 修改serializer 2019-07-01 19:08:47 +08:00
ibuler
ae690050e7 Stash 2019-07-01 18:22:40 +08:00
八千流
6273e6be9b [Bugfix] 修复用户批量命令View的bug (#2864) 2019-07-01 17:38:33 +08:00
BaiJiangJie
85d13c03e0 Merge pull request #2865 from jumpserver/dev_bugfix
[Bugfix] 解决审计员查看录像回放失败的问题
2019-07-01 17:38:01 +08:00
BaiJiangJie
a8e20ac1c1 [Bugfix] 解决审计员查看录像回放失败的问题 2019-07-01 17:36:47 +08:00
KayneWang
582365967d 允许配置email密码为空 (#2833)
[issue 2577](https://github.com/jumpserver/jumpserver/issues/2577)
2019-07-01 11:31:16 +08:00
八千流
768cfc7561 [Bugfix] 修复用户无权限执行批量命令却可直接访问批量执行页面的bug (#2857)
* [Bugfix] 修复用户无权限执行批量命令却可直接访问批量执行页面的bug

* [Update] 更改小问题

* [Update] 优化小问题

* [Update] 优化变量名

* [Update] 优化变量名(2)
2019-07-01 11:22:05 +08:00
八千流
297820b65a [Update] 优化组织名字过长显示不友好问题 (#2855)
* [Update] 优化组织名字过长显示不友好问题

* [Update] 优化组织名字前端盒子默认最小宽度为220px
2019-07-01 11:21:26 +08:00
BaiJiangJie
abad929485 [Update] 优化MFA认证允许时间差1min (#2858) 2019-07-01 11:07:28 +08:00
BaiJiangJie
43412d7ef6 [Update] 优化OpenID登录逻辑,配置文件添加禁用证书认证选项 (#2854)
* [Update] 优化OpenID登录逻辑,配置文件添加禁用证书认证选项

* [Update] 优化OpenID细节

* [Update] 优化OpenID, 可配置是否启用共享Session选项

* [Update] 配置文件添加OpenID默认配置项
2019-07-01 11:04:15 +08:00
ibuler
8f699fa366 [Update] 修改Permission 2019-06-30 20:10:34 +08:00
ibuler
8e9b3f134b [Update] 修改permission actions 2019-06-28 22:07:22 +08:00
ibuler
48ba1993e0 Merge remote-tracking branch 'origin/bugfix' into bugfix 2019-06-27 21:43:44 +08:00
ibuler
1a0ff422fe [Update] 优化树结构 2019-06-27 21:43:10 +08:00
BaiJiangJie
5de6563ab2 [Update] 解决连接windows资产出现幽灵会话的问题 2019-06-26 17:29:52 +08:00
ibuler
6d96b5dbaf [Update] 修改org mixin 2019-06-25 20:32:12 +08:00
BaiJiangJie
9ca4a8c941 Dev (#2838)
* Dev ansible windows 2 (#2783)

* [Update] 改密支持windows

* [Update] 修改asset表结构

* [Feature] Windows支持批量改密、测试可连接性等功能

* [Update] 处理创建资产时labels的问题

* [Update] 优化测试管理系统、系统用户可连接性任务执行逻辑

* [Update] 优化ansible任务逻辑;添加自动推送rdp系统用户功能

* [Update] 添加翻译

* [Update] 优化ansible任务逻辑(测试系统用户可连接性, 通过协议过滤资产)

* [Update] 更新翻译

* [Update] 更新翻译

* [Update] 推送windows系统用户,默认添加到Users、Remote Desktop Users组中

* [Update] 优化小细节

* [Update] 更新翻译,删除多余代码

* [Update] 更新翻译信息

* [Bugfix] 修复windows推送系统用户小bug (#2794)

* [Update] 邮件设置添加配置项:发送账号 (#2796)

* [Bugfix] 和资产相关的Serializer添加protocols字段; (#2800)

* [Bugfix] 和资产相关的Serializer添加protocols字段;

* [Bugfix] RemoteApp Form 修改过滤RDP协议资产

* [Bugfix] 修改小问题

* [Update] 用户授权相关API,如果需要切换到root org (#2803)

* [Update] 用户授权相关API,如果需要切换到root org

* [Update] 优化小问题

* [Update] 增加审计员权限控制 (#2792)

* [Update] 审计员

* [Update] 增加审计员的权限控制

* [Update] 增加审计员Api全校的控制

* [Update] 优化auditor的api权限控制

* [Update] 优化审计员权限控制

* [Update]优化管理员权限的View

* [Update] 优化超级管理权限的View

* [Update] 添加审计员切换组织查询会话管理数据

* [Update] 前端禁用审计员在线会话终断按钮

* [Update]优化细节问题

* [Update] Auth Info (#2806)

* [Update] 修改支持auth info导出

* [Update] 统一认证查看

* [Update] 修改auth book manager

* [Update] 修改auth info

* [Update] 完成修改auth info

* [Update] 优化api

* [Update] 修改assets 的related

* [Update] serializer mixin继承 (#2810)

* [Update] serializer mixin继承

* [Update] 修改system user更新serialzier

* [Update] 修改success message

* [Update] 添加一键禁用LDAP认证脚本 (#2813)

* [Update] 修改资产创建格式

* [Update] 兼容之前的protocols格式

* [Update] Merge master_bugfix to dev_bugfix (#2817)

* [Update] 邮件设置添加配置项:发送账号 (#2795)

* [Bugfix] 修复普通用户被授权的RemoteApp列表加载为空的bug

* [Bugfix] 修复普通用户加载被授权的RemoteApp为空的bug

* [Update] 修改邮件测试的接受者为发送者

* [Update] 修改小问题

* [Update] 修改资产授权序列类返回资产protocols的协议格式/, 同时添加protocol和port字段

* [Update] 修改文案 (#2823)

* [Update] 修改文案

* [Update] 修改文案2

* [Bugfix] 修复资产没有管理用户时获取connectivity字段失败的bug

* [Update] 优化测试可连接性时结果获取 (#2825)

* [Update] 修改资产使用patch方法更新时页面不提示messages信息

* [Update] 添加迁移文件,修改设置资产可连接性时管理用户为None的bug

* [Update] 修改org.middleware自动切换组织的bug (#2829)

* [Update] 修改org.middleware自动切换组织的bug

* [Update] 将切换组织逻辑移动到PermsUtil中

* [Update] 修改首页组织名称显示来源
2019-06-25 15:24:41 +08:00
BaiJiangJie
320b17c8db [Update] Merge local branch dev to master 2019-06-25 15:23:39 +08:00
ibuler
e8ebc94191 [Update] 修改assets users api 2019-06-25 14:32:25 +08:00
ibuler
f10a7a75ae [Update] 修改用户表结构 2019-06-25 11:51:25 +08:00
ibuler
e08d542c87 [Update] 修改users public_key等字段 2019-06-25 11:22:17 +08:00
ibuler
824ba433f6 [Update] 修改表结构 2019-06-24 22:16:39 +08:00
ibuler
327febaf59 [Update] 更改查看认证需要的MFA时间间隔 2019-06-24 20:39:45 +08:00
ibuler
0f8d4f5b32 [Update] 统一可连接性,添加sql debug 2019-06-24 20:16:18 +08:00
ibuler
63216addf6 [Update] 修改connectivity 2019-06-24 14:23:29 +08:00
ibuler
9dd951dd0d [Update] 优化可连接性 2019-06-21 20:57:51 +08:00
BaiJiangJie
ed8ae300ae [Update] 修改org.middleware自动切换组织的bug (#2829)
* [Update] 修改org.middleware自动切换组织的bug

* [Update] 将切换组织逻辑移动到PermsUtil中

* [Update] 修改首页组织名称显示来源
2019-06-21 17:09:18 +08:00
ibuler
2e6ba2ffb2 [Update] 添加debug 2019-06-21 16:03:37 +08:00
ibuler
dd4ef4c383 [Update] 添加重传guacamole的脚本 2019-06-21 16:00:59 +08:00
ibuler
ef717f888b Merge branch 'dev' into bugfix 2019-06-21 14:00:34 +08:00
BaiJiangJie
b15e06ffec Merge pull request #2828 from jumpserver/dev_bai2
[Update] 添加迁移文件,修改设置资产可连接性时管理用户为None的bug
2019-06-21 13:52:29 +08:00
BaiJiangJie
583fd410f5 [Update] 添加迁移文件,修改设置资产可连接性时管理用户为None的bug 2019-06-21 13:34:20 +08:00
ibuler
3e73dbdb11 [Update] 修改小问题 2019-06-21 12:45:32 +08:00
BaiJiangJie
908905918e Merge pull request #2827 from jumpserver/dev_bai
[Update] 修改资产使用patch方法更新时页面不提示messages信息
2019-06-21 12:02:11 +08:00
BaiJiangJie
3da43bf01d [Update] 修改资产使用patch方法更新时页面不提示messages信息 2019-06-21 12:01:18 +08:00
BaiJiangJie
bb1a941240 [Update] 优化测试可连接性时结果获取 (#2825) 2019-06-21 11:19:53 +08:00
BaiJiangJie
4fbb4c6082 Merge pull request #2824 from jumpserver/dev_bai
[Bugfix] 修复资产没有管理用户时获取connectivity字段失败的bug
2019-06-21 10:59:56 +08:00
BaiJiangJie
d563216e3f [Bugfix] 修复资产没有管理用户时获取connectivity字段失败的bug 2019-06-21 10:59:08 +08:00
BaiJiangJie
9a5c00e148 [Update] 修改文案 (#2823)
* [Update] 修改文案

* [Update] 修改文案2
2019-06-20 18:10:44 +08:00
BaiJiangJie
7e16ce41e3 Merge pull request #2820 from jumpserver/dev_perms
[Update] 修改资产授权序列类返回资产protocols的协议格式/, 同时添加protocol和port字段
2019-06-20 15:52:13 +08:00
BaiJiangJie
152c59365f [Update] 修改资产授权序列类返回资产protocols的协议格式/, 同时添加protocol和port字段 2019-06-20 15:50:28 +08:00
BaiJiangJie
895dfe475c Merge pull request #2818 from jumpserver/dev_remoteapp
[Update] 修改小问题
2019-06-20 15:12:41 +08:00
BaiJiangJie
29239e8b77 [Update] 修改小问题 2019-06-20 14:41:44 +08:00
BaiJiangJie
8bead0a33b [Update] Merge master_bugfix to dev_bugfix (#2817)
* [Update] 邮件设置添加配置项:发送账号 (#2795)

* [Bugfix] 修复普通用户被授权的RemoteApp列表加载为空的bug

* [Bugfix] 修复普通用户加载被授权的RemoteApp为空的bug

* [Update] 修改邮件测试的接受者为发送者
2019-06-20 11:45:45 +08:00
BaiJiangJie
844f9bf409 Merge pull request #2815 from jumpserver/master_bugfix
[Bugfix] 修复普通用户被授权的RemoteApp列表加载为空的bug
2019-06-20 11:45:26 +08:00
BaiJiangJie
c17d95dae0 [Update] 修改邮件测试的接受者为发送者 2019-06-20 11:38:37 +08:00
BaiJiangJie
50443de888 [Bugfix] 修复普通用户被授权的RemoteApp列表加载为空的bug 2019-06-20 11:25:35 +08:00
ibuler
04655b9042 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-06-19 19:29:29 +08:00
ibuler
8a87f316df [Update] 兼容之前的protocols格式 2019-06-19 19:29:12 +08:00
ibuler
2cd092af42 [Update] 修改资产创建格式 2019-06-19 19:25:21 +08:00
BaiJiangJie
7a8e616f17 [Update] 添加一键禁用LDAP认证脚本 (#2813) 2019-06-19 16:47:39 +08:00
老广
10616b8d9e [Update] serializer mixin继承 (#2810)
* [Update] serializer mixin继承

* [Update] 修改system user更新serialzier

* [Update] 修改success message
2019-06-19 16:45:14 +08:00
ibuler
d6165e5975 [Update] 修改assets 的related 2019-06-19 11:35:50 +08:00
BaiJiangJie
9cd75390bf [Update] Auth Info (#2806)
* [Update] 修改支持auth info导出

* [Update] 统一认证查看

* [Update] 修改auth book manager

* [Update] 修改auth info

* [Update] 完成修改auth info

* [Update] 优化api
2019-06-19 11:31:38 +08:00
八千流
8adaf629b4 [Update] 增加审计员权限控制 (#2792)
* [Update] 审计员

* [Update] 增加审计员的权限控制

* [Update] 增加审计员Api全校的控制

* [Update] 优化auditor的api权限控制

* [Update] 优化审计员权限控制

* [Update]优化管理员权限的View

* [Update] 优化超级管理权限的View

* [Update] 添加审计员切换组织查询会话管理数据

* [Update] 前端禁用审计员在线会话终断按钮

* [Update]优化细节问题
2019-06-19 10:47:26 +08:00
BaiJiangJie
c71f417ebf [Update] 用户授权相关API,如果需要切换到root org (#2803)
* [Update] 用户授权相关API,如果需要切换到root org

* [Update] 优化小问题
2019-06-17 19:27:02 +08:00
BaiJiangJie
795807ddbe [Bugfix] 和资产相关的Serializer添加protocols字段; (#2800)
* [Bugfix] 和资产相关的Serializer添加protocols字段;

* [Bugfix] RemoteApp Form 修改过滤RDP协议资产

* [Bugfix] 修改小问题
2019-06-17 16:46:01 +08:00
BaiJiangJie
7715e62def [Update] 邮件设置添加配置项:发送账号 (#2796) 2019-06-14 10:51:18 +08:00
BaiJiangJie
41a5a69164 [Update] 邮件设置添加配置项:发送账号 (#2795) 2019-06-14 10:36:29 +08:00
BaiJiangJie
5286bf3ac1 [Bugfix] 修复windows推送系统用户小bug (#2794) 2019-06-13 19:46:27 +08:00
老广
ddafd7ba26 Dev ansible windows 2 (#2783)
* [Update] 改密支持windows

* [Update] 修改asset表结构

* [Feature] Windows支持批量改密、测试可连接性等功能

* [Update] 处理创建资产时labels的问题

* [Update] 优化测试管理系统、系统用户可连接性任务执行逻辑

* [Update] 优化ansible任务逻辑;添加自动推送rdp系统用户功能

* [Update] 添加翻译

* [Update] 优化ansible任务逻辑(测试系统用户可连接性, 通过协议过滤资产)

* [Update] 更新翻译

* [Update] 更新翻译

* [Update] 推送windows系统用户,默认添加到Users、Remote Desktop Users组中

* [Update] 优化小细节

* [Update] 更新翻译,删除多余代码

* [Update] 更新翻译信息
2019-06-13 18:58:43 +08:00
BaiJiangJie
08a32028c1 Merge pull request #2790 from jumpserver/dev
Dev
2019-06-12 18:09:37 +08:00
BaiJiangJie
9f9f22548f [Update] Merge branch master to local dev 2019-06-12 17:57:58 +08:00
八千流
69b91df96b [Update]优化资产设置节点的问题 (#2786)
* [Update]优化资产设置节点的问题

* [Update]优化资产创建的节点设置
2019-06-12 17:49:30 +08:00
ibuler
3e86c07411 [Update] 修改授权节点显示 2019-06-12 17:04:49 +08:00
BaiJiangJie
eafef9fc7f Dev (#2785)
* [Update]优化用户页面的资产标签过滤功能 (#2781)

* [Update] 优化用户页面的资产标签下拉框选项

* [Update]增加用户页面的过滤资产标签功能

* [Update]优化用户页面的资产标签过滤

* [Update]优化用户页面的资产标签过滤代码

* [Update] 优化用户页面的资产标签过滤

* [Update] 优化用户API,创建用户添加组织关系 (#2776)

* [Update] 优化前端高度显示css (#2749)

* [Update] 修改授权树显示策略 (#2784)

* [Update] 修改授权树显示策略

* [Update] 是否允许用户执行批量命令

* [Update] 优化授权节点构建

* [Update] 修改节点大小判断

* [Update] 修改节点大小判断
2019-06-11 13:45:19 +08:00
老广
44d33f70e4 [Update] 修改授权树显示策略 (#2784)
* [Update] 修改授权树显示策略

* [Update] 是否允许用户执行批量命令

* [Update] 优化授权节点构建

* [Update] 修改节点大小判断

* [Update] 修改节点大小判断
2019-06-11 13:30:45 +08:00
BaiJiangJie
0574b43971 [Update] 优化前端高度显示css (#2749) 2019-06-11 10:10:04 +08:00
八千流
03d9570895 [Update] 优化用户API,创建用户添加组织关系 (#2776) 2019-06-11 10:09:40 +08:00
八千流
c300f0b549 [Update]优化用户页面的资产标签过滤功能 (#2781)
* [Update] 优化用户页面的资产标签下拉框选项

* [Update]增加用户页面的过滤资产标签功能

* [Update]优化用户页面的资产标签过滤

* [Update]优化用户页面的资产标签过滤代码

* [Update] 优化用户页面的资产标签过滤
2019-06-11 10:09:08 +08:00
BaiJiangJie
e5185ebd57 Merge pull request #2770 from jumpserver/dev
Dev
2019-06-03 16:35:58 +08:00
ibuler
1ba9351957 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-06-03 16:28:56 +08:00
ibuler
505b801423 [Update] 当授权规则发生改变时刷新所有缓存 2019-06-03 16:28:34 +08:00
BaiJiangJie
35a0b42bea Merge pull request #2769 from jumpserver/dev
[Update] 修改录像回放判断逻辑 (#2768)
2019-06-03 15:06:16 +08:00
BaiJiangJie
4f289963d1 [Update] 修改录像回放判断逻辑 (#2768) 2019-06-03 14:47:33 +08:00
BaiJiangJie
1631c32868 Merge pull request #2767 from jumpserver/dev
[Update] 解决csv导入导出的问题
2019-05-31 18:25:34 +08:00
ibuler
9721b805f3 [Update] 解决csv导入导出的问题 2019-05-31 18:20:24 +08:00
BaiJiangJie
d2b1b19404 Merge pull request #2765 from jumpserver/dev
Dev
2019-05-31 17:49:50 +08:00
ibuler
49632241b6 [Update] 导入解析csv coding 2019-05-31 17:40:57 +08:00
ibuler
b4f23f9731 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-31 17:04:23 +08:00
ibuler
1cdcc66dba [Update] 修改excel打开乱码 2019-05-31 17:04:00 +08:00
zbhlove100
0a1a260a22 Update README.md 2019-05-31 11:45:01 +08:00
BaiJiangJie
0e377cd0e9 Merge pull request #2762 from jumpserver/dev
[Bugfix] 修复RemoteApp用户列表显示Bug (#2761)
2019-05-31 10:40:51 +08:00
BaiJiangJie
3f3cc6359a [Bugfix] 修复RemoteApp用户列表显示Bug (#2761) 2019-05-31 10:39:33 +08:00
BaiJiangJie
eb0a6f00b5 Merge pull request #2758 from jumpserver/dev
Dev
2019-05-30 16:42:34 +08:00
BaiJiangJie
a87610a8d8 Merge pull request #2757 from jumpserver/dev_bai
[Update] 修改依赖版本号
2019-05-30 16:41:40 +08:00
BaiJiangJie
e3630a9961 [Update] 修改依赖版本号 2019-05-30 16:38:46 +08:00
BaiJiangJie
6ba78e2cf3 Merge pull request #2756 from jumpserver/dev
Dev
2019-05-30 16:08:44 +08:00
BaiJiangJie
63a06b5dd8 Merge pull request #2755 from jumpserver/dev_bai
[Update] 修改获取资产信息时返回org_id字段值
2019-05-30 16:07:42 +08:00
BaiJiangJie
ec60697912 [Update] 修改细节 2019-05-30 16:07:19 +08:00
BaiJiangJie
d6a8c04d45 [Update] 修改获取资产信息时返回org_id字段值 2019-05-30 16:02:03 +08:00
BaiJiangJie
d46f5858f8 Merge pull request #2737 from jumpserver/dev
Dev
2019-05-29 11:34:43 +08:00
BaiJiangJie
a8491eafea Merge pull request #2747 from jumpserver/dev_bai
[Update] 修改版本号
2019-05-29 10:44:23 +08:00
BaiJiangJie
0d046d8356 [Update] 修改版本号 2019-05-29 10:22:13 +08:00
八千流
9c55450a9e [Bugfix] 修复资源表可以选择多页的资源数据 (#2744)
* [Bugfix] 修复资源表可以选择多页的资源数据

* [Bugfix]修改小问题
2019-05-29 10:13:19 +08:00
BaiJiangJie
00e986a64e [Update] 优化LDAP用户导入功能,可导入跨页选取的所有用户 (#2745) 2019-05-29 10:11:58 +08:00
BaiJiangJie
dc4bf669b0 [Update] 修改RemoteApp迁移文件 (#2742) 2019-05-28 17:05:31 +08:00
BaiJiangJie
059a8de44a [Bufix] 修复用户更新时password_strategy字段不能为空的bug (#2741) 2019-05-28 12:11:55 +08:00
ibuler
aa25b7745c Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-28 10:28:49 +08:00
ibuler
1097b11115 [Update] 修改ldap支持ssl 2019-05-28 10:28:03 +08:00
BaiJiangJie
9f67daeb1e [Update] 优化AssetPermissionUtil动态设置系统用户actions属性逻辑 (#2739)
* [Update] 优化AssetPermissionUtil动态设置系统用户actions属性逻辑

* [Bugfix] 修复can't pickle dict_keys objects的bug
2019-05-27 20:04:41 +08:00
ibuler
2065692199 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-27 16:39:02 +08:00
ibuler
33e342f03f [Update] 修改菜单 2019-05-27 16:38:43 +08:00
八千流
7a6027f35a [Update] csv翻译 (#2736) 2019-05-27 16:12:19 +08:00
BaiJiangJie
e46a6f1d12 [Update] 添加应用下载器地址;添加系统设置中字段的最大、最小值限制 (#2735)
* [Update] 添加应用下载器地址

* [Update] 系统设置中的Form, 添加最大、最小值限制

* [Update] 更新翻译
2019-05-27 15:52:25 +08:00
ibuler
0f3996369b Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-27 15:50:54 +08:00
ibuler
0847539e02 [Update] 修改导出的csv名称 2019-05-27 15:50:44 +08:00
八千流
f576f2eda2 [Bugfix]修复解析器顺序bug,csv解析器需在file解析器前 (#2734) 2019-05-27 15:21:33 +08:00
BaiJiangJie
21ac3eaf8b [Update] 用户创建添加密码设置策略 (#2731)
* [Update] 优化创建用户的密码策略功能

* [Update] 优化用户初始密码设置以及清除初始密码

* [Update] 优化创建用户的密码策略功能

* [Update]统一变量名前缀

* [Update] 用户密码策略去掉自定义策略

* [Update] 修改小问题

* [Update] 优化创建用户密码策略

* [Update] 翻译

* [Update] 优化mfa按钮排布和间距

* [Update] 优化mfa按钮样式由前端控制

* [Update] 优化前端密码策略按钮的显示与隐藏

* [Update] 用户创建设置密码添加密码校验弹窗
2019-05-24 19:41:07 +08:00
ibuler
b237cbb20f Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-24 18:34:35 +08:00
ibuler
9b5b48dd1a [Update] 修改settings conf配置 2019-05-24 18:34:21 +08:00
八千流
b7eac837f7 [Update] 创建用户自定义邮件内容 (#2703)
* [Update] 增加创建用户自定义邮件内容功能

* [Update] 优化自定义创建用户邮件功能

* [Update] 修改自定义邮件内容的小图标icon

* [Update] 发送邮件引入样式,避免不同浏览器不显示邮件格式问题

* [Update] 优化创建用户自定义邮件内容

* [Update] 修改翻译

* [Update] 优化邮件内容的变量名

* [Update]优化自定义邮件内容

* [Update] 修改小问题
2019-05-24 18:12:58 +08:00
BaiJiangJie
217bb81722 [Update] 优化LDAPUtil逻辑 (#2728) 2019-05-24 11:11:50 +08:00
xiaomao
b791073802 [Update] 解决ldap映射is_active等字段为bool值的问题 (#2716) (#2721) 2019-05-23 18:55:28 +08:00
BaiJiangJie
45cb39e971 [Update] RemoteApp设置type vSphere Client的默认路径 (#2725) 2019-05-23 18:52:39 +08:00
BaiJiangJie
2df1dd2bb1 [Bugfix] LDAP同步用户列表中如果API获取到的用户名中有空格,在设置为id的时候默认会取第一个空格前的字符串作为用户名,导致不会导入用户 (#2726) 2019-05-23 18:52:19 +08:00
BaiJiangJie
4cd3dd3670 [Update] 更新RemoteApp (#2720)
* [Update] RemoteAppForm添加RemoteApp各类型参数保存逻辑

* [Update] RemoteApp添加默认应用路径
2019-05-22 18:56:34 +08:00
BaiJiangJie
b18ca8c94f [Update] 校验用户对RemoteApp权限API添加权限控制 (#2715) 2019-05-22 12:35:14 +08:00
ibuler
75fb37d247 Merge branch 'password' into dev 2019-05-22 10:26:26 +08:00
ibuler
f863ed0f4f [Update] 修改获取common store 2019-05-22 10:25:53 +08:00
BaiJiangJie
3eaf4cd142 [Update] 修改翻译 (#2714) 2019-05-22 10:01:41 +08:00
ibuler
7859499c97 [Update] 更新dockerfile 2019-05-21 17:46:39 +08:00
ibuler
e8ceb58292 [Update] 允许资产ip填写为host地址 2019-05-21 17:07:47 +08:00
BaiJiangJie
84610f2a2c [Bugfix] 修改PermsModel抽象后的objects-bug (#2713)
* [Bugfix] 修改PermsModel抽象后的objects-bug

* [Bugfix] 删除无用代码
2019-05-21 17:01:47 +08:00
BaiJiangJie
d906df5b00 [Update] 抽象BasePermission (#2710)
* [Update] AssetPermission/RemoteAppPermission抽象BasePermission

* [Update] Perms模块添加迁移文件

* [Update] Perms删除多余迁移文件

* [Update] Perms重新生成RemoteAppPermission迁移文件
2019-05-21 16:27:01 +08:00
八千流
22f362aab3 Dev csv (#2640)
* [Update] 封装JMSCSVRender和JMSCSVParser

* [Update] 更改JMSCSVRender,根据请求参数控制导出csv的字段和下载csv模板的字段

* [Update] 导入空数据,提示错误消息

* [Update] 修改用户导入和导出功能代码

* [Update] 修改导入路由为动态反向解析

* [Update] 修改JMSCSVRender和JMSCSVParser以及用户导入导出代码

* [Update] 优化parsers逻辑

* [Update] 优化parsers csv代码结构

* [Update] 优化renders csv代码逻辑

* [Update] 删除parsers csv多余代码

* [Update] 删除parsers csv多余变量

* [Update] 优化renders csv代码结构

* [Update] 优化renders csv代码结构2

* [Update] 优化renders csv获取header逻辑

* [Update] 优化Cache Resources ID View逻辑

* [Update] 优化ViewSet IDCacheFilterMixin逻辑

* [Update] csv: parser render 添加异常捕获逻辑

* [Update] 删除多余代码

* [Update] 优化前端代码

* [Update] 修改小问题

* [Update] 修改前端导出用户的问题

* [Update] 前端 - 优化数据导出逻辑 APIExportData

* [Update] 修复批量创建用户时发送created信号的bug

* [Update] 优化导入时错误信息展示

* [Update] 优化parser、render时,对于多对多字段的处理

* [Update] 修改前端上传空文件问题

* [Update] 添加IDExportFilter,控制下载模版时的queryset

* [Update] 修改判断导出模版时参数变量名 action => template

* [Update] 修复导入用户数据时,用户组不生效的bug

* [Update] 修改前端导入信息展示

* [Update] 抽象资源导入模版

* [Update] 优化资源导入模版

* [Update] 修改js设置url的params逻辑

* [Update] 修改users序列类控制read_only字段方式

* [Update] 资产列表采用新的导入/导出csv文件逻辑

* [Update] 修改导入资产时设置资产所在节点逻辑

* [Update] 添加用户组导入/导出功能

* [Update] 修改前端变量名

* [Update] 修改下载导入模版,不包含org字段

* [Update] 增加管理用户导入/导出功能

* [Update] 导入模版提供id字段(为了资源备份后导入直接使用); 修复资源导入时联合唯一字段不校验导致创建时报错的bug

* [Update] 增加系统用户导入/导出功能

* [Update] 排序资源导入/导出字段

* [Update] 翻译导入/导出的字段和模版

* [Update] 更改csv导出和导出模版数据的控制在render实现

* [Update] 资产添加 更新导入 功能

* [Update] 用户/用户组/管理用户/系统用户/ 添加导入更新

* [Update] 翻译

* [Update] 优化资源序列化中的label

* [Update] 去掉资源IDInFilterMixin过滤

* [Update] 翻译
2019-05-21 16:24:01 +08:00
BaiJiangJie
4942900886 [Update] RemoteApp修改左侧菜单描述 (#2709) 2019-05-21 16:10:48 +08:00
BaiJiangJie
1e505d3d0f [Update] OpenID Middleware去掉输出日志 (#2711) 2019-05-21 16:07:40 +08:00
BaiJiangJie
1eca517978 [Feature] 添加功能 RemoteApp (#2706)
* [Feature] RemoteApp添加Model

* [Feature] RemoteApp添加ViewSet API

* [Feature] RemoteApp添加获取connection-info API

* [Feature] Perms模块修改目录结构

* [Feature] RemoteAppPermission添加Model

* [Feature] RemoteAppPermission添加ViewSet API

* [Feature] RemoteAppPermission添加用户/用户组获取被授权的RemoteApp API

* [Feature] RemoteAppPermission添加校验用户对RemoteApp的权限 API

* [Feature] RemoteAppPermission添加获取用户授权的RemoteApp树 API

* [Feature] RemoteAppPermission添加<添加/移除>所授权的<用户/RemoteApp> API

* [Feature] RemoteApp添加创建、更新、详情、删除、用户RemoteApp等页面

* [Feature] RemoteAppPermission添加创建、更新、详情、删除、授权用户、授权RemoteApp等页面

* [Feature] RemoteApp从assets模块迁移到新添加的applications模块

* [Feature] RemoteApp/RemoteAppPermission添加迁移文件

* [Feature] RemoteApp/RemoteAppPermission修改小细节

* [Feature] RemoteApp/RemoteAppPermission修改小细节2

* [Feature] RemoteApp/RemoteAppPermission修改小细节3

* [Feature] RemoteApp更新迁移文件

* [Feature] RemoteApp/RemoteAppPermission添加翻译信息

* [Feature] RemoteApp/RemoteAppPermission删除迁移文件

* [Feature] RemoteApp/RemoteAppPermission添加迁移文件

* [Feature] RemoteApp/RemoteAppPermission修改代码风格
2019-05-20 19:39:53 +08:00
ibuler
38acce7460 [Update] 修改错误提示颜色 2019-05-20 12:36:35 +08:00
老广
3855fecc69 Password message (#2702)
* [Update] 密码信封

* [Update]  查看密码

* [Update] 支持查看密码

* [Update] 修改语言翻译

* [Update] 迁移ansible到2.8版本

* [Update] 修改auth book的可连接性

* [Update] 删除不使用的方法
2019-05-20 12:30:55 +08:00
ibuler
466b922ea0 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-16 14:41:40 +08:00
ibuler
9e52579ca6 [Update] 修改api meta data 2019-05-16 14:41:11 +08:00
老广
1ca1e519b6 Merge pull request #2695 from jumpserver/dev_bugfix
[Bugfix] 修复AssetUserManager.get获取username为""的AuthBook对象时,返回多个结果的bug
2019-05-16 10:38:47 +08:00
BaiJiangJie
54a9070c58 [Update] 资产节点API添加search功能 2019-05-16 10:36:47 +08:00
BaiJiangJie
e108aae3c0 [Bugfix] 修复AssetUserManager.get获取username为""的AuthBook对象时,返回多个结果的bug 2019-05-14 21:39:34 +08:00
syin
27c00410d3 add gcc libc-dev linux-headers make autoconf
add gcc libc-dev linux-headers make autoconf for pip install -r requirements
2019-05-10 11:52:03 +08:00
老广
74ae7d138e Merge pull request #2683 from jumpserver/dev
[Update] 修改排序节点排序规则
2019-05-10 11:35:46 +08:00
ibuler
20ce5d11a6 [Update] 修改排序节点排序规则 2019-05-10 11:33:26 +08:00
老广
cbe919c4b3 Merge pull request #2682 from jumpserver/dev
Dev
2019-05-10 11:31:28 +08:00
ibuler
497cba6b13 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-05-10 11:30:01 +08:00
ibuler
e2849be72c [Update] 修改节点树排序 2019-05-10 11:29:38 +08:00
老广
a025930957 Merge pull request #2642 from jumpserver/dev
Dev
2019-04-30 10:46:28 +08:00
老广
990c78e7cc Merge branch 'master' into dev 2019-04-30 10:46:10 +08:00
BaiJiangJie
0ef12906d3 [Update] 修改翻译信息 (#2643)
* [Update] 更新翻译信息

* [Update] 更新翻译信息
2019-04-30 10:30:58 +08:00
ibuler
61a37731ec Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-30 10:20:57 +08:00
ibuler
d3217b6a67 [Update] 修改版本号 2019-04-30 10:20:48 +08:00
BaiJiangJie
04266cc20b [Update] 修改Copyright (#2649) 2019-04-30 10:07:57 +08:00
ibuler
4f36cf7dd1 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-29 10:39:51 +08:00
ibuler
490041587b [Update] 修改copyright 2019-04-29 10:39:31 +08:00
BaiJiangJie
3a3da94468 [Update] 修改预留/auth/login/页面方式(admin=1);解决luna页面刷新不跳转openid认证的问题 (#2634) 2019-04-25 18:55:48 +08:00
BaiJiangJie
b7ad6cfe62 [Update] 防止 XSS (#2633)
* [Bugfix] 修改管理用户列表显示bug

* [Bugfix] 修复刷新批量命令页面的bug

* [Update] 防止 XSS
2019-04-25 18:16:41 +08:00
ibuler
4463e7545d [Update] 去掉多余的 2019-04-25 14:34:47 +08:00
ibuler
d0eafc8b8e [Update] Merge 2019-04-25 14:32:53 +08:00
ibuler
8b98c20d68 [Update] xss 2019-04-25 14:31:34 +08:00
BaiJiangJie
caa5060ecd [Update] 控制组织管理员不允许更新、删除超级用户;修复ViewSet API批量更新的bug (#2629)
* [Update] 控制组织管理员不允许编辑(更新、删除)超级用户 - 待续(控制批量更新API)

* [Update] 修改方法名称

* [Update] 控制组织管理员不允许批量更新包含超级用户的用户列表

* [Bugfix] 修复所有ViewSet API进行批量更新时rest_framework_bulk库内部的bug

* [Update] 修改 OpenID Middleware 日志输出模式 info => debug
2019-04-25 10:11:50 +08:00
BaiJiangJie
aabcf7f31c [Update] 授权列表下拉信息添加action字段 (#2618) 2019-04-22 12:31:40 +08:00
ibuler
40d48cdfe4 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-04-22 12:07:59 +08:00
ibuler
8196537878 [Update] 去掉logger.info openid msg 2019-04-22 12:07:48 +08:00
BaiJiangJie
33a00f043b [Bugfix] 修复创建azure类型的录像存储时前端的bug (#2617) 2019-04-22 11:55:14 +08:00
BaiJiangJie
f235e20153 [Feature] 授权规则添加 actions 选项,控制用户对资产的操作行为 (#2610)
* [Feature] 1. perms actions - 添加 Action Model

* [Feature] 2. perms actions - 添加 Action API

* [Feature] 3. perms actions - 授权规则: 添加actions字段

* [Feature] 4. perms actions - 授权规则创建页面: 设置 actions 默认 all

* [Feature] 5. perms actions - 资产授权工具: 动态给system_user设置actions属性; 修改授权相关的API-serializer类: 添加actions字段值

* [Feature] 6. perms actions - 更新API(用户使用系统用户连接资产时权限校验): 添加actions校验

* [Feature] 7. perms actions - 迁移文件中为已经存在的perms添加默认的action

* [Feature] 8. perms actions - 创建授权规则时设置默认action(如果actions字段值为空)

* [Feature] 9. check actions - 修改校验用户资产权限API逻辑(添加actions校验)

* [Feature] 10. check actions - 修改注释

* [Feature] 11. check actions - 添加API: 获取用户指定资产和系统用户被授权的actions

* [Feature] 12. check actions - 添加翻译信息
2019-04-22 11:42:20 +08:00
BaiJiangJie
cf2455c084 [Bugfix] 修复创建azure类型的录像存储时前端的bug (#2608) 2019-04-18 11:50:30 +08:00
BaiJiangJie
fc1068a9dc [Update] 更新翻译 (#2599) 2019-04-15 16:34:56 +08:00
zhnxin
35a0ca1875 Update node.py (#2527)
bugfix: node get_all_children逻辑
2019-04-15 14:43:49 +08:00
八千流
56519354b6 Email reset password (#2547)
* [Update]更改英文登录界面标题,可两行

* [Update] 更改用户通过邮箱修改密码后,该链接就失效

* [Update]更改页面左上角logo_text图片

* [Update] 优化发送邮箱改密连接失效代码

* [Update] 优化发送邮箱改密连接失效代码(2)

* [Update] 优化发送邮箱改密连接失效代码(3)

* [Update] 更新interface一键恢复默认的翻译

* [Update] 更改登录失败 用户名密码错误信息的翻译

* [Update] 优化生成token并设置缓存的代码
2019-04-15 14:37:45 +08:00
jokimina
78e4e13fb9 [Bugfix] IsSuperUserOrAppUser deny app user (#2558) 2019-04-15 14:33:40 +08:00
八千流
699b8d9980 [Update] 修改批量更新资产url过长导致的错误 (#2571) 2019-04-15 14:33:16 +08:00
zbhlove100
ba9581801c Update README.md 2019-04-09 16:57:01 +08:00
zbhlove100
0a5fdf4ea1 功能转为表格 2019-04-09 16:53:54 +08:00
zhangbohan
3849fa2b15 update readme 2019-04-09 15:27:37 +08:00
zhangbohan
0952cbc7c6 英文readme 2019-04-09 12:21:13 +08:00
zhangbohan
bb06c39dd4 更新readme 2019-04-09 12:09:11 +08:00
zhangbohan
d60dc31443 更新readme 2019-04-08 18:30:39 +08:00
老广
76b3cd8edd Merge pull request #2583 from jumpserver/dev
Dev
2019-04-04 12:33:16 +08:00
老广
638ba31694 Merge pull request #2582 from jumpserver/dev_bugfix_assets
[Bugfix] 修复资产对象获取所有节点时的bug
2019-04-04 12:21:42 +08:00
BaiJiangJie
c31b169cae [Bugfix] 修复资产对象获取所有节点时的bug 2019-04-04 12:17:53 +08:00
老广
fc167526ae Merge pull request #2575 from jumpserver/dev
[Bugfix] 修复OpenID判断退出登录的中间件逻辑的bug (#2574)
2019-04-03 12:19:23 +08:00
BaiJiangJie
55eff5eab9 [Bugfix] 修复OpenID判断退出登录的中间件逻辑的bug (#2574) 2019-04-03 12:18:58 +08:00
老广
f5a7f4e086 Merge pull request #2570 from jumpserver/dev
Bugfix (#2569)
2019-04-02 16:26:28 +08:00
老广
f7b0932cdd Bugfix (#2569)
* [Update] 修改Logo 的位置

* [Update] 修改镜像build
2019-04-02 16:25:39 +08:00
老广
ba89ce8fb9 Merge pull request #2560 from jumpserver/dev
[Update] 修复小问题 (#2557)
2019-03-29 19:34:28 +08:00
BaiJiangJie
9d62deeabe [Update] 修复小问题 (#2557)
* [Update] 修复小问题

* [Bugfix] 修复操作日志动作、资源类型过滤不成功的bug
2019-03-29 19:14:18 +08:00
老广
459b41f327 Merge pull request #2559 from jumpserver/dev
[Update] 修复重定向的问题
2019-03-29 15:54:20 +08:00
ibuler
3062e3f64a [Update] 修复重定向的问题 2019-03-29 15:53:31 +08:00
老广
c1362ca4e2 Merge pull request #2552 from jumpserver/dev
Dev
2019-03-28 13:13:36 +08:00
ibuler
9d24912ad9 [Update] Merge 2019-03-28 13:12:19 +08:00
老广
db290609a8 Merge pull request #2551 from jumpserver/dev
Dev
2019-03-28 13:07:34 +08:00
ibuler
4bc5eced6c Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-03-28 13:07:04 +08:00
BaiJiangJie
b82a66c83d [Update] 优化LDAP用户导入功能 (#2550) 2019-03-28 12:50:04 +08:00
ibuler
bf7079df9e [Update] 修改logo 2019-03-28 12:35:16 +08:00
ibuler
f137c5740e [Update] 添加openldap client 2019-03-28 11:53:52 +08:00
ibuler
ee47905966 [Update] 修改超时时间 2019-03-27 20:39:31 +08:00
fit2cloudrd
f6cd193f9e Update README.md (#2548)
* Dev (#2544)

* [Update] 更新翻译 (#2541)

* [Update] 更新缓存机制

* [Update] 增加task最大允许事件,并设置命令最大运行时间为60s

* Update README.md
2019-03-27 18:47:38 +08:00
ibuler
a31775dd23 [Update] 整理 copyright 和 版本 2019-03-27 17:46:35 +08:00
老广
30ba1e5886 Merge pull request #2545 from jumpserver/dev
Dev
2019-03-27 13:04:58 +08:00
ibuler
f97bfa7bf1 [Update] 修复s3创建时端点不含协议的提示 2019-03-27 12:49:46 +08:00
ibuler
ace028fa7f [Update] 统一可更改的interface 2019-03-27 12:20:43 +08:00
老广
69f6401e87 Dev (#2544)
* [Update] 更新翻译 (#2541)

* [Update] 更新缓存机制

* [Update] 增加task最大允许事件,并设置命令最大运行时间为60s
2019-03-27 11:21:21 +08:00
ibuler
bd4d974df1 [Update] merge 2019-03-27 11:13:35 +08:00
ibuler
6e7446f530 [Update] 增加task最大允许事件,并设置命令最大运行时间为60s 2019-03-27 11:12:34 +08:00
ibuler
afe9471aa2 [Update] 更新缓存机制 2019-03-26 19:46:04 +08:00
BaiJiangJie
4d56b84861 [Update] 更新翻译 (#2541) 2019-03-26 15:47:24 +08:00
老广
8fede58c64 Merge pull request #2456 from jumpserver/dev
Dev
2019-03-26 11:39:43 +08:00
ibuler
370904212f [Update] 修改翻译 2019-03-25 20:36:20 +08:00
ibuler
ae03a5aeb7 [Update] 翻译问题 2019-03-25 19:57:18 +08:00
ibuler
24a38841dd [Update] 修改Dockerfile 2019-03-22 18:26:25 +08:00
ibuler
bbc6156bd7 [Update] 数据库支持CA 2019-03-22 18:06:46 +08:00
ibuler
f387df41d7 [Update] 修改版本 2019-03-22 17:23:02 +08:00
ibuler
ceb8b2f5b3 [Update] 修改翻译 2019-03-22 17:16:02 +08:00
ibuler
877781a6ca [Update] 删掉一个migrations 2019-03-22 16:56:46 +08:00
BaiJiangJie
612d5efd1b [Update] 抽象Inventory,更新翻译 (#2530)
* [Update] 抽象Inventory, 支持自定义用户名密码构建Inventory

* [Update] 更新翻译
2019-03-22 16:52:26 +08:00
ibuler
7ea03801d0 [Update] 修改翻译 2019-03-22 15:57:37 +08:00
八千流
ce8f4b4a48 Ldap synchronization (#2512)
* [Add]初步实现ldap一键导入用户到jumpserver用户表里

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

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

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

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

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

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

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

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

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

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

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

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

* [Update] 更新翻译

* [Update] 更新翻译

* Export login log (#2511)

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

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

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

* [Update]更改导出登录日志按钮
2019-03-22 15:55:46 +08:00
ibuler
15179d2450 [Update] 修改批量命令的翻译 2019-03-22 15:55:20 +08:00
ibuler
9aae106970 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-03-22 15:19:24 +08:00
ibuler
c82044f6bc [Update] 修改一些bug 2019-03-22 15:18:47 +08:00
八千流
e4e6f59589 Export login log (#2511)
* [Add]增加登录日志导出功能

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

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

* [Update]更改导出登录日志按钮
2019-03-22 15:17:22 +08:00
BaiJiangJie
c8aa9d006f [Update] 更新翻译(改密计划) (#2525)
* [Update] 更新翻译(添加改密计划)

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

* [Update] 更新翻译

* [Update] 更新翻译
2019-03-22 09:42:39 +08:00
老广
311538dcf8 Bugfix (#2513)
* [Update] 用户页面添加跳转

* [Update] 网关测试支持nat, 修复创建node等id不能指定的问题, 修复settings频繁redis, 没有has_replay录像不可以播放
2019-03-19 19:09:09 +08:00
老广
324cf2469f Bugfix (#2506)
* [Update] 增加清理celery日志

* [Update] 修复两周前会话命令数量系那是问题

* [Update] 修复两周前会话命令数量系那是问题

* [Update] 修改结构

* [Update] 添加datatable失败的日志

* [Update] 转换配置文件格式

* [Update] 添加traceback
2019-03-18 11:34:13 +08:00
ibuler
01745ead1f [Update] 添加migrations 2019-03-18 10:26:40 +08:00
BaiJiangJie
4e705a52eb [Feature] 添加资产用户管理器 (#2489)
* [Feature] 1. 资产用户管理器

* [Feature] 2. 资产用户管理器: 更新AuthBook

* [Feature] 3. 资产用户管理器: 添加 AssetUser API

* [Feature] 4. AssetUser Model: 添加方法 load_related_asset_auth

* [Feature] 5. AdminUser: 更新管理用户获取认证信息时,先加载相关资产的认证

* [Feature] 6. SystemUser: 更新系统用户获取认证信息时,先加载相关资产的认证

* [Feature] 前端页面: 添加资产用户列表页面

* [Feature] 前端页面: 管理用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 前端页面: 系统用户的资产管理页面添加按钮: 修改资产用户认证信息

* [Feature] 优化: 从管理用户和系统用户的backend中获取相关资产用户的逻辑

* [Update] Fix 1

* [Feature] 优化: SystemUserBackend之filter功能

* [Feature] 优化: AdminUserBackend之filter功能

* [Feature] 优化: AdminUserBackend和SystemUserBackend功能

* [Feature] 更新翻译: 资产用户管理器

* [Update] 更新资产用户列表页名称为: asset_asset_user_list.html

* [Bugfix] 修改bug: SystemUserBackend 根据用户名过滤系统用户

* [Feature] 添加: 资产用户列表中可测试资产用户的连接性

* [Update] 修改: AdHoc model的run_as字段从SystemUser外键修改为username字符串

* [Feature] 添加: 获取系统用户认证信息(对应某个资产)API

* [Update] 更新: API获取asset user时进行排序

* [Bugfix] 修改: 资产用户可连接性CACHE_KEY

* [Update] 更新翻译信息

* [Update] 修改获取资产用户认证信息API的返回响应(200/400)

* [Update] 修改BaseUser获取特定资产的方法名

* [Update] 修改logger输出,AuthBook set_version_and_latest

* [Update] 修改日志输出添加exc_info参数

* [Update] 移除AuthBook迁移文件0026

* [Bugfix] 修复AdminUserBackend获取instances为空的bug
2019-03-18 10:15:33 +08:00
ibuler
9bb58afee1 [Update] bugfix 2019-03-11 10:27:40 +08:00
老广
b45b33380c Monitor (#2485)
* [Update]

* [Update] 修改fields
2019-03-11 10:06:45 +08:00
BaiJiangJie
c86a036ac6 [Update] OpenID认证流程添加详细日志 (#2462)
* [Update] OpenID认证流程添加详细日志

* [Update] 优化日志格式
2019-03-07 18:41:42 +08:00
ibuler
8694511d86 [Update] 完成 2019-03-06 14:58:25 +08:00
ibuler
58c4a46f6e [Update] 用户权限增加cache 2019-03-05 19:47:14 +08:00
ibuler
dfd26d88d4 [Update] 用户资产添加缓存 2019-03-04 20:45:57 +08:00
ibuler
dcf6959cff [Update] 每次启动失效设置的cache 2019-03-04 18:24:02 +08:00
ibuler
924affd978 [Update] 更新移动的model 2019-03-04 17:07:51 +08:00
ibuler
ad6d233c11 [Update] 修改表名称 2019-03-04 15:51:17 +08:00
ibuler
d84ab1d215 [Update] 修改迁移 2019-03-04 15:38:59 +08:00
ibuler
a5fc04e0ce [Update] merge 2019-03-04 11:36:10 +08:00
ibuler
7f71513085 [Update] 去掉fixtures 2019-03-04 11:32:28 +08:00
ibuler
6004ef3f0d [Update] 修改settings 2019-03-04 10:53:49 +08:00
ibuler
e76392a169 Merge branch 'stable' into dev 2019-03-04 10:47:31 +08:00
ibuler
53f0b2e9b0 [Update] 增加清理登陆日志的任务 2019-03-04 10:39:44 +08:00
ibuler
de79e36251 [Update] 增加配置 2019-03-04 10:17:35 +08:00
ibuler
c84e984eae [Update] 任务列表去掉日期 2019-03-01 18:26:13 +08:00
ibuler
6d65c967b1 [Update] config example增加radius 2019-03-01 18:21:14 +08:00
ibuler
8199ea84f4 [Bugfix] 修复有时导致的token解析失败 2019-03-01 13:50:57 +08:00
ibuler
ce1b0da09d [Update] Merge 2019-02-28 18:24:52 +08:00
ibuler
cd6bb848e9 [Update] Squash table 2019-02-28 18:23:39 +08:00
ibuler
34040fcd59 [Update] 移动model 2019-02-28 17:58:53 +08:00
ibuler
1969fb79fe [Update] 修改authentication backends 2019-02-28 15:12:45 +08:00
BaiJiangJie
904f64604b [Bugfix] 修复authentication模块之间引用的bug 2019-02-28 14:41:33 +08:00
ibuler
9b3509208d [Update] 修改authentication目录结构 2019-02-28 11:58:48 +08:00
BaiJiangJie
6700dc969f [Update] users认证逻辑迁移到authentication中 2019-02-27 20:55:28 +08:00
ibuler
21714cc411 [Update] Stash 2019-02-27 08:45:00 +08:00
ibuler
1a247d60e7 [Update] sudo命令添加help text 2019-02-26 16:49:55 +08:00
jokimina
adf8b1f7aa [Bugfix] operation on closed file. (#2438) 2019-02-26 12:42:34 +08:00
老广
69f640daa4 Api (#2439)
* [Update] 迁移settings到独立app

* [Update] 修改settings migrations

* [Update] 修改docs说明
2019-02-26 12:38:20 +08:00
老广
a43314f5be Merge pull request #2432 from jokimina/patch-5
[Bugfix] celery beat crontab task timezone issue
2019-02-25 10:43:52 +08:00
jokimina
599e8a7e37 [Bugfix] crontab task timezone issue 2019-02-24 21:34:45 +08:00
老广
0e00451e1f Merge pull request #2425 from jumpserver/dev
[Update] 修改ldap test密码为空
2019-02-22 19:21:21 +08:00
ibuler
866e5d2011 [Update] 修改ldap test密码为空 2019-02-22 19:14:07 +08:00
老广
232674b1c1 Merge pull request #2423 from jumpserver/dev
Dev
2019-02-22 17:10:05 +08:00
ibuler
ddf60d2512 [Update] 添加jms日志和修改entrypoint 2019-02-22 16:33:27 +08:00
ibuler
3e6e0153cf [Update] 修改entrypoint并增加debug日志 2019-02-22 16:14:50 +08:00
ibuler
6b984aac53 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2019-02-22 12:49:03 +08:00
ibuler
6d3ee8116e [Update] 修改版本号 2019-02-22 12:48:43 +08:00
老广
904a0f67dd Merge pull request #2422 from jumpserver/dev
Dev
2019-02-22 10:16:08 +08:00
BaiJiangJie
81e1ce2688 [Update] Interface - 1. settings 添加TERMINAL_HEADER_TITLE (#2421) 2019-02-21 19:36:15 +08:00
ibuler
7c422d2ed6 [Update] Merge 2019-02-21 19:24:52 +08:00
ibuler
2a5129c481 [Update] 修改支持记录cmd filter日志 2019-02-21 19:22:23 +08:00
BaiJiangJie
28cdfca14b [Update] 优化 License (#2420)
* [Update] License - 1.添加标签过滤器

* [Update] License - 2.添加翻译信息
2019-02-21 18:49:40 +08:00
ibuler
202aba048b [Update] 修改用户页面授权资产 2019-02-21 17:35:28 +08:00
ibuler
3c2a4703bc [Update] ldap允许空密码 2019-02-21 16:37:36 +08:00
ibuler
4904aac2df [Update] 更新依赖版本 2019-02-21 16:16:08 +08:00
ibuler
303f88d6ee [Update] 更新replay storage 配置 2019-02-21 16:13:08 +08:00
老广
cccc74279d Dev license (#2409)
* [Update] 更新用户组

* [Update] license - 更新翻译

* [Feature] interface - 自定义配置logo,修改前端(待续)

* [Update] interface更新前端页面,两个登陆首页的模版设

* [Update]修改新登陆模版全局变量,国际化翻译,登陆逻辑页面切换

* [Update] 优化Interface前端逻辑

* [Update] Interface 修改前端一些小问题

* [Update] License/Interface 生成翻译信息
2019-02-20 19:20:13 +08:00
老广
16db2abca5 Merge pull request #2419 from jumpserver/dev
Dev
2019-02-20 19:02:40 +08:00
ibuler
859f2d9795 [Update] 支持telnet自定义正则 2019-02-20 19:00:01 +08:00
ibuler
4fd9957bec [Update] 修改celery日志显示 2019-02-20 17:51:53 +08:00
老广
0ac6e6ba2c Merge pull request #2418 from jumpserver/dev
[Update] 修改gunicorn日志
2019-02-20 16:16:44 +08:00
ibuler
785cc04126 [Update] 修改gunicorn日志 2019-02-20 16:11:58 +08:00
老广
f269eae774 Merge pull request #2415 from jumpserver/dev
[Update] 修改导入
2019-02-20 10:41:59 +08:00
ibuler
6f19fcb702 [Update] 修改tasks 2019-02-20 10:40:43 +08:00
老广
b7b6218306 Merge pull request #2414 from jumpserver/dev
[Update] gunicorn的日志
2019-02-19 20:06:37 +08:00
ibuler
5cd809b48a [Update] 修改导入 2019-02-19 20:03:02 +08:00
ibuler
5a1b894138 [Update] gunicorn的日志 2019-02-19 16:59:00 +08:00
老广
4d402617b6 Merge pull request #2410 from jumpserver/dev
Dev
2019-02-19 13:04:00 +08:00
ibuler
666ef366e7 [Bugfix] 修复session不显示在线的问题 2019-02-19 12:50:33 +08:00
ibuler
28d029a553 [Update] 修改settings 2019-02-19 12:18:39 +08:00
老广
00763e986a Merge pull request #2408 from jumpserver/dev
[Update] 修改token有效期
2019-02-19 12:00:04 +08:00
ibuler
f9a7cca478 [Update] 修改token有效期 2019-02-19 10:29:25 +08:00
老广
d09b34e232 Merge pull request #2404 from jumpserver/dev
[Update] 修改配置文件判断
2019-02-18 15:10:36 +08:00
ibuler
2737675c36 [Update] 修改配置文件判断 2019-02-18 15:09:02 +08:00
老广
7591f40b2c Merge pull request #2402 from jumpserver/dev
[Update] 创建子节点支持id
2019-02-18 13:02:15 +08:00
ibuler
c4af6fa72d [Update] 创建子节点支持id 2019-02-18 12:59:36 +08:00
老广
19be7ac580 Merge pull request #2394 from jumpserver/dev
Dev
2019-02-15 11:16:54 +08:00
ibuler
49404f763d [Update] 修改terminal字体 2019-02-15 11:15:46 +08:00
ibuler
87f2a67789 [Update] 修改录像获取 2019-02-15 11:14:37 +08:00
老广
041edb6177 Merge pull request #2392 from jumpserver/dev
Dev
2019-02-15 10:44:32 +08:00
ibuler
df2fad76c7 [Update] 修改Dockerfile 优先使用阿里云镜像 2019-02-14 16:23:56 +08:00
ibuler
94020a8fbb [Update] 修改日志存储 2019-02-14 16:15:28 +08:00
ibuler
cb1e19d28f [Update] 修改录像路径使用utc 2019-02-14 15:47:55 +08:00
ibuler
0980dffb47 [Update] 修复一下新建两个节点的bug 2019-02-13 19:38:51 +08:00
ibuler
4051225ecb [Bugfix] 修复命令执行bug,修复修改日志级别后无法查看日志 2019-02-13 15:55:11 +08:00
老广
507518da04 Merge pull request #2369 from jumpserver/dev
[Update] 修改版本号
2019-01-29 19:33:06 +08:00
ibuler
d3bdbc0b81 [Update] 修改版本号 2019-01-29 19:24:28 +08:00
558 changed files with 24114 additions and 12804 deletions

View File

@@ -4,4 +4,6 @@ data/*
.github
tmp/*
django.db
celerybeat.pid
celerybeat.pid
### Vagrant ###
.vagrant/

2
.gitignore vendored
View File

@@ -34,3 +34,5 @@ data/static
docs/_build/
xpack
logs/*
### Vagrant ###
.vagrant/

View File

@@ -6,10 +6,12 @@ RUN useradd jumpserver
COPY ./requirements /tmp/requirements
RUN yum -y install epel-release openldap-clients telnet && cd /tmp/requirements && \
yum -y install $(cat rpm_requirements.txt)
RUN cd /tmp/requirements && pip install -r requirements.txt
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 -i https://mirrors.aliyun.com/pypi/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

199
README.md
View File

@@ -1,52 +1,205 @@
## Jumpserver
## Jumpserver 多云环境下更好用的堡垒机
![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=jumpserver)
![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=jumpserver)
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](http://www.paramiko.org/)
----
Jumpserver是全球首款完全开源的堡垒机使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的运维安全审计系统。
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver采纳分布式架构支持多机房跨区域部署中心节点提供 API各机房部署登录节点可横向扩展、无并发限制。
Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
改变世界,从一点点开始。
### 核心功能列表
----
### 功能
<table class="subscription-level-table">
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="4">身份验证 Authentication</td>
<td class="features-second-td-background-style" rowspan="3" >登录认证
</td>
<td class="features-third-td-background-style">资源统一登录和认证
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">LDAP 认证
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">支持 OpenID实现单点登录
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">多因子认证
</td>
<td class="features-third-td-background-style">MFAGoogle Authenticator
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="9">账号管理 Account</td>
<td class="features-second-td-background-style" rowspan="2">集中账号管理
</td>
<td class="features-third-td-background-style">管理用户管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">系统用户管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style" rowspan="4">统一密码管理
</td>
<td class="features-third-td-background-style">资产密码托管
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">自动生成密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">密码自动推送
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">密码过期设置
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style" rowspan="2">批量密码变更(X-PACK)
</td>
<td class="features-outline-td-background-style">定期批量修改密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">生成随机密码
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">多云环境的资产纳管(X-PACK)
</td>
<td class="features-outline-td-background-style">对私有云、公有云资产统一纳管
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="9">授权控制 Authorization</td>
<td class="features-second-td-background-style" rowspan="3">资产授权管理
</td>
<td class="features-third-td-background-style">资产树
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">资产或资产组灵活授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">节点内资产自动继承授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">RemoteApp(X-PACK)
</td>
<td class="features-outline-td-background-style">实现更细粒度的应用级授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-outline-td-background-style">组织管理(X-PACK)
</td>
<td class="features-outline-td-background-style">实现多租户管理,权限隔离
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">多维度授权
</td>
<td class="features-third-td-background-style">可对用户、用户组或系统角色授权
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">指令限制
</td>
<td class="features-third-td-background-style">限制特权指令使用,支持黑白名单
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">统一文件传输
</td>
<td class="features-third-td-background-style">SFTP 文件上传/下载
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">文件管理
</td>
<td class="features-third-td-background-style">Web SFTP 文件管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-first-td-background-style" rowspan="6">安全审计 Audit</td>
<td class="features-second-td-background-style" rowspan="2">会话管理
</td>
<td class="features-third-td-background-style">在线会话管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">历史会话管理
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style" rowspan="2">录像管理
</td>
<td class="features-third-td-background-style">Linux 录像支持
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-third-td-background-style">Windows 录像支持
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">指令审计
</td>
<td class="features-third-td-background-style">指令记录
</td>
</tr>
<tr class="subscription-level-tr-border">
<td class="features-second-td-background-style">文件传输审计
</td>
<td class="features-third-td-background-style">上传/下载记录审计
</td>
</tr>
</table>
![Jumpserver功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver-14.png "Jumpserver功能")
### 安装及使用文档
----
### 开始使用
- [Docker 快速安装文档](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
- [Step by Step 安装文档](http://docs.jumpserver.org/zh/docs/step_by_step.html)
- [完整文档](http://docs.jumpserver.org)
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
### 演示视频和系统截图
----
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
我们提供了演示视频和系统截图可以让你快速了解 Jumpserver。
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
### Demo 和 截图
我们提供了DEMO和截图可以让你快速了解Jumpserver
[DEMO](https://demo.jumpserver.org)
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
- [演示视频](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)
- [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
### SDK
----
我们编写了一些SDK供你其它系统快速和Jumpserver APi交互
- [python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
- [java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
我们编写了一些SDK供你其它系统快速和 Jumpserver API 交互
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK
### License & Copyright
Copyright (c) 2014-2018 Beijing Duizhan Tech, Inc., All rights reserved.
----
Copyright (c) 2014-2019 飞致云 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

60
README_EN.md Normal file
View File

@@ -0,0 +1,60 @@
## Jumpserver
![Total visitor](https://visitor-count-badge.herokuapp.com/total.svg?repo_id=jumpserver)
![Visitors in today](https://visitor-count-badge.herokuapp.com/today.svg?repo_id=jumpserver)
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-2.1-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Ansible](https://img.shields.io/badge/ansible-2.4.2.0-blue.svg?style=plastic)](https://www.ansible.com/)
[![Paramiko](https://img.shields.io/badge/paramiko-2.4.1-green.svg?style=plastic)](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
![Jumpserver 功能](https://jumpserver-release.oss-cn-hangzhou.aliyuncs.com/Jumpserver148.jpeg "Jumpserver 功能")
### 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 Normal file
View File

@@ -0,0 +1,56 @@
# -*- 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

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
__version__ = "1.4.6"

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1 @@
from .remote_app import *

View File

@@ -0,0 +1,31 @@
# coding: utf-8
#
from rest_framework import generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework_bulk import BulkModelViewSet
from ..hands import IsOrgAdmin, IsAppUser
from ..models import RemoteApp
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
__all__ = [
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
]
class RemoteAppViewSet(BulkModelViewSet):
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
queryset = RemoteApp.objects.all()
serializer_class = RemoteAppSerializer
pagination_class = LimitOffsetPagination
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
queryset = RemoteApp.objects.all()
permission_classes = (IsAppUser, )
serializer_class = RemoteAppConnectionInfoSerializer

View File

@@ -0,0 +1,7 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class ApplicationsConfig(AppConfig):
name = 'applications'

View File

@@ -0,0 +1,68 @@
# 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'
REMOTE_APP_TYPE_CHOICES = (
(
_('Browser'),
(
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
)
),
(
_('Database tools'),
(
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
)
),
(
_('Virtualization tools'),
(
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere 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_MAP_FIELDS = {
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
}

View File

@@ -0,0 +1 @@
from .remote_app import *

View File

@@ -0,0 +1,130 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django import forms
from orgs.mixins import OrgModelForm
from assets.models import SystemUser
from ..models import RemoteApp
from .. import const
__all__ = [
'RemoteAppCreateUpdateForm',
]
class RemoteAppTypeChromeForm(forms.ModelForm):
chrome_target = forms.CharField(
max_length=128, label=_('Target URL'), required=False
)
chrome_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
chrome_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm):
mysql_workbench_ip = forms.CharField(
max_length=128, label=_('Database IP'), required=False
)
mysql_workbench_name = forms.CharField(
max_length=128, label=_('Database name'), required=False
)
mysql_workbench_username = forms.CharField(
max_length=128, label=_('Database username'), required=False
)
mysql_workbench_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Database password'), required=False
)
class RemoteAppTypeVMwareForm(forms.ModelForm):
vmware_target = forms.CharField(
max_length=128, label=_('Target address'), required=False
)
vmware_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
vmware_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeCustomForm(forms.ModelForm):
custom_cmdline = forms.CharField(
max_length=128, label=_('Operating parameter'), required=False
)
custom_target = forms.CharField(
max_length=128, label=_('Target address'), required=False
)
custom_username = forms.CharField(
max_length=128, label=_('Login username'), required=False
)
custom_password = forms.CharField(
widget=forms.PasswordInput, strip=True,
max_length=128, label=_('Login password'), required=False
)
class RemoteAppTypeForms(
RemoteAppTypeChromeForm,
RemoteAppTypeMySQLWorkbenchForm,
RemoteAppTypeVMwareForm,
RemoteAppTypeCustomForm
):
pass
class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
def __init__(self, *args, **kwargs):
# 过滤RDP资产和系统用户
super().__init__(*args, **kwargs)
field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.has_protocol('rdp')
field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter(
protocol=SystemUser.PROTOCOL_RDP
)
class Meta:
model = RemoteApp
fields = [
'name', 'asset', 'system_user', 'type', 'path', 'comment'
]
widgets = {
'asset': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Asset')
}),
'system_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('System user')
})
}
def _clean_params(self):
app_type = self.data.get('type')
fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, [])
params = {}
for field in fields:
name = field['name']
value = self.cleaned_data[name]
params.update({name: value})
return params
def _save_params(self, instance):
params = self._clean_params()
instance.params = params
instance.save()
return instance
def save(self, commit=True):
instance = super().save(commit=commit)
instance = self._save_params(instance)
return instance

View File

@@ -0,0 +1,15 @@
"""
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

View File

@@ -0,0 +1,42 @@
# 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')},
),
]

View File

@@ -0,0 +1 @@
from .remote_app import *

View File

@@ -0,0 +1,89 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins 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')
)
system_user = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE,
verbose_name=_('System user')
)
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_MAP_FIELDS[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
}
@property
def system_user_info(self):
return {
'id': self.system_user.id,
'name': self.system_user.name
}

View File

@@ -0,0 +1 @@
from .remote_app import *

View File

@@ -0,0 +1,103 @@
# coding: utf-8
#
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from .. import const
from ..models import RemoteApp
__all__ = [
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
]
class RemoteAppParamsDictField(serializers.DictField):
"""
RemoteApp field => params
"""
@staticmethod
def filter_attribute(attribute, instance):
"""
过滤掉params字段值中write_only特性的key-value值
For example, the chrome_password field is not returned when serializing
{
'chrome_target': 'http://www.jumpserver.org/',
'chrome_username': 'admin',
'chrome_password': 'admin',
}
"""
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
if field.get('write_only', False):
attribute.pop(field['name'], None)
return attribute
def get_attribute(self, instance):
"""
序列化时调用
"""
attribute = super().get_attribute(instance)
attribute = self.filter_attribute(attribute, instance)
return attribute
@staticmethod
def filter_value(dictionary, value):
"""
过滤掉不属于当前app_type所包含的key-value值
"""
app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
fields_names = [field['name'] for field in fields]
no_need_keys = [k for k in value.keys() if k not in fields_names]
for k in no_need_keys:
value.pop(k)
return value
def get_value(self, dictionary):
"""
反序列化时调用
"""
value = super().get_value(dictionary)
value = self.filter_value(dictionary, value)
return value
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
params = RemoteAppParamsDictField()
class Meta:
model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
'comment', 'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display',
]
read_only_fields = [
'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display'
]
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
parameter_remote_app = serializers.SerializerMethodField()
class Meta:
model = RemoteApp
fields = [
'id', 'name', 'asset', 'system_user', '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

View File

@@ -0,0 +1,159 @@
{% extends '_base_create_update.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% block form %}
<form id="appForm" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.asset layout="horizontal" %}
{% bootstrap_field form.system_user layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.path layout="horizontal" %}
<div class="hr-line-dashed"></div>
{# chrome #}
<div class="chrome-fields">
{% bootstrap_field form.chrome_target layout="horizontal" %}
{% bootstrap_field form.chrome_username layout="horizontal" %}
{% bootstrap_field form.chrome_password layout="horizontal" %}
</div>
{# mysql workbench #}
<div class="mysql_workbench-fields">
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
{% bootstrap_field form.mysql_workbench_password layout="horizontal" %}
</div>
{# vmware #}
<div class="vmware_client-fields">
{% bootstrap_field form.vmware_target layout="horizontal" %}
{% bootstrap_field form.vmware_username layout="horizontal" %}
{% bootstrap_field form.vmware_password layout="horizontal" %}
</div>
{# custom #}
<div class="custom-fields">
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
{% bootstrap_field form.custom_target layout="horizontal" %}
{% bootstrap_field form.custom_username layout="horizontal" %}
{% bootstrap_field form.custom_password layout="horizontal" %}
</div>
{% bootstrap_field form.comment layout="horizontal" %}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script type="text/javascript">
var app_type_id = '#' + '{{ form.type.id_for_label }}';
var app_path_id = '#' + '{{ form.path.id_for_label }}';
var all_type_fields = [
'.chrome-fields',
'.mysql_workbench-fields',
'.vmware_client-fields',
'.custom-fields'
];
var app_type_map_default_fields_value = {
'chrome': {
'app_path': 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
},
'mysql_workbench': {
'app_path': 'C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe'
},
'vmware_client': {
'app_path': 'C:\\Program Files (x86)\\VMware\\Infrastructure\\Virtual Infrastructure Client\\Launcher\\VpxClient.exe'
},
'custom': {'app_path': ''}
};
function getAppType(){
return $(app_type_id+ " option:selected").val();
}
function initialDefaultValue(){
var app_type = getAppType();
var app_path = $(app_path_id).val();
if(app_path){
app_type_map_default_fields_value[app_type]['app_path'] = app_path
}
}
function setDefaultValue(){
// 设置类型相关字段的默认值
var app_type = getAppType();
var app_path = app_type_map_default_fields_value[app_type]['app_path'];
$(app_path_id).val(app_path)
}
function hiddenFields(){
var app_type = getAppType();
$.each(all_type_fields, function(index, value){
$(value).addClass('hidden')
});
$('.' + app_type + '-fields').removeClass('hidden');
}
function constructParams(data) {
var typeList = ['chrome', 'mysql_workbench', 'vmware_client', 'custom'];
var params = {};
$.each(typeList, function(index, value){
if (data.type === value){
for (var k in data){
if (k.startsWith(value)){
params[k] = data[k]
}
}
}
});
return params;
}
$(document).ready(function () {
$('.select2').select2({
closeOnSelect: true
});
initialDefaultValue();
hiddenFields();
setDefaultValue();
})
.on('change', app_type_id, function(){
hiddenFields();
setDefaultValue();
})
.on("submit", "form", function (evt) {
evt.preventDefault();
var the_url = '{% url "api-applications:remote-app-list" %}';
var redirect_to = '{% url "applications:remote-app-list" %}';
var method = "POST";
{% if type == "update" %}
the_url = '{% url "api-applications:remote-app-detail" object.id %}';
method = "PUT";
{% endif %}
var form = $("form");
var data = form.serializeObject();
data["params"] = constructParams(data);
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
})
;
</script>
{% endblock %}

View File

@@ -0,0 +1,109 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li class="active">
<a href="{% url 'applications:remote-app-detail' pk=remote_app.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'applications:remote-app-update' pk=remote_app.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-application">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-8" style="padding-left: 0;">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label"><b>{{ remote_app.name }}</b></span>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-wrench"></i>
</a>
<ul class="dropdown-menu dropdown-user">
</ul>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<table class="table">
<tbody>
<tr class="no-borders-tr">
<td>{% trans 'Name' %}:</td>
<td><b>{{ remote_app.name }}</b></td>
</tr>
<tr>
<td>{% trans 'Asset' %}:</td>
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
</tr>
<tr>
<td>{% trans 'System user' %}:</td>
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
</tr>
<tr>
<td>{% trans 'App type' %}:</td>
<td><b>{{ remote_app.get_type_display }}</b></td>
</tr>
<tr>
<td>{% trans 'App path' %}:</td>
<td><b>{{ remote_app.path }}</b></td>
</tr>
<tr>
<td>{% trans 'Date created' %}:</td>
<td><b>{{ remote_app.date_created }}</b></td>
</tr>
<tr>
<td>{% trans 'Created by' %}:</td>
<td><b>{{ remote_app.created_by }}</b></td>
</tr>
<tr>
<td>{% trans 'Comment' %}:</td>
<td><b>{{ remote_app.comment }}</b></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.nodes_selected = {};
$(document).ready(function () {
})
.on('click', '.btn-delete-application', function () {
var $this = $(this);
var name = "{{ remote_app.name }}";
var rid = "{{ remote_app.id }}";
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
var redirect_url = "{% url 'applications:remote-app-list' %}";
objectDelete($this, name, the_url, redirect_url);
})
</script>
{% endblock %}

View File

@@ -0,0 +1,90 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block help_message %}
<div class="alert alert-info help-message">
{% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %}
<b><a href="https://github.com/jumpserver/Jmservisor/releases" target="view_window" >{% trans 'Download application loader' %}</a></b>
</div>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div class="uc pull-left m-r-5">
<a href="{% url 'applications:remote-app-create' %}" class="btn btn-sm btn-primary"> {% trans "Create RemoteApp" %} </a>
</div>
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% endblock %}
{% block content_bottom_left %}{% endblock %}
{% block custom_foot_js %}
<script>
function initTable() {
var options = {
ele: $('#remote_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
cellData = htmlEscape(cellData);
{% url 'applications:remote-app-detail' pk=DEFAULT_PK as the_url %}
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var hostname = htmlEscape(cellData.hostname);
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
}}
],
ajax_url: '{% url "api-applications:remote-app-list" %}',
columns: [
{data: "id"},
{data: "name" },
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment"},
{data: "id", orderable: false}
],
op_html: $('#actions').html()
};
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function(){
initTable();
})
.on('click', '.btn-delete', function () {
var $this = $(this);
var $data_table = $('#remote_app_list_table').DataTable();
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
var rid = $this.data('rid');
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
objectDelete($this, name, the_url);
setTimeout( function () {
$data_table.ajax.reload();
}, 3000);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,79 @@
{% extends 'base.html' %}
{% load i18n static %}
{% block custom_head_css_js %}
<script src="{% static 'js/jquery.form.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var inited = false;
var remote_app_table, url;
function initTable() {
if (inited){
return
} else {
inited = true;
}
url = '{% url "api-perms:my-remote-apps" %}';
var options = {
ele: $('#remote_app_list_table'),
columnDefs: [
{targets: 1, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData);
$(td).html(name)
}},
{targets: 3, createdCell: function (td, cellData, rowData) {
var hostname = htmlEscape(cellData.hostname);
$(td).html(hostname);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
$(td).html(name);
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(conn_btn)
}}
],
ajax_url: url,
columns: [
{data: "id"},
{data: "name"},
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment", orderable: false},
{data: "id", orderable: false}
]
};
remote_app_table = jumpserver.initServerSideDataTable(options);
return remote_app_table
}
$(document).ready(function(){
initTable();
})
</script>
{% endblock %}

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,7 @@
# coding: utf-8
#
__all__ = [
]

View File

@@ -0,0 +1,20 @@
# coding:utf-8
#
from django.urls import path
from rest_framework_bulk.routes import BulkRouter
from .. import api
app_name = 'applications'
router = BulkRouter()
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/',
api.RemoteAppConnectionInfoApi.as_view(),
name='remote-app-connection-info')
]
urlpatterns += router.urls

View File

@@ -0,0 +1,16 @@
# coding:utf-8
from django.urls import path
from .. import views
app_name = 'applications'
urlpatterns = [
# RemoteApp
path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'),
path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
path('remote-app/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
path('remote-app/<uuid:pk>/', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
# User RemoteApp view
path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
]

View File

@@ -0,0 +1 @@
from .remote_app import *

View File

@@ -0,0 +1,105 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.detail import DetailView
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
from common.const import create_success_msg, update_success_msg
from ..models import RemoteApp
from .. import forms
__all__ = [
'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView',
'RemoteAppDetailView', 'UserRemoteAppListView',
]
class RemoteAppListView(PermissionsMixin, TemplateView):
template_name = 'applications/remote_app_list.html'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Applications'),
'action': _('RemoteApp list'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
template_name = 'applications/remote_app_create_update.html'
model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list')
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Applications'),
'action': _('Create RemoteApp'),
'type': 'create'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return create_success_msg % ({'name': cleaned_data['name']})
class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
template_name = 'applications/remote_app_create_update.html'
model = RemoteApp
form_class = forms.RemoteAppCreateUpdateForm
success_url = reverse_lazy('applications:remote-app-list')
permission_classes = [IsOrgAdmin]
def get_initial(self):
return {k: v for k, v in self.object.params.items()}
def get_context_data(self, **kwargs):
context = {
'app': _('Applications'),
'action': _('Update RemoteApp'),
'type': 'update'
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def get_success_message(self, cleaned_data):
return update_success_msg % ({'name': cleaned_data['name']})
class RemoteAppDetailView(PermissionsMixin, DetailView):
template_name = 'applications/remote_app_detail.html'
model = RemoteApp
context_object_name = 'remote_app'
permission_classes = [IsOrgAdmin]
def get_context_data(self, **kwargs):
context = {
'app': _('Applications'),
'action': _('RemoteApp detail'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)
class UserRemoteAppListView(PermissionsMixin, TemplateView):
template_name = 'applications/user_remote_app_list.html'
permission_classes = [IsValidUser]
def get_context_data(self, **kwargs):
context = {
'action': _('My RemoteApp'),
}
kwargs.update(context)
return super().get_context_data(**kwargs)

View File

@@ -5,3 +5,4 @@ from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *

View File

@@ -20,7 +20,7 @@ from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from common.mixins import IDInFilterMixin
from common.mixins import IDInCacheFilterMixin
from common.utils import get_logger
from ..hands import IsOrgAdmin
from ..models import AdminUser, Asset
@@ -36,7 +36,7 @@ __all__ = [
]
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
"""
Admin user api set, for add,delete,update,list,retrieve resource
"""

View File

@@ -1,19 +1,27 @@
# -*- coding: utf-8 -*-
#
import uuid
import random
from rest_framework import generics
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_bulk import BulkModelViewSet
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
from rest_framework.pagination import LimitOffsetPagination
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.core.cache import cache
from django.db.models import Q
from common.mixins import IDInFilterMixin
from common.utils import get_logger
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from orgs.mixins import OrgBulkModelViewSet
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
from ..models import Asset, AdminUser, Node
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
@@ -25,21 +33,37 @@ logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetListUpdateApi',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi'
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
]
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
"""
API endpoint that allows Asset to be viewed or edited.
"""
filter_fields = ("hostname", "ip")
search_fields = filter_fields
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
queryset = Asset.objects.all()
serializer_class = serializers.AssetSerializer
pagination_class = LimitOffsetPagination
permission_classes = (IsOrgAdminOrAppUser,)
success_message = _("%(hostname)s was %(action)s successfully")
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)
def filter_node(self, queryset):
node_id = self.request.query_params.get("node_id")
@@ -61,7 +85,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = queryset.filter(
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
)
return queryset
return queryset.distinct()
def filter_admin_user_id(self, queryset):
admin_user_id = self.request.query_params.get('admin_user_id')
@@ -77,13 +101,8 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
queryset = self.filter_admin_user_id(queryset)
return queryset
def get_queryset(self):
queryset = super().get_queryset().distinct()
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
"""
Asset bulk update api
"""
@@ -92,6 +111,21 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
permission_classes = (IsOrgAdmin,)
class AssetBulkUpdateSelectAPI(APIView):
permission_classes = (IsOrgAdmin,)
def post(self, request, *args, **kwargs):
assets_id = request.data.get('assets_id', '')
if assets_id:
spm = uuid.uuid4().hex
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
cache.set(key, assets_id, 300)
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
return Response({'url': url})
error = _('Please select assets that need to be updated')
return Response({'error': error}, status=400)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
@@ -132,8 +166,8 @@ class AssetGatewayApi(generics.RetrieveAPIView):
asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \
asset.domain.gateways.filter(protocol=asset.protocol).exists():
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
asset.domain.gateways.filter(protocol='ssh').exists():
gateway = random.choice(asset.domain.gateways.filter(protocol='ssh'))
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
return Response(serializer.data)
else:

View File

@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
#
from rest_framework.response import Response
from rest_framework import viewsets, status, generics
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
from common.mixins import IDInCacheFilterMixin
from ..backends import AssetUserManager
from ..models import Asset, Node, SystemUser, AdminUser
from .. import serializers
from ..tasks import test_asset_users_connectivity_manual
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
'AssetUserExportViewSet',
]
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 in ("node_id", "system_user_id", "admin_user_id"):
continue
kwargs[field] = value
return queryset.filter(**kwargs)
class AssetUserSearchBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
value = request.GET.get('search')
if not value:
return queryset
_queryset = AssetUserManager.none()
for field in view.search_fields:
if field in ("node_id", "system_user_id", "admin_user_id"):
continue
_queryset |= queryset.filter(**{field: value})
return _queryset
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
pagination_class = LimitOffsetPagination
serializer_class = serializers.AssetUserSerializer
permission_classes = [IsOrgAdminOrAppUser]
http_method_names = ['get', 'post']
filter_fields = [
"id", "ip", "hostname", "username", "asset_id", "node_id",
"system_user_id", "admin_user_id"
]
search_fields = filter_fields
filter_backends = (
filters.OrderingFilter,
AssetUserFilterBackend, AssetUserSearchBackend,
)
def get_queryset(self):
# 尽可能先返回更少的数据
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
node_id = self.request.GET.get('node_id')
admin_user_id = self.request.GET.get("admin_user_id")
system_user_id = self.request.GET.get("system_user_id")
kwargs = {}
assets = None
manager = AssetUserManager()
if system_user_id:
system_user = get_object_or_404(SystemUser, id=system_user_id)
assets = system_user.get_all_assets()
username = system_user.username
elif admin_user_id:
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
assets = admin_user.assets.all()
username = admin_user.username
manager.prefer('admin_user')
if asset_id:
asset = get_object_or_404(Asset, id=asset_id)
assets = [asset]
elif node_id:
node = get_object_or_404(Node, id=node_id)
assets = node.get_all_assets()
if username:
kwargs['username'] = username
if assets is not None:
kwargs['assets'] = assets
queryset = manager.filter(**kwargs)
return queryset
class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer
http_method_names = ['get']
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
status_code = status.HTTP_200_OK
if not instance:
status_code = status.HTTP_400_BAD_REQUEST
return Response(serializer.data, status=status_code)
def get_object(self):
query_params = self.request.query_params
username = query_params.get('username')
asset_id = query_params.get('asset_id')
prefer = query_params.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id)
try:
manger = AssetUserManager()
instance = manger.get(username, asset, prefer=prefer)
except Exception as e:
logger.error(e, exc_info=True)
return None
else:
return instance
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset users connective
"""
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.TaskIDSerializer
def get_asset_users(self):
username = self.request.GET.get('username')
asset_id = self.request.GET.get('asset_id')
prefer = self.request.GET.get("prefer")
asset = get_object_or_none(Asset, pk=asset_id)
manager = AssetUserManager()
asset_users = manager.filter(username=username, assets=[asset], prefer=prefer)
return asset_users
def retrieve(self, request, *args, **kwargs):
asset_users = self.get_asset_users()
prefer = self.request.GET.get("prefer")
kwargs = {}
if prefer == "admin_user":
kwargs["run_as_admin"] = True
task = test_asset_users_connectivity_manual.delay(asset_users, **kwargs)
return Response({"task": task.id})
class AssetUserPushApi(generics.CreateAPIView):
"""
Test asset users connective
"""
serializer_class = serializers.AssetUserPushSerializer
permission_classes = (IsOrgAdminOrAppUser,)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
asset = serializer.validated_data["asset"]
username = serializer.validated_data["username"]
pass

View File

@@ -51,9 +51,10 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
model = Gateway
object = None
def get(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs):
self.object = self.get_object(Gateway.objects.all())
ok, e = self.object.test_connective()
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:

View File

@@ -13,11 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from rest_framework_bulk import BulkModelViewSet
from rest_framework.pagination import LimitOffsetPagination
from django.db.models import Count
from common.utils import get_logger
from orgs.mixins import OrgBulkModelViewSet
from ..hands import IsOrgAdmin
from ..models import Label
from .. import serializers
@@ -27,7 +27,7 @@ logger = get_logger(__file__)
__all__ = ['LabelViewSet']
class LabelViewSet(BulkModelViewSet):
class LabelViewSet(OrgBulkModelViewSet):
filter_fields = ("name", "value")
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)

View File

@@ -26,6 +26,7 @@ from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
from .. import serializers
from ..utils import NodeUtil
logger = get_logger(__file__)
@@ -39,6 +40,8 @@ __all__ = [
class NodeViewSet(viewsets.ModelViewSet):
filter_fields = ('value', 'key', )
search_fields = filter_fields
queryset = Node.objects.all()
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.NodeSerializer
@@ -77,12 +80,10 @@ class NodeListAsTreeApi(generics.ListAPIView):
serializer_class = TreeNodeSerializer
def get_queryset(self):
queryset = [node.as_tree_node() for node in Node.objects.all()]
return queryset
def filter_queryset(self, queryset):
if self.request.query_params.get('refresh', '0') == '1':
queryset = self.refresh_nodes(queryset)
queryset = Node.objects.all()
util = NodeUtil()
nodes = util.get_nodes_by_queryset(queryset)
queryset = [node.as_tree_node() for node in nodes]
return queryset
@staticmethod
@@ -111,38 +112,38 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
is_root = False
def get_queryset(self):
self.check_need_refresh_nodes()
node_key = self.request.query_params.get('key')
if node_key:
self.node = Node.objects.get(key=node_key)
queryset = self.node.get_children(with_self=False)
else:
self.is_root = True
self.node = Node.root()
queryset = list(self.node.get_children(with_self=True))
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
queryset.extend(list(nodes_invalid))
util = NodeUtil()
# 是否包含自己
with_self = False
if not node_key:
node_key = Node.root().key
with_self = True
self.node = util.get_node_by_key(node_key)
queryset = self.node.get_children(with_self=with_self)
queryset = [node.as_tree_node() for node in queryset]
queryset = sorted(queryset)
return queryset
def filter_assets(self, queryset):
include_assets = self.request.query_params.get('assets', '0') == '1'
if not include_assets:
return queryset
assets = self.node.get_assets()
assets = self.node.get_assets().only(
"id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
)
for asset in assets:
queryset.append(asset.as_tree_node(self.node))
return queryset
def filter_queryset(self, queryset):
queryset = self.filter_assets(queryset)
queryset = self.filter_refresh_nodes(queryset)
return queryset
def filter_refresh_nodes(self, queryset):
def check_need_refresh_nodes(self):
if self.request.query_params.get('refresh', '0') == '1':
Node.expire_nodes_assets_amount()
Node.expire_nodes_full_value()
return queryset
Node.refresh_nodes()
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
@@ -163,12 +164,13 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
def create(self, request, *args, **kwargs):
instance = self.get_object()
value = request.data.get("value")
_id = request.data.get('id') or None
values = [child.value for child in instance.get_children()]
if value in values:
raise ValidationError(
'The same level node name cannot be the same'
)
node = instance.create_child(value=value)
node = instance.create_child(value=value, _id=_id)
return Response(self.serializer_class(instance=node).data, status=201)
def get_object(self):

View File

@@ -21,6 +21,8 @@ from rest_framework.pagination import LimitOffsetPagination
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.mixins import IDInCacheFilterMixin
from orgs.mixins import OrgBulkModelViewSet
from ..models import SystemUser, Asset
from .. import serializers
from ..tasks import push_system_user_to_assets_manual, \
@@ -30,7 +32,7 @@ from ..tasks import push_system_user_to_assets_manual, \
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi',
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
@@ -38,7 +40,7 @@ __all__ = [
]
class SystemUserViewSet(BulkModelViewSet):
class SystemUserViewSet(OrgBulkModelViewSet):
"""
System user api set, for add,delete,update,list,retrieve resource
"""
@@ -68,6 +70,22 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
return Response(status=204)
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
"""
Get system user with asset auth info
"""
queryset = SystemUser.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
def get_object(self):
instance = super().get_object()
aid = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=aid)
instance.load_specific_asset_auth(asset)
return instance
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api

View File

@@ -0,0 +1 @@
from .manager import AssetUserManager

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
from ..models import AdminUser
from .asset_user import AssetUserBackend
class AdminUserBackend(AssetUserBackend):
model = AdminUser
backend = 'AdminUser'

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
from .base import BaseBackend
class AssetUserBackend(BaseBackend):
model = None
backend = "AssetUser"
@classmethod
def filter_queryset_more(cls, queryset):
return queryset
@classmethod
def filter(cls, username=None, assets=None, **kwargs):
queryset = cls.model.objects.all()
prefer_id = kwargs.get('prefer_id')
if prefer_id:
queryset = queryset.filter(id=prefer_id)
instances = cls.construct_authbook_objects(queryset, assets)
return instances
if username:
queryset = queryset.filter(username=username)
if assets:
queryset = queryset.filter(assets__in=assets).distinct()
queryset = cls.filter_queryset_more(queryset)
instances = cls.construct_authbook_objects(queryset, assets)
return instances
@classmethod
def construct_authbook_objects(cls, asset_users, assets):
instances = []
for asset_user in asset_users:
if not assets:
assets = asset_user.assets.all()
for asset in assets:
instance = asset_user.construct_to_authbook(asset)
instance.backend = cls.backend
instances.append(instance)
return instances

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
#
import uuid
from abc import abstractmethod
class BaseBackend:
@classmethod
@abstractmethod
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
"""
:param username: 用户名
:param assets: <Asset>对象
:param latest: 是否是最新记录
:param prefer: 优先使用
:param prefer_id: 使用id
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
"""
pass
class AssetUserQuerySet(list):
def order_by(self, *ordering):
_ordering = []
reverse = False
for i in ordering:
if i[0] == '-':
reverse = True
i = i[1:]
_ordering.append(i)
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
return self
def filter_in(self, kwargs):
in_kwargs = {}
queryset = []
for k, v in kwargs.items():
if len(v) == 0:
return self
if k.find("__in") >= 0:
in_kwargs[k] = v
for k in in_kwargs:
kwargs.pop(k)
if len(in_kwargs) == 0:
return self
for i in self:
matched = True
for k, v in in_kwargs.items():
key = k.split('__')[0]
attr = getattr(i, key, None)
# 如果属性或者value中是uuid,则转换成string
if isinstance(v[0], uuid.UUID):
v = [str(i) for i in v]
if isinstance(attr, uuid.UUID):
attr = str(attr)
if attr not in v:
matched = False
if matched:
queryset.append(i)
return AssetUserQuerySet(queryset)
def filter_equal(self, kwargs):
def filter_it(obj):
wanted = []
real = []
for k, v in kwargs.items():
wanted.append(v)
value = getattr(obj, k)
if isinstance(value, uuid.UUID):
value = str(value)
real.append(value)
return wanted == real
if len(kwargs) > 0:
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
else:
queryset = self
return queryset
def filter(self, **kwargs):
queryset = self.filter_in(kwargs).filter_equal(kwargs)
return queryset
def __or__(self, other):
self.extend(other)
return self

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
#
from ..models import AuthBook
from .base import BaseBackend
class AuthBookBackend(BaseBackend):
@classmethod
def filter(cls, username=None, assets=None, latest=True, **kwargs):
queryset = AuthBook.objects.all()
if username is not None:
queryset = queryset.filter(username=username)
if assets:
queryset = queryset.filter(asset__in=assets)
if latest:
queryset = queryset.latest_version()
return queryset
@classmethod
def create(cls, **kwargs):
auth_info = {
'password': kwargs.pop('password', ''),
'public_key': kwargs.pop('public_key', ''),
'private_key': kwargs.pop('private_key', '')
}
obj = AuthBook.objects.create(**kwargs)
obj.set_auth(**auth_info)
return obj

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
#
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from .base import AssetUserQuerySet
from .db import AuthBookBackend
from .system_user import SystemUserBackend
from .admin_user import AdminUserBackend
class NotSupportError(Exception):
pass
class AssetUserManager:
"""
资产用户管理器
"""
ObjectDoesNotExist = ObjectDoesNotExist
MultipleObjectsReturned = MultipleObjectsReturned
NotSupportError = NotSupportError
MSG_NOT_EXIST = '{} Object matching query does not exist'
MSG_MULTIPLE = '{} get() returned more than one object ' \
'-- it returned {}!'
backends = (
('db', AuthBookBackend),
('system_user', SystemUserBackend),
('admin_user', AdminUserBackend),
)
_prefer = "system_user"
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
if assets is not None and not assets:
return AssetUserQuerySet([])
if prefer:
self._prefer = prefer
instances_map = {}
instances = []
for name, backend in self.backends:
if name != "db" and self._prefer != name:
continue
_instances = backend.filter(
username=username, assets=assets, latest=latest,
prefer=self._prefer, prefer_id=prefer_id,
)
instances_map[name] = _instances
# 如果不是获取最新版本就不再merge
if not latest:
for _instances in instances_map.values():
instances.extend(_instances)
return AssetUserQuerySet(instances)
# merge的顺序
ordering = ["db"]
if self._prefer == "system_user":
ordering.extend(["system_user", "admin_user"])
else:
ordering.extend(["admin_user", "system_user"])
# 根据prefer决定优先使用系统用户或管理用户谁的
ordering_instances = [instances_map.get(i, []) for i in ordering]
instances = self._merge_instances(*ordering_instances)
return AssetUserQuerySet(instances)
def get(self, username, asset, **kwargs):
instances = self.filter(username, assets=[asset], **kwargs)
if len(instances) == 1:
return instances[0]
elif len(instances) == 0:
self.raise_does_not_exist(self.__class__.__name__)
else:
self.raise_multiple_return(self.__class__.__name__, len(instances))
def raise_does_not_exist(self, name):
raise self.ObjectDoesNotExist(self.MSG_NOT_EXIST.format(name))
def raise_multiple_return(self, name, length):
raise self.MultipleObjectsReturned(self.MSG_MULTIPLE.format(name, length))
@staticmethod
def create(**kwargs):
instance = AuthBookBackend.create(**kwargs)
return instance
def all(self):
return self.filter()
def prefer(self, s):
self._prefer = s
return self
@staticmethod
def none():
return AssetUserQuerySet()
@staticmethod
def _merge_instances(*args):
instances = list(args[0])
keywords = [obj.keyword for obj in instances]
for _instances in args[1:]:
need_merge_instances = [obj for obj in _instances if obj.keyword not in keywords]
need_merge_keywords = [obj.keyword for obj in need_merge_instances]
instances.extend(need_merge_instances)
keywords.extend(need_merge_keywords)
return instances

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#
import itertools
from assets.models import SystemUser
from .asset_user import AssetUserBackend
class SystemUserBackend(AssetUserBackend):
model = SystemUser
backend = 'SystemUser'
@classmethod
def filter_queryset_more(cls, queryset):
queryset = cls._distinct_system_users_by_username(queryset)
return queryset
@classmethod
def _distinct_system_users_by_username(cls, system_users):
system_users = sorted(
system_users,
key=lambda su: (su.username, su.priority, su.date_updated),
reverse=True,
)
results = itertools.groupby(system_users, key=lambda su: su.username)
system_users = [next(result[1]) for result in results]
return system_users

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
# from django.conf import settings
from .db import AuthBookBackend
# from .vault import VaultBackend
def get_backend():
default_backend = AuthBookBackend
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
# return VaultBackend
return default_backend

View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
#
from .base import BaseBackend
class VaultBackend(BaseBackend):
@classmethod
def filter(cls, username=None, asset=None, latest=True):
pass

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
UPDATE_ASSETS_HARDWARE_TASKS = [
{
@@ -10,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
}
]
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
TEST_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
@@ -19,6 +19,14 @@ TEST_ADMIN_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
@@ -31,8 +39,43 @@ TEST_SYSTEM_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
TEST_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
TASK_OPTIONS = {
'timeout': 10,
'forks': 10,
}
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(CONN_UNREACHABLE, _("Unreachable")),
(CONN_REACHABLE, _('Reachable')),
(CONN_UNKNOWN, _("Unknown")),
)

View File

@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins import OrgModelForm
from ..models import Asset, AdminUser
from ..models import Asset, Node
logger = get_logger(__file__)
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
__all__ = [
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
]
class ProtocolForm(forms.Form):
name = forms.ChoiceField(
choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh',
widget=forms.Select(attrs={'class': 'form-control protocol-name'})
)
port = forms.IntegerField(
max_value=65534, min_value=1, label=_("Port"), initial=22,
widget=forms.TextInput(attrs={'class': 'form-control protocol-port'})
)
class AssetCreateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
nodes_field = self.fields['nodes']
nodes_field.choices = ((n.id, n.full_value) for n in
Node.get_queryset())
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'port', 'comment',
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain', 'protocol',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'port': forms.TextInput(),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
@@ -97,24 +113,12 @@ class AssetBulkUpdateForm(OrgModelForm):
}
)
)
port = forms.IntegerField(
label=_('Port'), required=False, min_value=1, max_value=65535,
)
admin_user = forms.ModelChoiceField(
required=False, queryset=AdminUser.objects,
label=_("Admin user"),
widget=forms.Select(
attrs={
'class': 'select2',
'data-placeholder': _('Admin user')
}
)
)
class Meta:
model = Asset
fields = [
'assets', 'port', 'admin_user', 'labels', 'nodes', 'platform'
'assets', 'admin_user', 'labels', 'platform',
'domain',
]
widgets = {
'labels': forms.SelectMultiple(
@@ -125,6 +129,13 @@ class AssetBulkUpdateForm(OrgModelForm):
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 重写其他字段为不再required
for name, field in self.fields.items():
if name != 'assets':
field.required = False
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:

View File

@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
import re
from orgs.mixins import OrgModelForm
from ..models import CommandFilter, CommandFilterRule
@@ -15,6 +18,8 @@ class CommandFilterForm(OrgModelForm):
class CommandFilterRuleForm(OrgModelForm):
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
class Meta:
model = CommandFilterRule
fields = [
@@ -25,3 +30,11 @@ class CommandFilterRuleForm(OrgModelForm):
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
}),
}
def clean_content(self):
content = self.cleaned_data.get("content")
if self.invalid_pattern.search(content):
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
msg = _("Content should not be contain: {}").format(invalid_char)
raise ValidationError(msg)
return content

View File

@@ -64,8 +64,11 @@ class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
model = Gateway
fields = [
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
'private_key_file', 'is_active', 'comment',
'private_key', 'is_active', 'comment',
]
help_texts = {
'protocol': _("SSH gateway support proxy SSH,RDP,VNC")
}
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),

View File

@@ -26,56 +26,50 @@ class PasswordAndKeyAuthForm(forms.ModelForm):
label=_("Password"),
)
# Need use upload private key file except paste private key content
private_key_file = forms.FileField(required=False, label=_("Private key"))
private_key = forms.FileField(required=False, label=_("Private key"))
def clean_private_key_file(self):
private_key_file = self.cleaned_data['private_key_file']
def clean_private_key(self):
private_key_f = self.cleaned_data['private_key']
password = self.cleaned_data['password']
if private_key_file:
key_string = private_key_file.read()
private_key_file.seek(0)
if private_key_f:
key_string = private_key_f.read()
private_key_f.seek(0)
key_string = key_string.decode()
if not validate_ssh_private_key(key_string, password):
raise forms.ValidationError(_('Invalid private key'))
return private_key_file
msg = _('Invalid private key, Only support '
'RSA/DSA format key')
raise forms.ValidationError(msg)
return private_key_f
def validate_password_key(self):
password = self.cleaned_data['password']
private_key_file = self.cleaned_data.get('private_key_file', '')
private_key_f = self.cleaned_data.get('private_key', '')
if not password and not private_key_file:
if not password and not private_key_f:
raise forms.ValidationError(_(
'Password and private key file must be input one'
))
def gen_keys(self):
password = self.cleaned_data.get('password', '') or None
private_key_file = self.cleaned_data['private_key_file']
private_key_f = self.cleaned_data['private_key']
public_key = private_key = None
if private_key_file:
private_key = private_key_file.read().strip().decode('utf-8')
if private_key_f:
private_key = private_key_f.read().strip().decode('utf-8')
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
return private_key, public_key
class AdminUserForm(PasswordAndKeyAuthForm):
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
admin_user = super().save(commit=commit)
password = self.cleaned_data.get('password', '') or None
private_key, public_key = super().gen_keys()
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
return admin_user
def clean(self):
super().clean()
if not self.instance:
super().validate_password_key()
raise forms.ValidationError("Use api to save")
class Meta:
model = AdminUser
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
fields = ['name', 'username', 'password', 'private_key', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
@@ -87,55 +81,13 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
auto_generate_key = forms.BooleanField(initial=True, required=False)
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
system_user = super().save()
password = self.cleaned_data.get('password', '') or None
login_mode = self.cleaned_data.get('login_mode', '') or None
protocol = self.cleaned_data.get('protocol') or None
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
private_key, public_key = super().gen_keys()
if login_mode == SystemUser.LOGIN_MANUAL or \
protocol in [SystemUser.PROTOCOL_RDP,
SystemUser.PROTOCOL_TELNET,
SystemUser.PROTOCOL_VNC]:
system_user.auto_push = 0
auto_generate_key = False
system_user.save()
if auto_generate_key:
logger.info('Auto generate key and set system user auth')
system_user.auto_gen_auth()
else:
system_user.set_auth(password=password, private_key=private_key,
public_key=public_key)
return system_user
def clean(self):
super().clean()
auto_generate = self.cleaned_data.get('auto_generate_key')
if not self.instance and not auto_generate:
super().validate_password_key()
def clean_username(self):
username = self.data.get('username')
login_mode = self.data.get('login_mode')
protocol = self.data.get('protocol')
if username:
return username
if login_mode == SystemUser.LOGIN_AUTO and \
protocol != SystemUser.PROTOCOL_VNC:
msg = _('* Automatic login mode must fill in the username.')
raise forms.ValidationError(msg)
return username
raise forms.ValidationError("Use api to save")
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key_file', 'auto_push', 'sudo',
'password', 'private_key', 'auto_push', 'sudo',
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
]
widgets = {
@@ -150,5 +102,6 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
'priority': _('1-100, High level will be using login asset as default, '
'if user was granted more than 2 system user'),
'login_mode': _('If you choose manual login mode, you do not '
'need to fill in the username and password.')
'need to fill in the username and password.'),
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
}

View File

@@ -11,6 +11,5 @@
"""
from common.permissions import AdminUserRequiredMixin
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
from users.models import User, UserGroup

View File

@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-05 10:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
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'},
),
]

View File

@@ -0,0 +1,158 @@
# 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'),
),
]

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-09 15:31
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0002_auto_20180105_1807'),
]
operations = [
migrations.AlterField(
model_name='asset',
name='cluster',
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
]

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-25 04:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0003_auto_20180109_2331'),
]
operations = [
migrations.AlterField(
model_name='assetgroup',
name='created_by',
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
),
]

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-26 08:37
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0004_auto_20180125_1218'),
]
operations = [
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=set([('name', 'value')]),
),
migrations.AddField(
model_name='asset',
name='labels',
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
),
]

View File

@@ -1,39 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-30 07:02
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0005_auto_20180126_1637'),
]
operations = [
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',
),
]

View File

@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-02-25 10:15
from __future__ import unicode_literals
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0006_auto_20180130_1502'),
]
operations = [
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, unique=True, verbose_name='Value')),
('child_mark', models.IntegerField(default=0)),
('date_create', models.DateTimeField(auto_now_add=True)),
],
),
migrations.RemoveField(
model_name='asset',
name='cluster',
),
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'),
),
]

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-06 10:04
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0007_auto_20180225_1815'),
]
operations = [
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'),
),
]

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 04:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0008_auto_20180306_1804'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-07 09:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
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'),
),
]

View File

@@ -0,0 +1,220 @@
# 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')},
),
]

View File

@@ -1,55 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-03-26 01:57
from __future__ import unicode_literals
import assets.models.utils
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0010_auto_20180307_1749'),
]
operations = [
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(max_length=128, 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.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-04 05:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0011_auto_20180326_0957'),
]
operations = [
migrations.AlterField(
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'),
),
]

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-11 03:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0012_auto_20180404_1302'),
]
operations = [
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'),
),
]

View File

@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-27 04:45
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0013_auto_20180411_1135'),
]
operations = [
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='gateway',
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'),
),
]

View File

@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-10 04:35
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0014_auto_20180427_1245'),
]
operations = [
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='gateway',
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'),
),
]

View File

@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-05-11 04:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0015_auto_20180510_1235'),
]
operations = [
migrations.AlterField(
model_name='node',
name='value',
field=models.CharField(max_length=128, verbose_name='Value'),
),
]

View File

@@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-07-02 06:15
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
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):
dependencies = [
('assets', '0016_auto_20180511_1203'),
]
operations = [
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='gateway',
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='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(migrate_win_to_ssh_protocol),
]

View File

@@ -1,84 +0,0 @@
# Generated by Django 2.0.7 on 2018-08-07 03:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0017_auto_20180702_1415'),
]
operations = [
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.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')},
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 2.0.7 on 2018-08-16 05:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0018_auto_20180807_1116'),
]
operations = [
migrations.AddField(
model_name='asset',
name='cpu_vcpus',
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'value', 'org_id')},
),
]

View File

@@ -0,0 +1,21 @@
# 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'},
),
]

View File

@@ -0,0 +1,43 @@
# 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'},
),
]

View File

@@ -0,0 +1,23 @@
# 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'),
),
]

View File

@@ -0,0 +1,29 @@
# 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'),
),
]

View File

@@ -0,0 +1,13 @@
# 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 = [
]

View File

@@ -0,0 +1,19 @@
# 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'),
),
]

View File

@@ -0,0 +1,53 @@
# 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'),
),
]

View File

@@ -0,0 +1,75 @@
# 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'),
),
]

View File

@@ -0,0 +1,73 @@
# 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',
),
]

View File

@@ -0,0 +1,39 @@
# 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'),
]

View File

@@ -0,0 +1,34 @@
# 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'),
),
]

View File

@@ -0,0 +1,18 @@
# 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'),
),
]

View File

@@ -1,9 +1,11 @@
from .user import *
from .asset import *
from .label import Label
from .user import *
from .cluster import *
from .group import *
from .domain import *
from .node import *
from .asset import *
from .cmd_filter import *
from .authbook import *
from .utils import *
from .authbook import *

View File

@@ -6,17 +6,16 @@ import uuid
import logging
import random
from functools import reduce
from collections import defaultdict
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from collections import OrderedDict, defaultdict
from django.core.cache import cache
from .user import AdminUser, SystemUser
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .utils import Connectivity
from orgs.mixins import OrgModelMixin, OrgManager
__all__ = ['Asset']
__all__ = ['Asset', 'ProtocolsMixin']
logger = logging.getLogger(__name__)
@@ -46,19 +45,12 @@ class AssetQuerySet(models.QuerySet):
def valid(self):
return self.active()
def has_protocol(self, name):
return self.filter(protocols__contains=name)
class Asset(OrgModelMixin):
# Important
PLATFORM_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Windows2016', 'Windows(2016)'),
('Other', 'Other'),
)
class ProtocolsMixin:
protocols = ''
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
@@ -70,21 +62,117 @@ class Asset(OrgModelMixin):
(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
@classmethod
def get_all_nodes_keys(cls):
"""
:return: {asset.id: [node.key, ]}
"""
from .node import Node
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cached = cache.get(cache_key)
if cached:
return cached
assets = Asset.objects.all().only('id').prefetch_related(
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
)
assets_nodes_keys = {}
for asset in assets:
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
return assets_nodes_keys
@classmethod
def expire_all_nodes_keys_cache(cls):
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
cache.delete(cache_key)
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
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.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=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=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
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.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
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"))
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
# Some information
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
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
@@ -111,13 +199,7 @@ class Asset(OrgModelMixin):
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)()
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
_connectivity = None
def __str__(self):
return '{0.hostname}({0.ip})'.format(self)
@@ -127,50 +209,33 @@ class Asset(OrgModelMixin):
warning = ''
if not self.is_active:
warning += ' inactive'
else:
return True, ''
return False, warning
if warning:
return False, warning
return True, warning
def support_ansible(self):
if self.platform in ("Windows", "Windows2016", "Other"):
return False
if self.protocol != 'ssh':
return False
return True
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016"):
def is_windows(self):
if self.platform in ("Windows", "Windows2016"):
return True
else:
return False
def get_nodes(self):
from .node import Node
nodes = self.nodes.all() or [Node.root()]
return nodes
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016", "Other"):
return True
else:
return False
def get_all_nodes(self, flat=False):
nodes = []
for node in self.get_nodes():
_nodes = node.get_ancestor(with_self=True)
_nodes.append(_nodes)
if flat:
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
return nodes
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
@classmethod
def get_queryset_by_fullname_list(cls, fullname_list):
org_fullname_map = defaultdict(list)
for fullname in fullname_list:
hostname, org = cls.split_fullname(fullname)
org_fullname_map[org].append(hostname)
filter_arg = Q()
for org, hosts in org_fullname_map.items():
if org.is_real():
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
else:
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
return Asset.objects.filter(filter_arg)
@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):
@@ -184,25 +249,30 @@ class Asset(OrgModelMixin):
@property
def connectivity(self):
if not self.is_unixlike():
return self.REACHABLE
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
cached = cache.get(key, None)
return cached if cached is not None else self.UNKNOWN
if self._connectivity:
return self._connectivity
if not self.admin_user:
return Connectivity.unknown()
connectivity = self.admin_user.get_asset_connectivity(self)
return connectivity
@connectivity.setter
def connectivity(self, value):
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
cache.set(key, value, 3600*2)
if not self.admin_user:
return
self.admin_user.set_asset_connectivity(self, value)
def get_auth_info(self):
if self.admin_user:
return {
'username': self.admin_user.username,
'password': self.admin_user.password,
'private_key': self.admin_user.private_key_file,
'become': self.admin_user.become_info,
}
if not self.admin_user:
return {}
self.admin_user.load_specific_asset_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
@@ -214,34 +284,6 @@ class Asset(OrgModelMixin):
fake_node.is_node = False
return fake_node
def to_json(self):
info = {
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
}
if self.domain and self.domain.gateway_set.all():
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
return info
def _to_secret_json(self):
"""
Ansible use it create inventory
Todo: May be move to ops implements it
"""
data = self.to_json()
if self.admin_user:
admin_user = self.admin_user
data.update({
'username': admin_user.username,
'password': admin_user.password,
'private_key': admin_user.private_key_file,
'become': admin_user.become_info,
'groups': [node.value for node in self.nodes.all()],
})
return data
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
@@ -263,9 +305,8 @@ class Asset(OrgModelMixin):
'id': self.id,
'hostname': self.hostname,
'ip': self.ip,
'port': self.port,
'protocols': self.protocols_as_list,
'platform': self.platform,
'protocol': self.protocol,
}
}
}
@@ -278,21 +319,27 @@ class Asset(OrgModelMixin):
@classmethod
def generate_fake(cls, count=100):
from .user import AdminUser, SystemUser
from random import seed, choice
import forgery_py
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=forgery_py.internet.user_name(True),
hostname='.'.join(ip),
admin_user=choice(AdminUser.objects.all()),
port=22,
created_by='Fake')
try:
asset.save()
asset.protocols = 'ssh/22'
if nodes and len(nodes) > 3:
_nodes = random.sample(nodes, 3)
else:

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins import OrgManager
from .base import AssetUser
__all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def latest_version(self):
return self.filter(is_latest=True)
class AuthBookManager(OrgManager):
pass
class AuthBook(AssetUser):
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 _set_latest(self):
self._remove_pre_obj_latest()
self.is_latest = True
self.save()
def _get_pre_obj(self):
pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset
).latest_version().first()
return pre_obj
def _remove_pre_obj_latest(self):
pre_obj = self._get_pre_obj()
if pre_obj:
pre_obj.is_latest = False
pre_obj.save()
def _set_version(self):
pre_obj = self._get_pre_obj()
if pre_obj:
self.version = pre_obj.version + 1
else:
self.version = 1
self.save()
def set_version_and_latest(self):
self._set_version()
self._set_latest()
def get_related_assets(self):
return [self.asset]
def generate_id_with_asset(self, asset):
return self.id
@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)

View File

@@ -5,64 +5,47 @@ 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 get_signer, ssh_key_string_to_obj, ssh_key_gen
from common.utils import (
get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
)
from common.validators import alphanumeric
from common import fields
from orgs.mixins import OrgModelMixin
from .utils import private_key_validator
from .utils import private_key_validator, Connectivity
signer = get_signer()
logger = get_logger(__file__)
class AssetUser(OrgModelMixin):
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])
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
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)
date_updated = models.DateTimeField(auto_now=True)
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'))
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
CONNECTIVITY_CHOICES = (
(UNREACHABLE, _("Unreachable")),
(REACHABLE, _('Reachable')),
(UNKNOWN, _("Unknown")),
)
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
ASSET_USER_CACHE_TIME = 3600 * 24
@property
def password(self):
if self._password:
return signer.unsign(self._password)
else:
return None
@password.setter
def password(self, password_raw):
raise AttributeError("Using set_auth do that")
# self._password = signer.sign(password_raw)
@property
def private_key(self):
if self._private_key:
return signer.unsign(self._private_key)
@private_key.setter
def private_key(self, private_key_raw):
raise AttributeError("Using set_auth do that")
# self._private_key = signer.sign(private_key_raw)
_prefer = "system_user"
@property
def private_key_obj(self):
if self._private_key:
key_str = signer.unsign(self._private_key)
return ssh_key_string_to_obj(key_str, password=self.password)
if self.private_key:
return ssh_key_string_to_obj(self.private_key, password=self.password)
else:
return None
@@ -72,22 +55,13 @@ class AssetUser(OrgModelMixin):
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_str = signer.unsign(self._private_key)
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
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
@property
def public_key(self):
key = signer.unsign(self._public_key)
if key:
return key
else:
return None
@property
def public_key_obj(self):
if self.public_key:
@@ -97,38 +71,153 @@ class AssetUser(OrgModelMixin):
pass
return None
@property
def part_id(self):
i = '-'.join(str(self.id).split('-')[:3])
return i
def get_related_assets(self):
assets = self.assets.all()
return assets
def set_auth(self, password=None, private_key=None, public_key=None):
update_fields = []
if password:
self._password = signer.sign(password)
update_fields.append('_password')
self.password = password
update_fields.append('password')
if private_key:
self._private_key = signer.sign(private_key)
update_fields.append('_private_key')
self.private_key = private_key
update_fields.append('private_key')
if public_key:
self._public_key = signer.sign(public_key)
update_fields.append('_public_key')
self.public_key = public_key
update_fields.append('public_key')
if update_fields:
self.save(update_fields=update_fields)
def get_auth(self, asset=None):
pass
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
@property
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 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)
def get_asset_user(self, asset):
from ..backends import AssetUserManager
try:
manager = AssetUserManager().prefer(self._prefer)
other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
return other
except Exception as e:
logger.error(e, exc_info=True)
return None
def load_specific_asset_auth(self, asset):
instance = self.get_asset_user(asset)
if instance:
self._merge_auth(instance)
def _merge_auth(self, other):
if other.password:
self.password = other.password
if other.public_key:
self.public_key = other.public_key
if other.private_key:
self.private_key = other.private_key
def clear_auth(self):
self._password = ''
self._private_key = ''
self._public_key = ''
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 = str(uuid.uuid4())
private_key, public_key = ssh_key_gen(
username=self.username
)
self.set_auth(password=password,
private_key=private_key,
public_key=public_key)
self.set_auth(
password=password, private_key=private_key,
public_key=public_key
)
def auto_gen_auth_password(self):
password = str(uuid.uuid4())
self.set_auth(password=password)
def _to_secret_json(self):
"""Push system user use it"""
@@ -140,5 +229,26 @@ class AssetUser(OrgModelMixin):
'private_key': self.private_key_file,
}
def generate_id_with_asset(self, asset):
user_id = [self.part_id]
asset_id = str(asset.id).split('-')[3:]
ids = user_id + asset_id
return '-'.join(ids)
def construct_to_authbook(self, asset):
from . import AuthBook
fields = [
'name', 'username', 'comment', 'org_id',
'password', 'private_key', 'public_key',
'date_created', 'date_updated', 'created_by'
]
i = self.generate_id_with_asset(asset)
obj = AuthBook(id=i, asset=asset, version=0, is_latest=True)
for field in fields:
value = getattr(self, field)
setattr(obj, field, value)
return obj
class Meta:
abstract = True

View File

@@ -17,7 +17,7 @@ __all__ = [
class CommandFilter(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=64, verbose_name=_("Name"))
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)
@@ -27,6 +27,9 @@ class CommandFilter(OrgModelMixin):
def __str__(self):
return self.name
class Meta:
verbose_name = _("Command filter")
class CommandFilterRule(OrgModelMixin):
TYPE_REGEX = 'regex'
@@ -58,6 +61,7 @@ class CommandFilterRule(OrgModelMixin):
class Meta:
ordering = ('-priority', 'action')
verbose_name = _("Command filter rule")
@property
def _pattern(self):

View File

@@ -60,7 +60,9 @@ class Gateway(AssetUser):
unique_together = [('name', 'org_id')]
verbose_name = _("Gateway")
def test_connective(self):
def test_connective(self, local_port=None):
if local_port is None:
local_port = self.port
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
proxy = paramiko.SSHClient()
@@ -73,15 +75,15 @@ class Gateway(AssetUser):
pkey=self.private_key_obj)
except(paramiko.AuthenticationException,
paramiko.BadAuthenticationType,
paramiko.SSHException) as e:
paramiko.SSHException,
paramiko.ssh_exception.NoValidConnectionsError) as e:
return False, str(e)
sock = proxy.get_transport().open_channel(
'direct-tcpip', ('127.0.0.1', self.port), ('127.0.0.1', 0)
)
try:
client.connect("127.0.0.1", port=self.port,
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,

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
import uuid
import re
from django.db import models, transaction
from django.db.models import Q
@@ -8,49 +9,195 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.cache import cache
from orgs.mixins import OrgModelMixin
from orgs.mixins import OrgModelMixin, OrgManager
from orgs.utils import set_current_org, get_current_org
from orgs.models import Organization
__all__ = ['Node']
class Node(OrgModelMixin):
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)
class NodeQuerySet(models.QuerySet):
def delete(self):
raise PermissionError("Bulk delete node deny")
class FamilyMixin:
_parents = None
_children = None
_all_children = None
is_node = True
_assets_amount = None
_full_value_cache_key = '_NODE_VALUE_{}'
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
class Meta:
verbose_name = _("Node")
def __str__(self):
return self.full_value
def __eq__(self, other):
if not other:
return False
return self.key == other.key
def __gt__(self, other):
if self.is_root():
return True
self_key = [int(k) for k in self.key.split(':')]
other_key = [int(k) for k in other.key.split(':')]
return self_key.__lt__(other_key)
def __lt__(self, other):
return not self.__gt__(other)
@property
def name(self):
return self.value
def children(self):
if self._children:
return self._children
pattern = r'^{0}:[0-9]+$'.format(self.key)
return Node.objects.filter(key__regex=pattern)
@children.setter
def children(self, value):
self._children = value
@property
def all_children(self):
if self._all_children:
return self._all_children
pattern = r'^{0}:'.format(self.key)
return Node.objects.filter(
key__regex=pattern
)
def get_children(self, with_self=False):
children = list(self.children)
if with_self:
children.append(self)
return children
def get_all_children(self, with_self=False):
children = self.all_children
if with_self:
children = list(children)
children.append(self)
return children
@property
def parents(self):
if self._parents:
return self._parents
ancestor_keys = self.get_ancestor_keys()
ancestor = Node.objects.filter(
key__in=ancestor_keys
).order_by('key')
return ancestor
@parents.setter
def parents(self, value):
self._parents = value
def get_ancestor(self, with_self=False):
parents = self.parents
if with_self:
parents = list(parents)
parents.append(self)
return parents
@property
def parent(self):
if self._parents:
return self._parents[0]
if self.is_root():
return self
try:
parent = Node.objects.get(key=self.parent_key)
return parent
except Node.DoesNotExist:
return Node.root()
@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()
for child in children:
child.key = child.key.replace(old_key, self.key, 1)
child.save()
self.save()
def get_sibling(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):
ancestor = self.get_ancestor()
children = self.get_all_children()
return [*tuple(ancestor), self, *tuple(children)]
def get_ancestor_keys(self, with_self=False):
parent_keys = []
key_list = self.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 is_children(self, other):
pattern = re.compile(r'^{0}:[0-9]+$'.format(self.key))
return pattern.match(other.key)
def is_parent(self, other):
pattern = re.compile(r'^{0}:[0-9]+$'.format(other.key))
return pattern.match(self.key)
@property
def parent_key(self):
parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
@property
def parents_keys(self, with_self=False):
keys = []
key_list = self.key.split(":")
if not with_self:
key_list.pop()
for i in range(len(key_list)):
keys.append(':'.join(key_list))
key_list.pop()
return keys
class FullValueMixin:
_full_value_cache_key = '_NODE_VALUE_{}'
_full_value = ''
key = ''
@property
def full_value(self):
if self._full_value:
return self._full_value
key = self._full_value_cache_key.format(self.key)
cached = cache.get(key)
if cached:
return cached
if self.is_root():
return self.value
parent_full_value = self.parent.full_value
value = parent_full_value + ' / ' + self.value
self.full_value = value
return value
@full_value.setter
def full_value(self, value):
self._full_value = value
key = self._full_value_cache_key.format(self.key)
cache.set(key, value, 3600*24)
def expire_full_value(self):
key = self._full_value_cache_key.format(self.key)
cache.delete_pattern(key+'*')
@classmethod
def expire_nodes_full_value(cls, nodes=None):
key = cls._full_value_cache_key.format('*')
cache.delete_pattern(key+'*')
class AssetsAmountMixin:
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
_assets_amount = None
key = ''
cache_time = 3600 * 24 * 7
@property
def assets_amount(self):
@@ -65,53 +212,82 @@ class Node(OrgModelMixin):
if cached is not None:
return cached
assets_amount = self.get_all_assets().count()
cache.set(cache_key, assets_amount, 3600)
self.assets_amount = assets_amount
return assets_amount
@assets_amount.setter
def assets_amount(self, value):
self._assets_amount = value
cache_key = self._assets_amount_cache_key.format(self.key)
cache.set(cache_key, value, self.cache_time)
def expire_assets_amount(self):
ancestor_keys = self.get_ancestor_keys(with_self=True)
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
cache_keys = [self._assets_amount_cache_key.format(k) for k in
ancestor_keys]
cache.delete_many(cache_keys)
@classmethod
def expire_nodes_assets_amount(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_assets_amount()
return
key = cls._assets_amount_cache_key.format('*')
cache.delete_pattern(key)
@property
def full_value(self):
key = self._full_value_cache_key.format(self.key)
cached = cache.get(key)
if cached:
return cached
if self.is_root():
return self.value
parent_full_value = self.parent.full_value
value = parent_full_value + ' / ' + self.value
key = self._full_value_cache_key.format(self.key)
cache.set(key, value, 3600)
return value
def expire_full_value(self):
key = self._full_value_cache_key.format(self.key)
cache.delete_pattern(key+'*')
@classmethod
def expire_nodes_full_value(cls, nodes=None):
if nodes:
for node in nodes:
node.expire_full_value()
return
key = cls._full_value_cache_key.format('*')
cache.delete_pattern(key+'*')
def refresh_nodes(cls):
from ..utils import NodeUtil
util = NodeUtil(with_assets_amount=True)
util.set_assets_amount()
util.set_full_value()
class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
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.full_value
def __eq__(self, other):
if not other:
return False
return self.id == other.id
def __gt__(self, other):
# if self.is_root() and not other.is_root():
# return False
# elif not self.is_root() and other.is_root():
# return True
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 other_parent_key and \
self_parent_key == other_parent_key:
return self.value > other.value
# if len(self_parent_key) < len(other_parent_key):
# return True
# elif len(self_parent_key) > len(other_parent_key):
# return False
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):
@@ -134,39 +310,12 @@ class Node(OrgModelMixin):
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
def create_child(self, value):
def create_child(self, value, _id=None):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(key=child_key, value=value)
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
return child
def get_children(self, with_self=False):
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
return self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
def get_all_children(self, with_self=False):
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
return self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
def get_sibling(self, with_self=False):
key = ':'.join(self.key.split(':')[:-1])
pattern = r'^{}:[0-9]+$'.format(key)
sibling = self.__class__.objects.filter(
key__regex=pattern.format(self.key)
)
if not with_self:
sibling = sibling.exclude(key=self.key)
return sibling
def get_family(self):
ancestor = self.get_ancestor()
children = self.get_all_children()
return [*tuple(ancestor), self, *tuple(children)]
def get_assets(self):
from .asset import Asset
if self.is_default_node():
@@ -194,7 +343,7 @@ class Node(OrgModelMixin):
return self.get_all_assets().valid()
def is_default_node(self):
return self.is_root() and self.key == '0'
return self.is_root() and self.key == '1'
def is_root(self):
if self.key.isdigit():
@@ -202,52 +351,6 @@ class Node(OrgModelMixin):
else:
return False
@property
def parent_key(self):
parent_key = ":".join(self.key.split(":")[:-1])
return parent_key
@property
def parent(self):
if self.is_root():
return self
try:
parent = self.__class__.objects.get(key=self.parent_key)
return parent
except Node.DoesNotExist:
return self.__class__.root()
@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()
for child in children:
child.key = child.key.replace(old_key, self.key, 1)
child.save()
self.save()
def get_ancestor_keys(self, with_self=False):
parent_keys = []
key_list = self.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(self, with_self=False):
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
ancestor = self.__class__.objects.filter(
key__in=ancestor_keys
).order_by('key')
return ancestor
@classmethod
def create_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
@@ -275,13 +378,12 @@ class Node(OrgModelMixin):
@classmethod
def default_node(cls):
defaults = {'value': 'Default'}
return cls.objects.get_or_create(defaults=defaults, key='1')
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
return obj
def as_tree_node(self):
from common.tree import TreeNode
from ..serializers import NodeSerializer
name = '{} ({})'.format(self.value, self.assets_amount)
node_serializer = NodeSerializer(instance=self)
data = {
'id': self.key,
'name': name,
@@ -290,18 +392,37 @@ class Node(OrgModelMixin):
'isParent': True,
'open': self.is_root(),
'meta': {
'node': node_serializer.data,
'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 delete(self, using=None, keep_parents=False):
if self.children or self.get_assets():
return
return super().delete(using=using, keep_parents=keep_parents)
@classmethod
def get_queryset(cls):
from ..utils import NodeUtil
util = NodeUtil()
return sorted(util.nodes)
@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()
for i in range(count):
node = random.choice(cls.objects.all())
node.create_child('Node {}'.format(i))

View File

@@ -4,14 +4,15 @@
import logging
from django.core.cache import cache
from functools import reduce
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, MaxValueValidator
from common.utils import get_signer
from ..const import SYSTEM_USER_CONN_CACHE_KEY
from .base import AssetUser
from .asset import Asset
__all__ = ['AdminUser', 'SystemUser']
@@ -31,7 +32,8 @@ class AdminUser(AssetUser):
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='', max_length=128)
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
_prefer = "admin_user"
def __str__(self):
return self.name
@@ -60,31 +62,6 @@ class AdminUser(AssetUser):
info = None
return info
def get_related_assets(self):
assets = self.asset_set.all()
return assets
@property
def assets_amount(self):
return self.get_related_assets().count()
@property
def connectivity(self):
from .asset import Asset
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
data = {
'unreachable': [],
'reachable': [],
}
for asset_id, hostname in assets:
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
value = cache.get(key, Asset.UNKNOWN)
if value == Asset.REACHABLE:
data['reachable'].append(hostname)
elif value == Asset.UNREACHABLE:
data['unreachable'].append(hostname)
return data
class Meta:
ordering = ['name']
unique_together = [('name', 'org_id')]
@@ -140,78 +117,19 @@ class SystemUser(AssetUser):
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)
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
def __str__(self):
return '{0.name}({0.username})'.format(self)
def to_json(self):
return {
'id': self.id,
'name': self.name,
'username': self.username,
'protocol': self.protocol,
'priority': self.priority,
'auto_push': self.auto_push,
}
def get_related_assets(self):
assets = set(self.assets.all())
return assets
@property
def connectivity(self):
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
value = cache.get(cache_key, None)
if not value or 'unreachable' not in value:
return {'unreachable': [], 'reachable': []}
else:
return value
@connectivity.setter
def connectivity(self, value):
data = self.connectivity
unreachable = data['unreachable']
reachable = data['reachable']
for host in value.get('dark', {}).keys():
if host not in unreachable:
unreachable.append(host)
if host in reachable:
reachable.remove(host)
for host in value.get('contacted'):
if host not in reachable:
reachable.append(host)
if host in unreachable:
unreachable.remove(host)
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
cache.set(cache_key, data, 3600)
@property
def assets_unreachable(self):
return self.connectivity.get('unreachable')
@property
def assets_reachable(self):
return self.connectivity.get('reachable')
@property
def login_mode_display(self):
return self.get_login_mode_display()
def is_need_push(self):
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
return True
else:
return False
def set_cache(self):
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
def expire_cache(self):
cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
@property
def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule
@@ -229,17 +147,18 @@ class SystemUser(AssetUser):
return False, matched_cmd
return True, None
@classmethod
def get_system_user_by_id_or_cached(cls, sid):
cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
if cached:
return cached
try:
system_user = cls.objects.get(id=sid)
system_user.set_cache()
return system_user
except cls.DoesNotExist:
return None
def get_all_assets(self):
args = [Q(systemuser=self)]
pattern = set()
nodes_keys = self.nodes.all().values_list('key', flat=True)
for key in nodes_keys:
pattern.add(r'^{0}$|^{0}:'.format(key))
pattern = '|'.join(list(pattern))
if pattern:
args.append(Q(nodes__key__regex=pattern))
args = reduce(lambda x, y: x | y, args)
assets = Asset.objects.filter(args).distinct()
return assets
class Meta:
ordering = ['name']

View File

@@ -2,11 +2,17 @@
# -*- 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']
__all__ = [
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
]
def init_model():
@@ -31,5 +37,72 @@ def private_key_validator(value):
)
if __name__ == '__main__':
pass
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=0):
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()

View File

@@ -8,3 +8,4 @@ from .system_user import *
from .node import *
from .domain import *
from .cmd_filter import *
from .asset_user import *

View File

@@ -1,49 +1,36 @@
# -*- coding: utf-8 -*-
#
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from ..models import Node, AdminUser
from ..const import ADMIN_USER_CONN_CACHE_KEY
from orgs.mixins import BulkOrgResourceModelSerializer
from .base import AuthSerializer
from .base import AuthSerializer, AuthSerializerMixin
class AdminUserSerializer(serializers.ModelSerializer):
class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
"""
管理用户
"""
assets_amount = serializers.SerializerMethodField()
unreachable_amount = serializers.SerializerMethodField()
reachable_amount = serializers.SerializerMethodField()
class Meta:
list_serializer_class = AdaptedBulkListSerializer
model = AdminUser
fields = '__all__'
fields = [
'id', 'name', 'username', 'password', 'private_key', 'public_key',
'comment', 'assets_amount', 'date_created', 'date_updated', 'created_by',
]
read_only_fields = ['date_created', 'date_updated', 'created_by', 'assets_amount']
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
return [f for f in fields if not f.startswith('_')]
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('dark'))
else:
return 0
@staticmethod
def get_reachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('contacted'))
else:
return 0
@staticmethod
def get_assets_amount(obj):
return obj.assets_amount
extra_kwargs = {
'password': {"write_only": True},
'private_key': {"write_only": True},
'public_key': {"write_only": True},
'assets_amount': {'label': _('Asset')},
}
class AdminUserAuthSerializer(AuthSerializer):

View File

@@ -1,86 +1,132 @@
# -*- coding: utf-8 -*-
#
from rest_framework import serializers
from rest_framework_bulk.serializers import BulkListSerializer
from django.db.models import Prefetch
from django.utils.translation import ugettext_lazy as _
from common.mixins import BulkSerializerMixin
from ..models import Asset
from .system_user import AssetSystemUserSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label
from .base import ConnectivitySerializer
__all__ = [
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolsField',
]
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
class ProtocolField(serializers.RegexField):
protocols = '|'.join(dict(Asset.PROTOCOL_CHOICES).keys())
default_error_messages = {
'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
}
regex = r'^(%s)/(\d{1,5})$' % protocols
def __init__(self, *args, **kwargs):
super().__init__(self.regex, **kwargs)
def validate_duplicate_protocols(values):
errors = []
names = []
for value in values:
if not value or '/' not in value:
continue
name = value.split('/')[0]
if name in names:
errors.append(_("Protocol duplicate: {}").format(name))
names.append(name)
errors.append('')
if any(errors):
raise serializers.ValidationError(errors)
class ProtocolsField(serializers.ListField):
default_validators = [validate_duplicate_protocols]
def __init__(self, *args, **kwargs):
kwargs['child'] = ProtocolField()
kwargs['allow_null'] = True
kwargs['allow_empty'] = True
kwargs['min_length'] = 1
kwargs['max_length'] = 4
super().__init__(*args, **kwargs)
def to_representation(self, value):
if not value:
return []
return value.split(' ')
class AssetSerializer(BulkOrgResourceModelSerializer):
protocols = ProtocolsField(label=_('Protocols'), required=False)
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
"""
资产的数据结构
"""
class Meta:
model = Asset
list_serializer_class = BulkListSerializer
fields = '__all__'
validators = []
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'ip', 'hostname', 'protocol', 'port',
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
'hostname_raw', 'comment', 'created_by', 'date_created',
'hardware_info', 'connectivity',
]
read_only_fields = (
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'created_by', 'date_created',
)
extra_kwargs = {
'protocol': {'write_only': True},
'port': {'write_only': True},
'hardware_info': {'label': _('Hardware info')},
'org_name': {'label': _('Org name')}
}
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('labels', 'nodes')\
.select_related('admin_user')
queryset = queryset.prefetch_related(
Prefetch('nodes', queryset=Node.objects.all().only('id')),
Prefetch('labels', queryset=Label.objects.all().only('id')),
).select_related('admin_user', 'domain')
return queryset
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend([
'hardware_info', 'connectivity', 'org_name'
])
return fields
def compatible_with_old_protocol(self, validated_data):
protocols_data = validated_data.pop("protocols", [])
# 兼容老的api
name = validated_data.get("protocol")
port = validated_data.get("port")
if not protocols_data and name and port:
protocols_data.insert(0, '/'.join([name, str(port)]))
elif not name and not port and protocols_data:
protocol = protocols_data[0].split('/')
validated_data["protocol"] = protocol[0]
validated_data["port"] = int(protocol[1])
if protocols_data:
validated_data["protocols"] = ' '.join(protocols_data)
class AssetAsNodeSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
def create(self, validated_data):
self.compatible_with_old_protocol(validated_data)
instance = super().create(validated_data)
return instance
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构
"""
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
system_users_join = serializers.SerializerMethodField()
# nodes = NodeTMPSerializer(many=True, read_only=True)
class Meta:
model = Asset
fields = (
"id", "hostname", "ip", "port", "system_users_granted",
"is_active", "system_users_join", "os", 'domain',
"platform", "comment", "protocol", "org_id", "org_name",
)
@staticmethod
def get_system_users_join(obj):
system_users = [s.username for s in obj.system_users_granted]
return ', '.join(system_users)
class MyAssetGrantedSerializer(AssetGrantedSerializer):
"""
普通用户获取授权的资产定义的数据结构
"""
class Meta:
model = Asset
fields = (
"id", "hostname", "system_users_granted",
"is_active", "system_users_join", "org_name",
"os", "platform", "comment", "org_id", "protocol"
)
def update(self, instance, validated_data):
self.compatible_with_old_protocol(validated_data)
return super().update(instance, validated_data)
class AssetSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']

View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from orgs.mixins import BulkOrgResourceModelSerializer
from ..models import AuthBook, Asset
from ..backends import AssetUserManager
from .base import ConnectivitySerializer, AuthSerializerMixin
__all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
'AssetUserExportSerializer', 'AssetUserPushSerializer',
]
class BasicAssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['hostname', 'ip']
class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
ip = serializers.CharField(read_only=True, label=_("IP"))
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
backend = serializers.CharField(read_only=True, label=_("Backend"))
class Meta:
model = AuthBook
list_serializer_class = AdaptedBulkListSerializer
read_only_fields = (
'date_created', 'date_updated', 'created_by',
'is_latest', 'version', 'connectivity',
)
fields = [
"id", "hostname", "ip", "username", "password", "asset", "version",
"is_latest", "connectivity", "backend",
"date_created", "date_updated", "private_key", "public_key",
]
extra_kwargs = {
'username': {'required': True},
'password': {'write_only': True},
'private_key': {'write_only': True},
'public_key': {'write_only': True},
}
def create(self, validated_data):
if not validated_data.get("name") and validated_data.get("username"):
validated_data["name"] = validated_data["username"]
instance = AssetUserManager.create(**validated_data)
return instance
class AssetUserExportSerializer(AssetUserSerializer):
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True,
required=False, label=_('Password')
)
public_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True,
required=False, label=_('Public key')
)
private_key = serializers.CharField(
max_length=4096, allow_blank=True, allow_null=True,
required=False, label=_('Private key')
)
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta:
model = AuthBook
fields = ['password', 'private_key', 'public_key']
class AssetUserPushSerializer(serializers.Serializer):
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
username = serializers.CharField(max_length=1024)
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass

Some files were not shown because too many files have changed in this diff Show More