Compare commits

...

590 Commits
1.5.5 ... 2.0.1

Author SHA1 Message Date
老广
2acc1dc875 Merge pull request #4134 from jumpserver/fix-mfa-1.5
Fix mfa 1.5
2020-06-22 19:05:15 +08:00
xinwen
32ed43ba7b Merge branch 'v2.0' into fix-mfa-1.5 2020-06-22 19:04:42 +08:00
xinwen
3e993fd044 [Update] 调整MFA绑定策略 V2 2020-06-22 19:02:14 +08:00
xinwen
005573b53b [Fix] 重新绑定 MFA 的漏洞 2020-06-22 18:03:45 +08:00
BaiJiangJie
408b2d6dbd Merge pull request #4125 from jumpserver/v2.0_bugfix
v2.0 添加改密计划安全模式配置项
2020-06-20 19:24:17 +08:00
BaiJiangJie
ebc63b9410 Merge pull request #4123 from jumpserver/v1.5_bugfix
[Update] 添加改密计划安全模式配置项
2020-06-20 16:20:53 +08:00
Michael Bai
f1e5c7c2bb 添加改密计划安全模式配置项 2020-06-20 16:18:58 +08:00
Michael Bai
fcb0aefe3c 更改改密计划安全模式配置项名 2020-06-20 15:47:39 +08:00
Bai
29666cc8d3 [Update] 添加改密计划安全模式配置项 2020-06-19 20:41:51 +08:00
BaiJiangJie
92fc0ceb16 Merge pull request #4118 from jumpserver/master
Update readme
2020-06-18 15:03:32 +08:00
BaiJiangJie
217ea03c18 Merge pull request #4117 from jumpserver/dev
Dev
2020-06-18 15:02:40 +08:00
BaiJiangJie
923f0ed477 Update README.md 2020-06-18 15:01:08 +08:00
BaiJiangJie
3c6cfaa6cf Update README.md 2020-06-18 14:59:13 +08:00
BaiJiangJie
0bfe255966 Update README.md 2020-06-18 14:58:00 +08:00
BaiJiangJie
af5d531131 Merge pull request #4116 from jumpserver/dev
Dev
2020-06-18 10:56:36 +08:00
Bai
10d58ef424 [Update] Merge from master to dev 2020-06-17 19:30:37 +08:00
Bai
64064cb526 [Update] 注释节点树print 2020-06-17 17:47:45 +08:00
Eric_Lee
46941037dd add SECURITY_COMMAND_EXECUTION (#4114) 2020-06-17 15:03:41 +08:00
xinwen
8ad71b6dd9 [Update] Move LOCAL_DYNAMIC_SETTINGS (#4113) 2020-06-16 18:11:23 +08:00
ibuler
220ccda04d fix: 修改domain创建的assets不是必填的 2020-06-16 17:55:18 +08:00
Eric_Lee
6d30fe797c fix ldap test bug (#4110) 2020-06-16 17:25:57 +08:00
xinwen
3318df1771 [Fix] 日志审计/FTP日志 (#4109) 2020-06-16 16:33:53 +08:00
Eric_Lee
0ccd806eca add system user perm api (#4108) 2020-06-16 16:12:59 +08:00
xinwen
7ebe1c2916 [Update] 优化 dynamic settings (#4107) 2020-06-16 15:54:19 +08:00
ibuler
08904c2a9f Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-16 15:30:39 +08:00
ibuler
19e34270d1 feat: gather user添加搜索字段 2020-06-16 15:30:25 +08:00
Bai
afa515d570 Merge branch 'dev' of https://github.com/jumpserver/jumpserver into dev 2020-06-16 15:04:38 +08:00
Bai
bcba408517 [Update] 修改用户source默认local 2020-06-16 15:04:01 +08:00
ibuler
80d94074e7 feat: 修改资产的标签搜索 2020-06-16 14:56:19 +08:00
ibuler
9347405f08 feat: terminal增加搜索 2020-06-16 13:50:21 +08:00
ibuler
da4ad11a69 fix: 修改session command 翻译 2020-06-16 13:39:49 +08:00
ibuler
b81e424e80 fix: 修改翻译 2020-06-16 12:20:16 +08:00
ibuler
1f15937139 feat: 修改翻译,添加settings 2020-06-16 11:04:59 +08:00
Bai
f4fa011714 [Update] 修改mfa校验code无效翻译 2020-06-15 20:31:50 +08:00
xinwen
c5a9a85818 [Update] 完善 作业中心/任务列表 (#4105) 2020-06-15 19:42:36 +08:00
ibuler
e23bfa0f69 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-15 17:55:13 +08:00
ibuler
451690fe8b fix: 修改重定向 2020-06-15 17:44:40 +08:00
Eric_Lee
5bea782b9f add assignee field for ticket (#4104) 2020-06-15 17:24:05 +08:00
Bai
f26f7ca1e7 [Update] 修改parsers,处理dict字段值;解决remote-app csv导入时异常 2020-06-15 16:34:51 +08:00
BaiJiangJie
fb9ac54843 Merge pull request #4103 from jumpserver/master_mfa
[Update] 修改MFA应用下载图片
2020-06-15 13:57:35 +08:00
Bai
927ca162d0 [Update] 修改MFA应用下载图片 2020-06-15 13:55:56 +08:00
Bai
583d295fd1 Merge branch 'dev' of https://github.com/jumpserver/jumpserver into dev 2020-06-15 13:50:05 +08:00
Bai
b51af1f7d7 [Update] 修改MFA应用下载图片 2020-06-15 13:49:51 +08:00
xinwen
edcf9921fe [Update] apps/assets/serializers/system_user.py (#4102) 2020-06-15 13:24:52 +08:00
Eric_Lee
eef172d0e2 Merge pull request #4098 from jumpserver/fix-ops-adhoc
[Fix] ops.models.adhoc (#4096)
2020-06-12 12:23:05 +08:00
Eric_Lee
5b407fe8bc Merge pull request #4095 from jumpserver/update-user
[Update] 修改 用户相关 Serializer
2020-06-12 12:22:16 +08:00
Bai
1bb9048910 [Update] 标签序列类assets字段required为False 2020-06-12 11:45:03 +08:00
xinwen
787cdbcadf [Fix] ops.models.adhoc (#4096) 2020-06-12 11:44:29 +08:00
xinwen
b14ca14120 [Fix] ops.models.adhoc (#4096) 2020-06-11 20:30:59 +08:00
ibuler
4b2fd0d0da fix: 修复系统用户过滤权限规则的bug 2020-06-11 19:36:34 +08:00
xinwen
3393f18399 [Update] 修改 用户相关 Serializer 2020-06-11 18:24:56 +08:00
ibuler
7e1a379e47 feat: 修改org返回的数据结构 2020-06-11 18:05:18 +08:00
ibuler
213fdd461b fix: 修改翻译 2020-06-11 14:33:42 +08:00
ibuler
148c7ffb43 fix: 添加注释 2020-06-11 14:10:55 +08:00
ibuler
75be45ce43 feat: 使用新的对称加密方式: aes 2020-06-11 12:10:00 +08:00
ibuler
04eb670ada Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-11 11:29:28 +08:00
ibuler
66f3706142 feat: 添加aes fix: 修改时区的bug 2020-06-11 11:28:37 +08:00
xinwen
9ea98bf2b2 [Feature] 添加 会话管理/历史会话/下载 api (#4093) 2020-06-10 17:34:56 +08:00
ibuler
4695f80172 faet: 修改翻译 2020-06-10 17:17:16 +08:00
ibuler
0452d53c3f Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-10 16:00:24 +08:00
ibuler
ec30ef1f8b feat: readme添加版本说明 2020-06-10 15:57:41 +08:00
Eric_Lee
1c0ad08d80 fix ldap test i18n msg (#4092) 2020-06-10 15:10:43 +08:00
ibuler
e1ab453780 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-10 12:58:43 +08:00
ibuler
1a6597b572 feat: 修改command filter数据结构 2020-06-10 12:58:18 +08:00
xinwen
865522953a [Feature] 作业中心/任务列表/任务详情 最后运行成功或失败的主机 (#4090) 2020-06-09 20:26:23 +08:00
ibuler
4d4a107101 feat: 修改oid获取顺序,添加从cookie中获取 2020-06-09 16:43:07 +08:00
ibuler
82f70cb0dc feat: ticket添加翻译 2020-06-09 16:10:31 +08:00
ibuler
820186c6d0 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-09 15:16:01 +08:00
ibuler
4468e2d379 feat: 限制gateway 仅有ssh协议 2020-06-09 15:08:15 +08:00
Eric_Lee
bd802e6a50 add logo urls (#4088) 2020-06-09 14:06:11 +08:00
ibuler
9362c272cb Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-09 10:19:07 +08:00
ibuler
ee4534ac4b feat: public setting添加key 2020-06-09 10:17:16 +08:00
xinwen
7ef09a4ca1 [Update] UserSerializer 添加 login_confirm_settings (#4086) 2020-06-08 17:16:30 +08:00
ibuler
31daaed4cd feat: 添加auth confirm 到public setting 2020-06-08 16:48:10 +08:00
ibuler
71202e83f5 feat: 修改用户的api 2020-06-05 20:15:23 +08:00
ibuler
b1640e5592 fix: 修改common resource api的权限,否则auditor无法使用 2020-06-05 14:43:40 +08:00
ibuler
076b7babcb 修改批量命令的api 2020-06-05 14:20:34 +08:00
ibuler
8569910658 feat: command exexution audit log的搜索 2020-06-05 14:07:23 +08:00
xinwen
34c556d375 [Fix] spm (#4082) 2020-06-05 10:42:03 +08:00
ibuler
a43d6ad34d feat: 资产添加admin_user_display 2020-06-04 20:26:42 +08:00
ibuler
ca6825008b Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-06-04 20:00:48 +08:00
ibuler
9c6f118dbd feat: 添加批量命令的relation 2020-06-04 20:00:39 +08:00
Bai
5730e60089 [Update] 修改用户profile序列类 2020-06-04 17:24:31 +08:00
Bai
afc7f3bb9c [Update] 修改用户public_key生成密钥的view 2020-06-04 17:05:36 +08:00
Bai
c411b0a38e Merge branch 'dev' of https://github.com/jumpserver/jumpserver into dev 2020-06-04 17:02:52 +08:00
Bai
403b6fc563 [Update] 修改用户更新Public_key的条件判断 2020-06-04 17:02:43 +08:00
xinwen
55ae8bb5e6 [Fix] 系统设置/邮件设置/测试链接失败 (#4081) 2020-06-04 16:47:43 +08:00
Eric_Lee
dbcf785e42 add flower view (#4078) 2020-06-04 15:10:02 +08:00
ibuler
e6cd126045 feat: 修改api的权限 2020-06-04 14:55:33 +08:00
Bai
420f3c0c4c [Update] 添加celery task log view 2020-06-04 14:30:44 +08:00
Bai
9b2c5cb305 [Update] 修改用户profile序列类3 2020-06-03 21:00:37 +08:00
Bai
907f0068db [Update] 修改用户profile序列类2 2020-06-03 20:21:28 +08:00
Bai
a16b3260ba Merge branch 'dev' of https://github.com/jumpserver/jumpserver into dev 2020-06-03 18:20:43 +08:00
Bai
1845821f6c [Update] 修改用户profile序列类 2020-06-03 18:20:33 +08:00
ibuler
27d906a877 feat: 去掉js i18n catalog 2020-06-03 16:26:10 +08:00
xinwen
431ba36a26 [Fix] 会话管理/命令记录 时间过滤 bug (#4070) 2020-06-03 15:05:55 +08:00
Eric_Lee
229c782157 change PAGE_SIZE_CHOICE to string value (#4069) 2020-06-03 14:06:44 +08:00
ibuler
5bacab7475 fix: 重置密钥到auth中 2020-06-03 12:44:45 +08:00
ibuler
999286a089 fix: 修复用户公钥错误引起的profile bug 2020-06-03 12:32:52 +08:00
ibuler
68ccaf0cb3 feat: 去掉第一次登录的那个导航 2020-06-03 12:26:53 +08:00
ibuler
8d58d58519 feat: 修改profile view 2020-06-03 11:58:16 +08:00
ibuler
8efc0331de feat: 删掉所有view, templates, forms 2020-06-03 11:43:43 +08:00
ibuler
7c479c2479 feat: 修改docs的url 2020-06-03 11:17:00 +08:00
ibuler
96551856a2 [Update] 部分view放到auth中 2020-06-03 11:06:44 +08:00
ibuler
b1f5cc7728 [Update] 禁用view 2020-06-03 10:38:44 +08:00
ibuler
1a84661ca9 feat: 修改filterset_fields => filter_fields,option方法不支持filterset 2020-06-02 20:02:22 +08:00
ibuler
c87b9f203f feat: 修改依赖库版本 2020-06-02 15:51:24 +08:00
老广
9442acfb74 Celery version (#4064)
* [Update] 升级celery版本

* [Update] 修改redis-cache 版本
2020-06-02 15:44:45 +08:00
ibuler
50bea55732 merge: with dev 2020-06-02 15:43:42 +08:00
ibuler
a4ece2b271 feat: audits中添加id字段 2020-06-02 15:41:27 +08:00
xinwen
b460e4abaa [Fix] 日志审计 日期过滤与排序bug (#4063) 2020-06-02 15:40:07 +08:00
BaiJiangJie
087ba9ae95 Merge pull request #4059 from jumpserver/lina
Lina
2020-06-01 14:05:22 +08:00
BaiJiangJie
19926e67e1 Merge pull request #4058 from jumpserver/dev
Dev
2020-06-01 14:02:56 +08:00
BaiJiangJie
af15622319 Merge pull request #4057 from jumpserver/dev_ldap
[Update] 添加LDAP USER_QUERY_FIELD字段值为username,解决数据库因大小敏感导致LDAP用户认证首次成功…
2020-06-01 12:51:30 +08:00
Bai
9850633350 [Update] 添加LDAP USER_QUERY_FIELD字段值为username,解决数据库因大小敏感导致LDAP用户认证首次成功,再次失败的问题;如果不配置此字段,则查询使用小写,创建可能会使用大写,最终导致username重复而创建用户失败; 2020-06-01 12:06:39 +08:00
ibuler
5f2345852d Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-06-01 11:41:55 +08:00
ibuler
ad1c17aa7b feat: 仅支持fields_size=mini,small 2020-06-01 10:10:28 +08:00
xinwen
3a79bfd5f6 [Update] 添加一些 i18n (#4052) 2020-05-29 15:18:25 +08:00
Bai
5ee8519274 [Update] 修改AccessKey序列类 2020-05-29 14:04:50 +08:00
Bai
ff546774e9 Merge branch 'lina' of https://github.com/jumpserver/jumpserver into lina 2020-05-28 20:46:30 +08:00
Bai
1f4fc9b6f0 [Update] 修改API-Key序列类 2020-05-28 20:46:17 +08:00
xinwen
f8142e23cd [Fix] 权限管理-> 资产与数据库 Bug (#4049) 2020-05-28 19:08:48 +08:00
Bai
196f1654ab Merge branch 'lina' of https://github.com/jumpserver/jumpserver into lina 2020-05-28 17:47:27 +08:00
Bai
3b8a24eeb7 [Update] 添加用户profile public-key序列类 2020-05-28 17:47:18 +08:00
xinwen
7dde15cb04 [Feature] 权限管理-> 远程应用 添加 api (#4040)
* [Feature] 权限管理-> 远程应用 添加 api

* [Feature] 添加 RelationMixin
2020-05-28 17:00:42 +08:00
Bai
3e5d949610 [Update] 添加用户profile password序列类 2020-05-28 16:10:28 +08:00
Bai
25c3691f6b Merge branch 'lina' of https://github.com/jumpserver/jumpserver into lina 2020-05-28 15:03:07 +08:00
Bai
f3bc6c0b22 [Update] 修改用户profile序列类 2020-05-28 15:02:53 +08:00
ibuler
caf0d85939 [feat] 修改remote app, database app的过滤参数 2020-05-28 14:47:30 +08:00
ibuler
a463f632e8 [Update] 优化重定向 2020-05-27 20:33:09 +08:00
ibuler
a0e6d09770 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-27 19:51:45 +08:00
BaiJiangJie
54623a5b06 Merge pull request #4047 from jumpserver/lina_dev
Lina dev
2020-05-27 18:46:48 +08:00
Bai
7afff5e392 [Update] merge from dev to lina 2020-05-27 18:44:12 +08:00
ibuler
9a5fee5a4c Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-27 18:03:14 +08:00
ibuler
a840e611cd [Update] 修改core 的base url 2020-05-27 18:02:18 +08:00
Bai
566419cac4 Merge branch 'lina' of https://github.com/jumpserver/jumpserver into lina 2020-05-27 17:27:38 +08:00
Bai
7b362bfc76 [Update] 用户序列类添加mfa相关字段 2020-05-27 17:27:28 +08:00
ibuler
f528dd4888 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-26 19:00:00 +08:00
ibuler
3c95c6fe11 [Feat] 添加失效用户权限的api 2020-05-26 18:56:03 +08:00
BaiJiangJie
0b17d55f30 Merge pull request #4041 from jumpserver/dev_oidc
[Update] 修改依赖版本号 jumpserver-django-oidc-rp==0.3.7.5
2020-05-26 14:23:52 +08:00
Bai
3861943518 [Update] 修改依赖版本号 jumpserver-django-oidc-rp==0.3.7.5 2020-05-26 14:23:04 +08:00
老广
71a72dd957 Merge pull request #4033 from jumpserver/update-i18n
[Update] i18n
2020-05-25 01:53:02 -05:00
老广
78ac1968dd Merge pull request #4039 from jumpserver/asset_permission-add-field
[Update] perms.serializers.asset_permission.AssetPermissionSerializer…
2020-05-25 01:52:35 -05:00
xinwen
93453cc8c3 [Update] perms.serializers.asset_permission.AssetPermissionSerializer 添加字段 2020-05-25 14:50:07 +08:00
xinwen
11527b9033 [Update] i18n 2020-05-22 18:18:40 +08:00
ibuler
2ff2266417 [Update] 修改 SerializerMixin 还原之前的更改 2020-05-21 20:35:44 +08:00
老广
1ef1bea703 Merge pull request #4029 from jumpserver/dev
修改了readme,增加安全建议
2020-05-21 07:06:49 -05:00
ibuler
6804f23b51 [Update] 修改readme 2020-05-21 20:01:53 +08:00
ibuler
ea7e1f19b2 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-05-21 19:55:36 +08:00
ibuler
3320e6105c [Update] 删掉没用的内容 2020-05-21 19:00:47 +08:00
老广
072e74ce49 Merge pull request #4027 from jumpserver/update-org-user
[Update] org retrieve api
2020-05-21 03:15:05 -05:00
xinwen
492b1c4311 [Update] org retrieve api 2020-05-21 16:09:42 +08:00
老广
0babada459 Merge pull request #4025 from jumpserver/update-org-user-add-id
[Update] orgs.serializers.OrgReadSerializer add `id`
2020-05-21 02:43:35 -05:00
xinwen
7b0993959e [Update] orgs.serializers.OrgReadSerializer add id 2020-05-21 15:40:41 +08:00
ibuler
701582fe38 [Update] 修改license的判断方法 2020-05-21 14:47:00 +08:00
BaiJiangJie
d289960ff2 Merge pull request #3998 from jumpserver/dev
Dev
2020-05-21 14:00:37 +08:00
BaiJiangJie
6adf37a30d Merge pull request #4022 from jumpserver/dev_tran
[Update] 修改翻译信息
2020-05-20 19:43:43 +08:00
ibuler
b371676813 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-20 19:35:48 +08:00
Bai
2e0eab9289 [Update] 修改翻译信息 2020-05-20 19:35:35 +08:00
ibuler
0cac8d66b3 [Update] 修改index api 2020-05-20 19:35:33 +08:00
BaiJiangJie
2053e9210e Merge pull request #4021 from jumpserver/dev_asset
[Bugfix] 修复只显示当前节点下资产不生效的问题
2020-05-20 18:21:59 +08:00
Bai
7c44f74068 [Bugfix] 修复只显示当前节点下资产不生效的问题 2020-05-20 18:20:01 +08:00
老广
3e0f5af848 Merge pull request #4019 from jumpserver/update-some-i18n
[Update] 一些本地化
2020-05-20 05:17:12 -05:00
xinwen
245d28b03d [Update] 一些本地化 2020-05-20 17:45:50 +08:00
Bai
75f4f6d0a2 [Update] 资产授权序列类is_expired, is_valid设置为read_only 2020-05-19 20:50:33 +08:00
Bai
f9e167cb0e Merge branch 'lina' of https://github.com/jumpserver/jumpserver into lina 2020-05-19 20:36:41 +08:00
Bai
f224e49de7 [Update] 去掉资产授权序列类中不存在的字段 2020-05-19 20:36:17 +08:00
BaiJiangJie
4ec4869896 Merge pull request #4012 from jumpserver/dev_permsasset
[Update] 授权资产树返回org_id字段值
2020-05-19 19:37:13 +08:00
Bai
63ff868553 [Update] 授权资产树返回org_id字段值 2020-05-19 19:36:19 +08:00
BaiJiangJie
234130091b Merge pull request #4011 from jumpserver/dev_conf
[Update] 修改配置文件demo
2020-05-19 18:38:47 +08:00
Bai
216e0f28b9 [Update] 修改配置文件demo 2020-05-19 18:36:56 +08:00
BaiJiangJie
f3483484d7 Merge pull request #4010 from jumpserver/dev_openid
Dev openid
2020-05-19 18:35:30 +08:00
Bai
d704a35ead [Update] 修改依赖版本号 jumpserver-django-oidc-rp==0.3.7.4 2020-05-19 18:33:33 +08:00
Bai
b97deec1de [Update] openid 继续使用配置项 base_site_url 2020-05-19 18:24:26 +08:00
BaiJiangJie
5b6488e1b2 Merge pull request #4008 from jumpserver/dev_bugfix
Dev bugfix
2020-05-19 15:31:10 +08:00
Bai
be3fdac8a9 [Update] 修复API-key不能删除的问题 2020-05-19 15:28:31 +08:00
Bai
96afd82341 [Update] 修复任务列表,执行历史中stat汇总数据显示问题 2020-05-19 15:15:17 +08:00
Bai
728e4b7edd [Update] 修复授权模块的信号处理监听不到的问题 2020-05-19 11:47:49 +08:00
Bai
73c3de97b8 [Update] 取消用户资产页面中,资产详情系统用户值为undefine的问题 2020-05-18 18:14:19 +08:00
Bai
e98626988b [Update] 解决开启OpenID,为用户强制启用MFA导致,登录时报错的问题 2020-05-18 18:02:37 +08:00
Bai
01a52812f0 [Update] 修改用户MFA禁用/更新的View 2020-05-18 15:49:53 +08:00
Bai
041d99f0be [Update] 更新remote-app用户/用户组时自动刷新页面 2020-05-18 15:40:54 +08:00
Bai
75edc26a10 [Update] 修改翻译,禁用资产提示不支持ansible的错误提示 2020-05-18 15:25:24 +08:00
Bai
2f7b169405 [Update] 修改授权用户/用户组页面删除用户组失败的问题 2020-05-18 15:24:55 +08:00
ibuler
76ef9b292b [Update] 修改public api 2020-05-18 14:55:16 +08:00
ibuler
1540cbdcaa [Update] 修改csv render 2020-05-18 11:52:50 +08:00
BaiJiangJie
eefe0709f8 Merge pull request #4007 from maninhill/patch-1
Update README.md
2020-05-17 07:44:37 +08:00
maninhill
5ae6e81a1d Update README.md 2020-05-17 00:02:35 +08:00
BaiJiangJie
8a62488cb9 Merge pull request #4003 from jumpserver/dev_v
[Update] 修改版本号1.5.9
2020-05-14 16:15:17 +08:00
Bai
e951b64b0a [Update] 修改版本号1.5.9 2020-05-14 16:14:17 +08:00
ibuler
5d129fd0da Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-14 14:51:05 +08:00
ibuler
b529127461 [Update] 修改user role 2020-05-14 14:49:54 +08:00
BaiJiangJie
2e40b9607e Merge pull request #4002 from jumpserver/dev_bai
[Update] 用户资产详情Modal添加comment
2020-05-14 14:28:24 +08:00
Bai
d878089ebd [Update] 用户资产详情Modal添加comment 2020-05-14 14:26:00 +08:00
BaiJiangJie
9d32285446 Merge pull request #3997 from jumpserver/dev_bai
[Update] 修改向导文案
2020-05-14 11:01:35 +08:00
Bai
4658a4c90f [Update] 修改向导文案 2020-05-14 11:00:24 +08:00
Bai
d06ea2944e [Update] 修改Dashboard API 2020-05-14 10:53:30 +08:00
wojiushixiaobai
d50ea83f40 [Fix]修改文档链接 2020-05-13 15:21:04 +08:00
ibuler
154aad1e22 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-13 11:06:24 +08:00
ibuler
17163dd909 [Update] 用户Profile添加在当前组织的角色 2020-05-13 11:05:58 +08:00
BaiJiangJie
b789a8bb05 Merge pull request #3994 from jumpserver/lina_assetuser
[Update] 修复AssetUserViewSet 使用Option方法时error
2020-05-12 20:32:58 +08:00
Bai
9341ce9f84 [Update] 修复AssetUserViewSet 使用Option方法时error 2020-05-12 20:31:32 +08:00
ibuler
4d0f7d0254 Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-05-12 17:57:51 +08:00
ibuler
195cbbbe42 [Update] 修改profile serialzier 2020-05-12 17:57:35 +08:00
xinwen
6e5e340a25 Merge pull request #3993 from jumpserver/update-assets-filter
[Update] Asset filter 添加 platform__base 字段
2020-05-12 17:26:59 +08:00
xinwen
eb74d13059 [Update] Asset filter 添加 platform__base 字段 2020-05-12 17:24:51 +08:00
老广
16f916c40a Merge pull request #3992 from jumpserver/update-assets-filter
Update assets filter
2020-05-12 04:11:49 -05:00
xinwen
4dd6d4498b [Update] Asset filter 添加 platform__name 字段 2020-05-12 17:09:36 +08:00
ibuler
dd5bf546df [Update] 修改serialzier_class 2020-05-12 15:37:37 +08:00
ibuler
d6debde566 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-11 19:28:24 +08:00
ibuler
efc66cc7ee [Update] 默认关闭debug 2020-05-11 19:28:02 +08:00
BaiJiangJie
b310731ba7 Merge pull request #3990 from jumpserver/lina_dev
Lina dev
2020-05-11 17:45:51 +08:00
Bai
4bd2681bf0 [Update] Merge from dev to lina 2020-05-11 17:43:48 +08:00
BaiJiangJie
38e09753f4 Merge pull request #3989 from jumpserver/dev_master
Dev master
2020-05-11 17:38:46 +08:00
Bai
98e3adbb11 [Update] Merge from master to dev 2020-05-11 17:38:01 +08:00
BaiJiangJie
c700b101c1 Merge pull request #3988 from jumpserver/master_otp
[Bugfix] 修复用户页面更新MFA时解除管理员强制启用的Bug
2020-05-11 16:58:40 +08:00
Bai
7b3647e78a [Bugfix] 修复用户页面更新MFA时解除管理员强制启用的Bug 2020-05-11 16:56:52 +08:00
BaiJiangJie
f4f042c407 Merge pull request #3987 from jumpserver/master_index
[Update] 仪表盘缓存key添加组织id
2020-05-11 15:20:00 +08:00
Bai
0a8eeca629 [Update] 仪表盘缓存key添加组织id 2020-05-11 15:12:42 +08:00
BaiJiangJie
fdd55511a6 Merge pull request #3986 from jumpserver/lina_dev
Lina dev
2020-05-11 14:04:52 +08:00
Bai
0cff6ab29b [Update] Merge fromo dev to lina 2020-05-11 14:03:24 +08:00
ibuler
cda677a30f [update] 修改remote apps的serializer 2020-05-11 12:35:33 +08:00
ibuler
5571651c02 [Update] 升级requirements 2020-05-09 16:13:06 +08:00
ibuler
2680396680 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-09 16:08:32 +08:00
ibuler
227f97c2f5 [Update] 修改remote apps 字段 2020-05-09 16:08:09 +08:00
老广
ad3231c8a3 Merge pull request #3982 from jumpserver/update-ops-migrate
[Update] 重建 ops 0018 迁移脚本
2020-05-09 01:54:37 -05:00
ibuler
e39d8dce3c [Update] 修改fields 2020-05-09 14:52:44 +08:00
ibuler
0bdc425c55 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-05-09 14:51:47 +08:00
ibuler
0a7f63cc5e [Update] 修改remote app api 2020-05-09 14:51:19 +08:00
xinwen
c6e0e9a79a [Update] 重建 ops 0018 迁移脚本 2020-05-09 14:50:22 +08:00
BaiJiangJie
4cebfc7f6a [Update] 修改翻译 (#3981)
* [Update] 修改翻译

* [Update] 修改翻译2
2020-05-09 13:29:39 +08:00
老广
7fde392774 Merge pull request #3980 from jumpserver/update-audits-verbose
[Update] audits 模块为一些 models 字段添加 verbose 信息
2020-05-08 22:40:34 -05:00
xinwen
3ee051303a [Update] audits 模块为一些 models 字段添加 verbose 信息 2020-05-09 11:29:25 +08:00
老广
9fa31be4bf Merge pull request #3976 from jumpserver/add-audits-apis
[Add] audits apis
2020-05-08 08:04:14 -05:00
老广
ae2e4049db Merge pull request #3979 from jumpserver/lina_setting_dict_field
AUTH_LDAP_USER_ATTR_MAP to Dict field
2020-05-08 08:02:04 -05:00
Eric
48b71bb11b AUTH_LDAP_USER_ATTR_MAP to Dict field 2020-05-08 20:58:13 +08:00
BaiJiangJie
7e4edc3c63 Merge pull request #3978 from jumpserver/dev_index
[Update] 修改dashboard中使用的变量名
2020-05-08 20:03:08 +08:00
Bai
11b3c57c92 [Update] 修改dashboard中使用的变量名 2020-05-08 20:02:00 +08:00
xinwen
e339ed1fb3 [Add] audits apis 2020-05-08 17:59:58 +08:00
BaiJiangJie
2d48c8028b Merge pull request #3975 from jumpserver/dev_master
Dev master
2020-05-08 16:42:32 +08:00
Bai
185c53d311 [Update] Merge from master to dev 2020-05-08 16:40:07 +08:00
BaiJiangJie
2f030c02ec Merge pull request #3973 from jumpserver/master_org
[Update] 优化仪表盘、组织详情页
2020-05-08 16:27:49 +08:00
老广
f4457ff1e2 Merge pull request #3972 from jumpserver/PERM_CACHE_CONFIG
Bugfix Perm cache config
2020-05-08 03:04:33 -05:00
ibuler
c6ed6d8acb [Update] 去掉debug信息 2020-05-08 15:58:00 +08:00
ibuler
331cfe2aed [Bugfix] 修复节点数量缓存显示不对的bug 2020-05-08 15:53:58 +08:00
Bai
e48dbabef2 [Update] 修改翻译 2020-05-08 15:50:01 +08:00
Bai
181973f235 [Update] 优化仪表盘页面加载 2020-05-08 15:41:56 +08:00
ibuler
440a2ad241 Merge branch 'PERM_CACHE_CONFIG' into dev 2020-05-08 14:25:36 +08:00
ibuler
1936a6d5ee [Update] 去掉perms cache enable settings 2020-05-08 14:25:28 +08:00
老广
4517a92b2b Merge pull request #3971 from jumpserver/add-ftp-log-api
[Add] ftp-logs api
2020-05-08 00:52:03 -05:00
ibuler
44a2a51f59 Merge branch 'PERM_CACHE_CONFIG' into dev 2020-05-08 13:13:47 +08:00
ibuler
2d4498578a [Update] 修改授权缓存配置的默认值 2020-05-08 13:13:38 +08:00
xinwen
ac902501ec [Add] ftp-logs api 2020-05-08 12:46:18 +08:00
老广
363b5d04d9 Merge pull request #3969 from jumpserver/update-login-logs
[Update] login-logs api 添加 `mfa_display`
2020-05-07 22:23:37 -05:00
xinwen
dec89ae5ee [Update] login-logs api 添加 mfa_display 2020-05-08 10:08:57 +08:00
老广
5d37269a6c Merge pull request #3967 from jumpserver/lina_status_code
[Update] Reponse status code from 401 to 400
2020-05-07 04:03:34 -05:00
Eric
9a39ccd37d [Update] Reponse status code from 401 to 400 2020-05-07 16:57:57 +08:00
BaiJiangJie
5896ea9c63 Merge pull request #3963 from jumpserver/dev_oidc
[Update] 修改依赖版本jumpserver-django-oidc-rp==0.3.7.3
2020-05-06 21:06:27 +08:00
Bai
8323de1c07 [Update] 修改依赖版本jumpserver-django-oidc-rp==0.3.7.3 2020-05-06 21:05:23 +08:00
ibuler
8c0bf0b71b [Update] Login log 添加display field 2020-05-06 19:35:19 +08:00
老广
5812c50a33 Merge pull request #3960 from jumpserver/lina_setting
[Update] save settings
2020-05-06 04:05:51 -05:00
Eric
7b339df430 [Update] save settings 2020-05-06 16:36:36 +08:00
Bai
aec78dc3c7 [Update] 添加组织详情->组织用户tab页,解决组织详情页面加载超时问题 2020-05-06 15:31:08 +08:00
ibuler
6bb13a26f5 [Update] 修改用户serializer 2020-04-30 16:58:08 +08:00
ibuler
b92137afd9 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-04-30 16:53:21 +08:00
老广
962763dc7b Merge pull request #3954 from xuxinwen/feature-audits-apis
[Feature] 添加 login-logs API
2020-04-30 03:52:57 -05:00
xuxinwen
f4eca83a49 [feature] 添加 login-logs API 2020-04-30 16:40:55 +08:00
老广
02135ea04f Merge pull request #3948 from jumpserver/settings_api
[Update] Add settings api
2020-04-29 18:25:10 +08:00
Eric
79eb838250 [Update] setting fields automatically generated by serializer 2020-04-29 18:10:42 +08:00
Eric
82710294f4 Merge branch 'lina' of https://github.com/jumpserver/jumpserver into settings_api 2020-04-29 17:09:50 +08:00
Eric
2d18acf6f7 [Update] settings api 2020-04-29 17:04:58 +08:00
BaiJiangJie
a860bed34f [Update] 修改config_example配置(openid) (#3951)
* [Update] 修改config_example配置(openid)

* [Update] 修改config_example配置(openid)2
2020-04-29 16:57:10 +08:00
ibuler
f8c323cf5c [Update] 优化user group serializer 2020-04-29 16:01:14 +08:00
ibuler
b6f5b335bd Merge branch 'api_perf' into lina 2020-04-29 15:47:46 +08:00
Eric
f451f8a979 Merge branch 'lina' of https://github.com/jumpserver/jumpserver into settings_api 2020-04-29 14:33:22 +08:00
ibuler
5c4dfabc48 [Update] 修改settings 2020-04-29 14:32:51 +08:00
BaiJiangJie
72ccaf7b1c Merge pull request #3949 from jumpserver/dev_openid
Dev openid
2020-04-29 14:25:32 +08:00
Bai
e4b788a012 [Update] 修改依赖版本 jumpserver-dajngo-oidc-rp (0.3.7.2) 2020-04-29 14:07:54 +08:00
Bai
6eaba4e2fb [Update] openid配置分块 2020-04-29 14:03:48 +08:00
ibuler
0c0c0e6d6f Merge branch 'dev' into lina 2020-04-29 13:00:47 +08:00
Eric
20cf7c7c52 [Update] Add settings api 2020-04-29 11:09:35 +08:00
ibuler
230d6137f3 Merge branch 'lina' of github.com:jumpserver/jumpserver into lina 2020-04-29 10:21:06 +08:00
ibuler
aa9533eb5b [Update] Groups users字段可以显示 2020-04-29 10:19:25 +08:00
Bai
23f9454e5d [Update] 修改Url name (oidc => openi) 2020-04-29 01:12:00 +08:00
Bai
9eee79f7d4 [Update] 调整openid backend顺序;openid 用户创建/更新添加日志输出 2020-04-29 00:43:54 +08:00
Bai
c0089a98f4 [Update] 修改openid信号名称 2020-04-28 22:29:56 +08:00
Bai
87242c13a1 [Update] 修改信号监听kwargs 2020-04-28 21:06:13 +08:00
Bai
184432a2a6 [Update] 更新OpenID的配置项以及对应的信号监听 2020-04-28 21:00:22 +08:00
BaiJiangJie
555861c319 Merge pull request #3943 from jumpserver/dev_conf
[Update] 修改config_example(openid)
2020-04-27 12:50:03 +08:00
Bai
a505995f49 [Update] 修改config_example(openid) 2020-04-27 11:36:11 +08:00
BaiJiangJie
b477b649f5 Merge pull request #3942 from jumpserver/dev_docker
[Update] 修改Dockerfile pypi 镜像源(阿里=>清华)
2020-04-26 22:41:49 +08:00
Michael Bai
6e18383531 [Update] 修改Dockerfile pypi 镜像源(阿里=>清华) 2020-04-26 22:40:56 +08:00
BaiJiangJie
7833ff6671 Dev oidc (#3941)
* [Update] oidc_rp获取token添加headers base64编码

* [Update] 移除对oidc_rp的支持

* [Update] 移除对oidc_rp的支持2

* [Update] 修改OpenID配置(添加新配置项,并对旧配置项做兼容)

* [Update] 移除所有与Keycloak相关的模块

* [Update] 添加jumpserver-django-oidc-rp的使用

* [Update] 更新登录重定向地址(oidc)

* [Update] oidc添加一些配置参数;处理用户登录/创建/更新等信号

* [Update] 修改退出登录逻辑

* [Update] 添加oidc user登录成功的信号机制

* [Update] 修改mfa认证choices内容 (otp => code)

* [Update] 添加OpenID backend password 认证失败信号机制;修改引入common包问题

* [Update] 用户Token/Auth API 校验用户时,传入request参数(解决登录成功日志记录的问题)

* [Update] 添加依赖jumpserver-django-oidc-rp==0.3.7.1

* [Update] oidc认证模块说明
2020-04-26 20:36:17 +08:00
BaiJiangJie
5d433456d4 Merge pull request #3937 from jumpserver/dev_index
[Update] 更新仪表盘数据显示问题
2020-04-24 11:30:30 +08:00
Bai
fc5ec3f21c [Update] 更新仪表盘数据显示问题 2020-04-24 11:29:01 +08:00
ibuler
efb5d4135a [Update] 优化api字段显示 2020-04-23 11:14:02 +08:00
ibuler
5f2c9c3801 [Update] 添加 authentication backend header 2020-04-22 15:13:04 +08:00
BaiJiangJie
56f38e57bc Merge pull request #3932 from jumpserver/dev_oidc
[Update] 添加oidc-op配置信息
2020-04-22 11:11:47 +08:00
Bai
586c04cba6 [Update] 添加oidc-op配置信息 2020-04-22 11:09:13 +08:00
Bai
306605915c [Update] 添加依赖 django-oidc-rp==0.3.4 2020-04-22 00:31:59 +08:00
BaiJiangJie
272701a8fd Dev oidc (#3930)
* [Update] 添加django-oidc-rp支持

* [Update] 添加django-oidc-rp支持2

* [Update] 调试django-oidc-rp对keycloak的支持

* [Update] 调试django-oidc-rp对keycloak的支持2

* [Update] 修改oidc_rp创建用户/更新用户的功能

* [Update] oidc_rp添加支持password认证

* [Update] 重写oidc_rp end session view

* [Update] 优化 oidc_rp view backend url 等引用关系
2020-04-22 00:22:24 +08:00
BaiJiangJie
9febe488b5 Merge pull request #3929 from jumpserver/lina_terminal
[Update] add terminal model fileds
2020-04-21 22:23:53 +08:00
Eric
c8c6ba1c19 [Update] add terminal model fileds 2020-04-21 17:37:29 +08:00
老广
2df7bd8510 Merge pull request #3926 from jumpserver/fix_otp_error
Fix otp error
2020-04-20 17:00:54 +08:00
老广
c16a986c4b Merge pull request #3925 from jumpserver/bugfix_many_sqlquery_perm_asset
[Bugfix] 修复查询授权时额外查询platform的问题
2020-04-20 16:58:14 +08:00
ibuler
44db0e8a5d [Bugfix] 修复查询授权时额外查询platform的问题 2020-04-20 16:56:47 +08:00
ibuler
91c994924f [Update] 修改报错文案 2020-04-20 10:44:45 +08:00
ibuler
61407331bc [Bugfix] 修复otp登录时导致的500 2020-04-20 10:37:07 +08:00
BaiJiangJie
1b1a686b96 Merge pull request #3922 from jumpserver/dev
Dev
2020-04-18 23:19:11 +08:00
BaiJiangJie
cc30a20b7c Merge pull request #3921 from jumpserver/dev
Dev
2020-04-18 23:15:42 +08:00
BaiJiangJie
0379e5160c [Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题2 (#3919)
* [Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题2

* [Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题3

* [Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题4
2020-04-18 23:14:25 +08:00
BaiJiangJie
386ce629ac Merge pull request #3918 from jumpserver/fix_connectivity
[Update] 修复资产页面可连接性
2020-04-18 10:42:55 +08:00
BaiJiangJie
5677bf0995 Merge pull request #3915 from jumpserver/dev
Dev
2020-04-18 01:27:09 +08:00
BaiJiangJie
c17dc26f8c Merge pull request #3916 from jumpserver/dev_org
[Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题
2020-04-18 01:24:21 +08:00
Bai
b4cc2bbff9 [Bugfix] 修复真实组织下,新建的用户角色显示不稳定的问题 2020-04-18 01:22:48 +08:00
BaiJiangJie
e74289b223 [Update] 修改用户密码重置页面错误信息提示2 (#3914) 2020-04-17 23:41:50 +08:00
ibuler
98afa032a7 [Update] 修复资产页面可连接性 2020-04-17 16:32:38 +08:00
BaiJiangJie
0d3fab216b Merge pull request #3910 from jumpserver/dev_user
[Update] 修改用户密码重置页面错误信息提示
2020-04-17 11:27:26 +08:00
Bai
081f4b1c0a [Update] 修改用户密码重置页面错误信息提示 2020-04-17 11:26:29 +08:00
BaiJiangJie
e9fe5b3004 Merge pull request #3908 from jumpserver/dev
Dev
2020-04-16 18:30:54 +08:00
BaiJiangJie
1de2923dd3 Merge pull request #3907 from jumpserver/dev_user
[Update] 修改忘记密码页面的错误信息提示
2020-04-16 18:30:07 +08:00
Bai
6890d549ed [Update] 修改忘记密码页面的错误信息提示 2020-04-16 18:29:02 +08:00
老广
9426f58a6b Merge pull request #3904 from jumpserver/dev
[Bugfix] 修复显示未授权资产的bug
2020-04-15 23:17:08 -05:00
ibuler
6a2a0013a8 [Update] 去掉debug 2020-04-16 12:12:24 +08:00
ibuler
864a4c0485 [Bugfix] 修复显示未授权资产的bug 2020-04-16 12:08:52 +08:00
BaiJiangJie
8fa08d7ea3 Merge pull request #3902 from jumpserver/dev
Dev
2020-04-16 11:31:38 +08:00
ibuler
3648a1458b [Update] 修改command options get view filter 2020-04-16 11:27:46 +08:00
BaiJiangJie
5001f48982 Merge pull request #3899 from jumpserver/dev
Dev
2020-04-14 19:46:36 +08:00
BaiJiangJie
93bf15adc9 Merge pull request #3898 from jumpserver/dev_email
[Update] 修改测试邮件后,配置获取不稳定的Bug(显示给settings赋值导致)
2020-04-14 19:45:06 +08:00
Bai
83eb8f77d1 [Update] 修改测试邮件后,配置获取不稳定的Bug(显示给settings赋值导致) 2020-04-14 19:40:56 +08:00
BaiJiangJie
4eaaa2462b Merge pull request #3897 from jumpserver/dev
Dev
2020-04-14 17:53:52 +08:00
BaiJiangJie
0910236f12 Merge pull request #3896 from jumpserver/dev_bai
Dev bai
2020-04-14 17:52:49 +08:00
Bai
77149bf36c [Update] 修改监控多个会话时,总是覆盖同一个tab页 2020-04-14 17:50:47 +08:00
Bai
6337d0d0a1 [Update] 修改会话can_join为property 2020-04-14 16:12:24 +08:00
BaiJiangJie
093e3924a2 Merge pull request #3894 from jumpserver/dev
Dev
2020-04-14 16:00:08 +08:00
BaiJiangJie
e143408e57 Merge pull request #3893 from jumpserver/dev_req
[Update] 修改依赖版本jms-storage=0.0.29
2020-04-14 15:58:24 +08:00
Bai
dfdf33bbb5 [Update] 修改依赖版本jms-storage=0.0.29 2020-04-14 15:54:59 +08:00
BaiJiangJie
8fd5e6521f Merge pull request #3892 from jumpserver/dev_bai
Dev bai
2020-04-14 13:20:50 +08:00
BaiJiangJie
6a2b9cd9bf Merge pull request #3891 from jumpserver/dev_bai
[Update] 修复系统设置-终端设置-资产排序设置不生效的Bug
2020-04-14 13:19:57 +08:00
Bai
55fa5800b1 [Update] 修复系统设置-终端设置-资产排序设置不生效的Bug 2020-04-14 13:18:33 +08:00
BaiJiangJie
6cdba2e8d2 Merge pull request #3890 from jumpserver/dev_bai
Dev bai
2020-04-14 12:56:20 +08:00
BaiJiangJie
043aa08f23 Merge pull request #3889 from jumpserver/dev_bai
Dev bai
2020-04-14 12:55:35 +08:00
Bai
63519ec076 [Update] 修改命令记录/操作/改密日志搜索字段没清空的问题 2020-04-14 12:51:13 +08:00
Bai
48fa26a3bc [Update] 修改操作日志搜索字段没清空的问题 2020-04-14 11:03:12 +08:00
Bai
d969563e43 [Update] 修复自定义平台列表在详情页删除失败的bug 2020-04-14 10:42:40 +08:00
BaiJiangJie
287236a447 Merge pull request #3885 from jumpserver/dev_trans
[Update] 更新翻译
2020-04-13 19:23:38 +08:00
Bai
4fb4a9f622 [Update] 更新翻译 2020-04-13 19:21:53 +08:00
BaiJiangJie
4dcd4749c3 Merge pull request #3884 from jumpserver/dev
Dev
2020-04-13 18:24:54 +08:00
BaiJiangJie
508d1c2c1f Merge pull request #3883 from jumpserver/master_bai
Master bai
2020-04-13 18:23:43 +08:00
BaiJiangJie
600ea42633 Merge pull request #3882 from jumpserver/master_bai
[Update] 修改OpenID用户获取username的问题
2020-04-13 18:22:23 +08:00
Bai
41cbd3e0f6 [Update] 修改OpenID用户获取username的问题 2020-04-13 18:21:49 +08:00
BaiJiangJie
e593ccb01c Merge pull request #3879 from jumpserver/dev_bai
Dev bai
2020-04-13 14:22:31 +08:00
Bai
d5e1ca7908 [Update] 添加翻译 2020-04-13 14:19:42 +08:00
Bai
06fb502a1d [Update] 修改文案: 加入 -> 监控 2020-04-13 14:17:08 +08:00
Bai
3f6d7637c3 [Update] 更新用户添加CAS来源选项 2020-04-13 13:40:13 +08:00
ibuler
0d2b4d7ca3 [Update] 添加ids filter 2020-04-13 10:40:13 +08:00
BaiJiangJie
42c5c02709 Merge pull request #3871 from jumpserver/lina_dev
Lina dev
2020-04-10 14:50:05 +08:00
Bai
d1d73da322 [Update] Merge from dev 2020-04-10 14:46:35 +08:00
BaiJiangJie
88fcf8dbd7 Merge pull request #3869 from jumpserver/lina_user
[Update] 修改用户Profile序列类返回的admin_orgs
2020-04-10 12:49:57 +08:00
Bai
396bc9b6ae [Update] 修改用户ViewSet序列类返回的admin_orgs(解决组织管理员登录的Bug) 2020-04-10 12:47:16 +08:00
BaiJiangJie
27ffed1be9 Merge pull request #3868 from jumpserver/dev_user
[Update] 修改用户ViewSet序列类返回的admin_orgs(解决组织管理员登录的Bug)
2020-04-10 12:40:12 +08:00
Bai
af9d42695f [Update] 修改用户ViewSet序列类返回的admin_orgs(解决组织管理员登录的Bug) 2020-04-10 12:38:05 +08:00
BaiJiangJie
422b4424fe Merge pull request #3867 from jumpserver/dev_ab
[Update] 修复AuthBook删除失败的Bug
2020-04-09 23:40:18 +08:00
Michael Bai
dc172e1ef0 [Update] 修复AuthBook删除失败的Bug 2020-04-09 23:39:17 +08:00
ibuler
cd7946f3f0 [Update] 修改一些bug 2020-04-09 18:58:22 +08:00
BaiJiangJie
4994a4a387 Dev bai (#3865)
* [Update] 更新config-example(CAS)

* [Update] 更新config-example(CAS)2

* [Update] 更新config-example(CAS)3
2020-04-09 13:39:11 +08:00
BaiJiangJie
e26eba9919 Merge pull request #3864 from jumpserver/1.5.8
1.5.8
2020-04-09 11:36:38 +08:00
Bai
44cd82f3e1 [Update] 修改翻译 2020-04-09 10:59:00 +08:00
Bai
adf14b0f2e [Update] 修改版本号 2020-04-09 10:46:24 +08:00
Bai
f5a7c4131d [Update] 修改校验join session的url-name 2020-04-09 10:43:25 +08:00
ibuler
e7031d0ac1 [Update] 修改serailizer mixin 2020-04-09 10:33:20 +08:00
Bai
ef27188f86 [Update] 修改Bug: CommandViewSet的Option方法Error 2020-04-08 18:08:22 +08:00
BaiJiangJie
a7734812fc Merge pull request #3860 from jumpserver/1.5.8_bai
[Update] 命令记录导出: 只导出满足搜索条件的数据
2020-04-08 01:35:16 +08:00
Michael Bai
fb62fbde6c [Update] 命令记录导出: 只导出满足搜索条件的数据 2020-04-08 01:34:10 +08:00
BaiJiangJie
521f4ea86b Merge pull request #3857 from jumpserver/1.5.8_bai
1.5.8 bai
2020-04-07 12:32:54 +08:00
Bai
e9827c8b25 [Update] 添加会话加入校验API 2020-04-07 12:26:47 +08:00
Bai
29b099efc0 [Update] 添加加入会话按钮 2020-04-02 19:45:56 +08:00
Bai
e9fc56c056 [Update] 修改日志日期显示问题 2020-04-02 16:02:55 +08:00
Bai
e9103ee608 [Update] 对自定义MetaDict字段值进行strip 2020-04-02 15:14:30 +08:00
ibuler
f8dae2a3c9 [Update] 时区允许设置 2020-04-01 17:27:32 +08:00
BaiJiangJie
52c6244b2b Merge pull request #3842 from jumpserver/dev
Dev
2020-03-28 21:36:54 +08:00
BaiJiangJie
7977294c5c Merge pull request #3841 from jumpserver/dev
Dev
2020-03-28 21:36:09 +08:00
BaiJiangJie
e1331084e5 Merge pull request #3840 from jumpserver/dev_readme
Dev readme
2020-03-28 21:35:23 +08:00
Michael Bai
5268c0faae [Update] OpenID登录成功后,如果用户没有email字段则构造默认的email 2020-03-28 21:34:17 +08:00
Michael Bai
b2d9b69874 [Update] 修改readme 2020-03-28 21:26:27 +08:00
BaiJiangJie
336589af98 Merge pull request #3836 from jumpserver/1.5.8_cap
1.5.8 cap
2020-03-27 11:28:39 +08:00
Bai
5087c0e06f Merge branch 'dev' into 1.5.8_cap 2020-03-27 11:26:09 +08:00
BaiJiangJie
43ecd39d0e Merge pull request #3835 from jumpserver/dev
Dev
2020-03-27 11:09:09 +08:00
BaiJiangJie
209b23c5ce Merge pull request #3834 from jumpserver/dev_req
[Update] 修改依赖jms-storage==0.0.28
2020-03-27 11:08:42 +08:00
Bai
7e7d4401e6 [Update] 修改依赖jms-storage==0.0.28 2020-03-27 11:07:55 +08:00
BaiJiangJie
b4c3471d2c Merge pull request #3833 from jumpserver/dev
Dev
2020-03-27 10:48:09 +08:00
BaiJiangJie
f4a822062f Merge pull request #3829 from jumpserver/dev_bai
[Update] 优化创建AuthBook对象
2020-03-26 13:13:35 +08:00
Bai
4583beaec0 [Update] 创建AuthBook对象的方法从ModelManager中移动到Model中,保留原生的对象create方法;同时添加事务处理; 2020-03-26 13:11:38 +08:00
BaiJiangJie
42a6feb35e Merge pull request #3822 from jumpserver/dev_lock
[Update] 优化创建AuthBook逻辑;添加锁机制(基于redis-lock);
2020-03-24 23:20:38 +08:00
Bai
4503df910d [Update] 优化创建AuthBook逻辑;添加锁机制(基于redis-lock); 2020-03-24 18:40:20 +08:00
BaiJiangJie
a09b0c6c06 Merge pull request #3818 from jumpserver/dev_bai
[Update] 修改任务执行日期显示本地时间
2020-03-24 08:57:45 +08:00
Michael Bai
55c7e06185 [Update] 修改任务执行日期显示本地时间 2020-03-24 01:37:09 +08:00
老广
a95a0da6f7 Merge pull request #3816 from jumpserver/dev
[Bugfix] 修复因数据不支持timezone引起的仪表盘数据为空的bug
2020-03-23 17:56:45 +08:00
ibuler
0fd43f48f0 [Bugfix] 修复因数据不支持timezone引起的仪表盘数据为空的bug 2020-03-23 17:55:28 +08:00
ibuler
dffd05cd20 [Update] 修改组织获取优先级 2020-03-23 10:52:26 +08:00
BaiJiangJie
681046119d Merge pull request #3811 from jumpserver/dev
Dev
2020-03-23 03:22:00 +08:00
BaiJiangJie
b19c49da41 Merge pull request #3810 from jumpserver/dev_bai
[Update] 修改翻译;升级依赖;
2020-03-23 03:20:30 +08:00
Michael Bai
cc31c04b5f [Update] 升级依赖 2020-03-23 03:15:51 +08:00
Michael Bai
89f62d8e6b [Update] 更新翻译 2020-03-23 03:06:53 +08:00
Michael Bai
41b73c3701 [Update] 修改提示播放器下载地址 2020-03-23 03:02:22 +08:00
BaiJiangJie
cff3a790ef Merge pull request #3809 from jumpserver/dev_bai
[Update] 修改Node generate fake时死循环的问题
2020-03-23 02:50:06 +08:00
Michael Bai
8328edd69c [Update] 修改Node generate fake时死循环的问题 2020-03-23 02:32:24 +08:00
ibuler
9bf5d6dd45 [Update] 用户profile中添加orgs 2020-03-20 18:21:27 +08:00
BaiJiangJie
ac9a0c6d26 Merge pull request #3803 from jumpserver/dev
[Update] 禁用ansible连接复用
2020-03-20 17:55:38 +08:00
ibuler
064cb16d25 [Update] 禁用ansible连接复用 2020-03-20 16:45:38 +08:00
BaiJiangJie
f89b1fd44b Merge pull request #3798 from jumpserver/dev
[Bugfix] 修复重置并下载ssh key的bug
2020-03-19 16:28:34 +08:00
ibuler
b25096925b [Bugfix] 修复重置并下载ssh key的bug 2020-03-19 16:28:08 +08:00
BaiJiangJie
e8ff576324 Merge pull request #3797 from jumpserver/dev
[Update] 修复收藏资产数量不对的问题
2020-03-19 15:43:44 +08:00
ibuler
5ac1467564 [Update] 修复收藏数量不对的问题 2020-03-19 15:41:41 +08:00
BaiJiangJie
1c54e5acd8 Merge pull request #3796 from jumpserver/dev
Dev
2020-03-19 14:26:23 +08:00
ibuler
b8d0272e37 [Update] 修改测试系统用户显示 2020-03-19 11:47:39 +08:00
ibuler
0b7a90b83c Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-03-19 11:13:34 +08:00
ibuler
7cf000262d [Update] django cas ng 退出逻辑有问题,暂时屏蔽 2020-03-19 11:11:18 +08:00
BaiJiangJie
fa7e0d84f9 Merge pull request #3794 from jumpserver/dev_bai
[Update] 修改批量移除节点时不选择节点给出提示信息
2020-03-19 10:27:26 +08:00
Bai
98f6d0146c [Update] 修改批量移除节点时不选择节点给出提示信息 2020-03-19 10:21:19 +08:00
ibuler
d0fac3f838 [Bugfix] 修复task 列表、 2020-03-18 19:58:08 +08:00
ibuler
a41988d2b5 [Bugfix] 修改配置文件,支持cas proxy 2020-03-18 19:10:27 +08:00
BaiJiangJie
b801d2f2e9 Update README.md 2020-03-18 17:16:53 +08:00
BaiJiangJie
6bed9210dd Merge pull request #3793 from jumpserver/dev_bai
[Update] 修改readme2
2020-03-18 17:15:15 +08:00
Bai
eac4630272 [Update] 修改readme2 2020-03-18 17:14:21 +08:00
BaiJiangJie
11a0d72b01 Merge pull request #3792 from jumpserver/dev_readme
[Update] 修改readme
2020-03-18 17:09:21 +08:00
Bai
7dcd04ca1a [Update] 修改readme 2020-03-18 17:03:49 +08:00
BaiJiangJie
ccfb151fb1 Merge pull request #3791 from jumpserver/dev_version
[Update] 更新版本号
2020-03-18 16:43:12 +08:00
Bai
ae98eee0c7 [Update] 更新版本号 2020-03-18 16:34:37 +08:00
BaiJiangJie
829a4406a2 Merge pull request #3790 from jumpserver/dev_bai
[Update] 修改执行task时切换root组织2
2020-03-18 14:04:14 +08:00
Bai
91c42e8530 [Update] 修改执行task时切换root组织2 2020-03-18 14:02:36 +08:00
BaiJiangJie
250acea751 Merge pull request #3789 from jumpserver/dev_bai
[Update] 修改执行task时切换root组织
2020-03-18 13:49:04 +08:00
Bai
00d9f71384 [Update] 修改执行task时切换root组织 2020-03-18 13:45:49 +08:00
BaiJiangJie
ffce909ee3 Merge pull request #3783 from jumpserver/bugfix
Bugfix
2020-03-18 11:05:51 +08:00
BaiJiangJie
0e62ea787c Merge pull request #3785 from jumpserver/login_image_2
[Fix] 修改登录图片亮度
2020-03-18 10:44:55 +08:00
jym503558564
cedb862420 [Fix] 修改登录图片亮度 2020-03-17 17:28:53 +08:00
Bai
a6054ff6a5 [Update] 修复批量删除资产,当待删除数量和资产总数相等时,提示删除失败。 2020-03-17 17:06:59 +08:00
Bai
a67b445026 [Update] 修复录像存储ceph的help_text 2020-03-17 16:15:37 +08:00
BaiJiangJie
8d33990050 Merge pull request #3782 from jumpserver/login_image_2
[Fix] 修改登录界面LOGO
2020-03-17 15:32:18 +08:00
jym503558564
516e75cbf4 [Fix] 修改登录界面LOGO 2020-03-17 15:30:59 +08:00
Bai
15ca775005 [Update] 删除推送系统用户时关于task的log 2020-03-17 15:21:12 +08:00
Bai
41ca43bf33 Merge branch 'bugfix' of https://github.com/jumpserver/jumpserver into bugfix 2020-03-17 15:02:56 +08:00
Bai
c6a604fd5f [Update] 修复显示创建者字段值的问题 2020-03-17 15:02:44 +08:00
ibuler
8e84efb296 [Update] 修改一些文案和翻译 2020-03-17 13:51:41 +08:00
ibuler
caee286973 [Update] 修改华为云始终有效的bug 2020-03-17 10:59:49 +08:00
ibuler
672b82c3d6 [Bugfix] 修复可能无法找到command execution的bug 2020-03-16 19:24:48 +08:00
ibuler
55554a025f [Bugfix] 修复动态系统用户更新用户时无法推送的bug, 修复通过relation api无法触发m2m_change的bug 2020-03-16 19:15:29 +08:00
ibuler
c91ce2b99f [Bugfix] 修复动态系统用户返回auth info的bug 2020-03-16 16:59:45 +08:00
ibuler
02a901467b [Bugfix] 修复动态系统用户登录windows的bug 2020-03-16 16:13:54 +08:00
ibuler
00b3c7c945 [Bugfix] 修复动态系统用户执行批量命令的bug 2020-03-16 15:23:15 +08:00
ibuler
2f9598ba49 [Update] 修复ops task中任务某版本任务的bug 2020-03-16 14:41:24 +08:00
ibuler
3138abb00c [Bugfix] 修复ops列表任务无法删除的bug 2020-03-16 14:18:29 +08:00
ibuler
3afb8647bd Merge branch 'dev' of github.com:jumpserver/jumpserver into dev 2020-03-13 13:57:42 +08:00
ibuler
0682f4fc90 [Update] 修改readme 2020-03-13 13:57:21 +08:00
BaiJiangJie
44ec69fdbd Merge pull request #3775 from jumpserver/dev_bai
[Update] 修改只有local用户需要发送创建成功邮件、发送密码过期邮件;
2020-03-13 12:36:40 +08:00
Bai
04945809a5 [Update] 修改只有local用户需要发送创建成功邮件、发送密码过期邮件; 2020-03-13 12:33:09 +08:00
BaiJiangJie
e38b113d7e Merge pull request #3771 from jumpserver/dev_trans
[Update] 更新翻译
2020-03-12 19:05:50 +08:00
Bai
eb448dc3f2 [Update] 更新翻译 2020-03-12 19:00:22 +08:00
BaiJiangJie
954a97bba7 Merge pull request #3770 from jumpserver/dev_req
[Update] 更新依赖jms-storage=0.0.27
2020-03-12 16:34:33 +08:00
Bai
41e03c629f [Update] 更新依赖jms-storage=0.0.27 2020-03-12 16:33:33 +08:00
老广
1fd2e782f8 1.5.7 Merge to dev (#3766)
* [Update] 暂存,优化解决不了问题

* [Update] 待续(小白)

* [Update] 修改asset user

* [Update] 计划再次更改

* [Update] 修改asset user

* [Update] 暂存与喜爱

* [Update] Add id in

* [Update] 阶段性完成ops task该做

* [Update] 修改asset user api

* [Update] 修改asset user 任务,查看认证等

* [Update] 基本完成asset user改造

* [Update] dynamic user only allow 1

* [Update] 修改asset user task

* [Update] 修改node admin user task api

* [Update] remove file header license

* [Update] 添加sftp root

* [Update] 暂存

* [Update] 暂存

* [Update] 修改翻译

* [Update] 修改系统用户改为同名后,用户名改为空

* [Update] 基本完成CAS调研

* [Update] 支持cas server

* [Update] 支持cas server

* [Update] 添加requirements

* [Update] 为方便调试添加mysql ipython到包中

* [Update] 添加huaweiyun翻译

* [Update] 增加下载session 录像

* [Update] 只有第一次通知replay离线的使用方法

* [Update] 暂存一下

* [Bugfix] 获取系统用户信息报错

* [Bugfix] 修改system user info

* [Update] 改成清理10天git status

* [Update] 修改celery日志保留时间

* [Update]修复部分pip包依赖的版本不兼容问题 (#3672)

* [Update] 修复用户更新页面会清空用户public_key的问题

* Fix broken dependencies

Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>

* [Update] 修改获取系统用户auth info

* [Update] Remove log

* [Bugfix] 修复sftp home设置的bug

* [Update] 授权的系统用户添加sftp root

* [Update] 修改系统用户关联的用户

* [Update] 修改placeholder

* [Update] 优化获取授权的系统用户

* [Update] 修改tasks

* [Update] tree service update

* [Update] 暂存

* [Update] 基本完成用户授权树和资产树改造

* [Update] Dashbaord perf

* [update] Add huawei cloud sdk requirements

* [Updte] 优化dashboard页面

* [Update] system user auth info 添加id

* [Update] 修改系统用户serializer

* [Update] 优化api

* [Update] LDAP Test Util (#3720)

* [Update] LDAPTestUtil 1

* [Update] LDAPTestUtil 2

* [Update] LDAPTestUtil 3

* [Update] LDAPTestUtil 4

* [Update] LDAPTestUtil 5

* [Update] LDAPTestUtil 6

* [Update] LDAPTestUtil 7

* [Update] session 已添加is success,并且添加display serializer

* [Bugfix] 修复无法删除空节点的bug

* [Update] 命令记录分组织显示

* [Update] Session is_success 添加迁移文件

* [Update] 批量命令添加org_id

* [Update] 修复一些文案,修改不绑定MFA,不能ssh登录

* [Update] 修改replay api, 返回session信息

* [Update] 解决无效es导致访问命令记录页面失败的问题

* [Update] 拆分profile view

* [Update] 修改一个翻译

* [Update] 修改aysnc api框架

* [Update] 命令列表添加risk level

* [Update] 完成录像打包下载

* [Update] 更改登陆otp页面

* [Update] 修改command 存储redis_level

* [Update] 修改翻译

* [Update] 修改系统用户的用户列表字段

* [Update] 使用新logo和统一Jumpserver为JumpServer

* [Update] 优化cloud task

* [Update] 统一period task

* [Update] 统一period form serializer字段

* [Update] 修改period task

* [Update] 修改资产网关信息

* [Update] 用户授权资产树资产信息添加domain

* [Update] 修改翻译

* [Update] 测试可连接性

* 1.5.7 bai (#3764)

* [Update] 修复index页面Bug;修复测试资产用户可连接性问题;

* [Update] 修改测试资产用户可连接

* [Bugfix] 修复backends问题

* [Update] 修改marksafe依赖版本

* [Update] 修改测试资产用户可连接性

* [Update] 修改检测服务器性能时获取percent值

* [Update] 更新依赖boto3=1.12.14

Co-authored-by: Yanzhe Lee <lee.yanzhe@yanzhe.org>
Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
Co-authored-by: Bai <bugatti_it@163.com>
2020-03-12 16:24:38 +08:00
老广
1f6a8e8f02 Merge pull request #3698 from jumpserver/readme
[Update] 修改README中 jumpserver ==> JumpServer
2020-03-12 10:39:26 +08:00
老广
eca0a9a7d7 Merge pull request #3723 from jumpserver/settings_bug
Settings bug
2020-02-26 16:19:39 +08:00
老广
775f8f2ffd Merge pull request #3722 from jumpserver/settings_bug
Settings bug
2020-02-26 16:18:24 +08:00
ibuler
1e8ef8c925 [Update] Merge with master 2020-02-26 16:15:42 +08:00
ibuler
c354b55f61 [update] 修复获取settings有问题的bug 2020-02-26 16:12:41 +08:00
ibuler
76ac0215fe [update] 修复获取settings有问题的bug 2020-02-26 16:12:08 +08:00
老广
4f93a3ca92 Merge pull request #3716 from jumpserver/dev
Dev
2020-02-24 12:08:32 +08:00
ibuler
bcae30814d [Update] 修改导入 2020-02-24 12:06:02 +08:00
老广
98bb6c63f5 [Update] 修改切换组织后页面跳转 (#3715) 2020-02-24 12:00:20 +08:00
老广
8b8b11ce1e [Update] 恢复到原来的sql (#3707) 2020-02-15 20:49:20 +08:00
BaiJiangJie
5f61f2b555 Merge pull request #3702 from jumpserver/dev_interface
[Update] 修改终端获取登录标题配置的逻辑
2020-02-14 10:45:37 +08:00
Bai
62d2e01cdf [Update] 修改终端获取登录标题配置的逻辑 2020-02-14 10:40:18 +08:00
jym503558564
5e89ee9202 [Update] 修改README中 jumpserver ==> JumpServer 2020-02-13 19:58:14 +08:00
老广
5d313a827b [Update] 优化session 索引 is_finished (#3697) 2020-02-13 15:17:13 +08:00
ibuler
edf6baa52d [Update] 修改api创建的token有效期是600s 2020-02-07 17:24:52 +08:00
老广
3c69860b24 [Update] 优化sql, or方式改为union (#3682)
* [Update] 优化sql, or方式改为union

* [Update] 优化union操作,直接union后,queryset 的一些参数不能使用,如annoate, 如filter assets__isnull=True
2020-02-05 15:56:28 +08:00
老广
aa2255a87e [Update] sql优化,查询用户资产权限时,使用union替代or (#3681) 2020-02-05 12:10:24 +08:00
BaiJiangJie
3cc9e0a66c Merge pull request #3680 from jumpserver/dev
Dev
2020-02-03 14:25:24 +08:00
BaiJiangJie
ec1f6677ec Merge pull request #3679 from jumpserver/dev_session
[Bugfix] 处理心跳时guacamole上报的session为str处理为list
2020-02-03 14:08:37 +08:00
Bai
ae98fb4332 [Bugfix] 处理心跳时guacamole上报的session为str处理为list 2020-02-03 13:54:59 +08:00
Yanzhe Lee
29730757b8 Infer User.name from OpenID UserInfo.name (#3674)
Signed-off-by: YanzheL <lee.yanzhe@yanzhe.org>
2020-01-27 10:15:06 +08:00
BaiJiangJie
1c2feedb27 Merge pull request #3661 from jumpserver/dev
Dev
2020-01-20 11:36:18 +08:00
BaiJiangJie
b227d9cdc1 Merge pull request #3660 from jumpserver/dev_auth
[Update] 修复用户更新页面会清空用户public_key的问题
2020-01-20 11:28:40 +08:00
Bai
da6a0c286d [Update] 修复用户更新页面会清空用户public_key的问题 2020-01-20 11:27:34 +08:00
BaiJiangJie
13a042bc0f Merge pull request #3658 from jumpserver/dev_loginip
[Update] 修改获取用户登录的ip和type
2020-01-19 12:14:34 +08:00
Bai
7f9644dbac [Update] 修改获取用户登录的ip和type 2020-01-19 12:13:42 +08:00
BaiJiangJie
ece9b16351 Merge pull request #3651 from jumpserver/dev
Dev
2020-01-16 11:39:26 +08:00
BaiJiangJie
23b896b301 Merge pull request #3650 from jumpserver/dev_i18n
[Update] 翻译应用树更节点(API)
2020-01-16 11:31:55 +08:00
Bai
dd52baae12 [Update] 翻译应用树更节点(API) 2020-01-16 11:31:08 +08:00
BaiJiangJie
eb1ca4c0f2 Merge pull request #3649 from jumpserver/dev_copyright
[Update] 修改copyright
2020-01-16 10:36:48 +08:00
Bai
8e75e519fa [Update] 修改copyright 2020-01-16 10:35:00 +08:00
BaiJiangJie
389c6b5a84 Merge pull request #3648 from jumpserver/dev_i18n
[Update] 更新翻译
2020-01-16 09:59:22 +08:00
Bai
6fc7a4cb21 [Update] 更新翻译 2020-01-16 09:58:00 +08:00
BaiJiangJie
68455156a3 Merge pull request #3646 from jumpserver/dev_ops
[Update] 更新命令批量执行资产树(将没有ssh协议的资产设置为nocheck)
2020-01-15 10:47:39 +08:00
Bai
2827a64095 [Update] 更新命令批量执行资产树(将没有ssh协议的资产设置为nocheck) 2020-01-15 10:46:41 +08:00
BaiJiangJie
c21505e92d Merge pull request #3644 from jumpserver/dev_ops
[Update] 批量命令执行开放rdp系统用户
2020-01-14 17:34:40 +08:00
Bai
7d855e5ad8 [Update] 批量命令执行开放rdp系统用户 2020-01-14 17:33:41 +08:00
BaiJiangJie
17956bf0db Merge pull request #3633 from jumpserver/dev
Dev
2020-01-14 16:56:04 +08:00
BaiJiangJie
267a7fc9f7 Merge pull request #3643 from jumpserver/dev_i18n
[Update] 更新翻译
2020-01-14 16:55:44 +08:00
Bai
9ef4762817 [Update] 更新翻译 2020-01-14 16:54:52 +08:00
八千流
29457ad867 Assets_permission_action 优化 (#3585)
* [Update] 初步实现 actions的checkbox层叠嵌套

* [Update] 优化actions的checkbox层叠嵌套

* [Update] 修改校验用户/系统用户/资产的权限API可能会出现的问题

* [Update] 更新资产授权中动作的展示

* [Update] 更新资产授权中动作的展示 2

* [Update] 更新资产授权中动作的展示 3

Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com>
2020-01-14 16:44:28 +08:00
BaiJiangJie
d5082d1379 Merge pull request #3639 from jumpserver/dev_su_detail
[Update] 系统用户详情(mysql)开放命令过滤器
2020-01-14 11:08:32 +08:00
Bai
c07536b26f [Update] 系统用户详情(mysql)开放命令过滤器 2020-01-14 11:07:35 +08:00
BaiJiangJie
678999b9f5 Merge pull request #3638 from jumpserver/dev_su_mysql
[Update] 系统用户(mysql)开放命令过滤器
2020-01-14 11:03:29 +08:00
Bai
4295aec492 [Update] 系统用户(mysql)开放命令过滤器 2020-01-14 10:53:52 +08:00
BaiJiangJie
75a2b9eac2 Merge pull request #3636 from jumpserver/dev_perms
[Update] 授权的 RemoteApp/DatabaseApp 进行去重
2020-01-13 18:57:33 +08:00
Bai
2fc68ca6f1 [Update] 授权的 RemoteApp/DatabaseApp 进行去重 2020-01-13 18:56:58 +08:00
BaiJiangJie
56a156b717 Merge pull request #3634 from jumpserver/dev_req
[Update] 修改依赖
2020-01-13 17:28:44 +08:00
Bai
7c59134635 [Update] 修改依赖 2020-01-13 17:28:00 +08:00
BaiJiangJie
f98f0e2c06 Merge pull request #3632 from jumpserver/dev_i18n
Dev i18n
2020-01-13 14:24:41 +08:00
Bai
63746accf9 [Update] 修改js默认语言为中文 2020-01-13 14:23:33 +08:00
Bai
670e2d7352 [Update] 更新翻译 2020-01-13 14:13:45 +08:00
BaiJiangJie
db3b60faf9 Merge pull request #3631 from jumpserver/dev_admin_user
[Update] 修改删除管理用户(存在关联资产)的Bug
2020-01-13 14:09:39 +08:00
Bai
f1b8c1965d [Update] 修改删除管理用户(存在关联资产)的Bug 2020-01-13 14:07:42 +08:00
BaiJiangJie
aa2a77ee7e Merge pull request #3630 from jumpserver/dev_req
[Update] 修改依赖
2020-01-13 13:52:31 +08:00
Bai
b107d15097 [Update] 修改依赖 2020-01-13 13:51:59 +08:00
BaiJiangJie
52b9c400eb Merge pull request #3629 from jumpserver/dev
Dev
2020-01-13 12:51:13 +08:00
BaiJiangJie
851bd99eaf Merge pull request #3628 from jumpserver/dev_node
[Update] 修改资产树移动资产的问题
2020-01-13 12:50:49 +08:00
Bai
0c0eb843ab [Update] 修改资产树移动资产的问题 2020-01-13 12:50:01 +08:00
BaiJiangJie
bd19f8afe8 Merge pull request #3624 from jumpserver/dev
Dev
2020-01-10 18:27:49 +08:00
BaiJiangJie
bafffc95b8 Merge pull request #3621 from jumpserver/dev_node
[Update] 资产节点添加资产时,祖先节点授权的系统用户关联对应资产
2020-01-10 16:47:44 +08:00
Bai
14581ac775 [Update] 资产节点添加资产时,祖先节点授权的系统用户关联对应资产 2020-01-10 16:46:18 +08:00
BaiJiangJie
6ddd48bfb1 Merge pull request #3618 from jumpserver/dev_swift
[Update] 修改Swift录像存储创建参数及API返回值
2020-01-10 11:44:23 +08:00
Bai
ff1828bdd6 [Update] 修改Swift录像存储创建参数及API返回值 2020-01-10 11:42:53 +08:00
BaiJiangJie
84e5177ce2 Merge pull request #3614 from jumpserver/dev_ldap
[Update] 修改LDAP认证逻辑,密码为空时认证失败
2020-01-09 11:18:12 +08:00
Bai
1c9be184fd [Update] 修改LDAP认证逻辑,密码为空时认证失败 2020-01-09 11:16:13 +08:00
BaiJiangJie
57155c2469 [Update] 更新 Readme (#3608)
* [Update] readme 1

* [Update] readme 2

* [Update] readme 3

* [Update] readme 4

* [Update] readme 5
2020-01-08 12:17:56 +08:00
BaiJiangJie
f9578f4474 Merge pull request #3606 from jumpserver/dev
Dev
2020-01-07 17:34:07 +08:00
BaiJiangJie
a37e14221e Merge pull request #3603 from jumpserver/dev_systemuser
[Update] 更新系统用户认证信息private_key转换
2020-01-07 15:56:16 +08:00
Bai
005272cdc0 [Update] 更新系统用户认证信息private_key转换 2020-01-07 15:53:53 +08:00
BaiJiangJie
f88cb3da20 Merge pull request #3600 from jumpserver/dev_i18n
[Update] dateRange插件支持i18n
2020-01-06 18:24:14 +08:00
Bai
9f42dfb26e [Update] dateRange插件支持i18n 2 2020-01-06 18:23:35 +08:00
Bai
f702fc7d93 [Update] dateRange插件支持i18n 2020-01-06 18:21:12 +08:00
BaiJiangJie
05adb4e95f Merge pull request #3598 from jumpserver/dev_terminal
[Update] 更新Session表asset字段长度128
2020-01-06 15:36:51 +08:00
Bai
210a2d7fe6 [Update] 更新Session表asset字段长度128 2020-01-06 15:36:03 +08:00
BaiJiangJie
c9dc1ea254 Merge pull request #3596 from jumpserver/dev_ops
[Update] 修改作业中心批量命令资产树收藏夹循环现实的问题
2020-01-06 14:48:08 +08:00
Bai
79b5618756 [Update] 修改作业中心批量命令资产树收藏夹循环现实的问题 2020-01-06 14:46:59 +08:00
BaiJiangJie
bf922459ff [Update] 修改创建按钮样式;Open 数据库应用;取消资产/系统用户/组织等名称对于特殊字符的限制; (#3594)
* [Update] 修改创建按钮样式

* [Update] Open数据库应用

* [Update] 取消资产/系统用户/组织等名称对于特殊字符的限制
2020-01-06 11:43:37 +08:00
BaiJiangJie
a27fb18a17 Merge pull request #3591 from jumpserver/dev
Dev
2020-01-03 17:03:37 +08:00
BaiJiangJie
f62f750266 Merge pull request #3588 from jumpserver/dev_mfa
Dev mfa
2020-01-03 15:31:43 +08:00
BaiJiangJie
b072e98148 [Update] 修改确认用户认证成功和绑定MFA的前后逻辑(解决绕过绑定MFA的漏洞;解决管理员重置用户MFA后自动退出的问题) 2020-01-03 15:26:38 +08:00
BaiJiangJie
352bfeeb7a [Update] 绑定MFA页面(二维码显示) 2020-01-03 11:41:07 +08:00
BaiJiangJie
1d7bdd5f5f Merge pull request #3586 from jumpserver/dev_bai
[Bugfix] 修改BlockLoginError的msg值在初始化中设置(数据表没生成之前启动会有问题)
2020-01-02 16:25:07 +08:00
BaiJiangJie
8702761303 [Bugfix] 修改BlockLoginError的msg值在初始化中设置(数据表没生成之前启动会有问题) 2020-01-02 16:24:13 +08:00
BaiJiangJie
ce3cc80037 Merge pull request #3583 from jumpserver/dev_version
[Update] 修改版本号(v1.5.6)
2019-12-31 18:50:56 +08:00
BaiJiangJie
9556d33d1c [Update] 修改版本号(v1.5.6) 2019-12-31 18:49:21 +08:00
BaiJiangJie
02650c9cdc Merge pull request #3582 from jumpserver/dev_session
[Update] 添加清除幽灵mysql会话
2019-12-31 16:49:20 +08:00
BaiJiangJie
75c6047236 [Update] 添加清除幽灵mysql会话 2019-12-31 16:47:25 +08:00
BaiJiangJie
e6c369cfd8 Merge pull request #3581 from jumpserver/dev_session
[Update] 添加清除幽灵rdp会话
2019-12-31 15:39:04 +08:00
BaiJiangJie
131496bfee [Update] 添加清除幽灵rdp会话 2019-12-31 15:38:10 +08:00
BaiJiangJie
c084412e53 [Update] 解决Vault翻页再次选择资产时不能设置到select框的问题:select.style === single 时 (#3579)
* [Update] 解决Vault翻页再次选择资产时不能设置到select框的问题:select.style === single 时

* [Update] 优化1
2019-12-30 16:40:38 +08:00
BaiJiangJie
ad81d6c28e Merge pull request #3576 from jumpserver/dev_audit
[Update] 修改操作日志title文案:操作者
2019-12-30 12:27:58 +08:00
BaiJiangJie
08fb8f5d92 [Update] 修改操作日志title文案:操作者 2019-12-30 12:27:14 +08:00
BaiJiangJie
4df08f0521 Merge pull request #3575 from jumpserver/dev_storage
[Update] 命令存储ES字段设置为required
2019-12-30 11:55:25 +08:00
BaiJiangJie
c963937f00 [Update] 命令存储ES字段设置为required 2019-12-30 11:54:36 +08:00
BaiJiangJie
b64ae358fb Merge pull request #3572 from jumpserver/dev_bai
[Update] 修改右击节点rMenu菜单show offset
2019-12-27 16:59:30 +08:00
BaiJiangJie
42c5783e43 [Update] 修改右击节点rMenu菜单show offset 2019-12-27 16:42:36 +08:00
BaiJiangJie
a2350e7f1d Merge pull request #3571 from jumpserver/bugfix
[Bugfix] 修复vault导出选择id的bug
2019-12-27 16:05:28 +08:00
ibuler
b3114a1f3d [Bugfix] 修复vault导出选择id的bug 2019-12-27 16:03:45 +08:00
BaiJiangJie
b4cf540e51 [Update] 操作日志添加新的Record Model;用户登录日志采用同步机制;修改DatabaseAppAPI权限(加入AppUser); (#3570)
* [Update] 操作日志 Model Need Record 添加RemoteApp、DatabaseApp、DatabaseAppPermission

* [Update] 用户登录日志,采用同步机制

* [Update] 修改DatabaseApp API权限OrgAdmin和AppUser
2019-12-27 16:00:32 +08:00
BaiJiangJie
deeb9cdfa6 Merge pull request #3564 from jumpserver/dev_bai
[Update] 修改校验用户资产权限API不使用缓存
2019-12-25 10:30:45 +08:00
BaiJiangJie
7a6a1b9b59 [Update] 修改校验用户资产权限API不使用缓存 2019-12-25 10:29:58 +08:00
BaiJiangJie
15d1e021de Merge pull request #3559 from jumpserver/dev_command
[Update] 支持Windows批量命令
2019-12-23 11:23:46 +08:00
BaiJiangJie
f063832bc6 [Update] 支持Windows批量命令2 2019-12-23 11:22:46 +08:00
BaiJiangJie
1d30c1900d [Update] 支持Windows批量命令 2019-12-21 19:44:25 +08:00
BaiJiangJie
78a227af3e Merge pull request #3557 from jumpserver/dev_bai_bugfix
[Update] 修改Adhoc字段become可以为None
2019-12-20 17:38:56 +08:00
BaiJiangJie
e978308335 [Update] 修改Adhoc字段become可以为空 2019-12-20 17:36:06 +08:00
BaiJiangJie
7193d7fc1b Merge pull request #3556 from jumpserver/dev_trans
[Update] 更新翻译(用户详情页)
2019-12-20 16:20:43 +08:00
BaiJiangJie
d6a95d3f1a [Update] 更新翻译(用户详情页) 2019-12-20 16:19:51 +08:00
BaiJiangJie
829e1f4cac [Update] 修改用户详情页面 (#3555)
* [Update] 用户详情添加远程应用授权页面

* [Update] 用户详情添加授权的远程应用页面

* [Update] 用户详情添加授权的数据库应用页面

* [Update] 用户详情添加数据库应用授权页面

* [Update] 修改用户详情nav的active属性设置

* [Update] 修改用户详情页面导航

* [Update] 抽象用户详情页面

* [Update] 修改用户详情页面

* [Update] 修改用户详情页面nav header
2019-12-20 15:55:59 +08:00
BaiJiangJie
b365ba7982 [Update] 修改用户列表批量删除判断条件 (#3553) 2019-12-19 16:23:21 +08:00
BaiJiangJie
9cfccf8a1b [Update] 更新翻译 (#3552)
* [Update] 更新翻译(数据库应用)

* [Update] 更新翻译(数据库应用)2

* [Update] 更新翻译(数据库应用)3
2019-12-19 16:08:02 +08:00
BaiJiangJie
16f727c60d [Feature] 添加功能:数据库应用 (#3551)
* [Update] 添加数据库应用Model

* [Update] 添加数据库应用ViewSet

* [Update] 添加数据库应用HTML

* [Update] 更新数据库应用迁移文件

* [Update] 添加数据库应用授权Model

* [Update] 添加数据库应用授权ViewSet(待续)

* [Update] 添加数据库应用授权ViewSet(完结)

* [Update] 添加数据库应用授权View(待续)

* [Update] 添加数据库应用授权View(待续2)

* [Update] 修改远程应用授权View(小问题)

* [Update] 添加数据库应用授权View(待续3)

* [Update] 添加数据库应用授权View(完结)

* [Update] 添加数据库应用授权相关API

* [Update] 添加数据库应用View(用户页面)

* [Update] 修改数据库应用授权Model/View/API(系统用户)

* [Update] 修改系统用户Model/View(添加mysql协议)

* [Update] 修改用户页面(我的应用)

* [Update] 添加迁移文件

* [Update] 添加迁移文件2

* [Update] 续添加迁移文件2(Model更改)

* [Update] 修改系统用户序列类(mysql协议自动生成密码问题)

* [Update] 修改数据库应用/资产等授权序列类

* [Update] 修改命令列表/会话详情命令溢出

* [Update] 修改授权详情中添加系统用户的过滤

* [Update] 修改列表动作的宽度
2019-12-19 15:28:17 +08:00
BaiJiangJie
ac2ba63856 [Update] 修改资产授权url (#3550) 2019-12-19 10:48:18 +08:00
老广
55c95c58f6 Add new model to operate log (#3546)
* [Update] 添加一下model到operate log, [platform,remoteapppermission,changeauthplan,gatherusertask]

* [Bugfix] 修改了返回platform的几个位置,修改了command execution的url

* [Update] 优化ops task表结构,避免列表页查询几十次sql, 优化了基础的encryptjsonfield

* [Update] 修改adhoc 返回的become字段,避免密码泄露

* [Update] 修改变量名称
2019-12-18 15:37:53 +08:00
八千流
907703d911 [Update] 修复 工单按用户搜索无效的问题 (#3540) 2019-12-18 15:09:51 +08:00
老广
e1919d0a62 Asset meta (#3539)
- 更改了资产表单,影响
  - 资产创建和更新
- 增加了资产平台数据库,影响
  - 平台创建更新和删除
- 更改了资产的platform字段,又一个字符字段,改为一个外键,影响 
  - 资产创建和更新
  - 资产连接 [windows,linux]
  - 测试连接等ansible任务
  - 自动化云导入
- 更改了资产的序列化器,影响
  - 资产创建更新列表
- 统一了树列表基础模板,影响
  - 资产列表页,权限列表页,vault页,资产收集页
- 统一了导入导出组件,影响
  - 资产导入导出
  - 用户导入导出
  - 用户组导入导出
  - 系统用户导入导出
  - 管理用户导入导出
  - vault导出导出
  - 收集用户列表导入导出
- 修改用户更新密码信号,影响
  - 修改用户密码产生的改密日志

- 新增Model instance序列化工具函数,影响
  - 操作日志生成
- 修改api mixin,新增 serializer_classes字段,serializer_classes = {"default": "", "display": "", "list": .., "other_action": ""}, 根据用户请求的方式返回不同的serializer_class,影响

  - 用户的viewset
  - 资产权限的viewset
- 统一系统配置中的tab切换
- 统一没有nav的页面,影响
  - 重置密码
  - 忘记密码
  - 重置中设置密码
  - 独立的message页面
- 修改用户组列表页,不再返还用户组下的用户,仅有数量
- 组织的一些方法变为layzproperty,避免重复计算
- 修改用户组详情页,影响
  - 用户组增加删除用户
2019-12-16 16:53:29 +08:00
BaiJiangJie
4ac4b517f4 [Update] 更新依赖(psutil) (#3529) 2019-12-11 12:03:30 +08:00
BaiJiangJie
f296dce935 [Update] 修改config 加载逻辑 (#3528) 2019-12-11 11:53:46 +08:00
BaiJiangJie
bc5a240121 [Update] 更新迁移文件(ReplayStorage Type,RemoteApp Type) (#3524) 2019-12-10 17:06:44 +08:00
BaiJiangJie
b4498f2267 [Update] 修改 RemoteApp 前端 Form 渲染逻辑 (#3523)
* [Update] 修改 RemoteApp 前端 Form 渲染逻辑

* [Update] RemoteApp 表单添加默认值
2019-12-10 15:16:16 +08:00
BaiJiangJie
b2932803b0 [Update] 修改settings DEFAULT_AUTO_SCHEMA_CLASS 路径 (#3518) 2019-12-09 16:33:02 +08:00
BaiJiangJie
cea336a8ce [Update] 用户第三方认证后,只在创建时修改用户来源信息;修改检验用户有效性逻辑; (#3517)
* [Update] 用户第三方认证后,只在创建时修改用户来源信息

* [Update] 修改检验用户有效性逻辑(解决启用LDAP等认证时,显示用户名不存在)

* [Update] 修改检验用户有效性逻辑(解决启用LDAP等认证时,显示用户名不存在)2
2019-12-09 16:12:48 +08:00
BaiJiangJie
16864ca34e [Update] 用户列表添加移除操作(在其他组织中) (#3513)
* [Update] 用户列表添加移除操作(在其他组织中)

* [Update] 用户列表添加移除操作(在其他组织中)2
2019-12-09 11:50:52 +08:00
BaiJiangJie
c5785e17aa Dev node (#3511)
* [Update] 添加节点详情Modal

* [Update] 更新翻译
2019-12-06 15:29:01 +08:00
BaiJiangJie
f89c6124a6 [Update] 修改获取录像失败的问题 (#3509) 2019-12-06 11:36:36 +08:00
BaiJiangJie
47b1a13bea [Update] 添加存储类型:Ceph、Swift (#3508) 2019-12-05 18:19:27 +08:00
BaiJiangJie
df52240227 [Update] 修复录像回放 Bug (#3506) 2019-12-05 16:21:11 +08:00
老广
4944ac8e75 Config (#3502)
* [Update] 修改config

* [Update] 移动存储设置到到terminal中

* [Update] 修改permission 查看

* [Update] pre merge

* [Update] 录像存储

* [Update] 命令存储

* [Update] 添加存储测试可连接性

* [Update] 修改 meta 值的 key 为大写

* [Update] 修改 Terminal 相关 Storage 配置

* [Update] 删除之前获取录像/命令存储的代码

* [Update] 修改导入失败

* [Update] 迁移文件添加default存储

* [Update] 删除之前代码,添加help_text信息

* [Update] 删除之前代码

* [Update] 删除之前代码

* [Update] 抽象命令/录像存储 APIView

* [Update] 抽象命令/录像存储 APIView 1

* [Update] 抽象命令/录像存储 DictField

* [Update] 抽象命令/录像存储列表页面

* [Update] 修复CustomDictField的bug

* [Update] RemoteApp 页面添加 hidden

* [Update] 用户页面添加用户关联授权

* [Update] 修改存储测试可连接性 target

* [Update] 修改配置

* [Update] 修改存储前端 Form 渲染逻辑

* [Update] 修改存储细节

* [Update] 统一存储类型到 const 文件

* [Update] 修改迁移文件及Model,创建默认存储

* [Update] 修改迁移文件及Model初始化默认数据

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 修改迁移文件

* [Update] 限制删除默认存储配置,只允许创建扩展的存储类型

* [Update] 修改ip字段长度

* [Update] 修改ip字段长度

* [Update] 修改一些css

* [Update] 修改关联

* [Update] 添加操作日志定时清理

* [Update] 修改记录syslog的instance encoder

* [Update] 忽略登录产生的操作日志

* [Update] 限制更新存储时不覆盖原有AK SK 等字段

* [Update] 修改迁移文件添加comment字段

* [Update] 修改迁移文件

* [Update] 添加 comment 字段

* [Update] 修改默认存储no -> null

* [Update] 修改细节

* [Update] 更新翻译(存储配置

* [Update] 修改定时任务注册,修改系统用户资产、节点关系api

* [Update] 添加监控磁盘任务

* [Update] 修改session

* [Update] 拆分serializer

* [Update] 还原setting原来的manager
2019-12-05 15:09:25 +08:00
544 changed files with 17751 additions and 28375 deletions

View File

@@ -10,7 +10,7 @@ RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && pip install wheel && \
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt || pip install -r requirements.txt
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
COPY . /opt/jumpserver

382
README.md
View File

@@ -1,198 +1,230 @@
# Jumpserver 多云环境下更好用的堡垒机
# 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/)
[![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
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 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
JumpServer 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
改变世界,从一点点开始。
注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 Jumpserver 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
> 注: [KubeOperator](https://github.com/KubeOperator/KubeOperator) 是 JumpServer 团队在 Kubernetes 领域的的又一全新力作,欢迎关注和使用。
## 核心功能列表
## 特色优势
<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>
- 开源: 零门槛,线上快速获取和安装, 修复版本视情况而定;
, 修复版本视情况而定- 分布式: 轻松支持大规模并发访问;
- 无插件: 仅需浏览器,极致的 Web Terminal 使用体验;
- 多云支持: 一套系统,同时管理不同云上面的资产;
- 云端存储: 审计录像云端存储,永不丢失;
- 多租户: 一套系统,多个子公司和部门同时使用。
## 版本说明
自 v2.0.0 发布后, JumpServer 版本号命名将变更为v大版本.功能版本.Bug修复版本。比如
```
v2.0.1 是 v2.0.0 之后的Bug修复版本
v2.1.0 是 v2.0.0 之后的功能版本。
```
像其它优秀开源项目一样JumpServer 每个月会发布一个功能版本,并同时维护 3 个功能版本。比如:
```
在 v2.4 发布前,我们会同时维护 v2.1、v2.2、v2.3
在 v2.4 发布后,我们会同时维护 v2.2、v2.3、v2.4v2.1 会停止维护。
```
## 功能列表
<table>
<tr>
<td rowspan="8">身份认证<br>Authentication</td>
<td rowspan="5">登录认证</td>
<td>资源统一登录与认证</td>
</tr>
<tr>
<td>LDAP/AD 认证</td>
</tr>
<tr>
<td>RADIUS 认证</td>
</tr>
<tr>
<td>OpenID 认证(实现单点登录)</td>
</tr>
<tr>
<td>CAS 认证 (实现单点登录)</td>
</tr>
<tr>
<td rowspan="2">MFA认证</td>
<td>MFA 二次认证Google Authenticator</td>
</tr>
<tr>
<td>RADIUS 二次认证</td>
</tr>
<tr>
<td>登录复核X-PACK</td>
<td>用户登录行为受管理员的监管与控制</td>
</tr>
<tr>
<td rowspan="11">账号管理<br>Account</td>
<td rowspan="2">集中账号</td>
<td>管理用户管理</td>
</tr>
<tr>
<td>系统用户管理</td>
</tr>
<tr>
<td rowspan="4">统一密码</td>
<td>资产密码托管</td>
</tr>
<tr>
<td>自动生成密码</td>
</tr>
<tr>
<td>自动推送密码</td>
</tr>
<tr>
<td>密码过期设置</td>
</tr>
<tr>
<td rowspan="2">批量改密X-PACK</td>
<td>定期批量改密</td>
</tr>
<tr>
<td>多种密码策略</td>
</tr>
<tr>
<td>多云纳管X-PACK</td>
<td>对私有云、公有云资产自动统一纳管</td>
</tr>
<tr>
<td>收集用户X-PACK</td>
<td>自定义任务定期收集主机用户</td>
</tr>
<tr>
<td>密码匣子X-PACK</td>
<td>统一对资产主机的用户密码进行查看、更新、测试操作</td>
</tr>
<tr>
<td rowspan="15">授权控制<br>Authorization</td>
<td>多维授权</td>
<td>对用户、用户组、资产、资产节点、应用以及系统用户进行授权</td>
</tr>
<tr>
<td rowspan="4">资产授权</td>
<td>资产以树状结构进行展示</td>
</tr>
<tr>
<td>资产和节点均可灵活授权</td>
</tr>
<tr>
<td>节点内资产自动继承授权</td>
</tr>
<tr>
<td>子节点自动继承父节点授权</td>
</tr>
<tr>
<td rowspan="2">应用授权</td>
<td>实现更细粒度的应用级授权</td>
</tr>
<tr>
<td>MySQL 数据库应用、RemoteApp 远程应用X-PACK</td>
</tr>
<tr>
<td>动作授权</td>
<td>实现对授权资产的文件上传、下载以及连接动作的控制</td>
</tr>
<tr>
<td>时间授权</td>
<td>实现对授权资源使用时间段的限制</td>
</tr>
<tr>
<td>特权指令</td>
<td>实现对特权指令的使用(支持黑白名单)</td>
</tr>
<tr>
<td>命令过滤</td>
<td>实现对授权系统用户所执行的命令进行控制</td>
</tr>
<tr>
<td>文件传输</td>
<td>SFTP 文件上传/下载</td>
</tr>
<tr>
<td>文件管理</td>
<td>实现 Web SFTP 文件管理</td>
</tr>
<tr>
<td>工单管理X-PACK</td>
<td>支持对用户登录请求行为进行控制</td>
</tr>
<tr>
<td>组织管理X-PACK</td>
<td>实现多租户管理与权限隔离</td>
</tr>
<tr>
<td rowspan="7">安全审计<br>Audit</td>
<td>操作审计</td>
<td>用户操作行为审计</td>
</tr>
<tr>
<td rowspan="2">会话审计</td>
<td>在线会话内容审计</td>
</tr>
<tr>
<td>历史会话内容审计</td>
</tr>
<tr>
<td rowspan="2">录像审计</td>
<td>支持对 Linux、Windows 等资产操作的录像进行回放审计</td>
</tr>
<tr>
<td>支持对 RemoteAppX-PACK、MySQL 等应用操作的录像进行回放审计</td>
</tr>
<tr>
<td>指令审计</td>
<td>支持对资产和应用等操作的命令进行审计</td>
</tr>
<tr>
<td>文件传输</td>
<td>可对文件的上传、下载记录进行审计</td>
</tr>
</table>
## 安装及使用指南
- [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)
## 演示视频和截屏
我们提供了演示视频和系统截图可以让你快速了解 Jumpserver
## 快速开始
- [极速安装](https://docs.jumpserver.org/zh/master/install/setup_by_fast/)
- [完整文档](https://docs.jumpserver.org)
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
- [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
## SDK
## 案例研究
我们编写了一些SDK供您的其它系统快速和 Jumpserver API 交互:
- [JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [携程 JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [小红书的JumpServer堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)。
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK
## 安全说明
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/) 部署安装.
如果你发现安全问题,可以直接联系我们:
- ibuler@fit2cloud.com
- support@fit2cloud.com
- 400-052-0755
## License & Copyright
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

View File

@@ -1 +1,2 @@
from .remote_app import *
from .database_app import *

View File

@@ -0,0 +1,20 @@
# coding: utf-8
#
from orgs.mixins.api import OrgBulkModelViewSet
from .. import models
from .. import serializers
from ..hands import IsOrgAdminOrAppUser
__all__ = [
'DatabaseAppViewSet',
]
class DatabaseAppViewSet(OrgBulkModelViewSet):
model = models.DatabaseApp
filter_fields = ('name',)
search_fields = filter_fields
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.DatabaseAppSerializer

View File

@@ -15,7 +15,7 @@ __all__ = [
class RemoteAppViewSet(OrgBulkModelViewSet):
model = RemoteApp
filter_fields = ('name',)
filter_fields = ('name', 'type', 'comment')
search_fields = filter_fields
permission_classes = (IsOrgAdmin,)
serializer_class = RemoteAppSerializer

View File

@@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
# RemoteApp
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
REMOTE_APP_TYPE_CHROME = 'chrome'
@@ -12,29 +13,6 @@ 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 = [
@@ -60,9 +38,26 @@ REMOTE_APP_TYPE_CUSTOM_FIELDS = [
{'name': 'custom_password', 'write_only': True}
]
REMOTE_APP_TYPE_MAP_FIELDS = {
REMOTE_APP_TYPE_FIELDS_MAP = {
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
}
REMOTE_APP_TYPE_CHOICES = (
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'),
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
)
# DatabaseApp
DATABASE_APP_TYPE_MYSQL = 'mysql'
DATABASE_APP_TYPE_CHOICES = (
(DATABASE_APP_TYPE_MYSQL, 'MySQL'),
)

View File

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

View File

@@ -1,123 +0,0 @@
# coding: utf-8
#
from django.utils.translation import ugettext as _
from django import forms
from orgs.mixins.forms 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')
class Meta:
model = RemoteApp
fields = [
'name', 'asset', 'type', 'path', 'comment'
]
widgets = {
'asset': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Asset')
}),
}
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

@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by Jumpserver Team.
:copyright: (c) 2014-2018 by JumpServer Team.
:license: GPL v2, see LICENSE for more details.
"""

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.1.11 on 2019-12-10 08:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('applications', '0002_remove_remoteapp_system_user'),
]
operations = [
migrations.AlterField(
model_name='remoteapp',
name='type',
field=models.CharField(choices=[('chrome', 'Chrome'), ('mysql_workbench', 'MySQL Workbench'), ('vmware_client', 'vSphere Client'), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type'),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 2.1.11 on 2019-12-18 09:05
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('applications', '0003_auto_20191210_1659'),
]
operations = [
migrations.CreateModel(
name='DatabaseApp',
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('name', models.CharField(max_length=128, verbose_name='Name')),
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=128, verbose_name='Type')),
('host', models.CharField(db_index=True, max_length=128, verbose_name='Host')),
('port', models.IntegerField(default=3306, verbose_name='Port')),
('database', models.CharField(blank=True, db_index=True, max_length=128, null=True, verbose_name='Database')),
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
],
options={
'verbose_name': 'DatabaseApp',
'ordering': ('name',),
},
),
migrations.AlterUniqueTogether(
name='databaseapp',
unique_together={('org_id', 'name')},
),
]

View File

@@ -1 +1,2 @@
from .remote_app import *
from .database_app import *

View File

@@ -0,0 +1,42 @@
# coding: utf-8
#
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from .. import const
__all__ = ['DatabaseApp']
class DatabaseApp(CommonModelMixin, OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
type = models.CharField(
default=const.DATABASE_APP_TYPE_MYSQL,
choices=const.DATABASE_APP_TYPE_CHOICES,
max_length=128, verbose_name=_('Type')
)
host = models.CharField(
max_length=128, verbose_name=_('Host'), db_index=True
)
port = models.IntegerField(default=3306, verbose_name=_('Port'))
database = models.CharField(
max_length=128, blank=True, null=True, verbose_name=_('Database'),
db_index=True
)
comment = models.TextField(
max_length=128, default='', blank=True, verbose_name=_('Comment')
)
def __str__(self):
return self.name
class Meta:
unique_together = [('org_id', 'name'), ]
verbose_name = _("DatabaseApp")
ordering = ('name', )

View File

@@ -62,7 +62,7 @@ class RemoteApp(OrgModelMixin):
_parameters.append(self.type)
path = '\"%s\"' % self.path
_parameters.append(path)
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]:
for field in const.REMOTE_APP_TYPE_FIELDS_MAP[self.type]:
value = self.params.get(field['name'])
if value is None:
continue

View File

@@ -1 +1,2 @@
from .remote_app import *
from .database_app import *

View File

@@ -0,0 +1,26 @@
# coding: utf-8
#
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from .. import models
__all__ = [
'DatabaseAppSerializer',
]
class DatabaseAppSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = models.DatabaseApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'type', 'get_type_display', 'host', 'port',
'database', 'comment', 'created_by', 'date_created', 'date_updated',
]
read_only_fields = [
'created_by', 'date_created', 'date_updated'
'get_type_display',
]

View File

@@ -1,10 +1,11 @@
# coding: utf-8
#
import copy
from rest_framework import serializers
from common.serializers import AdaptedBulkListSerializer
from common.fields.serializer import CustomMetaDictField
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from .. import const
@@ -16,72 +17,54 @@ __all__ = [
]
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 RemoteAppParamsDictField(CustomMetaDictField):
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
default_type = const.REMOTE_APP_TYPE_CHROME
convert_key_remove_type_prefix = False
convert_key_to_upper = False
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
params = RemoteAppParamsDictField()
type_fields_map = const.REMOTE_APP_TYPE_FIELDS_MAP
class Meta:
model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'asset', 'type', 'path', 'params',
'comment', 'created_by', 'date_created', 'asset_info',
'get_type_display',
'id', 'name', 'asset', 'asset_info', 'type', 'get_type_display',
'path', 'params', 'date_created', 'created_by', 'comment',
]
read_only_fields = [
'created_by', 'date_created', 'asset_info',
'get_type_display'
]
def process_params(self, instance, validated_data):
new_params = copy.deepcopy(validated_data.get('params', {}))
tp = validated_data.get('type', '')
if tp != instance.type:
return new_params
old_params = instance.params
fields = self.type_fields_map.get(instance.type, [])
for field in fields:
if not field.get('write_only', False):
continue
field_name = field['name']
new_value = new_params.get(field_name, '')
old_value = old_params.get(field_name, '')
field_value = new_value if new_value else old_value
new_params[field_name] = field_value
return new_params
def update(self, instance, validated_data):
params = self.process_params(instance, validated_data)
validated_data['params'] = params
return super().update(instance, validated_data)
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
parameter_remote_app = serializers.SerializerMethodField()

View File

@@ -1,158 +0,0 @@
{% 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.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

@@ -1,105 +0,0 @@
{% 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 '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

@@ -1,85 +0,0 @@
{% extends '_base_list.html' %}
{% load i18n static %}
{% block 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>
{% 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 '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: 3, createdCell: function (td, cellData, rowData) {
var comment = htmlEscape(cellData);
$(td).html(comment)
}},
{targets: 5, 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: "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

@@ -1,73 +0,0 @@
{% 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 '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: 5, createdCell: function (td, cellData, rowData) {
var conn_btn = '<a href="{% url "luna-view" %}?type=remote_app&login_to=' + cellData +'" class="btn btn-xs btn-primary" target="_blank">{% 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: "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

@@ -11,10 +11,12 @@ app_name = 'applications'
router = BulkRouter()
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
router.register(r'database-apps', api.DatabaseAppViewSet, 'database-app')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
]
old_version_urlpatterns = [
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)
]

View File

@@ -1,16 +1,7 @@
# 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

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

View File

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

@@ -2,6 +2,7 @@ from .admin_user import *
from .asset import *
from .label import *
from .system_user import *
from .system_user_relation import *
from .node import *
from .domain import *
from .cmd_filter import *

View File

@@ -1,20 +1,10 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.db import transaction
from django.db.models import Count
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework.response import Response
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
@@ -44,6 +34,19 @@ class AdminUserViewSet(OrgBulkModelViewSet):
serializer_class = serializers.AdminUserSerializer
permission_classes = (IsOrgAdmin,)
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(assets_amount=Count('assets'))
return queryset
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
has_related_asset = instance.assets.exists()
if has_related_asset:
data = {'msg': _('Deleted failed, There are related assets')}
return Response(data=data, status=status.HTTP_400_BAD_REQUEST)
return super().destroy(request, *args, **kwargs)
class AdminUserAuthApi(generics.UpdateAPIView):
model = AdminUser

View File

@@ -1,27 +1,27 @@
# -*- coding: utf-8 -*-
#
import random
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView
from django.shortcuts import get_object_or_404
from common.utils import get_logger, get_object_or_none
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsSuperUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from ..models import Asset, Node
from ..models import Asset, Node, Platform
from .. import serializers
from ..tasks import update_asset_hardware_info_manual, \
test_asset_connectivity_manual
from ..tasks import (
update_asset_hardware_info_manual, test_asset_connectivity_manual
)
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
logger = get_logger(__file__)
__all__ = [
'AssetViewSet',
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
'AssetGatewayApi',
'AssetViewSet', 'AssetPlatformRetrieveApi',
'AssetGatewayListApi', 'AssetPlatformViewSet',
'AssetTaskCreateApi',
]
@@ -30,10 +30,16 @@ class AssetViewSet(OrgBulkModelViewSet):
API endpoint that allows Asset to be viewed or edited.
"""
model = Asset
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
filter_fields = (
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
"is_active"
)
search_fields = ("hostname", "ip")
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
serializer_class = serializers.AssetSerializer
serializer_classes = {
'default': serializers.AssetSerializer,
'display': serializers.AssetDisplaySerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
@@ -53,49 +59,68 @@ class AssetViewSet(OrgBulkModelViewSet):
self.set_assets_node(assets)
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
"""
Refresh asset hardware info
"""
class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all()
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.PlatformSerializer
def get_object(self):
asset_pk = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_pk)
return asset.platform
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
permission_classes = (IsSuperUser,)
serializer_class = serializers.PlatformSerializer
filter_fields = ['name', 'base']
search_fields = ['name']
def get_permissions(self):
if self.request.method.lower() in ['get', 'options']:
self.permission_classes = (IsOrgAdmin,)
return super().get_permissions()
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)
class AssetTaskCreateApi(generics.CreateAPIView):
model = Asset
serializer_class = serializers.AssetSerializer
serializer_class = serializers.AssetTaskSerializer
permission_classes = (IsOrgAdmin,)
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
task = update_asset_hardware_info_manual.delay(asset)
return Response({"task": task.id})
def get_object(self):
pk = self.kwargs.get("pk")
instance = get_object_or_404(Asset, pk=pk)
return instance
def perform_create(self, serializer):
asset = self.get_object()
action = serializer.validated_data["action"]
if action == "refresh":
task = update_asset_hardware_info_manual.delay(asset)
else:
task = test_asset_connectivity_manual.delay(asset)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
class AssetAdminUserTestApi(generics.RetrieveAPIView):
"""
Test asset admin user assets_connectivity
"""
model = Asset
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
task = test_asset_connectivity_manual.delay(asset)
return Response({"task": task.id})
class AssetGatewayApi(generics.RetrieveAPIView):
class AssetGatewayListApi(generics.ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GatewayWithAuthSerializer
model = Asset
def retrieve(self, request, *args, **kwargs):
asset_id = kwargs.get('pk')
def get_queryset(self):
asset_id = self.kwargs.get('pk')
asset = get_object_or_404(Asset, pk=asset_id)
if asset.domain and \
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:
return Response({"msg": "Not have gateway"}, status=404)
if not asset.domain:
return []
queryset = asset.domain.gateways.filter(protocol='ssh')
return queryset

View File

@@ -1,26 +1,24 @@
# -*- coding: utf-8 -*-
#
from rest_framework.response import Response
from rest_framework import generics
from rest_framework import filters
from rest_framework_bulk import BulkModelViewSet
from django.shortcuts import get_object_or_404
from django.http import Http404
import coreapi
from django.conf import settings
from rest_framework.response import Response
from rest_framework import generics, filters
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
from common.utils import get_object_or_none, get_logger
from common.mixins import CommonApiMixin
from ..backends import AssetUserManager
from ..models import Asset, Node, SystemUser, AdminUser
from ..models import Asset, Node, SystemUser
from .. import serializers
from ..tasks import test_asset_users_connectivity_manual
from ..tasks import (
test_asset_users_connectivity_manual, push_system_user_a_asset_manual
)
__all__ = [
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
'AssetUserExportViewSet',
'AssetUserViewSet', 'AssetUserAuthInfoViewSet', 'AssetUserTaskCreateAPI',
]
@@ -34,10 +32,17 @@ class AssetUserFilterBackend(filters.BaseFilterBackend):
value = request.GET.get(field)
if not value:
continue
if field in ("node_id", "system_user_id", "admin_user_id"):
if field == "node_id":
value = get_object_or_none(Node, pk=value)
kwargs["node"] = value
continue
elif field == "asset_id":
field = "asset"
kwargs[field] = value
return queryset.filter(**kwargs)
if kwargs:
queryset = queryset.filter(**kwargs)
logger.debug("Filter {}".format(kwargs))
return queryset
class AssetUserSearchBackend(filters.BaseFilterBackend):
@@ -45,140 +50,108 @@ class AssetUserSearchBackend(filters.BaseFilterBackend):
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.distinct()
queryset = queryset.search(value)
return queryset
class AssetUserLatestFilterBackend(filters.BaseFilterBackend):
def get_schema_fields(self, view):
return [
coreapi.Field(
name='latest', location='query', required=False,
type='string', example='1',
description='Only the latest version'
)
]
def filter_queryset(self, request, queryset, view):
latest = request.GET.get('latest') == '1'
if latest:
queryset = queryset.distinct()
return queryset
class AssetUserViewSet(CommonApiMixin, BulkModelViewSet):
serializer_class = serializers.AssetUserSerializer
serializer_classes = {
'default': serializers.AssetUserWriteSerializer,
'display': serializers.AssetUserReadSerializer,
'retrieve': serializers.AssetUserReadSerializer,
}
permission_classes = [IsOrgAdminOrAppUser]
http_method_names = ['get', 'post']
filter_fields = [
"id", "ip", "hostname", "username", "asset_id", "node_id",
"system_user_id", "admin_user_id"
"id", "ip", "hostname", "username",
"asset_id", "node_id",
"prefer", "prefer_id",
]
search_fields = filter_fields
filter_backends = (
filters.OrderingFilter,
search_fields = ["ip", "hostname", "username"]
filter_backends = [
AssetUserFilterBackend, AssetUserSearchBackend,
)
AssetUserLatestFilterBackend,
]
def allow_bulk_destroy(self, qs, filtered):
return False
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")
def get_object(self):
pk = self.kwargs.get("pk")
if pk is None:
return
queryset = self.get_queryset()
obj = queryset.get(id=pk)
return obj
kwargs = {}
assets = None
def get_exception_handler(self):
def handler(e, context):
logger.error(e, exc_info=True)
return Response({"error": str(e)}, status=400)
return handler
def perform_destroy(self, instance):
manager = AssetUserManager()
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')
manager.delete(instance)
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)
def get_queryset(self):
manager = AssetUserManager()
queryset = manager.all()
return queryset
class AssetUserExportViewSet(AssetUserViewSet):
serializer_class = serializers.AssetUserExportSerializer
http_method_names = ['get']
class AssetUserAuthInfoViewSet(AssetUserViewSet):
serializer_classes = {"default": serializers.AssetUserAuthInfoSerializer}
http_method_names = ['get', 'post']
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
if settings.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
serializer_class = serializers.AssetUserAuthInfoSerializer
permission_classes = [IsOrgAdminOrAppUser]
def get_permissions(self):
if settings.CONFIG.SECURITY_VIEW_AUTH_NEED_MFA:
self.permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
return super().get_permissions()
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:
raise Http404("Not found")
else:
return instance
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Test asset users connective
"""
class AssetUserTaskCreateAPI(generics.CreateAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.TaskIDSerializer
serializer_class = serializers.AssetUserTaskSerializer
filter_backends = AssetUserViewSet.filter_backends
filter_fields = AssetUserViewSet.filter_fields
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
queryset = manager.all()
for cls in self.filter_backends:
queryset = cls().filter_queryset(self.request, queryset, self)
return list(queryset)
def retrieve(self, request, *args, **kwargs):
def perform_create(self, serializer):
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})
# action = serializer.validated_data["action"]
# only this
# if action == "test":
task = test_asset_users_connectivity_manual.delay(asset_users)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
return task
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
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler

View File

@@ -18,5 +18,5 @@ class GatheredUserViewSet(OrgModelViewSet):
permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present']
filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname']
search_fields = ['username', 'asset__ip', 'asset__hostname']

View File

@@ -1,24 +1,11 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import namedtuple
from rest_framework import status
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, Http404
from common.utils import get_logger, get_object_or_none
from common.tree import TreeNodeSerializer
@@ -27,7 +14,8 @@ from orgs.mixins import generics
from ..hands import IsOrgAdmin
from ..models import Node
from ..tasks import (
update_assets_hardware_info_util, test_asset_connectivity_util
update_node_assets_hardware_info_manual,
test_node_assets_connectivity_manual,
)
from .. import serializers
@@ -36,9 +24,9 @@ logger = get_logger(__file__)
__all__ = [
'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi',
'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi',
'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi',
'TestNodeConnectiveApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi', 'RefreshNodesCacheApi',
'NodeAddChildrenApi', 'NodeListAsTreeApi',
'NodeChildrenAsTreeApi',
'NodeTaskCreateApi',
]
@@ -64,9 +52,9 @@ class NodeViewSet(OrgModelViewSet):
def destroy(self, request, *args, **kwargs):
node = self.get_object()
if node.has_children_or_contains_assets():
msg = _("Deletion failed and the node contains children or assets")
return Response(data={'msg': msg}, status=status.HTTP_403_FORBIDDEN)
if node.has_children_or_has_assets():
error = _("Deletion failed and the node contains children or assets")
return Response(data={'error': error}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs)
@@ -177,7 +165,7 @@ class NodeChildrenAsTreeApi(NodeChildrenApi):
if not include_assets:
return queryset
assets = self.instance.get_assets().only(
"id", "hostname", "ip", 'platform', "os",
"id", "hostname", "ip", "os",
"org_id", "protocols",
)
for asset in assets:
@@ -261,41 +249,41 @@ class NodeReplaceAssetsApi(generics.UpdateAPIView):
asset.nodes.set([instance])
class RefreshNodeHardwareInfoApi(APIView):
class NodeTaskCreateApi(generics.CreateAPIView):
model = Node
serializer_class = serializers.NodeTaskSerializer
permission_classes = (IsOrgAdmin,)
def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id)
assets = node.get_all_assets()
# task_name = _("更新节点资产硬件信息: {}".format(node.name))
task_name = _("Update node asset hardware information: {}").format(node.name)
task = update_assets_hardware_info_util.delay(assets, task_name=task_name)
return Response({"task": task.id})
def get_object(self):
node_id = self.kwargs.get('pk')
node = get_object_or_none(self.model, id=node_id)
return node
@staticmethod
def set_serializer_data(s, task):
data = getattr(s, '_data', {})
data["task"] = task.id
setattr(s, '_data', data)
class TestNodeConnectiveApi(APIView):
permission_classes = (IsOrgAdmin,)
model = Node
def get(self, request, *args, **kwargs):
node_id = kwargs.get('pk')
node = get_object_or_404(self.model, id=node_id)
assets = node.get_all_assets()
# task_name = _("测试节点下资产是否可连接: {}".format(node.name))
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
task = test_asset_connectivity_util.delay(assets, task_name=task_name)
return Response({"task": task.id})
class RefreshNodesCacheApi(APIView):
permission_classes = (IsOrgAdmin,)
def get(self, request, *args, **kwargs):
@staticmethod
def refresh_nodes_cache():
Node.refresh_nodes()
return Response("Ok")
Task = namedtuple('Task', ['id'])
task = Task(id="0")
return task
def perform_create(self, serializer):
action = serializer.validated_data["action"]
node = self.get_object()
if action == "refresh_cache" and node is None:
task = self.refresh_nodes_cache()
self.set_serializer_data(serializer, task)
return
if node is None:
raise Http404()
if action == "refresh":
task = update_node_assets_hardware_info_manual.delay(node)
else:
task = test_node_assets_connectivity_manual.delay(node)
self.set_serializer_data(serializer, task)
def delete(self, *args, **kwargs):
self.get(*args, **kwargs)
return Response(status=204)

View File

@@ -1,42 +1,25 @@
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.gnu.org/licenses/gpl-2.0.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.shortcuts import get_object_or_404
from django.conf import settings
from rest_framework.response import Response
from common.serializers import CeleryTaskSerializer
from common.utils import get_logger
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser, IsAppUser
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.mixins import generics
from orgs.utils import tmp_to_org
from ..models import SystemUser, Asset
from .. import serializers
from ..serializers import SystemUserWithAuthInfoSerializer
from ..tasks import (
push_system_user_to_assets_manual, test_system_user_connectivity_manual,
push_system_user_a_asset_manual, test_system_user_connectivity_a_asset,
push_system_user_a_asset_manual,
)
logger = get_logger(__file__)
__all__ = [
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
'SystemUserCommandFilterRuleListApi', 'SystemUserTaskApi',
]
@@ -48,6 +31,10 @@ class SystemUserViewSet(OrgBulkModelViewSet):
filter_fields = ("name", "username")
search_fields = filter_fields
serializer_class = serializers.SystemUserSerializer
serializer_classes = {
'default': serializers.SystemUserSerializer,
'list': serializers.SystemUserListSerializer,
}
permission_classes = (IsOrgAdminOrAppUser,)
@@ -57,7 +44,7 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
"""
model = SystemUser
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
serializer_class = SystemUserWithAuthInfoSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
@@ -70,88 +57,61 @@ class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
Get system user with asset auth info
"""
model = SystemUser
permission_classes = (IsAppUser,)
serializer_class = serializers.SystemUserAuthSerializer
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = SystemUserWithAuthInfoSerializer
def get_exception_handler(self):
def handler(e, context):
return Response({"error": str(e)}, status=400)
return handler
def get_object(self):
instance = super().get_object()
aid = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=aid)
instance.load_specific_asset_auth(asset)
return instance
username = instance.username
if instance.username_same_with_user:
username = self.request.query_params.get("username")
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, pk=asset_id)
with tmp_to_org(asset.org_id):
instance.load_asset_special_auth(asset=asset, username=username)
return instance
class SystemUserPushApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
model = SystemUser
class SystemUserTaskApi(generics.CreateAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
serializer_class = serializers.SystemUserTaskSerializer
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
nodes = system_user.nodes.all()
for node in nodes:
system_user.assets.add(*tuple(node.get_all_assets()))
task = push_system_user_to_assets_manual.delay(system_user)
return Response({"task": task.id})
def do_push(self, system_user, asset=None):
if asset is None:
task = push_system_user_to_assets_manual.delay(system_user)
else:
username = self.request.query_params.get('username')
task = push_system_user_a_asset_manual.delay(
system_user, asset, username=username
)
return task
class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
"""
Push system user to cluster assets api
"""
model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = CeleryTaskSerializer
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
@staticmethod
def do_test(system_user, asset=None):
task = test_system_user_connectivity_manual.delay(system_user)
return Response({"task": task.id})
class SystemUserAssetsListView(generics.ListAPIView):
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.AssetSimpleSerializer
filter_fields = ("hostname", "ip")
http_method_names = ['get']
search_fields = filter_fields
return task
def get_object(self):
pk = self.kwargs.get('pk')
return get_object_or_404(SystemUser, pk=pk)
def get_queryset(self):
def perform_create(self, serializer):
action = serializer.validated_data["action"]
asset = serializer.validated_data.get('asset')
system_user = self.get_object()
return system_user.assets.all()
class SystemUserPushToAssetApi(generics.RetrieveAPIView):
model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = push_system_user_a_asset_manual.delay(system_user, asset)
return Response({"task": task.id})
class SystemUserTestAssetConnectivityApi(generics.RetrieveAPIView):
model = SystemUser
permission_classes = (IsOrgAdmin,)
serializer_class = serializers.TaskIDSerializer
def retrieve(self, request, *args, **kwargs):
system_user = self.get_object()
asset_id = self.kwargs.get('aid')
asset = get_object_or_404(Asset, id=asset_id)
task = test_system_user_connectivity_a_asset.delay(system_user, asset)
return Response({"task": task.id})
if action == 'push':
task = self.do_push(system_user, asset)
else:
task = self.do_test(system_user, asset)
data = getattr(serializer, '_data', {})
data["task"] = task.id
setattr(serializer, '_data', data)
class SystemUserCommandFilterRuleListApi(generics.ListAPIView):

View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from django.db.models import F, Value
from django.db.models.signals import m2m_changed
from django.db.models.functions import Concat
from common.permissions import IsOrgAdmin
from orgs.mixins.api import OrgBulkModelViewSet
from orgs.utils import current_org
from .. import models, serializers
__all__ = [
'SystemUserAssetRelationViewSet', 'SystemUserNodeRelationViewSet',
'SystemUserUserRelationViewSet',
]
class RelationMixin:
def get_queryset(self):
queryset = self.model.objects.all()
org_id = current_org.org_id()
if org_id is not None:
queryset = queryset.filter(systemuser__org_id=org_id)
queryset = queryset.annotate(systemuser_display=Concat(
F('systemuser__name'), Value('('), F('systemuser__username'),
Value(')')
))
return queryset
def send_post_add_signal(self, instance):
if not isinstance(instance, list):
instance = [instance]
system_users_objects_map = defaultdict(list)
model, object_field = self.get_objects_attr()
for i in instance:
_id = getattr(i, object_field).id
system_users_objects_map[i.systemuser].append(_id)
sender = self.get_sender()
for system_user, objects in system_users_objects_map.items():
m2m_changed.send(
sender=sender, instance=system_user, action='post_add',
reverse=False, model=model, pk_set=objects
)
def get_sender(self):
return self.model
def get_objects_attr(self):
return models.Asset, 'asset'
def perform_create(self, serializer):
instance = serializer.save()
self.send_post_add_signal(instance)
class BaseRelationViewSet(RelationMixin, OrgBulkModelViewSet):
pass
class SystemUserAssetRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserAssetRelationSerializer
model = models.SystemUser.assets.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'asset', 'systemuser',
]
search_fields = [
"id", "asset__hostname", "asset__ip",
"systemuser__name", "systemuser__username"
]
def get_objects_attr(self):
return models.Asset, 'asset'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
asset_display=Concat(
F('asset__hostname'), Value('('),
F('asset__ip'), Value(')')
)
)
return queryset
class SystemUserNodeRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserNodeRelationSerializer
model = models.SystemUser.nodes.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'node', 'systemuser',
]
search_fields = [
"node__value", "systemuser__name", "systemuser_username"
]
def get_objects_attr(self):
return models.Node, 'node'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset \
.annotate(node_key=F('node__key'))
return queryset
class SystemUserUserRelationViewSet(BaseRelationViewSet):
serializer_class = serializers.SystemUserUserRelationSerializer
model = models.SystemUser.users.through
permission_classes = (IsOrgAdmin,)
filter_fields = [
'id', 'user', 'systemuser',
]
search_fields = [
"user__username", "user__name",
"systemuser__name", "systemuser__username",
]
def get_objects_attr(self):
from users.models import User
return User, 'user'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
user_display=Concat(
F('user__name'), Value('('),
F('user__username'), Value(')')
)
)
return queryset

View File

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

View File

@@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
#
from collections import defaultdict
from .base import BaseBackend
class AssetUserBackend(BaseBackend):
model = None
backend = "AssetUser"
@classmethod
def filter_queryset_more(cls, queryset):
return queryset
@classmethod
def filter(cls, username=None, assets=None, **kwargs):
queryset = cls.model.objects.all()
prefer_id = kwargs.get('prefer_id')
if prefer_id:
queryset = queryset.filter(id=prefer_id)
instances = cls.construct_authbook_objects(queryset, assets)
return instances
if username:
queryset = queryset.filter(username=username)
if assets:
queryset = queryset.filter(assets__in=assets).distinct()
queryset = cls.filter_queryset_more(queryset)
instances = cls.construct_authbook_objects(queryset, assets)
return instances
@classmethod
def construct_authbook_objects(cls, asset_users, assets):
instances = []
assets_user_assets_map = defaultdict(set)
if isinstance(asset_users, list):
assets_user_assets_map = {
asset_user.id: asset_user.assets.values_list('id', flat=True)
for asset_user in asset_users
}
else:
assets_user_assets = asset_users.values_list('id', 'assets')
for i, asset_id in assets_user_assets:
assets_user_assets_map[i].add(asset_id)
for asset_user in asset_users:
if not assets:
related_assets = asset_user.assets.all()
else:
assets_map = {a.id: a for a in assets}
related_assets = [
assets_map.get(i) for i in assets_user_assets_map.get(asset_user.id) if i in assets_map
]
for asset in related_assets:
instance = asset_user.construct_to_authbook(asset)
instance.backend = cls.backend
instances.append(instance)
return instances

View File

@@ -1,93 +1,48 @@
# -*- coding: utf-8 -*-
#
import uuid
from abc import abstractmethod
from ..models import Asset
class BaseBackend:
@classmethod
@abstractmethod
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
"""
:param username: 用户名
:param assets: <Asset>对象
:param latest: 是否是最新记录
:param prefer: 优先使用
:param prefer_id: 使用id
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
"""
def all(self):
pass
@abstractmethod
def filter(self, username=None, hostname=None, ip=None, assets=None,
node=None, prefer_id=None, **kwargs):
pass
class AssetUserQuerySet(list):
def order_by(self, *ordering):
_ordering = []
reverse = False
for i in ordering:
if i[0] == '-':
reverse = True
i = i[1:]
_ordering.append(i)
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
return self
@abstractmethod
def search(self, item):
pass
def filter_in(self, kwargs):
in_kwargs = {}
queryset = []
for k, v in kwargs.items():
if len(v) == 0:
return self
if k.find("__in") >= 0:
_k = k.split('__')[0]
in_kwargs[_k] = v
else:
in_kwargs[k] = v
for k in in_kwargs:
kwargs.pop(k)
@abstractmethod
def get_queryset(self):
pass
if len(in_kwargs) == 0:
return self
for i in self:
matched = False
for k, v in in_kwargs.items():
attr = getattr(i, k, None)
# 如果属性或者value中是uuid,则转换成string
if isinstance(v[0], uuid.UUID):
v = [str(i) for i in v]
if isinstance(attr, uuid.UUID):
attr = str(attr)
if v in attr:
matched = True
if matched:
queryset.append(i)
return AssetUserQuerySet(queryset)
@abstractmethod
def delete(self, union_id):
pass
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
@staticmethod
def qs_to_values(qs):
values = qs.values(
'hostname', 'ip', "asset_id",
'username', 'password', 'private_key', 'public_key',
'score', 'version',
"asset_username", "union_id",
'date_created', 'date_updated',
'org_id', 'backend',
)
return values
def filter(self, **kwargs):
queryset = self.filter_in(kwargs).filter_equal(kwargs)
return queryset
def distinct(self):
items = list(set(self))
self[:] = items
return self
def __or__(self, other):
self.extend(other)
return self
@staticmethod
def make_assets_as_id(assets):
if not assets:
return []
if isinstance(assets[0], Asset):
assets = [a.id for a in assets]
return assets

View File

@@ -1,29 +1,318 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext as _
from functools import reduce
from django.db.models import F, CharField, Value, IntegerField, Q, Count
from django.db.models.functions import Concat
from ..models import AuthBook
from common.utils import get_object_or_none
from orgs.utils import current_org
from ..models import AuthBook, SystemUser, Asset, AdminUser
from .base import BaseBackend
class 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
class DBBackend(BaseBackend):
union_id_length = 2
@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
def __init__(self, queryset=None):
if queryset is None:
queryset = self.all()
self.queryset = queryset
def _clone(self):
return self.__class__(self.queryset)
def all(self):
return AuthBook.objects.none()
def count(self):
return self.queryset.count()
def get_queryset(self):
return self.queryset
def delete(self, union_id):
cleaned_union_id = union_id.split('_')
# 如果union_id通不过本检查代表可能不是本backend, 应该返回空
if not self._check_union_id(union_id, cleaned_union_id):
return
return self._perform_delete_by_union_id(cleaned_union_id)
def _perform_delete_by_union_id(self, union_id_cleaned):
pass
def filter(self, assets=None, node=None, prefer=None, prefer_id=None,
union_id=None, id__in=None, **kwargs):
clone = self._clone()
clone._filter_union_id(union_id)
clone._filter_prefer(prefer, prefer_id)
clone._filter_node(node)
clone._filter_assets(assets)
clone._filter_other(kwargs)
clone._filter_id_in(id__in)
return clone
def _filter_union_id(self, union_id):
if not union_id:
return
cleaned_union_id = union_id.split('_')
# 如果union_id通不过本检查代表可能不是本backend, 应该返回空
if not self._check_union_id(union_id, cleaned_union_id):
self.queryset = self.queryset.none()
return
return self._perform_filter_union_id(union_id, cleaned_union_id)
def _check_union_id(self, union_id, cleaned_union_id):
return union_id and len(cleaned_union_id) == self.union_id_length
def _perform_filter_union_id(self, union_id, union_id_cleaned):
self.queryset = self.queryset.filter(union_id=union_id)
def _filter_assets(self, assets):
assets_id = self.make_assets_as_id(assets)
if assets_id:
self.queryset = self.queryset.filter(asset_id__in=assets_id)
def _filter_node(self, node):
pass
def _filter_id_in(self, ids):
if ids and isinstance(ids, list):
self.queryset = self.queryset.filter(union_id__in=ids)
@staticmethod
def clean_kwargs(kwargs):
return {k: v for k, v in kwargs.items() if v}
def _filter_other(self, kwargs):
kwargs = self.clean_kwargs(kwargs)
if kwargs:
self.queryset = self.queryset.filter(**kwargs)
def _filter_prefer(self, prefer, prefer_id):
pass
def search(self, item):
qs = []
for i in ['hostname', 'ip', 'username']:
kwargs = {i + '__startswith': item}
qs.append(Q(**kwargs))
q = reduce(lambda x, y: x | y, qs)
clone = self._clone()
clone.queryset = clone.queryset.filter(q).distinct()
return clone
class SystemUserBackend(DBBackend):
model = SystemUser.assets.through
backend = 'system_user'
prefer = backend
base_score = 0
union_id_length = 2
def _filter_prefer(self, prefer, prefer_id):
if prefer and prefer != self.prefer:
self.queryset = self.queryset.none()
if prefer_id:
self.queryset = self.queryset.filter(systemuser__id=prefer_id)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
system_user_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
asset_id=asset_id, systemuser__id=system_user_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
system_user_id, asset_id = union_id_cleaned
system_user = get_object_or_none(SystemUser, pk=system_user_id)
asset = get_object_or_none(Asset, pk=asset_id)
if all((system_user, asset)):
system_user.assets.remove(asset)
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
def get_annotate(self):
kwargs = dict(
hostname=F("asset__hostname"),
ip=F("asset__ip"),
username=F("systemuser__username"),
password=F("systemuser__password"),
private_key=F("systemuser__private_key"),
public_key=F("systemuser__public_key"),
score=F("systemuser__priority") + self.base_score,
version=Value(0, IntegerField()),
date_created=F("systemuser__date_created"),
date_updated=F("systemuser__date_updated"),
asset_username=Concat(F("asset__id"), Value("_"),
F("systemuser__username"),
output_field=CharField()),
union_id=Concat(F("systemuser_id"), Value("_"), F("asset_id"),
output_field=CharField()),
org_id=F("asset__org_id"),
backend=Value(self.backend, CharField())
)
return kwargs
def get_filter(self):
return dict(
systemuser__username_same_with_user=False,
)
def all(self):
kwargs = self.get_annotate()
filters = self.get_filter()
qs = self.model.objects.all().annotate(**kwargs)
if current_org.org_id() is not None:
filters['org_id'] = current_org.org_id()
qs = qs.filter(**filters)
qs = self.qs_to_values(qs)
return qs
class DynamicSystemUserBackend(SystemUserBackend):
backend = 'system_user_dynamic'
prefer = 'system_user'
union_id_length = 3
def get_annotate(self):
kwargs = super().get_annotate()
kwargs.update(dict(
username=F("systemuser__users__username"),
asset_username=Concat(
F("asset__id"), Value("_"),
F("systemuser__users__username"),
output_field=CharField()
),
union_id=Concat(
F("systemuser_id"), Value("_"), F("asset_id"),
Value("_"), F("systemuser__users__id"),
output_field=CharField()
),
users_count=Count('systemuser__users'),
))
return kwargs
def _perform_filter_union_id(self, union_id, union_id_cleaned):
system_user_id, asset_id, user_id = union_id_cleaned
self.queryset = self.queryset.filter(
asset_id=asset_id, systemuser_id=system_user_id,
union_id=union_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
system_user_id, asset_id, user_id = union_id_cleaned
system_user = get_object_or_none(SystemUser, pk=system_user_id)
if not system_user:
return
system_user.users.remove(user_id)
if system_user.users.count() == 0:
system_user.assets.remove(asset_id)
def get_filter(self):
return dict(
users_count__gt=0,
systemuser__username_same_with_user=True
)
class AdminUserBackend(DBBackend):
model = Asset
backend = 'admin_user'
prefer = backend
base_score = 200
def _filter_prefer(self, prefer, prefer_id):
if prefer and prefer != self.backend:
self.queryset = self.queryset.none()
if prefer_id:
self.queryset = self.queryset.filter(admin_user__id=prefer_id)
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(nodes__id=node.id)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
admin_user_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
id=asset_id, admin_user_id=admin_user_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
raise PermissionError(_("Could not remove asset admin user"))
def all(self):
qs = self.model.objects.all().annotate(
asset_id=F("id"),
username=F("admin_user__username"),
password=F("admin_user__password"),
private_key=F("admin_user__private_key"),
public_key=F("admin_user__public_key"),
score=Value(self.base_score, IntegerField()),
version=Value(0, IntegerField()),
date_updated=F("admin_user__date_updated"),
asset_username=Concat(F("id"), Value("_"), F("admin_user__username"), output_field=CharField()),
union_id=Concat(F("admin_user_id"), Value("_"), F("id"), output_field=CharField()),
backend=Value(self.backend, CharField()),
)
qs = self.qs_to_values(qs)
return qs
class AuthbookBackend(DBBackend):
model = AuthBook
backend = 'db'
prefer = backend
base_score = 400
def _filter_node(self, node):
if node:
self.queryset = self.queryset.filter(asset__nodes__id=node.id)
def _filter_prefer(self, prefer, prefer_id):
if not prefer or not prefer_id:
return
if prefer.lower() == "admin_user":
model = AdminUser
elif prefer.lower() == "system_user":
model = SystemUser
else:
self.queryset = self.queryset.none()
return
obj = get_object_or_none(model, pk=prefer_id)
if obj is None:
self.queryset = self.queryset.none()
return
username = obj.get_username()
if isinstance(username, str):
self.queryset = self.queryset.filter(username=username)
# dynamic system user return more username
else:
self.queryset = self.queryset.filter(username__in=username)
def _perform_filter_union_id(self, union_id, union_id_cleaned):
authbook_id, asset_id = union_id_cleaned
self.queryset = self.queryset.filter(
id=authbook_id, asset_id=asset_id,
)
def _perform_delete_by_union_id(self, union_id_cleaned):
authbook_id, asset_id = union_id_cleaned
authbook = get_object_or_none(AuthBook, pk=authbook_id)
if authbook.is_latest:
raise PermissionError(_("Latest version could not be delete"))
AuthBook.objects.filter(id=authbook_id).delete()
def all(self):
qs = self.model.objects.all().annotate(
hostname=F("asset__hostname"),
ip=F("asset__ip"),
score=F('version') + self.base_score,
asset_username=Concat(F("asset__id"), Value("_"), F("username"), output_field=CharField()),
union_id=Concat(F("id"), Value("_"), F("asset_id"), output_field=CharField()),
backend=Value(self.backend, CharField()),
)
qs = self.qs_to_values(qs)
return qs

View File

@@ -1,110 +1,162 @@
# -*- coding: utf-8 -*-
#
from itertools import chain, groupby
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
from orgs.utils import current_org
from common.utils import get_logger, lazyproperty
from common.struct import QuerySetChain
from ..models import AssetUser, AuthBook
from .db import (
AuthbookBackend, SystemUserBackend, AdminUserBackend,
DynamicSystemUserBackend
)
logger = get_logger(__name__)
class NotSupportError(Exception):
pass
class AssetUserManager:
"""
资产用户管理器
"""
class AssetUserQueryset:
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),
def __init__(self, backends=()):
self.backends = backends
self._distinct_queryset = None
def backends_queryset(self):
return [b.get_queryset() for b in self.backends]
@lazyproperty
def backends_counts(self):
return [b.count() for b in self.backends]
def filter(self, hostname=None, ip=None, username=None,
assets=None, asset=None, node=None,
id=None, prefer_id=None, prefer=None, id__in=None):
if not assets and asset:
assets = [asset]
kwargs = dict(
hostname=hostname, ip=ip, username=username,
assets=assets, node=node, prefer=prefer, prefer_id=prefer_id,
id__in=id__in, union_id=id,
)
logger.debug("Filter: {}".format(kwargs))
backends = []
for backend in self.backends:
clone = backend.filter(**kwargs)
backends.append(clone)
return self._clone(backends)
def _clone(self, backends=None):
if backends is None:
backends = self.backends
return self.__class__(backends)
def search(self, item):
backends = []
for backend in self.backends:
new = backend.search(item)
backends.append(new)
return self._clone(backends)
def distinct(self):
logger.debug("Distinct asset user queryset")
queryset_chain = chain(*(backend.get_queryset() for backend in self.backends))
queryset_sorted = sorted(
queryset_chain,
key=lambda item: (item["asset_username"], item["score"]),
reverse=True,
)
results = groupby(queryset_sorted, key=lambda item: item["asset_username"])
final = [next(result[1]) for result in results]
self._distinct_queryset = final
return self
def get(self, latest=False, **kwargs):
queryset = self.filter(**kwargs)
if latest:
queryset = queryset.distinct()
queryset = list(queryset)
count = len(queryset)
if count == 1:
data = queryset[0]
return data
elif count > 1:
msg = 'Should return 1 record, but get {}'.format(count)
raise MultipleObjectsReturned(msg)
else:
msg = 'No record found(org is {})'.format(current_org.name)
raise ObjectDoesNotExist(msg)
def get_latest(self, **kwargs):
return self.get(latest=True, **kwargs)
@staticmethod
def to_asset_user(data):
obj = AssetUser()
for k, v in data.items():
setattr(obj, k, v)
return obj
@property
def queryset(self):
if self._distinct_queryset is not None:
return self._distinct_queryset
return QuerySetChain(self.backends_queryset())
def count(self):
if self._distinct_queryset is not None:
return len(self._distinct_queryset)
else:
return sum(self.backends_counts)
def __getitem__(self, ndx):
return self.queryset.__getitem__(ndx)
def __iter__(self):
self._data = iter(self.queryset)
return self
def __next__(self):
return self.to_asset_user(next(self._data))
class AssetUserManager:
support_backends = (
('db', AuthbookBackend),
('system_user', SystemUserBackend),
('admin_user', AdminUserBackend),
('system_user_dynamic', DynamicSystemUserBackend),
)
_prefer = "system_user"
def __init__(self):
self.backends = [backend() for name, backend in self.support_backends]
self._queryset = AssetUserQueryset(self.backends)
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
if assets is not None and not assets:
return AssetUserQuerySet([])
def all(self):
return self._queryset
if prefer:
self._prefer = prefer
instances_map = {}
instances = []
for name, backend in self.backends:
# if name != "db":
# 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"])
def delete(self, obj):
name_backends_map = dict(self.support_backends)
backend_name = obj.backend
backend_cls = name_backends_map.get(backend_name)
union_id = obj.union_id
if backend_cls:
backend_cls().delete(union_id)
else:
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))
raise ObjectDoesNotExist("Not backend found")
@staticmethod
def create(**kwargs):
instance = AuthBookBackend.create(**kwargs)
return instance
# 使用create方法创建AuthBook对象解决并发创建问题添加锁机制
authbook = AuthBook.create(**kwargs)
return authbook
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
def __getattr__(self, item):
return getattr(self._queryset, item)

View File

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

View File

@@ -3,14 +3,5 @@
# 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

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

View File

@@ -1,14 +1,2 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT = _(
'Cannot contain special characters: [ {} ]'
).format(" ".join(['/', '\\']))
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN = r"[/\\]"
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG = \
_("* The contains characters that are not allowed")

View File

@@ -58,12 +58,14 @@ class AssetByNodeFilterBackend(filters.BaseFilterBackend):
if query_all:
pattern = node.get_all_children_pattern(with_self=True)
else:
pattern = node.get_children_key_pattern(with_self=True)
# pattern = node.get_children_key_pattern(with_self=True)
# 只显示当前节点下资产
pattern = r"^{}$".format(node.key)
return self.perform_query(pattern, queryset)
class LabelFilterBackend(filters.BaseFilterBackend):
sep = '#'
sep = ':'
query_arg = 'label'
def get_schema_fields(self, view):
@@ -82,6 +84,8 @@ class LabelFilterBackend(filters.BaseFilterBackend):
q = None
for kv in labels_query:
if '#' in kv:
self.sep = '#'
if self.sep not in kv:
continue
key, value = kv.strip().split(self.sep)[:2]

View File

@@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
#
from .asset import *
from .label import *
from .user import *
from .domain import *
from .cmd_filter import *

View File

@@ -1,190 +0,0 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from orgs.mixins.forms import OrgModelForm
from ..models import Asset, Node
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__)
__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)
if self.data:
return
nodes_field = self.fields['nodes']
if self.instance:
nodes_field.choices = [(n.id, n.full_value) for n in
self.instance.nodes.all()]
else:
nodes_field.choices = []
def add_nodes_initial(self, node):
nodes_field = self.fields['nodes']
nodes_field.choices.append((node.id, node.full_value))
nodes_field.initial = [node]
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'nodes-select2', 'data-placeholder': _('Nodes')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetUpdateForm(OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.data:
return
nodes_field = self.fields['nodes']
if self.instance:
nodes_field.choices = ((n.id, n.full_value) for n in
self.instance.nodes.all())
else:
nodes_field.choices = []
class Meta:
model = Asset
fields = [
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
'public_ip', 'number', 'comment', 'admin_user', 'labels',
'domain',
]
widgets = {
'nodes': forms.SelectMultiple(attrs={
'class': 'nodes-select2', 'data-placeholder': _('Node')
}),
'admin_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Admin user')
}),
'labels': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Label')
}),
'domain': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Domain')
}),
}
labels = {
'nodes': _("Node"),
}
help_texts = {
'hostname': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
'admin_user': _(
'root or other NOPASSWD sudo privilege user existed in asset,'
'If asset is windows or other set any one, more see admin user left menu'
),
'platform': _("Windows 2016 RDP protocol is different, If is window 2016, set it"),
'domain': _("If your have some network not connect with each other, you can set domain")
}
class AssetBulkUpdateForm(OrgModelForm):
assets = forms.ModelMultipleChoiceField(
required=True,
label=_('Select assets'), queryset=Asset.objects,
widget=forms.SelectMultiple(
attrs={
'class': 'select2',
'data-placeholder': _('Select assets')
}
)
)
class Meta:
model = Asset
fields = [
'assets', 'admin_user', 'labels', 'platform',
'domain',
]
widgets = {
'labels': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Label')}
),
'nodes': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Node')}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
# 重写其他字段为不再required
for name, field in self.fields.items():
if name != 'assets':
field.required = False
def set_fields_queryset(self):
assets_field = self.fields['assets']
if hasattr(self, 'data'):
assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
changed_fields = []
for field in self._meta.fields:
if self.data.get(field) not in [None, '']:
changed_fields.append(field)
cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields}
assets = cleaned_data.pop('assets')
labels = cleaned_data.pop('labels', [])
nodes = cleaned_data.pop('nodes', None)
assets = Asset.objects.filter(id__in=[asset.id for asset in assets])
assets.update(**cleaned_data)
if labels:
for asset in assets:
asset.labels.set(labels)
if nodes:
for asset in assets:
asset.nodes.set(nodes)
return assets

View File

@@ -1,40 +0,0 @@
# -*- 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.forms import OrgModelForm
from ..models import CommandFilter, CommandFilterRule
__all__ = ['CommandFilterForm', 'CommandFilterRuleForm']
class CommandFilterForm(OrgModelForm):
class Meta:
model = CommandFilter
fields = ['name', 'comment']
class CommandFilterRuleForm(OrgModelForm):
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
class Meta:
model = CommandFilterRule
fields = [
'filter', 'type', 'content', 'priority', 'action', 'comment'
]
widgets = {
'content': forms.Textarea(attrs={
'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

@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from orgs.mixins.forms import OrgModelForm
from ..models import Domain, Asset, Gateway
from .user import PasswordAndKeyAuthForm
__all__ = ['DomainForm', 'GatewayForm']
class DomainForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects, label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
)
class Meta:
model = Domain
fields = ['name', 'comment', 'assets']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
assets_field = self.fields.get('assets')
# 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data:
# 有instance 代表渲染更新表单, 否则是创建表单
# 前端渲染优化, 防止过多资产, 设置assets queryset为none
if self.instance:
assets_field.initial = self.instance.assets.all()
assets_field.queryset = self.instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
else:
assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
instance = super().save(commit=commit)
assets = self.cleaned_data['assets']
instance.assets.set(assets)
return instance
class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
password_field = self.fields.get('password')
password_field.help_text = _('Password should not contain special characters')
protocol_field = self.fields.get('protocol')
protocol_field.choices = [Gateway.PROTOCOL_CHOICES[0]]
def save(self, commit=True):
# Because we define custom field, so we need rewrite :method: `save`
instance = super().save()
password = self.cleaned_data.get('password')
private_key, public_key = super().gen_keys()
instance.set_auth(password=password, private_key=private_key)
return instance
class Meta:
model = Gateway
fields = [
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
'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

@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from ..models import Label, Asset
__all__ = ['LabelForm']
class LabelForm(forms.ModelForm):
assets = forms.ModelMultipleChoiceField(
queryset=Asset.objects.none(), label=_('Asset'), required=False,
widget=forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('Select assets')}
)
)
class Meta:
model = Label
fields = ['name', 'value', 'assets']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_fields_queryset()
def set_fields_queryset(self):
assets_field = self.fields.get('assets')
# 没有data代表是渲染表单, 有data代表是提交创建/更新表单
if not self.data:
# 有instance 代表渲染更新表单, 否则是创建表单
# 前端渲染优化, 防止过多资产, 设置assets queryset为none
if self.instance:
assets_field.initial = self.instance.assets.all()
assets_field.queryset = self.instance.assets.all()
else:
assets_field.queryset = Asset.objects.none()
else:
assets_field.queryset = Asset.objects.all()
def save(self, commit=True):
label = super().save(commit=commit)
assets = self.cleaned_data['assets']
label.assets.set(assets)
return label

View File

@@ -1,109 +0,0 @@
# -*- coding: utf-8 -*-
#
from django import forms
from django.utils.translation import gettext_lazy as _
from common.utils import validate_ssh_private_key, ssh_pubkey_gen, get_logger
from orgs.mixins.forms import OrgModelForm
from ..models import AdminUser, SystemUser
from ..const import GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT
logger = get_logger(__file__)
__all__ = [
'FileForm', 'SystemUserForm', 'AdminUserForm', 'PasswordAndKeyAuthForm',
]
class FileForm(forms.Form):
file = forms.FileField()
class PasswordAndKeyAuthForm(forms.ModelForm):
# Form field name can not start with `_`, so redefine it,
password = forms.CharField(
widget=forms.PasswordInput, max_length=128,
strip=True, required=False,
help_text=_('Password or private key passphrase'),
label=_("Password"),
)
# Need use upload private key file except paste private key content
private_key = forms.FileField(required=False, label=_("Private key"))
def clean_private_key(self):
private_key_f = self.cleaned_data['private_key']
password = self.cleaned_data['password']
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):
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_f = self.cleaned_data.get('private_key', '')
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_f = self.cleaned_data['private_key']
public_key = private_key = None
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):
raise forms.ValidationError("Use api to save")
class Meta:
model = AdminUser
fields = ['name', 'username', 'password', 'private_key', 'comment']
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
}
class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
# Admin user assets define, let user select, save it in form not in view
auto_generate_key = forms.BooleanField(initial=True, required=False)
def save(self, commit=True):
raise forms.ValidationError("Use api to save")
class Meta:
model = SystemUser
fields = [
'name', 'username', 'protocol', 'auto_generate_key',
'password', 'private_key', 'auto_push', 'sudo',
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
]
widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
'cmd_filters': forms.SelectMultiple(attrs={
'class': 'select2', 'data-placeholder': _('Command filter')
}),
}
help_texts = {
'name': GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_HELP_TEXT,
'auto_push': _('Auto push system user to asset'),
'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.'),
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
}

View File

@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2018 by Jumpserver Team.
:copyright: (c) 2014-2018 by JumpServer Team.
:license: GPL v2, see LICENSE for more details.
"""

View File

@@ -0,0 +1,48 @@
# Generated by Django 2.2.7 on 2019-12-06 07:26
import common.fields.model
from django.db import migrations, models
def create_internal_platform(apps, schema_editor):
model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
type_platforms = (
('Linux', 'Linux', None),
('Unix', 'Unix', None),
('MacOS', 'MacOS', None),
('BSD', 'BSD', None),
('Windows', 'Windows', None),
('Windows2016', 'Windows', {'security': 'tls'}),
('Other', 'Other', None),
)
for name, base, meta in type_platforms:
model.objects.using(db_alias).create(
name=name, base=base, internal=True, meta=meta
)
class Migration(migrations.Migration):
dependencies = [
('assets', '0043_auto_20191114_1111'),
]
operations = [
migrations.CreateModel(
name='Platform',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.SlugField(allow_unicode=True, unique=True, verbose_name='Name')),
('base', models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=16, verbose_name='Base')),
('charset', models.CharField(choices=[('utf8', 'UTF-8'), ('gbk', 'GBK')], default='utf8', max_length=8, verbose_name='Charset')),
('meta', common.fields.model.JsonDictTextField(blank=True, null=True, verbose_name='Meta')),
('internal', models.BooleanField(default=False, verbose_name='Internal')),
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
],
options={
'verbose_name': 'Platform'
}
),
migrations.RunPython(create_internal_platform)
]

View File

@@ -0,0 +1,47 @@
# Generated by Django 2.2.7 on 2019-12-06 08:07
import assets.models.asset
from django.db import migrations, models
import django.db.models.deletion
def migrate_platform_to_asset_type(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
platform_model = apps.get_model("assets", "Platform")
db_alias = schema_editor.connection.alias
platforms = platform_model.objects.using(db_alias).all()
platforms_map = {p.name: p for p in platforms}
for name, p in platforms_map.items():
asset_model.objects.using(db_alias)\
.filter(_platform=name)\
.update(platform=p)
class Migration(migrations.Migration):
dependencies = [
('assets', '0044_platform'),
]
operations = [
migrations.RenameField(
model_name='asset',
old_name='platform',
new_name='_platform',
),
migrations.AddField(
model_name='asset',
name='platform',
field=models.ForeignKey(
default=assets.models.asset.Platform.default,
on_delete=django.db.models.deletion.PROTECT,
related_name='assets', to='assets.Platform',
verbose_name='Platform'),
),
migrations.RunPython(migrate_platform_to_asset_type),
migrations.RemoveField(
model_name='asset',
name='_platform',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.1.11 on 2019-12-18 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0045_auto_20191206_1607'),
]
operations = [
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet'), ('vnc', 'vnc'), ('mysql', 'mysql')], default='ssh', max_length=16, verbose_name='Protocol'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 2.2.7 on 2020-01-06 07:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0046_auto_20191218_1705'),
]
operations = [
migrations.CreateModel(
name='AssetUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.authbook',),
),
]

View File

@@ -0,0 +1,35 @@
# Generated by Django 2.2.7 on 2019-12-30 07:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('assets', '0047_assetuser'),
]
operations = [
migrations.RemoveField(
model_name='authbook',
name='is_active',
),
migrations.AddField(
model_name='systemuser',
name='username_same_with_user',
field=models.BooleanField(default=False, verbose_name='Username same with user'),
),
migrations.AddField(
model_name='systemuser',
name='users',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Users'),
),
migrations.AddField(
model_name='systemuser',
name='groups',
field=models.ManyToManyField(blank=True, to='users.UserGroup',
verbose_name='User groups'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2020-01-19 07:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0048_auto_20191230_1512'),
]
operations = [
migrations.AddField(
model_name='systemuser',
name='sftp_root',
field=models.CharField(default='tmp', max_length=128, verbose_name='SFTP Root'),
),
]

View File

@@ -1,6 +1,8 @@
from .base import *
from .asset import *
from .label import Label
from .user import *
from .asset_user import *
from .cluster import *
from .group import *
from .domain import *

View File

@@ -11,10 +11,13 @@ from collections import OrderedDict
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .utils import Connectivity
from common.fields.model import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from .base import ConnectivityMixin
from .utils import Connectivity
__all__ = ['Asset', 'ProtocolsMixin']
__all__ = ['Asset', 'ProtocolsMixin', 'Platform']
logger = logging.getLogger(__name__)
@@ -37,6 +40,13 @@ def default_node():
return None
class AssetManager(OrgManager):
def get_queryset(self):
return super().get_queryset().annotate(
platform_base=models.F('platform__base')
)
class AssetQuerySet(models.QuerySet):
def active(self):
return self.filter(is_active=True)
@@ -119,6 +129,47 @@ class NodesRelationMixin:
return nodes
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def is_windows(self):
return self.base.lower() in ('windows',)
def is_unixlike(self):
return self.base.lower() in ("linux", "unix", "macos", "bsd")
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
# Important
PLATFORM_CHOICES = (
@@ -138,9 +189,8 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
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'))
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
@@ -175,7 +225,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created'))
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
objects = OrgManager.from_queryset(AssetQuerySet)()
objects = AssetManager.from_queryset(AssetQuerySet)()
_connectivity = None
def __str__(self):
@@ -190,20 +240,29 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
return False, warning
return True, warning
@lazyproperty
def platform_base(self):
return self.platform.base
@lazyproperty
def admin_user_display(self):
return self.admin_user.name
@lazyproperty
def admin_user_username(self):
"""求可连接性时直接用用户名去取避免再查一次admin user
serializer 中直接通过annotate方式返回了这个
"""
return self.admin_user.username
def is_windows(self):
if self.platform in ("Windows", "Windows2016"):
return True
else:
return False
return self.platform.is_windows()
def is_unixlike(self):
if self.platform not in ("Windows", "Windows2016", "Other"):
return True
else:
return False
return self.platform.is_unixlike()
def is_support_ansible(self):
return self.has_protocol('ssh') and self.platform not in ("Other",)
return self.has_protocol('ssh') and self.platform_base not in ("Other",)
@property
def cpu_info(self):
@@ -228,9 +287,11 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
def connectivity(self):
if self._connectivity:
return self._connectivity
if not self.admin_user:
if not self.admin_user_username:
return Connectivity.unknown()
connectivity = self.admin_user.get_asset_connectivity(self)
connectivity = ConnectivityMixin.get_asset_username_connectivity(
self, self.admin_user_username
)
return connectivity
@connectivity.setter
@@ -243,7 +304,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
if not self.admin_user:
return {}
self.admin_user.load_specific_asset_auth(self)
self.admin_user.load_asset_special_auth(self)
info = {
'username': self.admin_user.username,
'password': self.admin_user.password,
@@ -264,9 +325,9 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
def as_tree_node(self, parent_node):
from common.tree import TreeNode
icon_skin = 'file'
if self.platform.lower() == 'windows':
if self.platform_base.lower() == 'windows':
icon_skin = 'windows'
elif self.platform.lower() == 'linux':
elif self.platform_base.lower() == 'linux':
icon_skin = 'linux'
data = {
'id': str(self.id),
@@ -283,7 +344,7 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
'hostname': self.hostname,
'ip': self.ip,
'protocols': self.protocols_as_list,
'platform': self.platform,
'platform': self.platform_base,
}
}
}

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
from .authbook import AuthBook
class AssetUser(AuthBook):
hostname = ""
ip = ""
backend = ""
union_id = ""
asset_username = ""
class Meta:
proxy = True

View File

@@ -1,30 +1,32 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.db import models, transaction
from django.db.models import Max
from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgManager
from .base import AssetUser
from .base import BaseUser
__all__ = ['AuthBook']
class AuthBookQuerySet(models.QuerySet):
def latest_version(self):
return self.filter(is_latest=True).filter(is_active=True)
def delete(self):
if self.count() > 1:
raise PermissionError(_("Bulk delete deny"))
return super().delete()
class AuthBookManager(OrgManager):
pass
class AuthBook(AssetUser):
class AuthBook(BaseUser):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
version = models.IntegerField(default=1, verbose_name=_('Version'))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
backend = "db"
@@ -35,37 +37,42 @@ class AuthBook(AssetUser):
class Meta:
verbose_name = _('AuthBook')
def set_to_latest(self):
self.remove_pre_latest()
self.is_latest = True
self.save()
def get_pre_latest(self):
pre_obj = self.__class__.objects.filter(
username=self.username, asset=self.asset
).latest_version().first()
return pre_obj
def remove_pre_latest(self):
pre_obj = self.get_pre_latest()
if pre_obj:
pre_obj.is_latest = False
pre_obj.save()
def set_version(self):
pre_obj = self.get_pre_latest()
if pre_obj:
self.version = pre_obj.version + 1
else:
self.version = 1
self.save()
def get_related_assets(self):
return [self.asset]
def generate_id_with_asset(self, asset):
return self.id
@classmethod
def get_max_version(cls, username, asset):
version_max = cls.objects.filter(username=username, asset=asset) \
.aggregate(Max('version'))
version_max = version_max['version__max'] or 0
return version_max
@classmethod
def create(cls, **kwargs):
"""
使用并发锁机制创建AuthBook对象, (主要针对并发创建 username, asset 相同的对象时)
并更新其他对象的 is_latest=False (其他对象: 与当前对象的 username, asset 相同)
同时设置自己的 is_latest=True, version=max_version + 1
"""
username = kwargs['username']
asset = kwargs['asset']
key_lock = 'KEY_LOCK_CREATE_AUTH_BOOK_{}_{}'.format(username, asset.id)
with cache.lock(key_lock):
with transaction.atomic():
cls.objects.filter(
username=username, asset=asset, is_latest=True
).update(is_latest=False)
max_version = cls.get_max_version(username, asset)
kwargs.update({
'version': max_version + 1,
'is_latest': True
})
obj = cls.objects.create(**kwargs)
return obj
@property
def connectivity(self):
return self.get_asset_connectivity(self.asset)

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
#
import io
import os
import uuid
from hashlib import md5
@@ -11,90 +12,29 @@ 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, get_logger
ssh_key_string_to_obj, ssh_key_gen, get_logger, lazyproperty
)
from common.validators import alphanumeric
from common import fields
from orgs.mixins.models import OrgModelMixin
from .utils import private_key_validator, Connectivity
from .utils import 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], db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
class ConnectivityMixin:
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
_prefer = "system_user"
@property
def private_key_obj(self):
if self.private_key:
return ssh_key_string_to_obj(self.private_key, password=self.password)
else:
return None
@property
def private_key_file(self):
if not self.private_key_obj:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
id = ''
username = ''
@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 = password
update_fields.append('password')
if private_key:
self.private_key = private_key
update_fields.append('private_key')
if public_key:
self.public_key = public_key
update_fields.append('public_key')
if update_fields:
self.save(update_fields=update_fields)
def set_connectivity(self, summary):
unreachable = summary.get('dark', {}).keys()
reachable = summary.get('contacted', {}).keys()
@@ -141,18 +81,10 @@ class AssetUser(OrgModelMixin):
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)
@classmethod
def get_asset_username_connectivity(cls, asset, username):
key = cls.CONNECTIVITY_ASSET_CACHE_KEY.format(username, asset.id)
return Connectivity.get(key)
def get_asset_connectivity(self, asset):
key = self.get_asset_connectivity_key(asset)
@@ -165,28 +97,103 @@ class AssetUser(OrgModelMixin):
key = self.get_asset_connectivity_key(asset)
Connectivity.set(key, c)
def get_asset_user(self, asset):
class AuthMixin:
private_key = ''
password = ''
public_key = ''
username = ''
_prefer = 'system_user'
@property
def private_key_obj(self):
if self.private_key:
key_obj = ssh_key_string_to_obj(self.private_key, password=self.password)
return key_obj
else:
return None
@property
def private_key_file(self):
if not self.private_key_obj:
return None
project_dir = settings.PROJECT_DIR
tmp_dir = os.path.join(project_dir, 'tmp')
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
key_path = os.path.join(tmp_dir, key_name)
if not os.path.exists(key_path):
self.private_key_obj.write_private_key_file(key_path)
os.chmod(key_path, 0o400)
return key_path
def get_private_key(self):
if not self.private_key_obj:
return None
string_io = io.StringIO()
self.private_key_obj.write_private_key(string_io)
private_key = string_io.getvalue()
return private_key
@property
def public_key_obj(self):
if self.public_key:
try:
return sshpubkeys.SSHKey(self.public_key)
except TabError:
pass
return None
def set_auth(self, password=None, private_key=None, public_key=None):
update_fields = []
if password:
self.password = password
update_fields.append('password')
if private_key:
self.private_key = private_key
update_fields.append('private_key')
if public_key:
self.public_key = public_key
update_fields.append('public_key')
if update_fields:
self.save(update_fields=update_fields)
def has_special_auth(self, asset=None):
from .authbook import AuthBook
queryset = AuthBook.objects.filter(username=self.username)
if asset:
queryset = queryset.filter(asset=asset)
return queryset.exists()
def get_asset_user(self, asset, username=None):
from ..backends import AssetUserManager
if username is None:
username = self.username
try:
manager = AssetUserManager().prefer(self._prefer)
other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
manager = AssetUserManager()
other = manager.get_latest(
username=username, asset=asset,
prefer_id=self.id, prefer=self._prefer,
)
return other
except Exception as e:
logger.error(e, exc_info=True)
return None
def load_specific_asset_auth(self, asset):
instance = self.get_asset_user(asset)
def load_asset_special_auth(self, asset=None, username=None):
if not asset:
return self
instance = self.get_asset_user(asset, username=username)
if instance:
self._merge_auth(instance)
def _merge_auth(self, other):
if other.password:
self.password = other.password
if other.public_key:
self.public_key = other.public_key
if other.private_key:
if other.public_key or other.private_key:
self.private_key = other.private_key
self.public_key = other.public_key
def clear_auth(self):
self.password = ''
@@ -205,19 +212,57 @@ class AssetUser(OrgModelMixin):
)
return private_key, public_key
def auto_gen_auth(self):
password = str(uuid.uuid4())
private_key, public_key = ssh_key_gen(
username=self.username
)
def auto_gen_auth(self, password=True, key=True):
_password = None
_private_key = None
_public_key = None
if password:
_password = self.gen_password()
if key:
_private_key, _public_key = self.gen_key(self.username)
self.set_auth(
password=password, private_key=private_key,
public_key=public_key
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)
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
ASSET_USER_CACHE_TIME = 600
_prefer = "system_user"
def get_related_assets(self):
assets = self.assets.filter(org_id=self.org_id)
return assets
def get_username(self):
return self.username
@lazyproperty
def assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cached = cache.get(cache_key)
if not cached:
cached = self.get_related_assets().count()
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
return cached
def expire_assets_amount(self):
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
cache.delete(cache_key)
def _to_secret_json(self):
"""Push system user use it"""
@@ -229,26 +274,6 @@ 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

@@ -10,7 +10,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin
from .base import AssetUser
from .base import BaseUser
__all__ = ['Domain', 'Gateway']
@@ -39,7 +39,7 @@ class Domain(OrgModelMixin):
return random.choice(self.gateways)
class Gateway(AssetUser):
class Gateway(BaseUser):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_CHOICES = (

View File

@@ -11,9 +11,9 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.cache import cache
from common.utils import get_logger, timeit, lazyproperty
from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from orgs.utils import set_current_org, get_current_org, tmp_to_org
from orgs.utils import get_current_org, tmp_to_org, current_org
from orgs.models import Organization
@@ -26,63 +26,108 @@ class NodeQuerySet(models.QuerySet):
raise PermissionError("Bulk delete node deny")
class TreeCache:
updated_time_cache_key = 'NODE_TREE_UPDATED_AT_{}'
cache_time = 3600
assets_updated_time_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT_{}'
def __init__(self, tree, org_id):
now = time.time()
self.created_time = now
self.assets_created_time = now
self.tree = tree
self.org_id = org_id
def _has_changed(self, tp="tree"):
if tp == "assets":
key = self.assets_updated_time_cache_key.format(self.org_id)
else:
key = self.updated_time_cache_key.format(self.org_id)
updated_time = cache.get(key, 0)
if updated_time > self.created_time:
return True
else:
return False
@classmethod
def set_changed(cls, tp="tree", t=None, org_id=None):
if org_id is None:
org_id = current_org.id
if tp == "assets":
key = cls.assets_updated_time_cache_key.format(org_id)
else:
key = cls.updated_time_cache_key.format(org_id)
ttl = cls.cache_time
if not t:
t = time.time()
cache.set(key, t, ttl)
def tree_has_changed(self):
return self._has_changed("tree")
def set_tree_changed(self, t=None):
logger.debug("Set tree tree changed")
self.__class__.set_changed(t=t, tp="tree")
def assets_has_changed(self):
return self._has_changed("assets")
def set_tree_assets_changed(self, t=None):
logger.debug("Set tree assets changed")
self.__class__.set_changed(t=t, tp="assets")
def get(self):
if self.tree_has_changed():
self.renew()
return self.tree
if self.assets_has_changed():
self.tree.init_assets()
return self.tree
def renew(self):
new_obj = self.__class__.new(self.org_id)
self.tree = new_obj.tree
self.created_time = new_obj.created_time
self.assets_created_time = new_obj.assets_created_time
@classmethod
def new(cls, org_id=None):
from ..utils import TreeService
logger.debug("Create node tree")
if not org_id:
org_id = current_org.id
with tmp_to_org(org_id):
tree = TreeService.new()
obj = cls(tree, org_id)
obj.tree = tree
return obj
class TreeMixin:
tree_created_time = None
tree_updated_time_cache_key = 'NODE_TREE_UPDATED_AT'
tree_cache_time = 3600
tree_assets_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT'
tree_assets_created_time = None
_tree_service = None
_org_tree_map = {}
@classmethod
def tree(cls):
from ..utils import TreeService
tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0)
now = time.time()
# 什么时候重新初始化 _tree_service
if not cls.tree_created_time or \
tree_updated_time > cls.tree_created_time:
logger.debug("Create node tree")
tree = TreeService.new()
cls.tree_created_time = now
cls.tree_assets_created_time = now
cls._tree_service = tree
return tree
# 是否要重新初始化节点资产
node_assets_updated_time = cache.get(cls.tree_assets_cache_key, 0)
if not cls.tree_assets_created_time or \
node_assets_updated_time > cls.tree_assets_created_time:
cls._tree_service.init_assets()
cls.tree_assets_created_time = now
logger.debug("Refresh node tree assets")
return cls._tree_service
org_id = current_org.org_id()
t = cls.get_local_tree_cache(org_id)
if t is None:
t = TreeCache.new()
cls._org_tree_map[org_id] = t
return t.get()
@classmethod
def get_local_tree_cache(cls, org_id=None):
t = cls._org_tree_map.get(org_id)
return t
@classmethod
def refresh_tree(cls, t=None):
logger.debug("Refresh node tree")
key = cls.tree_updated_time_cache_key
ttl = cls.tree_cache_time
if not t:
t = time.time()
cache.set(key, t, ttl)
TreeCache.set_changed(tp="tree", t=t, org_id=current_org.id)
@classmethod
def refresh_node_assets(cls, t=None):
logger.debug("Refresh node assets")
key = cls.tree_assets_cache_key
ttl = cls.tree_cache_time
if not t:
t = time.time()
cache.set(key, t, ttl)
@staticmethod
def refresh_user_tree_cache():
"""
当节点-节点关系,节点-资产关系发生变化时,应该刷新用户授权树缓存
:return:
"""
from perms.utils.asset_permission import AssetPermissionUtilV2
AssetPermissionUtilV2.expire_all_user_tree_cache()
TreeCache.set_changed(tp="assets", t=t, org_id=current_org.id)
class FamilyMixin:
@@ -376,15 +421,6 @@ class SomeNodesMixin:
)
return obj
@classmethod
def empty_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.empty_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.empty_key
)
return obj
@classmethod
def default_node(cls):
with tmp_to_org(Organization.default()):
@@ -413,7 +449,6 @@ class SomeNodesMixin:
@classmethod
def initial_some_nodes(cls):
cls.default_node()
cls.empty_node()
cls.ungrouped_node()
cls.favorite_node()
@@ -523,13 +558,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
tree_node = TreeNode(**data)
return tree_node
def has_children_or_contains_assets(self):
if self.children or self.get_assets():
def has_children_or_has_assets(self):
if self.children or self.get_assets().exists():
return True
return False
def delete(self, using=None, keep_parents=False):
if self.has_children_or_contains_assets():
if self.has_children_or_has_assets():
return
return super().delete(using=using, keep_parents=keep_parents)
@@ -539,14 +574,13 @@ class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin
org = get_current_org()
if not org or not org.is_real():
Organization.default().change_to()
i = 0
while i < count:
nodes = list(cls.objects.all())
if count > 100:
length = 100
else:
length = count
nodes = list(cls.objects.all())
if count > 100:
length = 100
else:
length = count
for i in range(length):
node = random.choice(nodes)
node.create_child('Node {}'.format(i))
for i in range(length):
node = random.choice(nodes)
child = node.create_child('Node {}'.format(i))
print("{}. {}".format(i, child))

View File

@@ -4,23 +4,20 @@
import logging
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 .base import AssetUser
from common.utils import signer
from .base import BaseUser
from .asset import Asset
__all__ = ['AdminUser', 'SystemUser']
logger = logging.getLogger(__name__)
signer = get_signer()
class AdminUser(AssetUser):
class AdminUser(BaseUser):
"""
A privileged user that ansible can use it to push system user and so on
"""
@@ -88,16 +85,18 @@ class AdminUser(AssetUser):
continue
class SystemUser(AssetUser):
class SystemUser(BaseUser):
PROTOCOL_SSH = 'ssh'
PROTOCOL_RDP = 'rdp'
PROTOCOL_TELNET = 'telnet'
PROTOCOL_VNC = 'vnc'
PROTOCOL_MYSQL = 'mysql'
PROTOCOL_CHOICES = (
(PROTOCOL_SSH, 'ssh'),
(PROTOCOL_RDP, 'rdp'),
(PROTOCOL_TELNET, 'telnet'),
(PROTOCOL_VNC, 'vnc'),
(PROTOCOL_MYSQL, 'mysql'),
)
LOGIN_AUTO = 'auto'
@@ -106,9 +105,11 @@ class SystemUser(AssetUser):
(LOGIN_AUTO, _('Automatic login')),
(LOGIN_MANUAL, _('Manually login'))
)
username_same_with_user = models.BooleanField(default=False, verbose_name=_("Username same with user"))
nodes = models.ManyToManyField('assets.Node', blank=True, verbose_name=_("Nodes"))
assets = models.ManyToManyField('assets.Asset', blank=True, verbose_name=_("Assets"))
users = models.ManyToManyField('users.User', blank=True, verbose_name=_("Users"))
groups = models.ManyToManyField('users.UserGroup', blank=True, verbose_name=_("User groups"))
priority = models.IntegerField(default=20, verbose_name=_("Priority"), validators=[MinValueValidator(1), MaxValueValidator(100)])
protocol = models.CharField(max_length=16, choices=PROTOCOL_CHOICES, default='ssh', verbose_name=_('Protocol'))
auto_push = models.BooleanField(default=True, verbose_name=_('Auto push'))
@@ -116,9 +117,20 @@ class SystemUser(AssetUser):
shell = models.CharField(max_length=64, default='/bin/bash', verbose_name=_('Shell'))
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
sftp_root = models.CharField(default='tmp', max_length=128, verbose_name=_("SFTP Root"))
_prefer = 'system_user'
def __str__(self):
return '{0.name}({0.username})'.format(self)
username = self.username
if self.username_same_with_user:
username = 'dynamic'
return '{0.name}({1})'.format(self, username)
def get_username(self):
if self.username_same_with_user:
return list(self.users.values_list('username', flat=True))
else:
return self.username
@property
def nodes_amount(self):
@@ -134,6 +146,23 @@ class SystemUser(AssetUser):
else:
return False
@property
def is_need_cmd_filter(self):
return self.protocol not in [self.PROTOCOL_RDP, self.PROTOCOL_VNC]
@property
def is_need_test_asset_connective(self):
return self.protocol not in [self.PROTOCOL_MYSQL]
@property
def can_perm_to_asset(self):
return self.protocol not in [self.PROTOCOL_MYSQL]
def _merge_auth(self, other):
super()._merge_auth(other)
if self.username_same_with_user:
self.username = other.username
@property
def cmd_filter_rules(self):
from .cmd_filter import CommandFilterRule

View File

@@ -77,7 +77,7 @@ class Connectivity:
return cls(cls.UNKNOWN, timezone.now())
@classmethod
def set(cls, key, value, ttl=0):
def set(cls, key, value, ttl=None):
cache.set(key, value, ttl)
@classmethod

View File

@@ -55,3 +55,11 @@ class ReplaceNodeAdminUserSerializer(serializers.ModelSerializer):
class TaskIDSerializer(serializers.Serializer):
task = serializers.CharField(read_only=True)
class AssetUserTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
('test', 'test'),
)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
task = serializers.CharField(read_only=True)

View File

@@ -1,22 +1,20 @@
# -*- coding: utf-8 -*-
#
import re
from rest_framework import serializers
from django.db.models import Prefetch
from django.db.models import Prefetch, F, Count
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from common.serializers import AdaptedBulkListSerializer
from ..models import Asset, Node, Label
from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
)
from ..models import Asset, Node, Label, Platform
from .base import ConnectivitySerializer
__all__ = [
'AssetSerializer', 'AssetSimpleSerializer',
'ProtocolsField',
'AssetDisplaySerializer',
'ProtocolsField', 'PlatformSerializer',
'AssetDetailSerializer', 'AssetTaskSerializer',
]
@@ -65,30 +63,45 @@ class ProtocolsField(serializers.ListField):
class AssetSerializer(BulkOrgResourceModelSerializer):
platform = serializers.SlugRelatedField(
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
)
protocols = ProtocolsField(label=_('Protocols'), required=False)
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
"""
资产的数据结构
"""
class Meta:
model = Asset
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',
fields_mini = ['id', 'hostname', 'ip']
fields_small = fields_mini + [
'protocol', 'port', 'protocols', 'is_active', 'public_ip',
'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',
]
read_only_fields = (
fields_fk = [
'admin_user', 'admin_user_display', 'domain', 'platform'
]
fk_only_fields = {
'platform': ['name']
}
fields_m2m = [
'nodes', 'labels',
]
annotates_fields = {
# 'admin_user_display': 'admin_user__name'
}
fields_as = list(annotates_fields.keys())
fields = fields_small + fields_fk + fields_m2m + fields_as
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',
)
] + fields_as
extra_kwargs = {
'protocol': {'write_only': True},
'port': {'write_only': True},
@@ -96,22 +109,10 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'org_name': {'label': _('Org name')}
}
@staticmethod
def validate_hostname(hostname):
pattern = GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN
res = re.search(pattern, hostname)
if res is not None:
msg = GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
raise serializers.ValidationError(msg)
return hostname
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
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')
queryset = queryset.select_related('admin_user', 'domain', 'platform')
return queryset
def compatible_with_old_protocol(self, validated_data):
@@ -139,9 +140,49 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
return super().update(instance, validated_data)
class AssetDisplaySerializer(AssetSerializer):
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
class Meta(AssetSerializer.Meta):
fields = AssetSerializer.Meta.fields + [
'connectivity',
]
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset\
.annotate(admin_user_username=F('admin_user__username'))
return queryset
class PlatformSerializer(serializers.ModelSerializer):
meta = serializers.DictField(required=False, allow_null=True)
class Meta:
model = Platform
fields = [
'id', 'name', 'base', 'charset',
'internal', 'meta', 'comment'
]
class AssetDetailSerializer(AssetSerializer):
platform = PlatformSerializer(read_only=True)
class AssetSimpleSerializer(serializers.ModelSerializer):
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
class AssetTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
('refresh', 'refresh'),
('test', 'test'),
)
task = serializers.CharField(read_only=True)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)

View File

@@ -8,39 +8,23 @@ from common.serializers import AdaptedBulkListSerializer
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import AuthBook, Asset
from ..backends import AssetUserManager
from .base import ConnectivitySerializer, AuthSerializerMixin
__all__ = [
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
'AssetUserExportSerializer', 'AssetUserPushSerializer',
'AssetUserWriteSerializer', 'AssetUserReadSerializer',
'AssetUserAuthInfoSerializer', '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 AssetUserWriteSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
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",
'id', 'username', 'password', 'private_key', "public_key",
'asset', 'comment',
]
extra_kwargs = {
'username': {'required': True},
@@ -53,11 +37,35 @@ class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
if not validated_data.get("name") and validated_data.get("username"):
validated_data["name"] = validated_data["username"]
instance = AssetUserManager.create(**validated_data)
instance.set_to_latest()
return instance
class AssetUserExportSerializer(AssetUserSerializer):
class AssetUserReadSerializer(AssetUserWriteSerializer):
id = serializers.CharField(read_only=True, source='union_id', label=_("ID"))
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
ip = serializers.CharField(read_only=True, label=_("IP"))
asset = serializers.CharField(source='asset_id', label=_('Asset'))
backend = serializers.CharField(read_only=True, label=_("Backend"))
class Meta(AssetUserWriteSerializer.Meta):
read_only_fields = (
'date_created', 'date_updated',
'created_by', 'version',
)
fields = [
'id', 'username', 'password', 'private_key', "public_key",
'asset', 'hostname', 'ip', 'backend', 'version',
'date_created', "date_updated", 'comment',
]
extra_kwargs = {
'username': {'required': True},
'password': {'write_only': True},
'private_key': {'write_only': True},
'public_key': {'write_only': True},
}
class AssetUserAuthInfoSerializer(AssetUserReadSerializer):
password = serializers.CharField(
max_length=256, allow_blank=True, allow_null=True,
required=False, label=_('Password')
@@ -72,12 +80,6 @@ class AssetUserExportSerializer(AssetUserSerializer):
)
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
class Meta:
model = AuthBook
fields = ['password', 'private_key', 'public_key']
class AssetUserPushSerializer(serializers.Serializer):
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects, label=_("Asset"))
username = serializers.CharField(max_length=1024)

View File

@@ -5,6 +5,7 @@ from django.utils.translation import ugettext as _
from rest_framework import serializers
from common.utils import ssh_pubkey_gen, validate_ssh_private_key
from ..models import AssetUser
class AuthSerializer(serializers.ModelSerializer):
@@ -60,9 +61,6 @@ class AuthSerializerMixin:
if not value:
validated_data.pop(field, None)
# print(validated_data)
# raise serializers.ValidationError(">>>>>>")
def create(self, validated_data):
self.clean_auth_fields(validated_data)
return super().create(validated_data)
@@ -70,3 +68,15 @@ class AuthSerializerMixin:
def update(self, instance, validated_data):
self.clean_auth_fields(validated_data)
return super().update(instance, validated_data)
class AuthInfoSerializer(serializers.ModelSerializer):
private_key = serializers.ReadOnlyField(source='get_private_key')
class Meta:
model = AssetUser
fields = [
'username', 'password',
'private_key', 'public_key',
'date_updated',
]

View File

@@ -2,7 +2,6 @@
#
import re
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from common.fields import ChoiceDisplayField
from common.serializers import AdaptedBulkListSerializer
@@ -27,11 +26,20 @@ class CommandFilterSerializer(BulkOrgResourceModelSerializer):
class CommandFilterRuleSerializer(BulkOrgResourceModelSerializer):
serializer_choice_field = ChoiceDisplayField
# serializer_choice_field = ChoiceDisplayField
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
type_display = serializers.ReadOnlyField(source='get_type_display')
action_display = serializers.ReadOnlyField(source='get_action_display')
class Meta:
model = CommandFilterRule
fields_mini = ['id']
fields_small = fields_mini + [
'type', 'type_display', 'content', 'priority',
'action', 'action_display',
'comment', 'created_by', 'date_created', 'date_updated'
]
fields_fk = ['filter']
fields = '__all__'
list_serializer_class = AdaptedBulkListSerializer

View File

@@ -15,11 +15,18 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
class Meta:
model = Domain
fields = [
'id', 'name', 'asset_count', 'gateway_count', 'comment', 'assets',
'date_created'
fields_mini = ['id', 'name']
fields_small = fields_mini + [
'comment', 'date_created'
]
read_only_fields = ( 'asset_count', 'gateway_count', 'date_created')
fields_m2m = [
'asset_count', 'assets', 'gateway_count',
]
fields = fields_small + fields_m2m
read_only_fields = ('asset_count', 'gateway_count', 'date_created')
extra_kwargs = {
'assets': {'required': False}
}
list_serializer_class = AdaptedBulkListSerializer
@staticmethod
@@ -41,6 +48,16 @@ class GatewaySerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'date_updated', 'created_by', 'comment',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.protocol_limit_to_ssh()
def protocol_limit_to_ssh(self):
protocol_field = self.fields['protocol']
choices = protocol_field.choices
choices.pop('rdp')
protocol_field._choices = choices
class GatewayWithAuthSerializer(GatewaySerializer):
def get_field_names(self, declared_fields, info):
@@ -51,6 +68,8 @@ class GatewayWithAuthSerializer(GatewaySerializer):
return fields
class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):
gateways = GatewayWithAuthSerializer(many=True, read_only=True)

View File

@@ -16,7 +16,7 @@ class GatheredUserSerializer(OrgResourceModelSerializerMixin):
'present', 'date_created', 'date_updated'
]
read_only_fields = fields
labels = {
'hostname': _("Hostname"),
'ip': "IP"
extra_kwargs = {
'hostname': {'label': _("Hostname")},
'ip': {'label': 'IP'},
}

View File

@@ -20,6 +20,9 @@ class LabelSerializer(BulkOrgResourceModelSerializer):
read_only_fields = (
'category', 'date_created', 'asset_count', 'get_category_display'
)
extra_kwargs = {
'assets': {'required': False}
}
list_serializer_class = AdaptedBulkListSerializer
@staticmethod

View File

@@ -8,7 +8,7 @@ from ..models import Asset, Node
__all__ = [
'NodeSerializer', "NodeAddChildrenSerializer",
"NodeAssetsSerializer",
"NodeAssetsSerializer", "NodeTaskSerializer",
]
@@ -51,3 +51,12 @@ class NodeAssetsSerializer(BulkOrgResourceModelSerializer):
class NodeAddChildrenSerializer(serializers.Serializer):
nodes = serializers.ListField()
class NodeTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
('refresh', 'refresh'),
('test', 'test'),
('refresh_cache', 'refresh_cache'),
)
task = serializers.CharField(read_only=True)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)

View File

@@ -1,17 +1,21 @@
import re
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count
from common.serializers import AdaptedBulkListSerializer
from common.mixins.serializers import BulkSerializerMixin
from common.utils import ssh_pubkey_gen
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import SystemUser
from ..const import (
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN,
GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
)
from .base import AuthSerializer, AuthSerializerMixin
from assets.models import Node
from ..models import SystemUser, Asset
from .base import AuthSerializerMixin
__all__ = [
'SystemUserSerializer', 'SystemUserListSerializer',
'SystemUserSimpleSerializer', 'SystemUserAssetRelationSerializer',
'SystemUserNodeRelationSerializer', 'SystemUserTaskSerializer',
'SystemUserUserRelationSerializer', 'SystemUserWithAuthInfoSerializer',
]
class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
@@ -24,10 +28,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
model = SystemUser
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'username', 'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display', 'priority', 'protocol',
'id', 'name', 'username', 'protocol',
'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display',
'priority', 'username_same_with_user',
'auto_push', 'cmd_filters', 'sudo', 'shell', 'comment',
'assets_amount', 'nodes_amount', 'auto_generate_key'
'auto_generate_key', 'sftp_root',
'assets_amount', 'date_created', 'created_by'
]
extra_kwargs = {
'password': {"write_only": True},
@@ -39,15 +46,6 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'created_by': {'read_only': True},
}
@staticmethod
def validate_name(name):
pattern = GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_PATTERN
res = re.search(pattern, name)
if res is not None:
msg = GENERAL_FORBIDDEN_SPECIAL_CHARACTERS_ERROR_MSG
raise serializers.ValidationError(msg)
return name
def validate_auto_push(self, value):
login_mode = self.initial_data.get("login_mode")
protocol = self.initial_data.get("protocol")
@@ -72,17 +70,43 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
value = False
return value
def validate_username_same_with_user(self, username_same_with_user):
if not username_same_with_user:
return username_same_with_user
protocol = self.initial_data.get("protocol", "ssh")
queryset = SystemUser.objects.filter(
protocol=protocol, username_same_with_user=True
)
if self.instance:
queryset = queryset.exclude(id=self.instance.id)
exists = queryset.exists()
if not exists:
return username_same_with_user
error = _("Username same with user with protocol {} only allow 1").format(protocol)
raise serializers.ValidationError(error)
def validate_username(self, username):
if username:
return username
login_mode = self.initial_data.get("login_mode")
protocol = self.initial_data.get("protocol")
username_same_with_user = self.initial_data.get("username_same_with_user")
if username_same_with_user:
return ''
if login_mode == SystemUser.LOGIN_AUTO and \
protocol != SystemUser.PROTOCOL_VNC:
msg = _('* Automatic login mode must fill in the username.')
raise serializers.ValidationError(msg)
return username
def validate_sftp_root(self, value):
if value in ['home', 'tmp']:
return value
if not value.startswith('/'):
error = _("Path should starts with /")
raise serializers.ValidationError(error)
return value
def validate_password(self, password):
super().validate_password(password)
auto_gen_key = self.initial_data.get("auto_generate_key", False)
@@ -95,8 +119,12 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
def validate(self, attrs):
username = attrs.get("username", "manual")
auto_gen_key = attrs.pop("auto_generate_key", False)
protocol = attrs.get("protocol")
auto_gen_key = attrs.get("auto_generate_key", False)
if protocol not in [SystemUser.PROTOCOL_RDP, SystemUser.PROTOCOL_SSH]:
return attrs
if auto_gen_key:
password = SystemUser.gen_password()
attrs["password"] = password
@@ -111,27 +139,44 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
public_key = ssh_pubkey_gen(private_key, password=password,
username=username)
attrs["public_key"] = public_key
attrs.pop("auto_generate_key", None)
return attrs
class SystemUserListSerializer(SystemUserSerializer):
class Meta(SystemUserSerializer.Meta):
fields = [
'id', 'name', 'username', 'protocol',
'login_mode', 'login_mode_display',
'priority', "username_same_with_user",
'auto_push', 'sudo', 'shell', 'comment',
"assets_amount",
'auto_generate_key',
'sftp_root',
]
@classmethod
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('cmd_filters', 'nodes')
queryset = queryset.annotate(assets_amount=Count("assets"))
return queryset
class SystemUserAuthSerializer(AuthSerializer):
"""
系统用户认证信息
"""
class Meta:
model = SystemUser
class SystemUserWithAuthInfoSerializer(SystemUserSerializer):
class Meta(SystemUserSerializer.Meta):
fields = [
"id", "name", "username", "protocol",
"login_mode", "password", "private_key",
'id', 'name', 'username', 'protocol',
'password', 'public_key', 'private_key',
'login_mode', 'login_mode_display',
'priority', 'username_same_with_user',
'auto_push', 'sudo', 'shell', 'comment',
'auto_generate_key', 'sftp_root',
]
extra_kwargs = {
'nodes_amount': {'label': _('Node')},
'assets_amount': {'label': _('Asset')},
'login_mode_display': {'label': _('Login mode display')},
'created_by': {'read_only': True},
}
class SystemUserSimpleSerializer(serializers.ModelSerializer):
@@ -143,4 +188,65 @@ class SystemUserSimpleSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'username')
class RelationMixin(BulkSerializerMixin, serializers.Serializer):
systemuser_display = serializers.ReadOnlyField()
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['systemuser', "systemuser_display"])
return fields
class Meta:
list_serializer_class = AdaptedBulkListSerializer
class SystemUserAssetRelationSerializer(RelationMixin, serializers.ModelSerializer):
asset_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = SystemUser.assets.through
fields = [
'id', "asset", "asset_display",
]
class SystemUserNodeRelationSerializer(RelationMixin, serializers.ModelSerializer):
node_display = serializers.SerializerMethodField()
class Meta(RelationMixin.Meta):
model = SystemUser.nodes.through
fields = [
'id', 'node', "node_display",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tree = Node.tree()
def get_node_display(self, obj):
if hasattr(obj, 'node_key'):
return self.tree.get_node_full_tag(obj.node_key)
else:
return obj.node.full_value
class SystemUserUserRelationSerializer(RelationMixin, serializers.ModelSerializer):
user_display = serializers.ReadOnlyField()
class Meta(RelationMixin.Meta):
model = SystemUser.users.through
fields = [
'id', "user", "user_display",
]
class SystemUserTaskSerializer(serializers.Serializer):
ACTION_CHOICES = (
("test", "test"),
("push", "push"),
)
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
asset = serializers.PrimaryKeyRelatedField(
queryset=Asset.objects, allow_null=True, required=False, write_only=True
)
task = serializers.CharField(read_only=True)

View File

@@ -7,12 +7,15 @@ from django.db.models.signals import (
from django.db.models.aggregates import Count
from django.dispatch import receiver
from common.utils import get_logger, timeit
from common.utils import get_logger
from common.decorator import on_transaction_commit
from orgs.utils import tmp_to_root_org
from .models import Asset, SystemUser, Node, AuthBook
from .utils import TreeService
from .tasks import (
update_assets_hardware_info_util,
test_asset_connectivity_util,
push_system_user_to_assets_manual,
push_system_user_to_assets,
add_nodes_assets_to_system_users
)
@@ -93,6 +96,25 @@ def on_system_user_assets_change(sender, instance=None, action='', model=None, p
push_system_user_to_assets.delay(system_user, assets)
@receiver(m2m_changed, sender=SystemUser.users.through)
def on_system_user_users_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
"""
当系统用户和用户关系发生变化时,应该重新推送系统用户资产中
"""
if action != "post_add":
return
if not instance.username_same_with_user:
return
logger.debug("System user users change signal recv: {}".format(instance))
queryset = model.objects.filter(pk__in=pk_set)
if model == SystemUser:
system_users = queryset
else:
system_users = [instance]
for s in system_users:
push_system_user_to_assets_manual.delay(s)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
"""
@@ -112,6 +134,20 @@ def on_system_user_nodes_change(sender, instance=None, action=None, model=None,
add_nodes_assets_to_system_users.delay(nodes_keys, system_users)
@receiver(m2m_changed, sender=SystemUser.groups.through)
def on_system_user_groups_change(sender, instance=None, action=None, model=None,
pk_set=None, reverse=False, **kwargs):
"""
当系统用户和用户组关系发生变化时,应该将组下用户关联到新的系统用户上
"""
if action != "post_add" or reverse:
return
logger.info("System user groups update signal recv: {}".format(instance))
groups = model.objects.filter(pk__in=pk_set).annotate(users_count=Count("users"))
users = groups.filter(users_count__gt=0).values_list('users', flat=True)
instance.users.add(*tuple(users))
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
"""
@@ -120,6 +156,8 @@ def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
if action.startswith('post'):
logger.debug("Asset nodes change signal recv: {}".format(instance))
Node.refresh_assets()
with tmp_to_root_org():
Node.refresh_assets()
@receiver(m2m_changed, sender=Asset.nodes.through)
@@ -131,16 +169,21 @@ def on_asset_nodes_add(sender, instance=None, action='', model=None, pk_set=None
if action != "post_add":
return
logger.debug("Assets node add signal recv: {}".format(action))
queryset = model.objects.filter(pk__in=pk_set).values_list('id', flat=True)
if model == Node:
nodes = queryset
assets = [instance]
nodes = model.objects.filter(pk__in=pk_set).values_list('key', flat=True)
assets = [instance.id]
else:
nodes = [instance]
assets = queryset
# 节点资产发生变化时,将资产关联到节点关联的系统用户, 只关注新增的
nodes = [instance.key]
assets = model.objects.filter(pk__in=pk_set).values_list('id', flat=True)
# 节点资产发生变化时,将资产关联到节点及祖先节点关联的系统用户, 只关注新增的
nodes_ancestors_keys = set()
node_tree = TreeService.new()
for node in nodes:
ancestors_keys = node_tree.ancestors_ids(nid=node)
nodes_ancestors_keys.update(ancestors_keys)
system_users = SystemUser.objects.filter(nodes__key__in=nodes_ancestors_keys)
system_users_assets = defaultdict(set)
system_users = SystemUser.objects.filter(nodes__in=nodes)
for system_user in system_users:
system_users_assets[system_user].update(set(assets))
for system_user, _assets in system_users_assets.items():
@@ -189,9 +232,5 @@ def on_asset_nodes_remove(sender, instance=None, action='', model=None,
def on_node_update_or_created(sender, **kwargs):
# 刷新节点
Node.refresh_nodes()
@receiver(post_save, sender=AuthBook)
def on_authbook_created(sender, instance=None, created=True, **kwargs):
if created and instance:
instance.set_version()
with tmp_to_root_org():
Node.refresh_nodes()

View File

@@ -4,11 +4,12 @@ from celery import shared_task
from django.utils.translation import ugettext as _
from django.core.cache import cache
from orgs.utils import tmp_to_root_org, org_aware_func
from common.utils import get_logger
from ops.celery.decorator import register_as_period_task
from ..models import AdminUser
from .utils import clean_hosts
from .utils import clean_ansible_task_hosts
from .asset_connectivity import test_asset_connectivity_util
from . import const
@@ -20,7 +21,7 @@ __all__ = [
]
@shared_task(queue="ansible")
@org_aware_func("admin_user")
def test_admin_user_connectivity_util(admin_user, task_name):
"""
Test asset admin user can connect or not. Using ansible api do that
@@ -29,7 +30,7 @@ def test_admin_user_connectivity_util(admin_user, task_name):
:return:
"""
assets = admin_user.get_related_assets()
hosts = clean_hosts(assets)
hosts = clean_ansible_task_hosts(assets)
if not hosts:
return {}
summary = test_asset_connectivity_util(hosts, task_name)
@@ -51,10 +52,13 @@ def test_admin_user_connectivity_period():
logger.debug("Test admin user connectivity, less than 40 minutes, skip")
return
cache.set(key, 1, 60*40)
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(admin_user.name)
test_admin_user_connectivity_util(admin_user, task_name)
with tmp_to_root_org():
admin_users = AdminUser.objects.all()
for admin_user in admin_users:
task_name = _("Test admin user connectivity period: {}").format(
admin_user.name
)
test_admin_user_connectivity_util(admin_user, task_name)
cache.set(key, 1, 60*40)

View File

@@ -1,55 +1,55 @@
# ~*~ coding: utf-8 ~*~
from itertools import groupby
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from orgs.utils import org_aware_func
from ..models.utils import Connectivity
from . import const
from .utils import clean_hosts
from .utils import clean_ansible_task_hosts, group_asset_by_platform
logger = get_logger(__file__)
__all__ = ['test_asset_connectivity_util', 'test_asset_connectivity_manual']
__all__ = [
'test_asset_connectivity_util', 'test_asset_connectivity_manual',
'test_node_assets_connectivity_manual',
]
@shared_task(queue="ansible")
@org_aware_func("assets")
def test_asset_connectivity_util(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Test assets connectivity")
hosts = clean_hosts(assets)
hosts = clean_ansible_task_hosts(assets)
if not hosts:
return {}
platform_hosts_map = {}
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
for i in platform_hosts:
platform_hosts_map[i[0]] = list(i[1])
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_ADMIN_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_ADMIN_USER_CONN_TASKS
}
platform_tasks_map = {
"unixlike": const.PING_UNIXLIKE_TASKS,
"windows": const.PING_WINDOWS_TASKS
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
created_by = assets[0].org_id
for k, value in hosts_category.items():
if not value['hosts']:
for platform, _hosts in platform_hosts_map.items():
if not _hosts:
continue
logger.debug("System user not has special auth")
tasks = platform_tasks_map.get(platform)
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
task_name=task_name, hosts=_hosts, tasks=tasks,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
created_by=created_by,
)
raw, summary = task.run()
success = summary.get('success', False)
@@ -59,6 +59,7 @@ def test_asset_connectivity_util(assets, task_name=None):
results_summary['success'] &= success
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
continue
for asset in assets:
if asset.hostname in results_summary.get('dark', {}).keys():
@@ -79,3 +80,12 @@ def test_asset_connectivity_manual(asset):
return False, summary['dark']
else:
return True, ""
@shared_task(queue="ansible")
def test_node_assets_connectivity_manual(node):
task_name = _("Test if the assets under the node are connectable: {}".format(node.name))
assets = node.get_all_assets()
result = test_asset_connectivity_util(assets, task_name=task_name)
return result

View File

@@ -3,7 +3,9 @@
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from common.utils import get_logger, get_object_or_none
from orgs.utils import org_aware_func
from ..models import Asset
from . import const
from .utils import check_asset_can_run_ansible
@@ -13,15 +15,16 @@ logger = get_logger(__file__)
__all__ = [
'test_asset_user_connectivity_util', 'test_asset_users_connectivity_manual',
'get_test_asset_user_connectivity_tasks',
'get_test_asset_user_connectivity_tasks', 'test_user_connectivity',
'run_adhoc',
]
def get_test_asset_user_connectivity_tasks(asset):
if asset.is_unixlike():
tasks = const.TEST_ASSET_USER_CONN_TASKS
tasks = const.PING_UNIXLIKE_TASKS
elif asset.is_windows():
tasks = const.TEST_WINDOWS_ASSET_USER_CONN_TASKS
tasks = const.PING_WINDOWS_TASKS
else:
msg = _(
"The asset {} system platform {} does not "
@@ -32,46 +35,98 @@ def get_test_asset_user_connectivity_tasks(asset):
return tasks
@shared_task(queue="ansible")
def test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=False):
def run_adhoc(task_name, tasks, inventory):
"""
:param task_name
:param tasks
:param inventory
"""
from ops.ansible.runner import AdHocRunner
runner = AdHocRunner(inventory, options=const.TASK_OPTIONS)
result = runner.run(tasks, 'all', task_name)
return result.results_raw, result.results_summary
def test_user_connectivity(task_name, asset, username, password=None, private_key=None):
"""
:param task_name
:param asset
:param username
:param password
:param private_key
"""
from ops.inventory import JMSCustomInventory
tasks = get_test_asset_user_connectivity_tasks(asset)
if not tasks:
logger.debug("No tasks ")
return {}, {}
inventory = JMSCustomInventory(
assets=[asset], username=username, password=password,
private_key=private_key
)
raw, summary = run_adhoc(
task_name=task_name, tasks=tasks, inventory=inventory
)
return raw, summary
@org_aware_func("asset_user")
def test_asset_user_connectivity_util(asset_user, task_name):
"""
:param asset_user: <AuthBook>对象
:param task_name:
:param run_as_admin:
:return:
"""
from ops.utils import update_or_create_ansible_task
if not check_asset_can_run_ansible(asset_user.asset):
return
tasks = get_test_asset_user_connectivity_tasks(asset_user.asset)
if not tasks:
logger.debug("No tasks ")
try:
raw, summary = test_user_connectivity(
task_name=task_name, asset=asset_user.asset,
username=asset_user.username, password=asset_user.password,
private_key=asset_user.private_key
)
except Exception as e:
logger.warn("Failed run adhoc {}, {}".format(task_name, e))
return
args = (task_name,)
kwargs = {
'hosts': [asset_user.asset], 'tasks': tasks,
'pattern': 'all', 'options': const.TASK_OPTIONS,
'created_by': asset_user.org_id,
}
if run_as_admin:
kwargs["run_as_admin"] = True
else:
kwargs["run_as"] = asset_user.username
task, created = update_or_create_ansible_task(*args, **kwargs)
raw, summary = task.run()
asset_user.set_connectivity(summary)
@shared_task(queue="ansible")
def test_asset_users_connectivity_manual(asset_users, run_as_admin=False):
def test_asset_users_connectivity_manual(asset_users):
"""
:param asset_users: <AuthBook>对象
"""
for asset_user in asset_users:
task_name = _("Test asset user connectivity: {}").format(asset_user)
test_asset_user_connectivity_util(asset_user, task_name, run_as_admin=run_as_admin)
test_asset_user_connectivity_util(asset_user, task_name)
@shared_task(queue="ansible")
def push_asset_user_util(asset_user):
"""
:param asset_user: <Asset user>对象
"""
from .push_system_user import push_system_user_util
if not asset_user.backend.startswith('system_user'):
logger.error("Asset user is not from system user")
return
union_id = asset_user.union_id
union_id_list = union_id.split('_')
if len(union_id_list) < 2:
logger.error("Asset user union id length less than 2")
return
system_user_id = union_id_list[0]
asset_id = union_id_list[1]
asset = get_object_or_none(Asset, pk=asset_id)
system_user = None
if not asset:
return
hosts = check_asset_can_run_ansible([asset])
if asset.is_unixlike:
pass

View File

@@ -18,27 +18,10 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
}
]
TEST_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "ping",
}
}
]
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
{
"name": "ping",
"action": {
"module": "win_ping",
}
}
]
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
SYSTEM_USER_CONN_CACHE_KEY = "SYSTEM_USER_CONN_{}"
TEST_SYSTEM_USER_CONN_TASKS = [
PING_UNIXLIKE_TASKS = [
{
"name": "ping",
"action": {
@@ -46,7 +29,7 @@ TEST_SYSTEM_USER_CONN_TASKS = [
}
}
]
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
PING_WINDOWS_TASKS = [
{
"name": "ping",
"action": {
@@ -55,24 +38,6 @@ TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
}
]
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,
@@ -98,7 +63,9 @@ GATHER_ASSET_USERS_TASKS = [
"name": "get last login",
"action": {
"module": "shell",
"args": "users=$(getent passwd | grep -v 'nologin' | grep -v 'shudown' | awk -F: '{ print $1 }');for i in $users;do last -F $i -1 | head -1 | grep -v '^$' | awk '{ print $1\"@\"$3\"@\"$5,$6,$7,$8 }';done"
"args": "users=$(getent passwd | grep -v 'nologin' | "
"grep -v 'shudown' | awk -F: '{ print $1 }');for i in $users;do last -F $i -1 | "
"head -1 | grep -v '^$' | awk '{ print $1\"@\"$3\"@\"$5,$6,$7,$8 }';done"
}
}
]

View File

@@ -9,15 +9,16 @@ from django.utils.translation import ugettext as _
from common.utils import (
capacity_convert, sum_capacity, get_logger
)
from orgs.utils import org_aware_func
from . import const
from .utils import clean_hosts
from .utils import clean_ansible_task_hosts
logger = get_logger(__file__)
disk_pattern = re.compile(r'^hd|sd|xvd|vd|nv')
__all__ = [
'update_assets_hardware_info_util', 'update_asset_hardware_info_manual',
'update_assets_hardware_info_period',
'update_assets_hardware_info_period', 'update_node_assets_hardware_info_manual',
]
@@ -82,6 +83,7 @@ def set_assets_hardware_info(assets, result, **kwargs):
@shared_task
@org_aware_func("assets")
def update_assets_hardware_info_util(assets, task_name=None):
"""
Using ansible api to update asset hardware info
@@ -93,13 +95,13 @@ def update_assets_hardware_info_util(assets, task_name=None):
if task_name is None:
task_name = _("Update some assets hardware info")
tasks = const.UPDATE_ASSETS_HARDWARE_TASKS
hosts = clean_hosts(assets)
hosts = clean_ansible_task_hosts(assets)
if not hosts:
return {}
created_by = str(assets[0].org_id)
task, created = update_or_create_ansible_task(
task_name, hosts=hosts, tasks=tasks, created_by=created_by,
pattern='all', options=const.TASK_OPTIONS, run_as_admin=True,
task_name, hosts=hosts, tasks=tasks,
pattern='all', options=const.TASK_OPTIONS,
run_as_admin=True,
)
result = task.run()
set_assets_hardware_info(assets, result)
@@ -109,9 +111,7 @@ def update_assets_hardware_info_util(assets, task_name=None):
@shared_task(queue="ansible")
def update_asset_hardware_info_manual(asset):
task_name = _("Update asset hardware info: {}").format(asset.hostname)
update_assets_hardware_info_util(
[asset], task_name=task_name
)
update_assets_hardware_info_util([asset], task_name=task_name)
@shared_task(queue="ansible")
@@ -123,3 +123,11 @@ def update_assets_hardware_info_period():
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, update assets hardware info pass")
return
@shared_task(queue="ansible")
def update_node_assets_hardware_info_manual(node):
task_name = _("Update node asset hardware information: {}").format(node.name)
assets = node.get_all_assets()
result = update_assets_hardware_info_util.delay(assets, task_name=task_name)
return result

View File

@@ -7,10 +7,10 @@ from celery import shared_task
from django.utils.translation import ugettext as _
from django.utils import timezone
from orgs.utils import tmp_to_org
from orgs.utils import tmp_to_org, org_aware_func
from common.utils import get_logger
from ..models import GatheredUser, Node
from .utils import clean_hosts
from .utils import clean_ansible_task_hosts
from . import const
__all__ = ['gather_asset_users', 'gather_nodes_asset_users']
@@ -101,11 +101,12 @@ def add_asset_users(assets, results):
@shared_task(queue="ansible")
@org_aware_func("assets")
def gather_asset_users(assets, task_name=None):
from ops.utils import update_or_create_ansible_task
if task_name is None:
task_name = _("Gather assets users")
assets = clean_hosts(assets)
assets = clean_ansible_task_hosts(assets)
if not assets:
return
hosts_category = {
@@ -131,7 +132,7 @@ def gather_asset_users(assets, task_name=None):
task, created = update_or_create_ansible_task(
task_name=_task_name, hosts=value['hosts'], tasks=value['tasks'],
pattern='all', options=const.TASK_OPTIONS,
run_as_admin=True, created_by=value['hosts'][0].org_id,
run_as_admin=True,
)
raw, summary = task.run()
results[k].update(raw['ok'])

View File

@@ -1,11 +1,13 @@
# ~*~ coding: utf-8 ~*~
from itertools import groupby
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import encrypt_password, get_logger
from orgs.utils import tmp_to_org, org_aware_func
from . import const
from .utils import clean_hosts_by_protocol, clean_hosts
from .utils import clean_ansible_task_hosts, group_asset_by_platform
logger = get_logger(__file__)
@@ -15,31 +17,34 @@ __all__ = [
]
def get_push_linux_system_user_tasks(system_user):
def get_push_unixlike_system_user_tasks(system_user, username=None):
if username is None:
username = system_user.username
password = system_user.password
public_key = system_user.public_key
tasks = [
{
'name': 'Add user {}'.format(system_user.username),
'name': 'Add user {}'.format(username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present'.format(
system_user.username, system_user.shell,
username, system_user.shell or '/bin/bash',
),
}
},
{
'name': 'Add group {}'.format(system_user.username),
'name': 'Add group {}'.format(username),
'action': {
'module': 'group',
'args': 'name={} state=present'.format(
system_user.username,
),
'args': 'name={} state=present'.format(username),
}
},
{
'name': 'Check home dir exists',
'action': {
'module': 'stat',
'args': 'path=/home/{}'.format(system_user.username)
'args': 'path=/home/{}'.format(username)
},
'register': 'home_existed'
},
@@ -47,29 +52,29 @@ def get_push_linux_system_user_tasks(system_user):
'name': "Set home dir permission",
'action': {
'module': 'file',
'args': "path=/home/{0} owner={0} group={0} mode=700".format(system_user.username)
'args': "path=/home/{0} owner={0} group={0} mode=700".format(username)
},
'when': 'home_existed.stat.exists == true'
}
]
if system_user.password:
if password:
tasks.append({
'name': 'Set {} password'.format(system_user.username),
'name': 'Set {} password'.format(username),
'action': {
'module': 'user',
'args': 'name={} shell={} state=present password={}'.format(
system_user.username, system_user.shell,
encrypt_password(system_user.password, salt="K3mIlKK"),
username, system_user.shell,
encrypt_password(password, salt="K3mIlKK"),
),
}
})
if system_user.public_key:
if public_key:
tasks.append({
'name': 'Set {} authorized key'.format(system_user.username),
'name': 'Set {} authorized key'.format(username),
'action': {
'module': 'authorized_key',
'args': "user={} state=present key='{}'".format(
system_user.username, system_user.public_key
username, public_key
)
}
})
@@ -81,26 +86,27 @@ def get_push_linux_system_user_tasks(system_user):
sudo_tmp.append(s.strip(','))
sudo = ','.join(sudo_tmp)
tasks.append({
'name': 'Set {} sudo setting'.format(system_user.username),
'name': 'Set {} sudo setting'.format(username),
'action': {
'module': 'lineinfile',
'args': "dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf %s'".format(
system_user.username, sudo,
)
"validate='visudo -cf %s'".format(username, sudo)
}
})
return tasks
def get_push_windows_system_user_tasks(system_user):
def get_push_windows_system_user_tasks(system_user, username=None):
if username is None:
username = system_user.username
password = system_user.password
tasks = []
if not system_user.password:
if not password:
return tasks
tasks.append({
'name': 'Add user {}'.format(system_user.username),
task = {
'name': 'Add user {}'.format(username),
'action': {
'module': 'win_user',
'args': 'fullname={} '
@@ -112,84 +118,100 @@ def get_push_windows_system_user_tasks(system_user):
'password_never_expires=yes '
'groups="Users,Remote Desktop Users" '
'groups_action=add '
''.format(system_user.name,
system_user.username,
system_user.password),
''.format(username, username, password),
}
})
}
tasks.append(task)
return tasks
def get_push_system_user_tasks(host, system_user):
if host.is_unixlike():
tasks = get_push_linux_system_user_tasks(system_user)
elif host.is_windows():
tasks = get_push_windows_system_user_tasks(system_user)
else:
msg = _(
"The asset {} system platform {} does not "
"support run Ansible tasks".format(host.hostname, host.platform)
)
logger.info(msg)
tasks = []
def get_push_system_user_tasks(system_user, platform="unixlike", username=None):
"""
:param system_user:
:param platform:
:param username: 当动态时,近推送某个
:return:
"""
get_task_map = {
"unixlike": get_push_unixlike_system_user_tasks,
"windows": get_push_windows_system_user_tasks,
}
get_tasks = get_task_map.get(platform, get_push_unixlike_system_user_tasks)
if not system_user.username_same_with_user:
return get_tasks(system_user)
tasks = []
# 仅推送这个username
if username is not None:
tasks.extend(get_tasks(system_user, username))
return tasks
users = system_user.users.all().values_list('username', flat=True)
print(_("System user is dynamic: {}").format(list(users)))
for _username in users:
tasks.extend(get_tasks(system_user, _username))
return tasks
@shared_task(queue="ansible")
def push_system_user_util(system_user, assets, task_name):
@org_aware_func("system_user")
def push_system_user_util(system_user, assets, task_name, username=None):
from ops.utils import update_or_create_ansible_task
if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg)
return {}
# Set root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return {}
hosts = clean_hosts(assets)
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
if not hosts:
return {}
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
platform_hosts_map = {}
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
for i in platform_hosts:
platform_hosts_map[i[0]] = list(i[1])
for host in hosts:
system_user.load_specific_asset_auth(host)
tasks = get_push_system_user_tasks(host, system_user)
if not tasks:
continue
def run_task(_tasks, _hosts):
if not _tasks:
return
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=[host], tasks=tasks, pattern='all',
task_name=task_name, hosts=_hosts, tasks=_tasks, pattern='all',
options=const.TASK_OPTIONS, run_as_admin=True,
created_by=system_user.org_id,
)
task.run()
for platform, _hosts in platform_hosts_map.items():
if not _hosts:
continue
print(_("Start push system user for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
if not system_user.has_special_auth():
logger.debug("System user not has special auth")
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, _hosts)
continue
for _host in _hosts:
system_user.load_asset_special_auth(_host)
tasks = get_push_system_user_tasks(system_user, platform, username=username)
run_task(tasks, [_host])
@shared_task(queue="ansible")
def push_system_user_to_assets_manual(system_user):
assets = system_user.get_all_assets()
def push_system_user_to_assets_manual(system_user, username=None):
assets = system_user.get_related_assets()
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name=task_name)
return push_system_user_util(system_user, assets, task_name=task_name, username=username)
@shared_task(queue="ansible")
def push_system_user_a_asset_manual(system_user, asset):
task_name = _("Push system users to asset: {} => {}").format(
system_user.name, asset
def push_system_user_a_asset_manual(system_user, asset, username=None):
if username is None:
username = system_user.username
task_name = _("Push system users to asset: {}({}) => {}").format(
system_user.name, username, asset
)
return push_system_user_util(system_user, [asset], task_name=task_name)
return push_system_user_util(system_user, [asset], task_name=task_name, username=username)
@shared_task(queue="ansible")
def push_system_user_to_assets(system_user, assets):
def push_system_user_to_assets(system_user, assets, username=None):
task_name = _("Push system users to assets: {}").format(system_user.name)
return push_system_user_util(system_user, assets, task_name)
return push_system_user_util(system_user, assets, task_name, username=username)
@@ -199,4 +221,4 @@ def push_system_user_to_assets(system_user, assets):
# @after_app_shutdown_clean_periodic
# def push_system_user_period():
# for system_user in SystemUser.objects.all():
# push_system_user_related_nodes(system_user)
# push_system_user_related_nodes(system_user)

View File

@@ -1,13 +1,17 @@
from itertools import groupby
from collections import defaultdict
from celery import shared_task
from django.utils.translation import ugettext as _
from common.utils import get_logger
from orgs.utils import tmp_to_org, org_aware_func
from ..models import SystemUser
from . import const
from .utils import clean_hosts, clean_hosts_by_protocol
from .utils import (
clean_ansible_task_hosts, group_asset_by_platform
)
logger = get_logger(__name__)
__all__ = [
@@ -16,7 +20,7 @@ __all__ = [
]
@shared_task(queue="ansible")
@org_aware_func("system_user")
def test_system_user_connectivity_util(system_user, assets, task_name):
"""
Test system cant connect his assets or not.
@@ -27,41 +31,34 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
"""
from ops.utils import update_or_create_ansible_task
hosts = clean_hosts(assets)
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
if not hosts:
return {}
platform_hosts_map = {}
hosts_sorted = sorted(hosts, key=group_asset_by_platform)
platform_hosts = groupby(hosts_sorted, key=group_asset_by_platform)
for i in platform_hosts:
platform_hosts_map[i[0]] = list(i[1])
hosts = clean_hosts_by_protocol(system_user, hosts)
if not hosts:
return {}
hosts_category = {
'linux': {
'hosts': [],
'tasks': const.TEST_SYSTEM_USER_CONN_TASKS
},
'windows': {
'hosts': [],
'tasks': const.TEST_WINDOWS_SYSTEM_USER_CONN_TASKS
}
platform_tasks_map = {
"unixlike": const.PING_UNIXLIKE_TASKS,
"windows": const.PING_WINDOWS_TASKS
}
for host in hosts:
hosts_list = hosts_category['windows']['hosts'] if host.is_windows() \
else hosts_category['linux']['hosts']
hosts_list.append(host)
results_summary = dict(
contacted=defaultdict(dict), dark=defaultdict(dict), success=True
)
for k, value in hosts_category.items():
if not value['hosts']:
continue
task, created = update_or_create_ansible_task(
task_name=task_name, hosts=value['hosts'], tasks=value['tasks'],
def run_task(_tasks, _hosts, _username):
old_name = "{}".format(system_user)
new_name = "{}({})".format(system_user.name, _username)
_task_name = task_name.replace(old_name, new_name)
_task, created = update_or_create_ansible_task(
task_name=_task_name, hosts=_hosts, tasks=_tasks,
pattern='all', options=const.TASK_OPTIONS,
run_as=system_user.username, created_by=system_user.org_id,
run_as=_username,
)
raw, summary = task.run()
raw, summary = _task.run()
success = summary.get('success', False)
contacted = summary.get('contacted', {})
dark = summary.get('dark', {})
@@ -70,23 +67,45 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
results_summary['contacted'].update(contacted)
results_summary['dark'].update(dark)
for platform, _hosts in platform_hosts_map.items():
if not _hosts:
continue
if platform not in ["unixlike", "windows"]:
continue
tasks = platform_tasks_map[platform]
print(_("Start test system user connectivity for platform: [{}]").format(platform))
print(_("Hosts count: {}").format(len(_hosts)))
# 用户名不是动态的,用户名则是一个
if not system_user.username_same_with_user:
logger.debug("System user not has special auth")
run_task(tasks, _hosts, system_user.username)
# 否则需要多个任务
else:
users = system_user.users.all().values_list('username', flat=True)
print(_("System user is dynamic: {}").format(list(users)))
for username in users:
run_task(tasks, _hosts, username)
system_user.set_connectivity(results_summary)
return results_summary
@shared_task(queue="ansible")
@org_aware_func("system_user")
def test_system_user_connectivity_manual(system_user):
task_name = _("Test system user connectivity: {}").format(system_user)
assets = system_user.get_all_assets()
return test_system_user_connectivity_util(system_user, assets, task_name)
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
@shared_task(queue="ansible")
@org_aware_func("system_user")
def test_system_user_connectivity_a_asset(system_user, asset):
task_name = _("Test system user connectivity: {} => {}").format(
system_user, asset
)
return test_system_user_connectivity_util(system_user, [asset], task_name)
test_system_user_connectivity_util(system_user, [asset], task_name)
@shared_task(queue="ansible")
@@ -94,8 +113,9 @@ def test_system_user_connectivity_period():
if not const.PERIOD_TASK_ENABLED:
logger.debug("Period task disabled, test system user connectivity pass")
return
system_users = SystemUser.objects.all()
for system_user in system_users:
queryset_map = SystemUser.objects.all_group_by_org()
for org, system_user in queryset_map.items():
task_name = _("Test system user connectivity period: {}").format(system_user)
assets = system_user.get_all_assets()
test_system_user_connectivity_util(system_user, assets, task_name)
with tmp_to_org(org):
assets = system_user.get_related_assets()
test_system_user_connectivity_util(system_user, assets, task_name)

View File

@@ -7,7 +7,8 @@ from common.utils import get_logger
logger = get_logger(__file__)
__all__ = [
'check_asset_can_run_ansible', 'clean_hosts', 'clean_hosts_by_protocol'
'check_asset_can_run_ansible', 'clean_ansible_task_hosts',
'group_asset_by_platform',
]
@@ -23,23 +24,43 @@ def check_asset_can_run_ansible(asset):
return True
def clean_hosts(assets):
clean_assets = []
def check_system_user_can_run_ansible(system_user):
if not system_user.is_need_push():
msg = _("Push system user task skip, auto push not enable or "
"protocol is not ssh or rdp: {}").format(system_user.name)
logger.info(msg)
return False
# Push root as system user is dangerous
if system_user.username.lower() in ["root", "administrator"]:
msg = _("For security, do not push user {}".format(system_user.username))
logger.info(msg)
return False
# if system_user.protocol != "ssh":
# msg = _("System user protocol not ssh: {}".format(system_user))
# logger.info(msg)
# return False
return True
def clean_ansible_task_hosts(assets, system_user=None):
if system_user and not check_system_user_can_run_ansible(system_user):
return []
cleaned_assets = []
for asset in assets:
if not check_asset_can_run_ansible(asset):
continue
clean_assets.append(asset)
if not clean_assets:
cleaned_assets.append(asset)
if not cleaned_assets:
logger.info(_("No assets matched, stop task"))
return clean_assets
return cleaned_assets
def clean_hosts_by_protocol(system_user, assets):
hosts = [
asset for asset in assets
if asset.has_protocol(system_user.protocol)
]
if not hosts:
msg = _("No assets matched related system user protocol, stop task")
logger.info(msg)
return hosts
def group_asset_by_platform(asset):
if asset.is_unixlike():
return 'unixlike'
elif asset.is_windows():
return 'windows'
else:
return 'other'

View File

@@ -1,6 +0,0 @@
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import admin user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:admin-user-list" %}{% endblock %}

View File

@@ -1,4 +0,0 @@
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update admin user" %}{% endblock %}

View File

@@ -1,41 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_group_bulk_update_modal{% endblock %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_title%}{% trans "Update asset group" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<p class="text-success text-center">{% trans "Hint: only change the field you want to update." %}</p>
<form method="post" class="form-horizontal" action="" id="fm_asset_group_bulk_update">
<div class="form-group">
<label for="assets" class="col-sm-2 control-label">{% trans 'Assets' %}</label>
<div class="col-sm-9" id="select2-container">
<select name="assets" id="select2_groups" data-placeholder="{% trans 'Select Asset' %}" class="select2 form-control m-b" multiple>
{% for asset in assets %}
<option value="{{ asset.id }}">{{ asset.ip }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="system_users" class="col-sm-2 control-label">{% trans 'System users' %}</label>
<div class="col-sm-9" id="select2-container">
<select name="system_users" id="select2_groups" data-placeholder="{% trans 'Select System Users' %}" class="select2 form-control m-b" multiple>
{% for system_user in system_users %}
<option value="{{ system_user.id }}">{{ system_user.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-9 col-lg-9 col-sm-offset-2">
<div class="checkbox checkbox-success">
<input type="checkbox" name="enable_mfa" checked id="id_enable_mfa"><label for="id_enable_mfa">{% trans 'Enable-MFA' %}</label>
</div>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_asset_group_bulk_update{% endblock %}

View File

@@ -1,6 +0,0 @@
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import assets" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:asset-list" %}{% endblock %}

View File

@@ -1,214 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
{% block modal_class %}modal-lg{% endblock %}
{% block modal_id %}asset_list_modal{% endblock %}
{% block modal_title%}{% trans "Asset list" %}{% endblock %}
{% block modal_body %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<script src="{% static 'js/jquery.form.min.js' %}"></script>
<style>
.inmodal .modal-header {
padding: 10px 10px;
text-align: center;
}
#asset_modal_tree.ztree * {
background-color: white;
}
#asset_modal_tree.ztree {
background-color: white;
}
</style>
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-lg-3" id="split-left" style="padding-left: 3px;overflow: auto;max-height: 500px">
<div class="ibox float-e-margins">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager ">
<div id="asset_modal_tree" class="ztree">
{% trans 'Loading' %} ...
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-9 animated fadeInRight" id="split-right">
<div class="mail-box-header">
<table class="table table-striped table-bordered table-hover " id="asset_list_modal_table" style="width: 100%">
<thead>
<tr>
<th class="text-center"><input type="checkbox" class="ipt_check_all"></th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function syncTableSelectedAssetToSelect2(table) {
var assets = table.selected;
var options = [];
var select2Id = assetModalOption.select2Id;
$(select2Id + ' option').each(function (i, v) {
options.push(v.value)
});
table.selected_rows.forEach(function (i) {
var name = i.hostname + '(' + i.ip + ')';
var option = new Option(name, i.id, false, true);
if (options.indexOf(i.id) === -1) {
$(select2Id).append(option).trigger('change');
}
});
$(select2Id).val(assets).trigger('change');
}
// 解决input框中的资产和弹出表格中资产的显示不一致
function syncSelectedAssetsToModalTable(assetModalTable) {
var select2Id = assetModalOption.select2Id;
var inputAssets = $(select2Id).val();
var selectedAssets = assetModalTable.selected.concat();
// input assets无table assets选中则取消勾选(再次click)
if (selectedAssets.length !== 0) {
$.each(selectedAssets, function (index, assetId) {
if ($.inArray(assetId, inputAssets) === -1) {
$('#' + assetId).trigger('click'); // 取消勾选
}
});
}
// input assets有table assets没选则选中(click)
if (inputAssets) {
assetModalTable.selected = inputAssets;
$.each(inputAssets, function (index, assetId) {
var dom = document.getElementById(assetId);
if (dom !== null) {
var selected = dom.parentElement.parentElement.className.indexOf('selected')
}
if (selected === -1) {
$('#' + assetId).trigger('click');
}
});
}
}
defaultOnAssetModalConfirm = syncTableSelectedAssetToSelect2;
defaultOnModalTableDone = syncSelectedAssetsToModalTable;
var assetModalOption = {
selectStyle: 'multi',
select2Id: '#id_assets',
onModalTableDone: defaultOnModalTableDone,
onModalTreeDone: null,
onModalConfirm: defaultOnAssetModalConfirm,
};
var assetModalTable, assetModalTree = null;
function initAssetModalTable() {
if(assetModalTable){
return
}
if (assetModalOption.selectStyle === 'single') {
$('.ipt_check_all').addClass('hidden')
}
var options = {
ele: $('#asset_list_modal_table'),
ajax_url: '{% url "api-assets:asset-list" %}?show_current_asset=1',
columns: [
{data: "id"}, {data: "hostname" }, {data: "ip" }
],
lengthMenu: [[10, 25, 50], [10, 25, 50]],
pageLength: 10,
select_style: assetModalOption.selectStyle,
paging_numbers_length: 3
};
assetModalTable = jumpserver.initServerSideDataTable(options);
if (assetModalOption.onModalTableDone) {
assetModalOption.onModalTableDone(assetModalTable);
}
return assetModalTable
}
function onModalTreeNodeSelected(event, treeNode) {
var url = assetModalTable.ajax.url();
url = setUrlParam(url, "node_id", treeNode.meta.node.id);
url = setUrlParam(url, "show_current_asset", "");
assetModalTable.ajax.url(url);
assetModalTable.ajax.reload();
}
function initModalTree() {
var url = '{% url 'api-assets:node-children-tree' %}?assets=0';
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
async: {
enable: true,
url: url,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
callback: {
onSelected: onModalTreeNodeSelected
}
};
$.get(url, function(data, status){
$.fn.zTree.init($("#asset_modal_tree"), setting);
assetModalTree = $.fn.zTree.getZTreeObj("assetTree2");
if (assetModalOption.onModalTreeDone) {
assetModalOption.onModalTreeDone(assetModalTree);
}
return assetModalTree;
});
}
function setAssetModalOptions(options) {
assetModalOption = options;
}
$(document).ready(function(){
}).on('show.bs.modal', function () {
initAssetModalTable();
initModalTree();
}).on('click', '#btn_asset_modal_confirm', function () {
if (assetModalOption.onModalConfirm) {
assetModalOption.onModalConfirm(assetModalTable, assetModalTree);
}
$("#asset_list_modal").modal('hide');
})
</script>
{% endblock %}
{% block modal_button %}
{{ block.super }}
{% endblock %}
{% block modal_confirm_id %}btn_asset_modal_confirm{% endblock %}

View File

@@ -1,4 +0,0 @@
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update assets" %}{% endblock %}

View File

@@ -1,87 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}asset_user_auth_update_modal{% endblock %}
{% block modal_title%}{% trans "Update asset user auth" %}{% endblock %}
{% block modal_body %}
<form class="form-horizontal" role="form" onkeydown="if(event.keyCode==13){ $('#btn_asset_user_auth_update_modal_confirm').trigger('click'); return false;}">
{% csrf_token %}
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Hostname" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_hostname_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Username" %}</label>
<div class="col-sm-10">
<p class="form-control-static" id="id_username_p"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Password" %}</label>
<div class="col-sm-10">
<input class="form-control" id="id_password_auth" type="password" name="password" placeholder="{% trans 'Please input password' %}"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">{% trans "Private key" %}</label>
<div class="col-sm-10">
<div class="row bootstrap3-multi-input">
<div class="col-xs-12">
<input id="id_private_key" type="file" name="private_key"/>
</div>
</div>
</div>
</div>
</form>
<script>
var authHostname, authUsername, authAssetId = null;
$(document).ready(function () {
}).on("show.bs.modal", "#asset_user_auth_update_modal", function () {
$('#id_hostname_p').html(authHostname);
$('#id_username_p').html(authUsername);
$('#id_password_auth').parent().removeClass('has-error');
$('#id_password_auth').val('');
}).on('click', '#btn_asset_user_auth_update_modal_confirm', function(){
var password = $('#id_password_auth').val();
var privateKey = $('#id_private_key').prop('files');
var hasPrivateKey = privateKey.length > 0;
if (!password && !hasPrivateKey) {
$('#id_password_auth').parent().addClass('has-error');
return
}
var data = {
'asset': authAssetId,
'username': authUsername
};
if (password) {
data["password"] = password
}
var props = {
data: data,
url: "{% url 'api-assets:asset-user-list' %}",
form: $("form"),
method: 'POST',
success: function () {
toastr.success("{% trans 'Update successfully!' %}");
$("#asset_user_auth_update_modal").modal('hide');
}
};
if (hasPrivateKey) {
var reader = new FileReader();//新建一个FileReader
reader.readAsText(privateKey[0], "UTF-8");//读取文件
reader.onload = function(evt){ //读取完文件之后会回来这里
data["private_key"] = evt.target.result;
formSubmit(props);
}
}
if (!hasPrivateKey) {
formSubmit(props);
}
})
</script>
{% endblock %}
{% block modal_confirm_id %}btn_asset_user_auth_update_modal_confirm{% endblock %}

View File

@@ -1,98 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
{% block modal_id %}asset_user_auth_view{% endblock %}
{% block modal_title%}{% trans "Asset user auth" %}{% endblock %}
{% block modal_body %}
<style>
.inmodal .modal-body {
background: #fff;
}
</style>
<form class="form-horizontal" action="" style="padding-top: 20px">
<div class="auth-field">
<div class="form-group">
<label for="" class="col-sm-2 control-label">{% trans 'Hostname' %}</label>
<div class="col-sm-8">
<p class="form-control-static" id="id_hostname_view"></p>
</div>
</div>
<div class="form-group">
<label for="" class="col-sm-2 control-label">{% trans 'Username' %}</label>
<div class="col-sm-8" >
<p class="form-control-static" id="id_username_view"></p>
</div>
</div>
<div class="form-group">
<label for="" class="col-sm-2 control-label">{% trans 'Password' %}</label>
<div class="col-sm-8">
<input id="id_password_view" type="password" class="form-control" value="" readonly style="border: none;padding-left: 0;background-color: #fff;width: 100%">
</div>
<div class="col-sm-2" style="padding-left: 2px">
<a class="btn btn-white btn-sm btn-show-password"><i class="fa fa-eye"></i></a>
<a class="btn btn-white btn-sm btn-copy-password"><i class="fa fa-copy"></i></a>
</div>
</div>
</div>
</form>
<script src="{% static "js/plugins/clipboard/clipboard.min.js" %}"></script>
<script>
var showPassword = false;
var authAssetId = "";
var authHostname = "";
var authUsername = "";
var mfaFor = "";
function initClipboard() {
var clipboard = new Clipboard('.btn-copy-password', {
text: function (trigger) {
return $("#id_password_view").val()
}
});
clipboard.on("success", function (e) {
toastr.success("{% trans "Copy success" %}")
})
}
function showAuth() {
var url = "{% url "api-assets:asset-user-auth-info" %}?asset_id=" + authAssetId + "&username=" + authUsername;
if (prefer) {
url = setUrlParam(url, 'prefer', prefer)
}
$("#id_username_view").html(authUsername);
$("#id_hostname_view").html(authHostname);
var success = function (data) {
var password = data.password;
$("#id_password_view").val(password);
};
var error = function() {
var msg = "{% trans 'Get auth info error' %}";
toastr.error(msg)
};
requestApi({
url: url,
method: "GET",
success: success,
flash_message: false,
error: error
})
}
$(document).ready(function () {
initClipboard();
}).on("click", ".btn-show-password", function () {
showPassword = !showPassword;
if (showPassword) {
$("#id_password_view").attr("type", "text")
} else {
$("#id_password_view").attr("type", "password")
}
}).on("show.bs.modal", "#asset_user_auth_view", function () {
showAuth();
})
</script>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white close_btn2" type="button">{% trans "Close" %}</button>
{% endblock %}

View File

@@ -1,161 +0,0 @@
{% load i18n %}
<style>
.btn-group>.btn+.dropdown-toggle {
padding-right: 4px;
padding-left: 4px;
}
table.dataTable tbody tr.selected a {
color: rgb(103, 106, 108);;
}
</style>
<table class="table table-striped table-bordered table-hover" id="asset_user_list_table" style="width: 100%">
<thead>
<tr>
<th class="text-center">
<input type="checkbox" id="check_all" class="ipt_check_all" >
</th>
<th class="text-center">{% trans 'Hostname' %}</th>
<th class="text-center">{% trans 'IP' %}</th>
<th class="text-center">{% trans 'Username' %}</th>
<th class="text-center">{% trans 'Version' %}</th>
<th class="text-center">{% trans 'Connectivity'%}</th>
<th class="text-center">{% trans 'Datetime' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
{% include 'assets/_asset_user_auth_update_modal.html' %}
{% include 'assets/_asset_user_auth_view_modal.html' %}
{% include 'authentication/_mfa_confirm_modal.html' %}
<script>
var assetUserListUrl = "{% url "api-assets:asset-user-list" %}";
var assetUserTable;
var needPush = false;
var prefer = null;
var lastMFATime = "{{ request.session.MFA_VERIFY_TIME }}";
var testDatetime = "{% trans 'Test datetime: ' %}";
var mfaVerifyTTL = "{{ SECURITY_MFA_VERIFY_TTL }}";
var mfaNeedCheck = "{{ SECURITY_VIEW_AUTH_NEED_MFA }}" === "True";
function initAssetUserTable() {
var options = {
ele: $('#asset_user_list_table'),
toggle: true,
columnDefs: [
{
targets: 5, createdCell: function (td, cellData) {
var innerHtml = "";
if (cellData.status == 1) {
innerHtml = '<i class="fa fa-circle text-navy"></i>'
} else if (cellData.status == 0) {
innerHtml = '<i class="fa fa-circle text-danger"></i>'
} else {
innerHtml = '<i class="fa fa-circle text-warning"></i>'
}
var dateManual = toSafeLocalDateStr(cellData.datetime);
var dataContent = testDatetime + dateManual;
innerHtml = "<a data-toggle='popover' data-content='" + dataContent + "'" + 'data-placement="auto bottom"' + ">" + innerHtml + "</a>";
$(td).html(innerHtml);
}
},
{
targets: 6, createdCell: function (td, cellData) {
var data = toSafeLocalDateStr(cellData);
$(td).html(data);
},
},
{
targets: 7, createdCell: function (td, cellData, rowData) {
var view_btn = '<button class="btn btn-xs btn-primary m-l-xs btn-view-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans "View" %}</button>'
var update_btn = '<li><a class="btn-update-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Update' %}</a></li>';
var test_btn = '<li><a class="btn-test-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Test' %}</a></li>';
var push_btn = '<li><a class="btn-push-auth" data-user="username123" data-hostname="hostname123" data-asset="asset123">{% trans 'Push' %}</a></li>';
if (needPush) {
test_btn += push_btn;
}
var actions = '<div class="btn-group">' + view_btn +
' <button data-toggle="dropdown" class="btn btn-primary btn-xs dropdown-toggle">' +
' <span class="caret"></span>' +
' </button>' +
' <ul class="dropdown-menu">' +
update_btn + test_btn +
' </ul>' +
' </div>';
actions = actions.replaceAll("username123", rowData.username)
.replaceAll("hostname123", rowData.hostname)
.replaceAll("asset123", rowData.asset);
$(td).html(actions);
},
width: '70px'
}
],
ajax_url: assetUserListUrl,
columns: [
{data: "id"}, {data: "hostname"}, {data: "ip"},
{data: "username"}, {data: "version", orderable: false},
{data: "connectivity"},
{data: "date_created", orderable: false},
{data: "asset", orderable: false}
],
op_html: $('#actions').html()
};
table = jumpserver.initServerSideDataTable(options);
return table
}
$(document).ready(function(){
})
.on('click', '.btn-view-auth', function () {
authAssetId = $(this).data("asset") ;
authHostname = $(this).data("hostname");
authUsername = $(this).data('user');
if (!mfaNeedCheck){
$("#asset_user_auth_view").modal('show');
return
}
var now = new Date();
var nowTime = now.getTime() / 1000;
if ( !lastMFATime || nowTime - lastMFATime > mfaVerifyTTL ) {
mfaFor = "viewAuth";
$("#mfa_auth_confirm").modal("show");
} else {
$("#asset_user_auth_view").modal('show');
}
})
.on("success", '#mfa_auth_confirm', function () {
if (mfaFor !== "viewAuth") {
return
}
$("#asset_user_auth_view").modal("show");
})
.on('click', '.btn-update-auth', function() {
authUsername = $(this).data("user") ;
authHostname = $(this).data("hostname");
authAssetId = $(this).data("asset");
$("#asset_user_auth_update_modal").modal('show');
})
.on("click", '.btn-test-auth', function () {
authUsername = $(this).data("user") ;
authAssetId = $(this).data("asset");
var the_url = "{% url 'api-assets:asset-user-connective' %}" + "?asset_id=" + authAssetId + "&username=" + authUsername;
if (prefer) {
the_url = setUrlParam(the_url, "prefer", prefer)
}
var success = function (data) {
var task_id = data.task;
showCeleryTaskLog(task_id);
};
requestApi({
url: the_url,
method: 'GET',
success: success,
flash_message: false
});
})
</script>

View File

@@ -1,18 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}gateway_test{% endblock %}
{% block modal_title%}{% trans "Test gateway test connection" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<form method="post" class="form-horizontal" action="" id="test_gateway_form" style="padding-top: 10px">
<div class="form-group">
<input id="gateway_id" name="gateway_id" hidden>
<label for="port" class="col-sm-2 control-label">{% trans 'SSH Port' %}</label>
<div class="col-sm-9" id="select2-container">
<input id="ssh_test_port" name="port" class="form-control">
<span class="help-block">{% trans 'If use nat, set the ssh real port' %}</span>
</div>
</div>
</form>
{% endblock %}
{% block modal_confirm_id %}btn_gateway_test{% endblock %}

View File

@@ -1,343 +0,0 @@
{% load static %}
{% load i18n %}
<link href="{% static 'css/plugins/ztree/awesomeStyle/awesome.css' %}" rel="stylesheet">
{# <link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet">#}
<script type="text/javascript" src="{% static 'js/plugins/ztree/jquery.ztree.all.min.js' %}"></script>
<style type="text/css">
div#rMenu {
position: absolute;
visibility: hidden;
text-align: left;
{#top: 100%;#}
top: 0;
left: 0;
z-index: 999;
{#float: left;#}
padding: 0 0;
margin: 2px 0 0;
list-style: none;
background-clip: padding-box;
}
.dataTables_wrapper .dataTables_processing {
opacity: .9;
border: none;
}
div#rMenu li{
margin: 1px 0;
cursor: pointer;
list-style: none outside none;
}
.dropdown a:hover {
background-color: #f1f1f1
}
</style>
<div class="ibox treebox float-e-margins" style="overflow:auto;">
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager" id="tree-node-id">
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
{% trans 'Loading' %} ...
</div>
<div class="clearfix"></div>
</div>
</div>
</div>
<div id="rMenu">
<ul class="dropdown-menu menu-actions">
<li class="divider"></li>
<li id="m_create" tabindex="-1" onclick="addTreeNode();"><a><i class="fa fa-plus-square-o"></i> {% trans 'Add node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="editTreeNode();"><a><i class="fa fa-pencil-square-o"></i> {% trans 'Rename node' %}</a></li>
<li id="m_del" tabindex="-1" onclick="removeTreeNode();"><a><i class="fa fa-minus-square"></i> {% trans 'Delete node' %}</a></li>
</ul>
</div>
<script>
var zTree, rMenu = null;
var current_node_id = null;
var current_node = null;
var showMenu = false;
var treeUrl = '{% url 'api-assets:node-children-tree' %}?assets=0';
// options:
// {
// "onSelected": func,
// "showAssets": false,
// "beforeAsync": func()
// "showMenu": false,
// "otherMenu": "",
// "showAssets": false,
// }
var inited = false;
function initNodeTree(options) {
if (options.showAssets) {
treeUrl = setUrlParam(treeUrl, 'assets', '1')
}
var setting = {
view: {
dblClickExpand: false,
showLine: true
},
data: {
simpleData: {
enable: true
}
},
async: {
enable: true,
url: treeUrl,
autoParam: ["id=key", "name=n", "level=lv"],
type: 'get'
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isCopy: true,
isMove: true
}
},
callback: {
onRightClick: OnRightClick,
beforeClick: beforeClick,
onRename: onRename,
onSelected: options.onSelected || defaultCallback("On selected"),
beforeDrag: beforeDrag,
onDrag: onDrag,
beforeDrop: beforeDrop,
onDrop: onDrop,
beforeAsync: options.beforeAsync || defaultCallback("Before async")
}
};
$.get(treeUrl, function (data, status) {
zTree = $.fn.zTree.init($("#nodeTree"), setting, data);
rootNodeAddDom(zTree, function () {
const url = '{% url 'api-assets:refresh-nodes-cache' %}';
requestApi({
url: url,
method: 'GET',
flash_message: false,
success: function () {
initNodeTree(options);
}
});
});
inited = true;
});
if (inited) {
return
}
if (options.showMenu) {
showMenu = true;
rMenu = $("#rMenu");
}
if (options.otherMenu) {
$(".menu-actions").append(options.otherMenu)
}
return zTree
}
function addTreeNode() {
hideRMenu();
var parentNode = zTree.getSelectedNodes()[0];
if (!parentNode){
return
}
var url = "{% url 'api-assets:node-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", parentNode.meta.node.id);
$.post(url, {}, function (data, status){
if (status === "success") {
var newNode = {
id: data["key"],
name: data["value"],
pId: parentNode.id,
meta: {
"node": data
}
};
newNode.checked = zTree.getSelectedNodes()[0].checked;
zTree.addNodes(parentNode, 0, newNode);
var node = zTree.getNodeByParam('id', newNode.id, parentNode);
zTree.editName(node);
} else {
alert("{% trans 'Create node failed' %}")
}
});
}
function removeTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node.children && current_node.children.length > 0) {
toastr.error("{% trans 'Have child node, cancel' %}");
} else if (current_node.meta.node.assets_amount !== 0) {
toastr.error("{% trans 'Have assets, cancel' %}");
} else {
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", current_node_id);
$.ajax({
url: url,
method: "DELETE",
success: function () {
zTree.removeNode(current_node);
}
});
}
}
function editTreeNode() {
hideRMenu();
var current_node = zTree.getSelectedNodes()[0];
if (!current_node){
return
}
if (current_node) {
current_node.name = current_node.meta.node.value;
}
zTree.editName(current_node);
}
function OnRightClick(event, treeId, treeNode) {
if (!showMenu) {
return
}
if (!treeNode && event.target.tagName.toLowerCase() !== "button" && $(event.target).parents("a").length === 0) {
zTree.cancelSelectedNode();
showRMenu("root", event.clientX, event.clientY);
} else if (treeNode && !treeNode.noR) {
zTree.selectNode(treeNode);
showRMenu("node", event.clientX, event.clientY);
}
}
function showRMenu(type, x, y) {
var offset = $("#tree-node-id").offset();
x -= offset.left;
y -= offset.top;
x += document.body.scrollLeft;
y += document.body.scrollTop + document.documentElement.scrollTop;
rMenu.css({"top":y+"px", "left":x+"px", "visibility":"visible"});
$("#rMenu ul").show();
$("body").bind("mousedown", onBodyMouseDown);
}
function beforeClick(treeId, treeNode, clickFlag) {
return true;
}
function hideRMenu() {
if (rMenu) rMenu.css({"visibility": "hidden"});
$("body").unbind("mousedown", onBodyMouseDown);
}
function onBodyMouseDown(event){
if (!(event.target.id === "rMenu" || $(event.target).parents("#rMenu").length>0)) {
rMenu.css({"visibility" : "hidden"});
}
}
function onRename(event, treeId, treeNode, isCancel){
var url = "{% url 'api-assets:node-detail' pk=DEFAULT_PK %}"
.replace("{{ DEFAULT_PK }}", current_node_id);
var data = {"value": treeNode.name};
if (isCancel){
return
}
requestApi({
url: url,
body: JSON.stringify(data),
method: "PATCH",
success_message: "{% trans 'Rename success' %}",
success: function () {
var assets_amount = treeNode.meta.node.assets_amount;
if (!assets_amount) {
assets_amount = 0;
}
treeNode.name = treeNode.name + ' (' + assets_amount + ')';
zTree.updateNode(treeNode);
},
})
}
function beforeDrag() {
return true
}
function beforeDrop(treeId, treeNodes, targetNode, moveType) {
var treeNodesNames = [];
$.each(treeNodes, function (index, value) {
treeNodesNames.push(value.name);
});
var msg = "你想移动节点: `" + treeNodesNames.join(",") + "` 到 `" + targetNode.name + "` 下吗?";
return confirm(msg);
}
function onDrag(event, treeId, treeNodes) {
}
function onDrop(event, treeId, treeNodes, targetNode, moveType) {
var treeNodesIds = [];
$.each(treeNodes, function (index, value) {
treeNodesIds.push(value.meta.node.id);
});
var the_url = "{% url 'api-assets:node-add-children' pk=DEFAULT_PK %}".replace("{{ DEFAULT_PK }}", targetNode.meta.node.id);
var body = {nodes: treeNodesIds};
requestApi({
url: the_url,
method: "PUT",
body: JSON.stringify(body)
})
}
function defaultCallback(action) {
function logging() {
console.log(action)
}
return logging
}
function toggle() {
if (show === 0) {
$("#split-left").hide(500, function () {
$("#split-right").attr("class", "col-lg-12");
$("#toggle-icon").attr("class", "fa fa-angle-right fa-x");
show = 1;
});
} else {
$("#split-right").attr("class", "col-lg-9");
$("#toggle-icon").attr("class", "fa fa-angle-left fa-x");
$("#split-left").show(500);
show = 0;
}
}
$(document).ready(function () {
$('.treebox').css('height', window.innerHeight - 60);
})
.on('click', '.btn-show-current-asset', function(){
hideRMenu();
$(this).css('display', 'none');
$('#show_all_asset').css('display', 'inline-block');
setCookie('show_current_asset', '1');
location.reload()
})
.on('click', '.btn-show-all-asset', function(){
hideRMenu();
$(this).css('display', 'none');
$('#show_current_asset').css('display', 'inline-block');
setCookie('show_current_asset', '');
location.reload();
}).on('click', '.tree-toggle-btn', function (e) {
e.preventDefault();
toggle();
})
</script>

View File

@@ -1,250 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap3 %}
{% 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="ibox-title">
<h5>{{ action }}</h5>
<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>
<a class="close-link">
<i class="fa fa-times"></i>
</a>
</div>
</div>
<div class="ibox-content">
<form enctype="multipart/form-data" method="post" class="form-horizontal" action="" >
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<h3>{% trans 'Basic' %}</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.login_mode layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
<h3 id="auth_title_id">{% trans 'Auth' %}</h3>
{% block auth %}
<div class="auto-generate">
<div class="form-group">
<label for="{{ form.auto_generate_key.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto generate key' %}</label>
<div class="col-sm-8">
{{ form.auto_generate_key}}
</div>
</div>
</div>
<div class="auth-fields">
{% bootstrap_field form.password layout="horizontal" %}
{% bootstrap_field form.private_key layout="horizontal" %}
</div>
<div class="form-group">
<label for="{{ form.auto_push.id_for_label }}" class="col-sm-2 control-label">{% trans 'Auto push' %}</label>
<div class="col-sm-8">
{{ form.auto_push}}
</div>
</div>
{% endblock %}
<div id="command-filter-block">
<h3>{% trans 'Command filter' %}</h3>
{% bootstrap_field form.cmd_filters layout="horizontal" %}
</div>
<h3>{% trans 'Other' %}</h3>
{% bootstrap_field form.sudo layout="horizontal" %}
{% bootstrap_field form.shell layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-white" type="reset">{% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
var protocol_id = '#' + '{{ form.protocol.id_for_label }}';
var login_mode_id = '#' + '{{ form.login_mode.id_for_label }}';
var auto_generate_key = '#'+'{{ form.auto_generate_key.id_for_label }}';
var password_id = '#' + '{{ form.password.id_for_label }}';
var private_key_id = '#' + '{{ form.private_key.id_for_label }}';
var auto_push_id = '#' + '{{ form.auto_push.id_for_label }}';
var sudo_id = '#' + '{{ form.sudo.id_for_label }}';
var shell_id = '#' + '{{ form.shell.id_for_label }}';
function autoLoginModeProtocol() {
// 协议+自动登录模式字段控制
$('#auth_title_id').removeClass('hidden');
var protocol = $(protocol_id + " option:selected").text();
if (protocol === 'rdp') {
authFieldsDisplay();
$(auto_generate_key).closest('.form-group').removeClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').removeClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'vnc') {
$('.auth-fields').removeClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'telnet') {
$('.auth-fields').removeClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else {
authFieldsDisplay();
$(auto_generate_key).closest('.form-group').removeClass('hidden');
$(private_key_id).closest('.form-group').removeClass('hidden');
$(password_id).closest('.form-group').removeClass('hidden');
$(auto_push_id).closest('.form-group').removeClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
}
}
function manualLoginModeProtocol() {
// 协议+手动登录模式字段控制
$('#auth_title_id').addClass('hidden');
var protocol = $(protocol_id + " option:selected").text();
if (protocol === 'rdp') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'vnc') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').addClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else if (protocol === 'telnet') {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').addClass('hidden');
$(shell_id).closest('.form-group').addClass('hidden');
}
else {
$('.auth-fields').addClass('hidden');
$(auto_generate_key).closest('.form-group').addClass('hidden');
$(password_id).closest('.form-group').addClass('hidden');
$(private_key_id).closest('.form-group').addClass('hidden');
$(auto_push_id).closest('.form-group').addClass('hidden');
$('#command-filter-block').removeClass('hidden');
$(sudo_id).closest('.form-group').removeClass('hidden');
$(shell_id).closest('.form-group').removeClass('hidden');
}
}
function authFieldsDisplay() {
if ($(auto_generate_key).prop('checked')) {
$('.auth-fields').addClass('hidden');
} else {
$('.auth-fields').removeClass('hidden');
}
}
function fieldDisplay(){
var login_mode = $(login_mode_id).val();
if (login_mode === 'manual'){
manualLoginModeProtocol();
}
else if(login_mode === 'auto'){
autoLoginModeProtocol();
}
}
$(document).ready(function () {
$('.select2').select2();
authFieldsDisplay();
fieldDisplay();
})
.on('change', auto_generate_key, function(){
authFieldsDisplay();
})
.on('change', login_mode_id, function(){
fieldDisplay();
})
.on('change', protocol_id, function(){
fieldDisplay();
}).on("submit", "form", function (evt) {
evt.preventDefault();
{% block formUrl %}
var the_url = '{% url 'api-assets:system-user-list' %}';
var redirect_to = '{% url "assets:system-user-list" %}';
var method = "POST";
{% endblock %}
var form = $("form");
var data = form.serializeObject();
objectAttrsIsList(data, ['cmd_filters']);
objectAttrsIsBool(data, ["auto_generate_key", "auto_push"]);
data["private_key"] = $("#id_private_key").data('file');
var props = {
url: the_url,
data: data,
method: method,
form: form,
redirect_to: redirect_to
};
formSubmit(props);
}).on('change', '#id_private_key', function () {
readFile($(this)).on("onload", function (evt, data) {
$(this).data("file", data)
})
})
</script>
{% endblock %}

View File

@@ -1,6 +0,0 @@
{% extends '_import_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Import system user" %}{% endblock %}
{% block import_modal_download_template_url %}{% url "api-assets:system-user-list" %}{% endblock %}

View File

@@ -1,4 +0,0 @@
{% extends '_update_modal.html' %}
{% load i18n %}
{% block modal_title%}{% trans "Update system user" %}{% endblock %}

View File

@@ -1,24 +0,0 @@
{% extends '_modal.html' %}
{% load i18n %}
{% load static %}
<style>
.modal-body {
background-color: white !important;
}
</style>
{% block modal_id %}user_asset_detail_modal{% endblock %}
{% block modal_title %}{% trans "Asset detail" %}{% endblock %}
{% block modal_body %}
<div class="ibox-content" style="background-color: inherit">
<table class="table">
<tbody id="asset_detail_tbody">
</tbody>
</table>
</div>
{% endblock %}
{% block modal_button %}
<button data-dismiss="modal" class="btn btn-white" type="button">{% trans "Close" %}</button>
{% endblock %}

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