mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-15 00:25:16 +00:00
Compare commits
562 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05818671a7 | ||
|
|
7d4e9c5669 | ||
|
|
088f815a30 | ||
|
|
50f50a4f28 | ||
|
|
820dcaaae7 | ||
|
|
10de4f4ef8 | ||
|
|
214b4026ec | ||
|
|
a5e59e6787 | ||
|
|
907d93bcad | ||
|
|
c84df42ff4 | ||
|
|
8fb0d6315d | ||
|
|
72e746181f | ||
|
|
a10fa3c55b | ||
|
|
d0633adc38 | ||
|
|
d8ee6d6092 | ||
|
|
a315df29ca | ||
|
|
2e9711ae8a | ||
|
|
75f66a3809 | ||
|
|
fff9f31913 | ||
|
|
ef73469d5d | ||
|
|
05e4e7301c | ||
|
|
2d7d9d4de7 | ||
|
|
fdef282c57 | ||
|
|
39528a935b | ||
|
|
22b0b77b83 | ||
|
|
a188968e5d | ||
|
|
3c9814191d | ||
|
|
785b375f4a | ||
|
|
fbfbfcc274 | ||
|
|
42ff5a382a | ||
|
|
acc03f14dd | ||
|
|
0ee5d3b79f | ||
|
|
53a5c53857 | ||
|
|
e14bbb2698 | ||
|
|
6b56c43a3a | ||
|
|
d222376b1f | ||
|
|
c3a206b291 | ||
|
|
afffb50b94 | ||
|
|
45e06d215e | ||
|
|
2208320049 | ||
|
|
af2b6742cf | ||
|
|
4254775149 | ||
|
|
44d41e86c9 | ||
|
|
586d6e6abb | ||
|
|
9ab6c58676 | ||
|
|
3d9c0a212e | ||
|
|
f51c6efddc | ||
|
|
bb235f3e88 | ||
|
|
c706a410a2 | ||
|
|
d0c74a7704 | ||
|
|
32dacecd24 | ||
|
|
a7066a5c85 | ||
|
|
4fe74e9e25 | ||
|
|
637fc91413 | ||
|
|
b5aa69db61 | ||
|
|
89a98be6ce | ||
|
|
c6ec00e84d | ||
|
|
99ce688a70 | ||
|
|
3cdabaf883 | ||
|
|
be59bff3d0 | ||
|
|
28f0302e4d | ||
|
|
947f16e371 | ||
|
|
7e8f734301 | ||
|
|
69841a4198 | ||
|
|
4f7daa96a7 | ||
|
|
31c220605e | ||
|
|
2f439fd417 | ||
|
|
f4c835d47a | ||
|
|
c929c1a87e | ||
|
|
b7915ccdeb | ||
|
|
421e98696c | ||
|
|
00e1bf6cff | ||
|
|
9454854f6c | ||
|
|
74ae8636a8 | ||
|
|
d93305f6a4 | ||
|
|
332d75b62a | ||
|
|
5db704f793 | ||
|
|
37be53f334 | ||
|
|
5f59c729b7 | ||
|
|
a8af4e8804 | ||
|
|
8f5e91bc34 | ||
|
|
277bbc0798 | ||
|
|
d97654b851 | ||
|
|
7ec82cbc60 | ||
|
|
63c2343d2c | ||
|
|
d93fe11373 | ||
|
|
fedfbba30c | ||
|
|
49ccba85a2 | ||
|
|
9903adf71b | ||
|
|
48cfc4d4ce | ||
|
|
1f5cc1eef1 | ||
|
|
ce1bb66f23 | ||
|
|
6f9528d78d | ||
|
|
fc01b952b3 | ||
|
|
51f4c1cde0 | ||
|
|
8bb82d168b | ||
|
|
1ef16d55ef | ||
|
|
a242bdd3b1 | ||
|
|
97bc3d5e7c | ||
|
|
dbb27a7771 | ||
|
|
ea11aa4abb | ||
|
|
1af40b6ca0 | ||
|
|
1b220326bf | ||
|
|
8ed3bb8586 | ||
|
|
887cda3287 | ||
|
|
40de7d64c6 | ||
|
|
fbc948fd7c | ||
|
|
12b2f2f499 | ||
|
|
37c10c6cf5 | ||
|
|
3451c2b418 | ||
|
|
158e2a9112 | ||
|
|
1c7a09c9e3 | ||
|
|
28b7768601 | ||
|
|
d21bb5207b | ||
|
|
7a82c91bc8 | ||
|
|
bf2ed21020 | ||
|
|
560be610fe | ||
|
|
3d1d652cfb | ||
|
|
f70567f44b | ||
|
|
0b9308282f | ||
|
|
2259bc5ea2 | ||
|
|
eb93e634e0 | ||
|
|
e0802ef2c3 | ||
|
|
1993dba2e0 | ||
|
|
018bf80930 | ||
|
|
6eaa1bc3f9 | ||
|
|
d8d9174454 | ||
|
|
bd2f0168a1 | ||
|
|
a81601b0a1 | ||
|
|
3c166abd2d | ||
|
|
25d40388da | ||
|
|
74aa51578b | ||
|
|
48cde91dc1 | ||
|
|
9455a1c3c0 | ||
|
|
f19f28cf3e | ||
|
|
59426d4aa9 | ||
|
|
e9e601fe45 | ||
|
|
4d1da1d292 | ||
|
|
341b654078 | ||
|
|
875a069a18 | ||
|
|
106abc24f8 | ||
|
|
ab520ee345 | ||
|
|
e212d413dc | ||
|
|
6f2f025b04 | ||
|
|
a884d0d531 | ||
|
|
6cf11ab411 | ||
|
|
c8623c3b8c | ||
|
|
9d54baac09 | ||
|
|
382bb89e8e | ||
|
|
92aeecbc3e | ||
|
|
9219786f2d | ||
|
|
d2516cc328 | ||
|
|
ab9744d529 | ||
|
|
3ac3581622 | ||
|
|
13ca70b328 | ||
|
|
911abeddd0 | ||
|
|
4ba306f597 | ||
|
|
cc7ce04402 | ||
|
|
dfcf0995cd | ||
|
|
55b049c86a | ||
|
|
e5bdceed58 | ||
|
|
7bda48bd9f | ||
|
|
63a502ba62 | ||
|
|
e0d492f599 | ||
|
|
48e74ed0ea | ||
|
|
95f1a19a0a | ||
|
|
63f3fa98db | ||
|
|
fdfd2f97d2 | ||
|
|
4e3e166132 | ||
|
|
21ffa8b28a | ||
|
|
5f9f970abd | ||
|
|
ff85e2ef57 | ||
|
|
d40d231e9a | ||
|
|
6e86e3f118 | ||
|
|
cedf0e4532 | ||
|
|
05438baad9 | ||
|
|
e603730320 | ||
|
|
c0cff6d1fe | ||
|
|
65060a9416 | ||
|
|
e2f1754a8e | ||
|
|
cf10a8898f | ||
|
|
533f32ed48 | ||
|
|
8918da48f8 | ||
|
|
f3b5823cce | ||
|
|
6b43ad60b3 | ||
|
|
1a60f38c19 | ||
|
|
6330f2d42d | ||
|
|
033b44f9d2 | ||
|
|
bf5acf7ef1 | ||
|
|
522d19a7e7 | ||
|
|
4c34246750 | ||
|
|
52d528613e | ||
|
|
0809916b01 | ||
|
|
cc56c92562 | ||
|
|
447c3f3146 | ||
|
|
eba8e54261 | ||
|
|
a8c95093c4 | ||
|
|
0d72c4a7e0 | ||
|
|
963b0911cf | ||
|
|
e8f68eb6c1 | ||
|
|
59927ffcd9 | ||
|
|
aac5eed9fc | ||
|
|
a412864c80 | ||
|
|
3c825440f0 | ||
|
|
303cf41bb9 | ||
|
|
0512988979 | ||
|
|
423a487bd1 | ||
|
|
e415ef8354 | ||
|
|
ece8f082fb | ||
|
|
c52ea089da | ||
|
|
64e1e6191b | ||
|
|
58875d9a95 | ||
|
|
782bad916e | ||
|
|
1472f0437f | ||
|
|
1256944b96 | ||
|
|
218e425333 | ||
|
|
5562a04f79 | ||
|
|
75409519a1 | ||
|
|
62689b240b | ||
|
|
6f570bcbf1 | ||
|
|
b5962a098a | ||
|
|
371aff3251 | ||
|
|
bab4326aeb | ||
|
|
dc4ee2f0fa | ||
|
|
bf9a2714d3 | ||
|
|
29824fb7d2 | ||
|
|
5b1ee67820 | ||
|
|
3598bc79c3 | ||
|
|
17e1fe2acb | ||
|
|
858c7df86b | ||
|
|
2d07eeb16b | ||
|
|
1448d23ca6 | ||
|
|
2eb942a947 | ||
|
|
cdbdc853ea | ||
|
|
c3a54a8927 | ||
|
|
dfcbdb0c35 | ||
|
|
28ec1eb0ad | ||
|
|
5f6af8c07d | ||
|
|
e7d600ee50 | ||
|
|
b9f82fd0ac | ||
|
|
186c22decd | ||
|
|
9a20ec9c09 | ||
|
|
1b44172bc5 | ||
|
|
bbf5e28571 | ||
|
|
7bf1555c67 | ||
|
|
003601bbdd | ||
|
|
58182712a2 | ||
|
|
930eb1d2e1 | ||
|
|
98260b5b52 | ||
|
|
9a3065ad4c | ||
|
|
79554b47d3 | ||
|
|
31d2f2a799 | ||
|
|
1983533e76 | ||
|
|
e4880a247f | ||
|
|
e43da3d6e1 | ||
|
|
b064be3ec0 | ||
|
|
c33084421d | ||
|
|
7f7853dbc9 | ||
|
|
4d3856975b | ||
|
|
8905c27b86 | ||
|
|
034fee0f75 | ||
|
|
d41d58e30f | ||
|
|
ae690050e7 | ||
|
|
6273e6be9b | ||
|
|
85d13c03e0 | ||
|
|
a8e20ac1c1 | ||
|
|
582365967d | ||
|
|
768cfc7561 | ||
|
|
297820b65a | ||
|
|
abad929485 | ||
|
|
43412d7ef6 | ||
|
|
8f699fa366 | ||
|
|
8e9b3f134b | ||
|
|
48ba1993e0 | ||
|
|
1a0ff422fe | ||
|
|
5de6563ab2 | ||
|
|
6d96b5dbaf | ||
|
|
9ca4a8c941 | ||
|
|
320b17c8db | ||
|
|
e8ebc94191 | ||
|
|
f10a7a75ae | ||
|
|
e08d542c87 | ||
|
|
824ba433f6 | ||
|
|
327febaf59 | ||
|
|
0f8d4f5b32 | ||
|
|
63216addf6 | ||
|
|
9dd951dd0d | ||
|
|
ed8ae300ae | ||
|
|
2e6ba2ffb2 | ||
|
|
dd4ef4c383 | ||
|
|
ef717f888b | ||
|
|
b15e06ffec | ||
|
|
583fd410f5 | ||
|
|
3e73dbdb11 | ||
|
|
908905918e | ||
|
|
3da43bf01d | ||
|
|
bb1a941240 | ||
|
|
4fbb4c6082 | ||
|
|
d563216e3f | ||
|
|
9a5c00e148 | ||
|
|
7e16ce41e3 | ||
|
|
152c59365f | ||
|
|
895dfe475c | ||
|
|
29239e8b77 | ||
|
|
8bead0a33b | ||
|
|
844f9bf409 | ||
|
|
c17d95dae0 | ||
|
|
50443de888 | ||
|
|
04655b9042 | ||
|
|
8a87f316df | ||
|
|
2cd092af42 | ||
|
|
7a8e616f17 | ||
|
|
10616b8d9e | ||
|
|
d6165e5975 | ||
|
|
9cd75390bf | ||
|
|
8adaf629b4 | ||
|
|
c71f417ebf | ||
|
|
795807ddbe | ||
|
|
7715e62def | ||
|
|
41a5a69164 | ||
|
|
5286bf3ac1 | ||
|
|
ddafd7ba26 | ||
|
|
08a32028c1 | ||
|
|
9f9f22548f | ||
|
|
69b91df96b | ||
|
|
3e86c07411 | ||
|
|
eafef9fc7f | ||
|
|
44d33f70e4 | ||
|
|
0574b43971 | ||
|
|
03d9570895 | ||
|
|
c300f0b549 | ||
|
|
e5185ebd57 | ||
|
|
1ba9351957 | ||
|
|
505b801423 | ||
|
|
35a0b42bea | ||
|
|
4f289963d1 | ||
|
|
1631c32868 | ||
|
|
9721b805f3 | ||
|
|
d2b1b19404 | ||
|
|
49632241b6 | ||
|
|
b4f23f9731 | ||
|
|
1cdcc66dba | ||
|
|
0a1a260a22 | ||
|
|
0e377cd0e9 | ||
|
|
3f3cc6359a | ||
|
|
eb0a6f00b5 | ||
|
|
a87610a8d8 | ||
|
|
e3630a9961 | ||
|
|
6ba78e2cf3 | ||
|
|
63a06b5dd8 | ||
|
|
ec60697912 | ||
|
|
d6a8c04d45 | ||
|
|
d46f5858f8 | ||
|
|
a8491eafea | ||
|
|
0d046d8356 | ||
|
|
9c55450a9e | ||
|
|
00e986a64e | ||
|
|
dc4bf669b0 | ||
|
|
059a8de44a | ||
|
|
aa25b7745c | ||
|
|
1097b11115 | ||
|
|
9f67daeb1e | ||
|
|
2065692199 | ||
|
|
33e342f03f | ||
|
|
7a6027f35a | ||
|
|
e46a6f1d12 | ||
|
|
0f3996369b | ||
|
|
0847539e02 | ||
|
|
f576f2eda2 | ||
|
|
21ac3eaf8b | ||
|
|
b237cbb20f | ||
|
|
9b5b48dd1a | ||
|
|
b7eac837f7 | ||
|
|
217bb81722 | ||
|
|
b791073802 | ||
|
|
45cb39e971 | ||
|
|
2df1dd2bb1 | ||
|
|
4cd3dd3670 | ||
|
|
b18ca8c94f | ||
|
|
75fb37d247 | ||
|
|
f863ed0f4f | ||
|
|
3eaf4cd142 | ||
|
|
7859499c97 | ||
|
|
e8ceb58292 | ||
|
|
84610f2a2c | ||
|
|
d906df5b00 | ||
|
|
22f362aab3 | ||
|
|
4942900886 | ||
|
|
1e505d3d0f | ||
|
|
1eca517978 | ||
|
|
38acce7460 | ||
|
|
3855fecc69 | ||
|
|
466b922ea0 | ||
|
|
9e52579ca6 | ||
|
|
1ca1e519b6 | ||
|
|
54a9070c58 | ||
|
|
e108aae3c0 | ||
|
|
27c00410d3 | ||
|
|
74ae7d138e | ||
|
|
20ce5d11a6 | ||
|
|
cbe919c4b3 | ||
|
|
497cba6b13 | ||
|
|
e2849be72c | ||
|
|
a025930957 | ||
|
|
990c78e7cc | ||
|
|
0ef12906d3 | ||
|
|
61a37731ec | ||
|
|
d3217b6a67 | ||
|
|
04266cc20b | ||
|
|
4f36cf7dd1 | ||
|
|
490041587b | ||
|
|
3a3da94468 | ||
|
|
b7ad6cfe62 | ||
|
|
4463e7545d | ||
|
|
d0eafc8b8e | ||
|
|
8b98c20d68 | ||
|
|
caa5060ecd | ||
|
|
aabcf7f31c | ||
|
|
40d48cdfe4 | ||
|
|
8196537878 | ||
|
|
33a00f043b | ||
|
|
f235e20153 | ||
|
|
cf2455c084 | ||
|
|
fc1068a9dc | ||
|
|
35a0ca1875 | ||
|
|
56519354b6 | ||
|
|
78e4e13fb9 | ||
|
|
699b8d9980 | ||
|
|
ba9581801c | ||
|
|
0a5fdf4ea1 | ||
|
|
3849fa2b15 | ||
|
|
0952cbc7c6 | ||
|
|
bb06c39dd4 | ||
|
|
d60dc31443 | ||
|
|
76b3cd8edd | ||
|
|
638ba31694 | ||
|
|
c31b169cae | ||
|
|
fc167526ae | ||
|
|
55eff5eab9 | ||
|
|
f5a7f4e086 | ||
|
|
f7b0932cdd | ||
|
|
ba89ce8fb9 | ||
|
|
9d62deeabe | ||
|
|
459b41f327 | ||
|
|
3062e3f64a | ||
|
|
c1362ca4e2 | ||
|
|
9d24912ad9 | ||
|
|
db290609a8 | ||
|
|
4bc5eced6c | ||
|
|
b82a66c83d | ||
|
|
bf7079df9e | ||
|
|
f137c5740e | ||
|
|
ee47905966 | ||
|
|
f6cd193f9e | ||
|
|
a31775dd23 | ||
|
|
30ba1e5886 | ||
|
|
f97bfa7bf1 | ||
|
|
ace028fa7f | ||
|
|
69f6401e87 | ||
|
|
bd4d974df1 | ||
|
|
6e7446f530 | ||
|
|
afe9471aa2 | ||
|
|
4d56b84861 | ||
|
|
8fede58c64 | ||
|
|
370904212f | ||
|
|
ae03a5aeb7 | ||
|
|
24a38841dd | ||
|
|
bbc6156bd7 | ||
|
|
f387df41d7 | ||
|
|
ceb8b2f5b3 | ||
|
|
877781a6ca | ||
|
|
612d5efd1b | ||
|
|
7ea03801d0 | ||
|
|
ce8f4b4a48 | ||
|
|
15179d2450 | ||
|
|
9aae106970 | ||
|
|
c82044f6bc | ||
|
|
e4e6f59589 | ||
|
|
c8aa9d006f | ||
|
|
311538dcf8 | ||
|
|
324cf2469f | ||
|
|
01745ead1f | ||
|
|
4e705a52eb | ||
|
|
9bb58afee1 | ||
|
|
b45b33380c | ||
|
|
c86a036ac6 | ||
|
|
8694511d86 | ||
|
|
58c4a46f6e | ||
|
|
dfd26d88d4 | ||
|
|
dcf6959cff | ||
|
|
924affd978 | ||
|
|
ad6d233c11 | ||
|
|
d84ab1d215 | ||
|
|
a5fc04e0ce | ||
|
|
7f71513085 | ||
|
|
6004ef3f0d | ||
|
|
e76392a169 | ||
|
|
53f0b2e9b0 | ||
|
|
de79e36251 | ||
|
|
c84e984eae | ||
|
|
6d65c967b1 | ||
|
|
8199ea84f4 | ||
|
|
ce1b0da09d | ||
|
|
cd6bb848e9 | ||
|
|
34040fcd59 | ||
|
|
1969fb79fe | ||
|
|
904f64604b | ||
|
|
9b3509208d | ||
|
|
6700dc969f | ||
|
|
21714cc411 | ||
|
|
1a247d60e7 | ||
|
|
adf8b1f7aa | ||
|
|
69f640daa4 | ||
|
|
a43314f5be | ||
|
|
599e8a7e37 | ||
|
|
0e00451e1f | ||
|
|
866e5d2011 | ||
|
|
232674b1c1 | ||
|
|
ddf60d2512 | ||
|
|
3e6e0153cf | ||
|
|
6b984aac53 | ||
|
|
6d3ee8116e | ||
|
|
904a0f67dd | ||
|
|
81e1ce2688 | ||
|
|
7c422d2ed6 | ||
|
|
2a5129c481 | ||
|
|
28cdfca14b | ||
|
|
202aba048b | ||
|
|
3c2a4703bc | ||
|
|
4904aac2df | ||
|
|
303f88d6ee | ||
|
|
cccc74279d | ||
|
|
16db2abca5 | ||
|
|
859f2d9795 | ||
|
|
4fd9957bec | ||
|
|
0ac6e6ba2c | ||
|
|
785cc04126 | ||
|
|
f269eae774 | ||
|
|
6f19fcb702 | ||
|
|
b7b6218306 | ||
|
|
5cd809b48a | ||
|
|
5a1b894138 | ||
|
|
4d402617b6 | ||
|
|
666ef366e7 | ||
|
|
28d029a553 | ||
|
|
00763e986a | ||
|
|
f9a7cca478 | ||
|
|
d09b34e232 | ||
|
|
2737675c36 | ||
|
|
7591f40b2c | ||
|
|
c4af6fa72d | ||
|
|
19be7ac580 | ||
|
|
49404f763d | ||
|
|
87f2a67789 | ||
|
|
041edb6177 | ||
|
|
df2fad76c7 | ||
|
|
94020a8fbb | ||
|
|
cb1e19d28f | ||
|
|
0980dffb47 | ||
|
|
4051225ecb | ||
|
|
507518da04 | ||
|
|
d3bdbc0b81 |
@@ -4,4 +4,6 @@ data/*
|
||||
.github
|
||||
tmp/*
|
||||
django.db
|
||||
celerybeat.pid
|
||||
celerybeat.pid
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,3 +34,5 @@ data/static
|
||||
docs/_build/
|
||||
xpack
|
||||
logs/*
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
10
Dockerfile
10
Dockerfile
@@ -6,10 +6,12 @@ RUN useradd jumpserver
|
||||
|
||||
COPY ./requirements /tmp/requirements
|
||||
|
||||
RUN yum -y install epel-release openldap-clients telnet && cd /tmp/requirements && \
|
||||
yum -y install $(cat rpm_requirements.txt)
|
||||
|
||||
RUN cd /tmp/requirements && pip install -r requirements.txt
|
||||
RUN yum -y install epel-release && \
|
||||
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
|
||||
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
|
||||
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && \
|
||||
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
|
||||
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
|
||||
|
||||
COPY . /opt/jumpserver
|
||||
RUN echo > config.yml
|
||||
|
||||
199
README.md
199
README.md
@@ -1,52 +1,205 @@
|
||||
## Jumpserver
|
||||
## Jumpserver 多云环境下更好用的堡垒机
|
||||
|
||||

|
||||

|
||||
[](https://www.python.org/)
|
||||
[](https://www.djangoproject.com/)
|
||||
[](https://www.ansible.com/)
|
||||
[](http://www.paramiko.org/)
|
||||
|
||||
|
||||
----
|
||||
|
||||
Jumpserver是全球首款完全开源的堡垒机,使用GNU GPL v2.0开源协议,是符合 4A 的专业运维审计系统。
|
||||
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的运维安全审计系统。
|
||||
|
||||
Jumpserver使用Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
|
||||
|
||||
Jumpserver采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发限制。
|
||||
Jumpserver 采纳分布式架构,支持多机房跨区域部署,支持横向扩展,无资产数量及并发限制。
|
||||
|
||||
改变世界,从一点点开始。
|
||||
|
||||
### 核心功能列表
|
||||
----
|
||||
|
||||
### 功能
|
||||
<table class="subscription-level-table">
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="4">身份验证 Authentication</td>
|
||||
<td class="features-second-td-background-style" rowspan="3" >登录认证
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资源统一登录和认证
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">LDAP 认证
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">支持 OpenID,实现单点登录
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">多因子认证
|
||||
</td>
|
||||
<td class="features-third-td-background-style">MFA(Google Authenticator)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="9">账号管理 Account</td>
|
||||
<td class="features-second-td-background-style" rowspan="2">集中账号管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">管理用户管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">系统用户管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style" rowspan="4">统一密码管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资产密码托管
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">自动生成密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">密码自动推送
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">密码过期设置
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style" rowspan="2">批量密码变更(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">定期批量修改密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">生成随机密码
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">多云环境的资产纳管(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">对私有云、公有云资产统一纳管
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="9">授权控制 Authorization</td>
|
||||
<td class="features-second-td-background-style" rowspan="3">资产授权管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">资产树
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">资产或资产组灵活授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">节点内资产自动继承授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">RemoteApp(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">实现更细粒度的应用级授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-outline-td-background-style">组织管理(X-PACK)
|
||||
</td>
|
||||
<td class="features-outline-td-background-style">实现多租户管理,权限隔离
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">多维度授权
|
||||
</td>
|
||||
<td class="features-third-td-background-style">可对用户、用户组或系统角色授权
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">指令限制
|
||||
</td>
|
||||
<td class="features-third-td-background-style">限制特权指令使用,支持黑白名单
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">统一文件传输
|
||||
</td>
|
||||
<td class="features-third-td-background-style">SFTP 文件上传/下载
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">文件管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">Web SFTP 文件管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-first-td-background-style" rowspan="6">安全审计 Audit</td>
|
||||
<td class="features-second-td-background-style" rowspan="2">会话管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">在线会话管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">历史会话管理
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style" rowspan="2">录像管理
|
||||
</td>
|
||||
<td class="features-third-td-background-style">Linux 录像支持
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-third-td-background-style">Windows 录像支持
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">指令审计
|
||||
</td>
|
||||
<td class="features-third-td-background-style">指令记录
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="subscription-level-tr-border">
|
||||
<td class="features-second-td-background-style">文件传输审计
|
||||
</td>
|
||||
<td class="features-third-td-background-style">上传/下载记录审计
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||

|
||||
### 安装及使用文档
|
||||
----
|
||||
|
||||
### 开始使用
|
||||
- [Docker 快速安装文档](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
- [Step by Step 安装文档](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
- [完整文档](http://docs.jumpserver.org)
|
||||
|
||||
快速开始文档 [Docker安装](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
### 演示视频和系统截图
|
||||
----
|
||||
|
||||
一步一步安装文档 [详细部署](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
我们提供了演示视频和系统截图可以让你快速了解 Jumpserver。
|
||||
|
||||
也可以查看我们完整文档包括了使用和开发 [文档](http://docs.jumpserver.org)
|
||||
|
||||
### Demo 和 截图
|
||||
|
||||
我们提供了DEMO和截图可以让你快速了解Jumpserver
|
||||
|
||||
[DEMO](https://demo.jumpserver.org)
|
||||
[截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
- [演示视频](https://jumpserver.oss-cn-hangzhou.aliyuncs.com/jms-media/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91Jumpserver%20%E5%A0%A1%E5%9E%92%E6%9C%BA%20V1.5.0%20%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%20-%20final.mp4)
|
||||
- [系统截图](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
----
|
||||
|
||||
我们还编写了一些SDK,供你其它系统快速和Jumpserver APi交互,
|
||||
|
||||
- [python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver其它组件使用这个SDK完成交互
|
||||
- [java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK
|
||||
我们编写了一些SDK,供你的其它系统快速和 Jumpserver API 交互。
|
||||
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver 其它组件使用这个 SDK 完成交互
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的 Java 版本的 SDK
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2018 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
----
|
||||
|
||||
Copyright (c) 2014-2019 飞致云 FIT2CLOUD, All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
60
README_EN.md
Normal file
60
README_EN.md
Normal file
@@ -0,0 +1,60 @@
|
||||
## Jumpserver
|
||||
|
||||

|
||||

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

|
||||
|
||||
### Start
|
||||
|
||||
Quick start [Docker Install](http://docs.jumpserver.org/zh/docs/dockerinstall.html)
|
||||
|
||||
Step by Step deployment. [Docs](http://docs.jumpserver.org/zh/docs/step_by_step.html)
|
||||
|
||||
Full documentation [Docs](http://docs.jumpserver.org)
|
||||
|
||||
### Demo、Video 和 Snapshot
|
||||
|
||||
We provide online demo, demo video and screenshots to get you started quickly.
|
||||
|
||||
[Demo](https://demo.jumpserver.org/auth/login/?next=/)
|
||||
[Video](https://fit2cloud2-offline-installer.oss-cn-beijing.aliyuncs.com/tools/Jumpserver%20%E4%BB%8B%E7%BB%8Dv1.4.mp4)
|
||||
[Snapshot](http://docs.jumpserver.org/zh/docs/snapshot.html)
|
||||
|
||||
### SDK
|
||||
|
||||
We provide the SDK for your other systems to quickly interact with the Jumpserver API.
|
||||
|
||||
- [Python](https://github.com/jumpserver/jumpserver-python-sdk) Jumpserver other components use this SDK to complete the interaction.
|
||||
- [Java](https://github.com/KaiJunYan/jumpserver-java-sdk.git) 恺珺同学提供的Java版本的SDK thanks to 恺珺 for provide Java SDK
|
||||
|
||||
|
||||
### License & Copyright
|
||||
Copyright (c) 2014-2019 Beijing Duizhan Tech, Inc., All rights reserved.
|
||||
|
||||
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
56
Vagrantfile
vendored
Normal file
56
Vagrantfile
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
# Every Vagrant development environment requires a box. You can search for
|
||||
# boxes at https://vagrantcloud.com/search.
|
||||
config.vm.box_check_update = false
|
||||
config.vm.box = "centos/7"
|
||||
config.vm.hostname = "jumpserver"
|
||||
config.vm.network "private_network", ip: "172.17.8.101"
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = "4096"
|
||||
vb.cpus = 2
|
||||
vb.name = "jumpserver"
|
||||
end
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant", type: "rsync",
|
||||
rsync__verbose: true,
|
||||
rsync__exclude: ['.git*', 'node_modules*','*.log','*.box','Vagrantfile']
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
## 设置yum的阿里云源
|
||||
sudo curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
|
||||
sudo sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
|
||||
sudo curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
|
||||
sudo yum makecache
|
||||
|
||||
## 安装依赖包
|
||||
sudo yum install -y python36 python36-devel python36-pip \
|
||||
libtiff-devel libjpeg-devel libzip-devel freetype-devel \
|
||||
lcms2-devel libwebp-devel tcl-devel tk-devel sshpass \
|
||||
openldap-devel mariadb-devel mysql-devel libffi-devel \
|
||||
openssh-clients telnet openldap-clients gcc
|
||||
|
||||
## 配置pip阿里云源
|
||||
mkdir /home/vagrant/.pip
|
||||
cat << EOF | sudo tee -a /home/vagrant/.pip/pip.conf
|
||||
[global]
|
||||
timeout = 6000
|
||||
index-url = https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
[install]
|
||||
use-mirrors = true
|
||||
mirrors = https://mirrors.aliyun.com/pypi/simple/
|
||||
trusted-host=mirrors.aliyun.com
|
||||
EOF
|
||||
|
||||
python3.6 -m venv /home/vagrant/venv
|
||||
source /home/vagrant/venv/bin/activate
|
||||
echo 'source /home/vagrant/venv/bin/activate' >> /home/vagrant/.bash_profile
|
||||
SHELL
|
||||
end
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
__version__ = "1.4.6"
|
||||
|
||||
3
apps/applications/admin.py
Normal file
3
apps/applications/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
1
apps/applications/api/__init__.py
Normal file
1
apps/applications/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
31
apps/applications/api/remote_app.py
Normal file
31
apps/applications/api/remote_app.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
|
||||
from ..hands import IsOrgAdmin, IsAppUser
|
||||
from ..models import RemoteApp
|
||||
from ..serializers import RemoteAppSerializer, RemoteAppConnectionInfoSerializer
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppViewSet', 'RemoteAppConnectionInfoApi',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppViewSet(BulkModelViewSet):
|
||||
filter_fields = ('name',)
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
queryset = RemoteApp.objects.all()
|
||||
serializer_class = RemoteAppSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoApi(generics.RetrieveAPIView):
|
||||
queryset = RemoteApp.objects.all()
|
||||
permission_classes = (IsAppUser, )
|
||||
serializer_class = RemoteAppConnectionInfoSerializer
|
||||
7
apps/applications/apps.py
Normal file
7
apps/applications/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApplicationsConfig(AppConfig):
|
||||
name = 'applications'
|
||||
68
apps/applications/const.py
Normal file
68
apps/applications/const.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
# RemoteApp
|
||||
REMOTE_APP_BOOT_PROGRAM_NAME = '||jmservisor'
|
||||
|
||||
REMOTE_APP_TYPE_CHROME = 'chrome'
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH = 'mysql_workbench'
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT = 'vmware_client'
|
||||
REMOTE_APP_TYPE_CUSTOM = 'custom'
|
||||
|
||||
REMOTE_APP_TYPE_CHOICES = (
|
||||
(
|
||||
_('Browser'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_CHROME, 'Chrome'),
|
||||
)
|
||||
),
|
||||
(
|
||||
_('Database tools'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_MYSQL_WORKBENCH, 'MySQL Workbench'),
|
||||
)
|
||||
),
|
||||
(
|
||||
_('Virtualization tools'),
|
||||
(
|
||||
(REMOTE_APP_TYPE_VMWARE_CLIENT, 'vSphere Client'),
|
||||
)
|
||||
),
|
||||
(REMOTE_APP_TYPE_CUSTOM, _('Custom')),
|
||||
|
||||
)
|
||||
|
||||
# Fields attribute write_only default => False
|
||||
|
||||
REMOTE_APP_TYPE_CHROME_FIELDS = [
|
||||
{'name': 'chrome_target'},
|
||||
{'name': 'chrome_username'},
|
||||
{'name': 'chrome_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS = [
|
||||
{'name': 'mysql_workbench_ip'},
|
||||
{'name': 'mysql_workbench_name'},
|
||||
{'name': 'mysql_workbench_username'},
|
||||
{'name': 'mysql_workbench_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS = [
|
||||
{'name': 'vmware_target'},
|
||||
{'name': 'vmware_username'},
|
||||
{'name': 'vmware_password', 'write_only': True}
|
||||
]
|
||||
REMOTE_APP_TYPE_CUSTOM_FIELDS = [
|
||||
{'name': 'custom_cmdline'},
|
||||
{'name': 'custom_target'},
|
||||
{'name': 'custom_username'},
|
||||
{'name': 'custom_password', 'write_only': True}
|
||||
]
|
||||
|
||||
REMOTE_APP_TYPE_MAP_FIELDS = {
|
||||
REMOTE_APP_TYPE_CHROME: REMOTE_APP_TYPE_CHROME_FIELDS,
|
||||
REMOTE_APP_TYPE_MYSQL_WORKBENCH: REMOTE_APP_TYPE_MYSQL_WORKBENCH_FIELDS,
|
||||
REMOTE_APP_TYPE_VMWARE_CLIENT: REMOTE_APP_TYPE_VMWARE_CLIENT_FIELDS,
|
||||
REMOTE_APP_TYPE_CUSTOM: REMOTE_APP_TYPE_CUSTOM_FIELDS
|
||||
}
|
||||
1
apps/applications/forms/__init__.py
Normal file
1
apps/applications/forms/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
130
apps/applications/forms/remote_app.py
Normal file
130
apps/applications/forms/remote_app.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django import forms
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from assets.models import SystemUser
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppCreateUpdateForm',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppTypeChromeForm(forms.ModelForm):
|
||||
chrome_target = forms.CharField(
|
||||
max_length=128, label=_('Target URL'), required=False
|
||||
)
|
||||
chrome_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
chrome_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeMySQLWorkbenchForm(forms.ModelForm):
|
||||
mysql_workbench_ip = forms.CharField(
|
||||
max_length=128, label=_('Database IP'), required=False
|
||||
)
|
||||
mysql_workbench_name = forms.CharField(
|
||||
max_length=128, label=_('Database name'), required=False
|
||||
)
|
||||
mysql_workbench_username = forms.CharField(
|
||||
max_length=128, label=_('Database username'), required=False
|
||||
)
|
||||
mysql_workbench_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Database password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeVMwareForm(forms.ModelForm):
|
||||
vmware_target = forms.CharField(
|
||||
max_length=128, label=_('Target address'), required=False
|
||||
)
|
||||
vmware_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
vmware_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeCustomForm(forms.ModelForm):
|
||||
custom_cmdline = forms.CharField(
|
||||
max_length=128, label=_('Operating parameter'), required=False
|
||||
)
|
||||
custom_target = forms.CharField(
|
||||
max_length=128, label=_('Target address'), required=False
|
||||
)
|
||||
custom_username = forms.CharField(
|
||||
max_length=128, label=_('Login username'), required=False
|
||||
)
|
||||
custom_password = forms.CharField(
|
||||
widget=forms.PasswordInput, strip=True,
|
||||
max_length=128, label=_('Login password'), required=False
|
||||
)
|
||||
|
||||
|
||||
class RemoteAppTypeForms(
|
||||
RemoteAppTypeChromeForm,
|
||||
RemoteAppTypeMySQLWorkbenchForm,
|
||||
RemoteAppTypeVMwareForm,
|
||||
RemoteAppTypeCustomForm
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# 过滤RDP资产和系统用户
|
||||
super().__init__(*args, **kwargs)
|
||||
field_asset = self.fields['asset']
|
||||
field_asset.queryset = field_asset.queryset.has_protocol('rdp')
|
||||
field_system_user = self.fields['system_user']
|
||||
field_system_user.queryset = field_system_user.queryset.filter(
|
||||
protocol=SystemUser.PROTOCOL_RDP
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'name', 'asset', 'system_user', 'type', 'path', 'comment'
|
||||
]
|
||||
widgets = {
|
||||
'asset': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Asset')
|
||||
}),
|
||||
'system_user': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('System user')
|
||||
})
|
||||
}
|
||||
|
||||
def _clean_params(self):
|
||||
app_type = self.data.get('type')
|
||||
fields = const.REMOTE_APP_TYPE_MAP_FIELDS.get(app_type, [])
|
||||
params = {}
|
||||
for field in fields:
|
||||
name = field['name']
|
||||
value = self.cleaned_data[name]
|
||||
params.update({name: value})
|
||||
return params
|
||||
|
||||
def _save_params(self, instance):
|
||||
params = self._clean_params()
|
||||
instance.params = params
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=commit)
|
||||
instance = self._save_params(instance)
|
||||
return instance
|
||||
15
apps/applications/hands.py
Normal file
15
apps/applications/hands.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
jumpserver.__app__.hands.py
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This app depends other apps api, function .. should be import or write mack here.
|
||||
|
||||
Other module of this app shouldn't connect with other app.
|
||||
|
||||
:copyright: (c) 2014-2018 by Jumpserver Team.
|
||||
:license: GPL v2, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
42
apps/applications/migrations/0001_initial.py
Normal file
42
apps/applications/migrations/0001_initial.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-20 11:04
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RemoteApp',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('type', models.CharField(choices=[('Browser', (('chrome', 'Chrome'),)), ('Database tools', (('mysql_workbench', 'MySQL Workbench'),)), ('Virtualization tools', (('vmware_client', 'vSphere Client'),)), ('custom', 'Custom')], default='chrome', max_length=128, verbose_name='App type')),
|
||||
('path', models.CharField(max_length=128, verbose_name='App path')),
|
||||
('params', common.fields.model.EncryptJsonDictTextField(blank=True, default={}, max_length=4096, null=True, verbose_name='Parameters')),
|
||||
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
('comment', models.TextField(blank=True, default='', max_length=128, verbose_name='Comment')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
('system_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.SystemUser', verbose_name='System user')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'RemoteApp',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='remoteapp',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
]
|
||||
1
apps/applications/models/__init__.py
Normal file
1
apps/applications/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
89
apps/applications/models/remote_app.py
Normal file
89
apps/applications/models/remote_app.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
import uuid
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from common.fields.model import EncryptJsonDictTextField
|
||||
|
||||
from .. import const
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteApp',
|
||||
]
|
||||
|
||||
|
||||
class RemoteApp(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
asset = models.ForeignKey(
|
||||
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||
)
|
||||
system_user = models.ForeignKey(
|
||||
'assets.SystemUser', on_delete=models.CASCADE,
|
||||
verbose_name=_('System user')
|
||||
)
|
||||
type = models.CharField(
|
||||
default=const.REMOTE_APP_TYPE_CHROME,
|
||||
choices=const.REMOTE_APP_TYPE_CHOICES,
|
||||
max_length=128, verbose_name=_('App type')
|
||||
)
|
||||
path = models.CharField(
|
||||
max_length=128, blank=False, null=False,
|
||||
verbose_name=_('App path')
|
||||
)
|
||||
params = EncryptJsonDictTextField(
|
||||
max_length=4096, default={}, blank=True, null=True,
|
||||
verbose_name=_('Parameters')
|
||||
)
|
||||
created_by = models.CharField(
|
||||
max_length=32, null=True, blank=True, verbose_name=_('Created by')
|
||||
)
|
||||
date_created = models.DateTimeField(
|
||||
auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')
|
||||
)
|
||||
comment = models.TextField(
|
||||
max_length=128, default='', blank=True, verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("RemoteApp")
|
||||
unique_together = [('org_id', 'name')]
|
||||
ordering = ('name', )
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
"""
|
||||
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
|
||||
"""
|
||||
_parameters = list()
|
||||
_parameters.append(self.type)
|
||||
path = '\"%s\"' % self.path
|
||||
_parameters.append(path)
|
||||
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[self.type]:
|
||||
value = self.params.get(field['name'])
|
||||
if value is None:
|
||||
continue
|
||||
_parameters.append(value)
|
||||
_parameters = ' '.join(_parameters)
|
||||
return _parameters
|
||||
|
||||
@property
|
||||
def asset_info(self):
|
||||
return {
|
||||
'id': self.asset.id,
|
||||
'hostname': self.asset.hostname
|
||||
}
|
||||
|
||||
@property
|
||||
def system_user_info(self):
|
||||
return {
|
||||
'id': self.system_user.id,
|
||||
'name': self.system_user.name
|
||||
}
|
||||
1
apps/applications/serializers/__init__.py
Normal file
1
apps/applications/serializers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
103
apps/applications/serializers/remote_app.py
Normal file
103
apps/applications/serializers/remote_app.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from .. import const
|
||||
from ..models import RemoteApp
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppSerializer', 'RemoteAppConnectionInfoSerializer',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppParamsDictField(serializers.DictField):
|
||||
"""
|
||||
RemoteApp field => params
|
||||
"""
|
||||
@staticmethod
|
||||
def filter_attribute(attribute, instance):
|
||||
"""
|
||||
过滤掉params字段值中write_only特性的key-value值
|
||||
For example, the chrome_password field is not returned when serializing
|
||||
{
|
||||
'chrome_target': 'http://www.jumpserver.org/',
|
||||
'chrome_username': 'admin',
|
||||
'chrome_password': 'admin',
|
||||
}
|
||||
"""
|
||||
for field in const.REMOTE_APP_TYPE_MAP_FIELDS[instance.type]:
|
||||
if field.get('write_only', False):
|
||||
attribute.pop(field['name'], None)
|
||||
return attribute
|
||||
|
||||
def get_attribute(self, instance):
|
||||
"""
|
||||
序列化时调用
|
||||
"""
|
||||
attribute = super().get_attribute(instance)
|
||||
attribute = self.filter_attribute(attribute, instance)
|
||||
return attribute
|
||||
|
||||
@staticmethod
|
||||
def filter_value(dictionary, value):
|
||||
"""
|
||||
过滤掉不属于当前app_type所包含的key-value值
|
||||
"""
|
||||
app_type = dictionary.get('type', const.REMOTE_APP_TYPE_CHROME)
|
||||
fields = const.REMOTE_APP_TYPE_MAP_FIELDS[app_type]
|
||||
fields_names = [field['name'] for field in fields]
|
||||
no_need_keys = [k for k in value.keys() if k not in fields_names]
|
||||
for k in no_need_keys:
|
||||
value.pop(k)
|
||||
return value
|
||||
|
||||
def get_value(self, dictionary):
|
||||
"""
|
||||
反序列化时调用
|
||||
"""
|
||||
value = super().get_value(dictionary)
|
||||
value = self.filter_value(dictionary, value)
|
||||
return value
|
||||
|
||||
|
||||
class RemoteAppSerializer(BulkOrgResourceModelSerializer):
|
||||
params = RemoteAppParamsDictField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
|
||||
'comment', 'created_by', 'date_created', 'asset_info',
|
||||
'system_user_info', 'get_type_display',
|
||||
]
|
||||
read_only_fields = [
|
||||
'created_by', 'date_created', 'asset_info',
|
||||
'system_user_info', 'get_type_display'
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
|
||||
parameter_remote_app = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = RemoteApp
|
||||
fields = [
|
||||
'id', 'name', 'asset', 'system_user', 'parameter_remote_app',
|
||||
]
|
||||
read_only_fields = ['parameter_remote_app']
|
||||
|
||||
@staticmethod
|
||||
def get_parameter_remote_app(obj):
|
||||
parameter = {
|
||||
'program': const.REMOTE_APP_BOOT_PROGRAM_NAME,
|
||||
'working_directory': '',
|
||||
'parameters': obj.parameters,
|
||||
}
|
||||
return parameter
|
||||
@@ -0,0 +1,159 @@
|
||||
{% extends '_base_create_update.html' %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form %}
|
||||
<form id="appForm" method="post" class="form-horizontal">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field form.name layout="horizontal" %}
|
||||
{% bootstrap_field form.asset layout="horizontal" %}
|
||||
{% bootstrap_field form.system_user layout="horizontal" %}
|
||||
{% bootstrap_field form.type layout="horizontal" %}
|
||||
{% bootstrap_field form.path layout="horizontal" %}
|
||||
|
||||
<div class="hr-line-dashed"></div>
|
||||
|
||||
{# chrome #}
|
||||
<div class="chrome-fields">
|
||||
{% bootstrap_field form.chrome_target layout="horizontal" %}
|
||||
{% bootstrap_field form.chrome_username layout="horizontal" %}
|
||||
{% bootstrap_field form.chrome_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# mysql workbench #}
|
||||
<div class="mysql_workbench-fields">
|
||||
{% bootstrap_field form.mysql_workbench_ip layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_name layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_username layout="horizontal" %}
|
||||
{% bootstrap_field form.mysql_workbench_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# vmware #}
|
||||
<div class="vmware_client-fields">
|
||||
{% bootstrap_field form.vmware_target layout="horizontal" %}
|
||||
{% bootstrap_field form.vmware_username layout="horizontal" %}
|
||||
{% bootstrap_field form.vmware_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{# custom #}
|
||||
<div class="custom-fields">
|
||||
{% bootstrap_field form.custom_cmdline layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_target layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_username layout="horizontal" %}
|
||||
{% bootstrap_field form.custom_password layout="horizontal" %}
|
||||
</div>
|
||||
|
||||
{% bootstrap_field form.comment layout="horizontal" %}
|
||||
<div class="hr-line-dashed"></div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-4 col-sm-offset-2">
|
||||
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
|
||||
|
||||
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block custom_foot_js %}
|
||||
<script type="text/javascript">
|
||||
var app_type_id = '#' + '{{ form.type.id_for_label }}';
|
||||
var app_path_id = '#' + '{{ form.path.id_for_label }}';
|
||||
var all_type_fields = [
|
||||
'.chrome-fields',
|
||||
'.mysql_workbench-fields',
|
||||
'.vmware_client-fields',
|
||||
'.custom-fields'
|
||||
];
|
||||
var app_type_map_default_fields_value = {
|
||||
'chrome': {
|
||||
'app_path': 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
|
||||
},
|
||||
'mysql_workbench': {
|
||||
'app_path': 'C:\\Program Files\\MySQL\\MySQL Workbench 8.0 CE\\MySQLWorkbench.exe'
|
||||
},
|
||||
'vmware_client': {
|
||||
'app_path': 'C:\\Program Files (x86)\\VMware\\Infrastructure\\Virtual Infrastructure Client\\Launcher\\VpxClient.exe'
|
||||
},
|
||||
'custom': {'app_path': ''}
|
||||
};
|
||||
function getAppType(){
|
||||
return $(app_type_id+ " option:selected").val();
|
||||
}
|
||||
function initialDefaultValue(){
|
||||
var app_type = getAppType();
|
||||
var app_path = $(app_path_id).val();
|
||||
if(app_path){
|
||||
app_type_map_default_fields_value[app_type]['app_path'] = app_path
|
||||
}
|
||||
}
|
||||
function setDefaultValue(){
|
||||
// 设置类型相关字段的默认值
|
||||
var app_type = getAppType();
|
||||
var app_path = app_type_map_default_fields_value[app_type]['app_path'];
|
||||
$(app_path_id).val(app_path)
|
||||
}
|
||||
function hiddenFields(){
|
||||
var app_type = getAppType();
|
||||
$.each(all_type_fields, function(index, value){
|
||||
$(value).addClass('hidden')
|
||||
});
|
||||
$('.' + app_type + '-fields').removeClass('hidden');
|
||||
}
|
||||
function constructParams(data) {
|
||||
var typeList = ['chrome', 'mysql_workbench', 'vmware_client', 'custom'];
|
||||
var params = {};
|
||||
$.each(typeList, function(index, value){
|
||||
if (data.type === value){
|
||||
for (var k in data){
|
||||
if (k.startsWith(value)){
|
||||
params[k] = data[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$('.select2').select2({
|
||||
closeOnSelect: true
|
||||
});
|
||||
initialDefaultValue();
|
||||
hiddenFields();
|
||||
setDefaultValue();
|
||||
})
|
||||
.on('change', app_type_id, function(){
|
||||
hiddenFields();
|
||||
setDefaultValue();
|
||||
})
|
||||
.on("submit", "form", function (evt) {
|
||||
evt.preventDefault();
|
||||
var the_url = '{% url "api-applications:remote-app-list" %}';
|
||||
var redirect_to = '{% url "applications:remote-app-list" %}';
|
||||
var method = "POST";
|
||||
{% if type == "update" %}
|
||||
the_url = '{% url "api-applications:remote-app-detail" object.id %}';
|
||||
method = "PUT";
|
||||
{% endif %}
|
||||
var form = $("form");
|
||||
var data = form.serializeObject();
|
||||
data["params"] = constructParams(data);
|
||||
var props = {
|
||||
url: the_url,
|
||||
data: data,
|
||||
method: method,
|
||||
form: form,
|
||||
redirect_to: redirect_to
|
||||
};
|
||||
formSubmit(props);
|
||||
})
|
||||
;
|
||||
</script>
|
||||
{% endblock %}
|
||||
109
apps/applications/templates/applications/remote_app_detail.html
Normal file
109
apps/applications/templates/applications/remote_app_detail.html
Normal file
@@ -0,0 +1,109 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<link href="{% static 'css/plugins/select2/select2.min.css' %}" rel="stylesheet">
|
||||
<script src="{% static 'js/plugins/select2/select2.full.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="wrapper wrapper-content animated fadeInRight">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="panel-options">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="{% url 'applications:remote-app-detail' pk=remote_app.id %}" class="text-center"><i class="fa fa-laptop"></i> {% trans 'Detail' %} </a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-default" href="{% url 'applications:remote-app-update' pk=remote_app.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<a class="btn btn-outline btn-danger btn-delete-application">
|
||||
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="col-sm-8" style="padding-left: 0;">
|
||||
<div class="ibox float-e-margins">
|
||||
<div class="ibox-title">
|
||||
<span class="label"><b>{{ remote_app.name }}</b></span>
|
||||
<div class="ibox-tools">
|
||||
<a class="collapse-link">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
<i class="fa fa-wrench"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-user">
|
||||
</ul>
|
||||
<a class="close-link">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ibox-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr class="no-borders-tr">
|
||||
<td>{% trans 'Name' %}:</td>
|
||||
<td><b>{{ remote_app.name }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Asset' %}:</td>
|
||||
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'System user' %}:</td>
|
||||
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'App type' %}:</td>
|
||||
<td><b>{{ remote_app.get_type_display }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'App path' %}:</td>
|
||||
<td><b>{{ remote_app.path }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Date created' %}:</td>
|
||||
<td><b>{{ remote_app.date_created }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Created by' %}:</td>
|
||||
<td><b>{{ remote_app.created_by }}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans 'Comment' %}:</td>
|
||||
<td><b>{{ remote_app.comment }}</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
jumpserver.nodes_selected = {};
|
||||
$(document).ready(function () {
|
||||
})
|
||||
.on('click', '.btn-delete-application', function () {
|
||||
var $this = $(this);
|
||||
var name = "{{ remote_app.name }}";
|
||||
var rid = "{{ remote_app.id }}";
|
||||
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||
var redirect_url = "{% url 'applications:remote-app-list' %}";
|
||||
objectDelete($this, name, the_url, redirect_url);
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,90 @@
|
||||
{% extends '_base_list.html' %}
|
||||
{% load i18n static %}
|
||||
{% block help_message %}
|
||||
<div class="alert alert-info help-message">
|
||||
{% trans 'Before using this feature, make sure that the application loader has been uploaded to the application server and successfully published as a RemoteApp application' %}
|
||||
<b><a href="https://github.com/jumpserver/Jmservisor/releases" target="view_window" >{% trans 'Download application loader' %}</a></b>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block table_search %}{% endblock %}
|
||||
{% block table_container %}
|
||||
<div class="uc pull-left m-r-5">
|
||||
<a href="{% url 'applications:remote-app-create' %}" class="btn btn-sm btn-primary"> {% trans "Create RemoteApp" %} </a>
|
||||
</div>
|
||||
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'App type' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% block content_bottom_left %}{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
function initTable() {
|
||||
var options = {
|
||||
ele: $('#remote_app_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
cellData = htmlEscape(cellData);
|
||||
{% url 'applications:remote-app-detail' pk=DEFAULT_PK as the_url %}
|
||||
var detail_btn = '<a href="{{ the_url }}">' + cellData + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var hostname = htmlEscape(cellData.hostname);
|
||||
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData.name);
|
||||
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
|
||||
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var update_btn = '<a href="{% url "applications:remote-app-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
|
||||
$(td).html(update_btn + del_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: '{% url "api-applications:remote-app-list" %}',
|
||||
columns: [
|
||||
{data: "id"},
|
||||
{data: "name" },
|
||||
{data: "get_type_display", orderable: false},
|
||||
{data: "asset_info", orderable: false},
|
||||
{data: "system_user_info", orderable: false},
|
||||
{data: "comment"},
|
||||
{data: "id", orderable: false}
|
||||
],
|
||||
op_html: $('#actions').html()
|
||||
};
|
||||
jumpserver.initServerSideDataTable(options);
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
.on('click', '.btn-delete', function () {
|
||||
var $this = $(this);
|
||||
var $data_table = $('#remote_app_list_table').DataTable();
|
||||
var name = $(this).closest("tr").find(":nth-child(2)").children('a').html();
|
||||
var rid = $this.data('rid');
|
||||
var the_url = '{% url "api-applications:remote-app-detail" pk=DEFAULT_PK %}'.replace('{{ DEFAULT_PK }}', rid);
|
||||
objectDelete($this, name, the_url);
|
||||
setTimeout( function () {
|
||||
$data_table.ajax.reload();
|
||||
}, 3000);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,79 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block custom_head_css_js %}
|
||||
<script src="{% static 'js/jquery.form.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mail-box-header">
|
||||
<table class="table table-striped table-bordered table-hover " id="remote_app_list_table" >
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
<input type="checkbox" id="check_all" class="ipt_check_all" >
|
||||
</th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'App type' %}</th>
|
||||
<th class="text-center">{% trans 'Asset' %}</th>
|
||||
<th class="text-center">{% trans 'System user' %}</th>
|
||||
<th class="text-center">{% trans 'Comment' %}</th>
|
||||
<th class="text-center">{% trans 'Action' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block custom_foot_js %}
|
||||
<script>
|
||||
var inited = false;
|
||||
var remote_app_table, url;
|
||||
|
||||
function initTable() {
|
||||
if (inited){
|
||||
return
|
||||
} else {
|
||||
inited = true;
|
||||
}
|
||||
url = '{% url "api-perms:my-remote-apps" %}';
|
||||
var options = {
|
||||
ele: $('#remote_app_list_table'),
|
||||
columnDefs: [
|
||||
{targets: 1, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData);
|
||||
$(td).html(name)
|
||||
}},
|
||||
{targets: 3, createdCell: function (td, cellData, rowData) {
|
||||
var hostname = htmlEscape(cellData.hostname);
|
||||
$(td).html(hostname);
|
||||
}},
|
||||
{targets: 4, createdCell: function (td, cellData, rowData) {
|
||||
var name = htmlEscape(cellData.name);
|
||||
$(td).html(name);
|
||||
}},
|
||||
{targets: 6, createdCell: function (td, cellData, rowData) {
|
||||
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
|
||||
$(td).html(conn_btn)
|
||||
}}
|
||||
],
|
||||
ajax_url: url,
|
||||
columns: [
|
||||
{data: "id"},
|
||||
{data: "name"},
|
||||
{data: "get_type_display", orderable: false},
|
||||
{data: "asset_info", orderable: false},
|
||||
{data: "system_user_info", orderable: false},
|
||||
{data: "comment", orderable: false},
|
||||
{data: "id", orderable: false}
|
||||
]
|
||||
};
|
||||
remote_app_table = jumpserver.initServerSideDataTable(options);
|
||||
return remote_app_table
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initTable();
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
3
apps/applications/tests.py
Normal file
3
apps/applications/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
7
apps/applications/urls/__init__.py
Normal file
7
apps/applications/urls/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
]
|
||||
20
apps/applications/urls/api_urls.py
Normal file
20
apps/applications/urls/api_urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# coding:utf-8
|
||||
#
|
||||
|
||||
from django.urls import path
|
||||
from rest_framework_bulk.routes import BulkRouter
|
||||
|
||||
from .. import api
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
router = BulkRouter()
|
||||
router.register(r'remote-app', api.RemoteAppViewSet, 'remote-app')
|
||||
|
||||
urlpatterns = [
|
||||
path('remote-apps/<uuid:pk>/connection-info/',
|
||||
api.RemoteAppConnectionInfoApi.as_view(),
|
||||
name='remote-app-connection-info')
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
16
apps/applications/urls/views_urls.py
Normal file
16
apps/applications/urls/views_urls.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# coding:utf-8
|
||||
from django.urls import path
|
||||
from .. import views
|
||||
|
||||
app_name = 'applications'
|
||||
|
||||
urlpatterns = [
|
||||
# RemoteApp
|
||||
path('remote-app/', views.RemoteAppListView.as_view(), name='remote-app-list'),
|
||||
path('remote-app/create/', views.RemoteAppCreateView.as_view(), name='remote-app-create'),
|
||||
path('remote-app/<uuid:pk>/update/', views.RemoteAppUpdateView.as_view(), name='remote-app-update'),
|
||||
path('remote-app/<uuid:pk>/', views.RemoteAppDetailView.as_view(), name='remote-app-detail'),
|
||||
# User RemoteApp view
|
||||
path('user-remote-app/', views.UserRemoteAppListView.as_view(), name='user-remote-app-list')
|
||||
|
||||
]
|
||||
1
apps/applications/views/__init__.py
Normal file
1
apps/applications/views/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .remote_app import *
|
||||
105
apps/applications/views/remote_app.py
Normal file
105
apps/applications/views/remote_app.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
|
||||
from common.permissions import PermissionsMixin, IsOrgAdmin, IsValidUser
|
||||
from common.const import create_success_msg, update_success_msg
|
||||
|
||||
from ..models import RemoteApp
|
||||
from .. import forms
|
||||
|
||||
|
||||
__all__ = [
|
||||
'RemoteAppListView', 'RemoteAppCreateView', 'RemoteAppUpdateView',
|
||||
'RemoteAppDetailView', 'UserRemoteAppListView',
|
||||
]
|
||||
|
||||
|
||||
class RemoteAppListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'applications/remote_app_list.html'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Applications'),
|
||||
'action': _('RemoteApp list'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class RemoteAppCreateView(PermissionsMixin, SuccessMessageMixin, CreateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Applications'),
|
||||
'action': _('Create RemoteApp'),
|
||||
'type': 'create'
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return create_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppUpdateView(PermissionsMixin, SuccessMessageMixin, UpdateView):
|
||||
template_name = 'applications/remote_app_create_update.html'
|
||||
model = RemoteApp
|
||||
form_class = forms.RemoteAppCreateUpdateForm
|
||||
success_url = reverse_lazy('applications:remote-app-list')
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_initial(self):
|
||||
return {k: v for k, v in self.object.params.items()}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Applications'),
|
||||
'action': _('Update RemoteApp'),
|
||||
'type': 'update'
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
return update_success_msg % ({'name': cleaned_data['name']})
|
||||
|
||||
|
||||
class RemoteAppDetailView(PermissionsMixin, DetailView):
|
||||
template_name = 'applications/remote_app_detail.html'
|
||||
model = RemoteApp
|
||||
context_object_name = 'remote_app'
|
||||
permission_classes = [IsOrgAdmin]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'app': _('Applications'),
|
||||
'action': _('RemoteApp detail'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class UserRemoteAppListView(PermissionsMixin, TemplateView):
|
||||
template_name = 'applications/user_remote_app_list.html'
|
||||
permission_classes = [IsValidUser]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
'action': _('My RemoteApp'),
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
@@ -5,3 +5,4 @@ from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .asset_user import *
|
||||
|
||||
@@ -20,7 +20,7 @@ from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from common.utils import get_logger
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import AdminUser, Asset
|
||||
@@ -36,7 +36,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class AdminUserViewSet(IDInFilterMixin, BulkModelViewSet):
|
||||
class AdminUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
"""
|
||||
Admin user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import uuid
|
||||
import random
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
|
||||
from common.mixins import IDInFilterMixin
|
||||
from common.utils import get_logger
|
||||
from common.mixins import IDInCacheFilterMixin, ApiMessageMixin
|
||||
|
||||
from common.utils import get_logger, get_object_or_none
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from orgs.mixins import OrgBulkModelViewSet
|
||||
from ..const import CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX
|
||||
from ..models import Asset, AdminUser, Node
|
||||
from .. import serializers
|
||||
from ..tasks import update_asset_hardware_info_manual, \
|
||||
@@ -25,21 +33,37 @@ logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'AssetViewSet', 'AssetListUpdateApi',
|
||||
'AssetRefreshHardwareApi', 'AssetAdminUserTestApi',
|
||||
'AssetGatewayApi'
|
||||
'AssetGatewayApi', 'AssetBulkUpdateSelectAPI'
|
||||
]
|
||||
|
||||
|
||||
class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
class AssetViewSet(LabelFilter, OrgBulkModelViewSet):
|
||||
"""
|
||||
API endpoint that allows Asset to be viewed or edited.
|
||||
"""
|
||||
filter_fields = ("hostname", "ip")
|
||||
search_fields = filter_fields
|
||||
filter_fields = ("hostname", "ip", "systemuser__id", "admin_user__id")
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
queryset = Asset.objects.all()
|
||||
serializer_class = serializers.AssetSerializer
|
||||
pagination_class = LimitOffsetPagination
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
success_message = _("%(hostname)s was %(action)s successfully")
|
||||
|
||||
def set_assets_node(self, assets):
|
||||
if not isinstance(assets, list):
|
||||
assets = [assets]
|
||||
node_id = self.request.query_params.get('node_id')
|
||||
if not node_id:
|
||||
return
|
||||
node = get_object_or_none(Node, pk=node_id)
|
||||
if not node:
|
||||
return
|
||||
node.assets.add(*assets)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
assets = serializer.save()
|
||||
self.set_assets_node(assets)
|
||||
|
||||
def filter_node(self, queryset):
|
||||
node_id = self.request.query_params.get("node_id")
|
||||
@@ -61,7 +85,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
queryset = queryset.filter(
|
||||
nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key),
|
||||
)
|
||||
return queryset
|
||||
return queryset.distinct()
|
||||
|
||||
def filter_admin_user_id(self, queryset):
|
||||
admin_user_id = self.request.query_params.get('admin_user_id')
|
||||
@@ -77,13 +101,8 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
|
||||
queryset = self.filter_admin_user_id(queryset)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().distinct()
|
||||
queryset = self.get_serializer_class().setup_eager_loading(queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
class AssetListUpdateApi(IDInCacheFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
"""
|
||||
Asset bulk update api
|
||||
"""
|
||||
@@ -92,6 +111,21 @@ class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
|
||||
class AssetBulkUpdateSelectAPI(APIView):
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
assets_id = request.data.get('assets_id', '')
|
||||
if assets_id:
|
||||
spm = uuid.uuid4().hex
|
||||
key = CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX.format(spm)
|
||||
cache.set(key, assets_id, 300)
|
||||
url = reverse_lazy('assets:asset-bulk-update') + '?spm=%s' % spm
|
||||
return Response({'url': url})
|
||||
error = _('Please select assets that need to be updated')
|
||||
return Response({'error': error}, status=400)
|
||||
|
||||
|
||||
class AssetRefreshHardwareApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Refresh asset hardware info
|
||||
@@ -132,8 +166,8 @@ class AssetGatewayApi(generics.RetrieveAPIView):
|
||||
asset = get_object_or_404(Asset, pk=asset_id)
|
||||
|
||||
if asset.domain and \
|
||||
asset.domain.gateways.filter(protocol=asset.protocol).exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol=asset.protocol))
|
||||
asset.domain.gateways.filter(protocol='ssh').exists():
|
||||
gateway = random.choice(asset.domain.gateways.filter(protocol='ssh'))
|
||||
serializer = serializers.GatewayWithAuthSerializer(instance=gateway)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
|
||||
180
apps/assets/api/asset_user.py
Normal file
180
apps/assets/api/asset_user.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status, generics
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from rest_framework import filters
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from common.permissions import IsOrgAdminOrAppUser, NeedMFAVerify
|
||||
from common.utils import get_object_or_none, get_logger
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from ..backends import AssetUserManager
|
||||
from ..models import Asset, Node, SystemUser, AdminUser
|
||||
from .. import serializers
|
||||
from ..tasks import test_asset_users_connectivity_manual
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserViewSet', 'AssetUserAuthInfoApi', 'AssetUserTestConnectiveApi',
|
||||
'AssetUserExportViewSet',
|
||||
]
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AssetUserFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
kwargs = {}
|
||||
for field in view.filter_fields:
|
||||
value = request.GET.get(field)
|
||||
if not value:
|
||||
continue
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
kwargs[field] = value
|
||||
return queryset.filter(**kwargs)
|
||||
|
||||
|
||||
class AssetUserSearchBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
value = request.GET.get('search')
|
||||
if not value:
|
||||
return queryset
|
||||
_queryset = AssetUserManager.none()
|
||||
for field in view.search_fields:
|
||||
if field in ("node_id", "system_user_id", "admin_user_id"):
|
||||
continue
|
||||
_queryset |= queryset.filter(**{field: value})
|
||||
return _queryset
|
||||
|
||||
|
||||
class AssetUserViewSet(IDInCacheFilterMixin, BulkModelViewSet):
|
||||
pagination_class = LimitOffsetPagination
|
||||
serializer_class = serializers.AssetUserSerializer
|
||||
permission_classes = [IsOrgAdminOrAppUser]
|
||||
http_method_names = ['get', 'post']
|
||||
filter_fields = [
|
||||
"id", "ip", "hostname", "username", "asset_id", "node_id",
|
||||
"system_user_id", "admin_user_id"
|
||||
]
|
||||
search_fields = filter_fields
|
||||
filter_backends = (
|
||||
filters.OrderingFilter,
|
||||
AssetUserFilterBackend, AssetUserSearchBackend,
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
# 尽可能先返回更少的数据
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
node_id = self.request.GET.get('node_id')
|
||||
admin_user_id = self.request.GET.get("admin_user_id")
|
||||
system_user_id = self.request.GET.get("system_user_id")
|
||||
|
||||
kwargs = {}
|
||||
assets = None
|
||||
|
||||
manager = AssetUserManager()
|
||||
if system_user_id:
|
||||
system_user = get_object_or_404(SystemUser, id=system_user_id)
|
||||
assets = system_user.get_all_assets()
|
||||
username = system_user.username
|
||||
elif admin_user_id:
|
||||
admin_user = get_object_or_404(AdminUser, id=admin_user_id)
|
||||
assets = admin_user.assets.all()
|
||||
username = admin_user.username
|
||||
manager.prefer('admin_user')
|
||||
|
||||
if asset_id:
|
||||
asset = get_object_or_404(Asset, id=asset_id)
|
||||
assets = [asset]
|
||||
elif node_id:
|
||||
node = get_object_or_404(Node, id=node_id)
|
||||
assets = node.get_all_assets()
|
||||
|
||||
if username:
|
||||
kwargs['username'] = username
|
||||
if assets is not None:
|
||||
kwargs['assets'] = assets
|
||||
|
||||
queryset = manager.filter(**kwargs)
|
||||
return queryset
|
||||
|
||||
|
||||
class AssetUserExportViewSet(AssetUserViewSet):
|
||||
serializer_class = serializers.AssetUserExportSerializer
|
||||
http_method_names = ['get']
|
||||
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
|
||||
|
||||
class AssetUserAuthInfoApi(generics.RetrieveAPIView):
|
||||
serializer_class = serializers.AssetUserAuthInfoSerializer
|
||||
permission_classes = [IsOrgAdminOrAppUser, NeedMFAVerify]
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
status_code = status.HTTP_200_OK
|
||||
if not instance:
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
return Response(serializer.data, status=status_code)
|
||||
|
||||
def get_object(self):
|
||||
query_params = self.request.query_params
|
||||
username = query_params.get('username')
|
||||
asset_id = query_params.get('asset_id')
|
||||
prefer = query_params.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
try:
|
||||
manger = AssetUserManager()
|
||||
instance = manger.get(username, asset, prefer=prefer)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
else:
|
||||
return instance
|
||||
|
||||
|
||||
class AssetUserTestConnectiveApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.TaskIDSerializer
|
||||
|
||||
def get_asset_users(self):
|
||||
username = self.request.GET.get('username')
|
||||
asset_id = self.request.GET.get('asset_id')
|
||||
prefer = self.request.GET.get("prefer")
|
||||
asset = get_object_or_none(Asset, pk=asset_id)
|
||||
manager = AssetUserManager()
|
||||
asset_users = manager.filter(username=username, assets=[asset], prefer=prefer)
|
||||
return asset_users
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
asset_users = self.get_asset_users()
|
||||
prefer = self.request.GET.get("prefer")
|
||||
kwargs = {}
|
||||
if prefer == "admin_user":
|
||||
kwargs["run_as_admin"] = True
|
||||
task = test_asset_users_connectivity_manual.delay(asset_users, **kwargs)
|
||||
return Response({"task": task.id})
|
||||
|
||||
|
||||
class AssetUserPushApi(generics.CreateAPIView):
|
||||
"""
|
||||
Test asset users connective
|
||||
"""
|
||||
serializer_class = serializers.AssetUserPushSerializer
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
asset = serializer.validated_data["asset"]
|
||||
username = serializer.validated_data["username"]
|
||||
pass
|
||||
@@ -51,9 +51,10 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
|
||||
model = Gateway
|
||||
object = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object(Gateway.objects.all())
|
||||
ok, e = self.object.test_connective()
|
||||
local_port = self.request.data.get('port') or self.object.port
|
||||
ok, e = self.object.test_connective(local_port=local_port)
|
||||
if ok:
|
||||
return Response("ok")
|
||||
else:
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from rest_framework_bulk import BulkModelViewSet
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
from django.db.models import Count
|
||||
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgBulkModelViewSet
|
||||
from ..hands import IsOrgAdmin
|
||||
from ..models import Label
|
||||
from .. import serializers
|
||||
@@ -27,7 +27,7 @@ logger = get_logger(__file__)
|
||||
__all__ = ['LabelViewSet']
|
||||
|
||||
|
||||
class LabelViewSet(BulkModelViewSet):
|
||||
class LabelViewSet(OrgBulkModelViewSet):
|
||||
filter_fields = ("name", "value")
|
||||
search_fields = filter_fields
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
|
||||
@@ -26,6 +26,7 @@ from ..hands import IsOrgAdmin
|
||||
from ..models import Node
|
||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectivity_util
|
||||
from .. import serializers
|
||||
from ..utils import NodeUtil
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
@@ -39,6 +40,8 @@ __all__ = [
|
||||
|
||||
|
||||
class NodeViewSet(viewsets.ModelViewSet):
|
||||
filter_fields = ('value', 'key', )
|
||||
search_fields = filter_fields
|
||||
queryset = Node.objects.all()
|
||||
permission_classes = (IsOrgAdmin,)
|
||||
serializer_class = serializers.NodeSerializer
|
||||
@@ -77,12 +80,10 @@ class NodeListAsTreeApi(generics.ListAPIView):
|
||||
serializer_class = TreeNodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = [node.as_tree_node() for node in Node.objects.all()]
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
queryset = self.refresh_nodes(queryset)
|
||||
queryset = Node.objects.all()
|
||||
util = NodeUtil()
|
||||
nodes = util.get_nodes_by_queryset(queryset)
|
||||
queryset = [node.as_tree_node() for node in nodes]
|
||||
return queryset
|
||||
|
||||
@staticmethod
|
||||
@@ -111,38 +112,38 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
|
||||
is_root = False
|
||||
|
||||
def get_queryset(self):
|
||||
self.check_need_refresh_nodes()
|
||||
node_key = self.request.query_params.get('key')
|
||||
if node_key:
|
||||
self.node = Node.objects.get(key=node_key)
|
||||
queryset = self.node.get_children(with_self=False)
|
||||
else:
|
||||
self.is_root = True
|
||||
self.node = Node.root()
|
||||
queryset = list(self.node.get_children(with_self=True))
|
||||
nodes_invalid = Node.objects.exclude(key__startswith=self.node.key)
|
||||
queryset.extend(list(nodes_invalid))
|
||||
util = NodeUtil()
|
||||
# 是否包含自己
|
||||
with_self = False
|
||||
if not node_key:
|
||||
node_key = Node.root().key
|
||||
with_self = True
|
||||
self.node = util.get_node_by_key(node_key)
|
||||
queryset = self.node.get_children(with_self=with_self)
|
||||
queryset = [node.as_tree_node() for node in queryset]
|
||||
queryset = sorted(queryset)
|
||||
return queryset
|
||||
|
||||
def filter_assets(self, queryset):
|
||||
include_assets = self.request.query_params.get('assets', '0') == '1'
|
||||
if not include_assets:
|
||||
return queryset
|
||||
assets = self.node.get_assets()
|
||||
assets = self.node.get_assets().only(
|
||||
"id", "hostname", "ip", 'platform', "os", "org_id", "protocols",
|
||||
)
|
||||
for asset in assets:
|
||||
queryset.append(asset.as_tree_node(self.node))
|
||||
return queryset
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
queryset = self.filter_assets(queryset)
|
||||
queryset = self.filter_refresh_nodes(queryset)
|
||||
return queryset
|
||||
|
||||
def filter_refresh_nodes(self, queryset):
|
||||
def check_need_refresh_nodes(self):
|
||||
if self.request.query_params.get('refresh', '0') == '1':
|
||||
Node.expire_nodes_assets_amount()
|
||||
Node.expire_nodes_full_value()
|
||||
return queryset
|
||||
Node.refresh_nodes()
|
||||
|
||||
|
||||
class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
@@ -163,12 +164,13 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView):
|
||||
def create(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
value = request.data.get("value")
|
||||
_id = request.data.get('id') or None
|
||||
values = [child.value for child in instance.get_children()]
|
||||
if value in values:
|
||||
raise ValidationError(
|
||||
'The same level node name cannot be the same'
|
||||
)
|
||||
node = instance.create_child(value=value)
|
||||
node = instance.create_child(value=value, _id=_id)
|
||||
return Response(self.serializer_class(instance=node).data, status=201)
|
||||
|
||||
def get_object(self):
|
||||
|
||||
@@ -21,6 +21,8 @@ from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from common.utils import get_logger
|
||||
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
|
||||
from common.mixins import IDInCacheFilterMixin
|
||||
from orgs.mixins import OrgBulkModelViewSet
|
||||
from ..models import SystemUser, Asset
|
||||
from .. import serializers
|
||||
from ..tasks import push_system_user_to_assets_manual, \
|
||||
@@ -30,7 +32,7 @@ from ..tasks import push_system_user_to_assets_manual, \
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = [
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi',
|
||||
'SystemUserViewSet', 'SystemUserAuthInfoApi', 'SystemUserAssetAuthInfoApi',
|
||||
'SystemUserPushApi', 'SystemUserTestConnectiveApi',
|
||||
'SystemUserAssetsListView', 'SystemUserPushToAssetApi',
|
||||
'SystemUserTestAssetConnectivityApi', 'SystemUserCommandFilterRuleListApi',
|
||||
@@ -38,7 +40,7 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class SystemUserViewSet(BulkModelViewSet):
|
||||
class SystemUserViewSet(OrgBulkModelViewSet):
|
||||
"""
|
||||
System user api set, for add,delete,update,list,retrieve resource
|
||||
"""
|
||||
@@ -68,6 +70,22 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView):
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class SystemUserAssetAuthInfoApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Get system user with asset auth info
|
||||
"""
|
||||
queryset = SystemUser.objects.all()
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
serializer_class = serializers.SystemUserAuthSerializer
|
||||
|
||||
def get_object(self):
|
||||
instance = super().get_object()
|
||||
aid = self.kwargs.get('aid')
|
||||
asset = get_object_or_404(Asset, pk=aid)
|
||||
instance.load_specific_asset_auth(asset)
|
||||
return instance
|
||||
|
||||
|
||||
class SystemUserPushApi(generics.RetrieveAPIView):
|
||||
"""
|
||||
Push system user to cluster assets api
|
||||
|
||||
1
apps/assets/backends/__init__.py
Normal file
1
apps/assets/backends/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .manager import AssetUserManager
|
||||
10
apps/assets/backends/admin_user.py
Normal file
10
apps/assets/backends/admin_user.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AdminUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class AdminUserBackend(AssetUserBackend):
|
||||
model = AdminUser
|
||||
backend = 'AdminUser'
|
||||
40
apps/assets/backends/asset_user.py
Normal file
40
apps/assets/backends/asset_user.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AssetUserBackend(BaseBackend):
|
||||
model = None
|
||||
backend = "AssetUser"
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, **kwargs):
|
||||
queryset = cls.model.objects.all()
|
||||
prefer_id = kwargs.get('prefer_id')
|
||||
if prefer_id:
|
||||
queryset = queryset.filter(id=prefer_id)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
if username:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(assets__in=assets).distinct()
|
||||
queryset = cls.filter_queryset_more(queryset)
|
||||
instances = cls.construct_authbook_objects(queryset, assets)
|
||||
return instances
|
||||
|
||||
@classmethod
|
||||
def construct_authbook_objects(cls, asset_users, assets):
|
||||
instances = []
|
||||
for asset_user in asset_users:
|
||||
if not assets:
|
||||
assets = asset_user.assets.all()
|
||||
for asset in assets:
|
||||
instance = asset_user.construct_to_authbook(asset)
|
||||
instance.backend = cls.backend
|
||||
instances.append(instance)
|
||||
return instances
|
||||
86
apps/assets/backends/base.py
Normal file
86
apps/assets/backends/base.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
class BaseBackend:
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def filter(cls, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
"""
|
||||
:param username: 用户名
|
||||
:param assets: <Asset>对象
|
||||
:param latest: 是否是最新记录
|
||||
:param prefer: 优先使用
|
||||
:param prefer_id: 使用id
|
||||
:return: 元素为<AuthBook>的可迭代对象(<list> or <QuerySet>)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserQuerySet(list):
|
||||
def order_by(self, *ordering):
|
||||
_ordering = []
|
||||
reverse = False
|
||||
for i in ordering:
|
||||
if i[0] == '-':
|
||||
reverse = True
|
||||
i = i[1:]
|
||||
_ordering.append(i)
|
||||
self.sort(key=lambda obj: [getattr(obj, j) for j in _ordering], reverse=reverse)
|
||||
return self
|
||||
|
||||
def filter_in(self, kwargs):
|
||||
in_kwargs = {}
|
||||
queryset = []
|
||||
for k, v in kwargs.items():
|
||||
if len(v) == 0:
|
||||
return self
|
||||
if k.find("__in") >= 0:
|
||||
in_kwargs[k] = v
|
||||
for k in in_kwargs:
|
||||
kwargs.pop(k)
|
||||
|
||||
if len(in_kwargs) == 0:
|
||||
return self
|
||||
for i in self:
|
||||
matched = True
|
||||
for k, v in in_kwargs.items():
|
||||
key = k.split('__')[0]
|
||||
attr = getattr(i, key, None)
|
||||
# 如果属性或者value中是uuid,则转换成string
|
||||
if isinstance(v[0], uuid.UUID):
|
||||
v = [str(i) for i in v]
|
||||
if isinstance(attr, uuid.UUID):
|
||||
attr = str(attr)
|
||||
if attr not in v:
|
||||
matched = False
|
||||
if matched:
|
||||
queryset.append(i)
|
||||
return AssetUserQuerySet(queryset)
|
||||
|
||||
def filter_equal(self, kwargs):
|
||||
def filter_it(obj):
|
||||
wanted = []
|
||||
real = []
|
||||
for k, v in kwargs.items():
|
||||
wanted.append(v)
|
||||
value = getattr(obj, k)
|
||||
if isinstance(value, uuid.UUID):
|
||||
value = str(value)
|
||||
real.append(value)
|
||||
return wanted == real
|
||||
if len(kwargs) > 0:
|
||||
queryset = AssetUserQuerySet([i for i in self if filter_it(i)])
|
||||
else:
|
||||
queryset = self
|
||||
return queryset
|
||||
|
||||
def filter(self, **kwargs):
|
||||
queryset = self.filter_in(kwargs).filter_equal(kwargs)
|
||||
return queryset
|
||||
|
||||
def __or__(self, other):
|
||||
self.extend(other)
|
||||
return self
|
||||
29
apps/assets/backends/db.py
Normal file
29
apps/assets/backends/db.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from ..models import AuthBook
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class AuthBookBackend(BaseBackend):
|
||||
@classmethod
|
||||
def filter(cls, username=None, assets=None, latest=True, **kwargs):
|
||||
queryset = AuthBook.objects.all()
|
||||
if username is not None:
|
||||
queryset = queryset.filter(username=username)
|
||||
if assets:
|
||||
queryset = queryset.filter(asset__in=assets)
|
||||
if latest:
|
||||
queryset = queryset.latest_version()
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
auth_info = {
|
||||
'password': kwargs.pop('password', ''),
|
||||
'public_key': kwargs.pop('public_key', ''),
|
||||
'private_key': kwargs.pop('private_key', '')
|
||||
}
|
||||
obj = AuthBook.objects.create(**kwargs)
|
||||
obj.set_auth(**auth_info)
|
||||
return obj
|
||||
110
apps/assets/backends/manager.py
Normal file
110
apps/assets/backends/manager.py
Normal file
@@ -0,0 +1,110 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
|
||||
from .base import AssetUserQuerySet
|
||||
from .db import AuthBookBackend
|
||||
from .system_user import SystemUserBackend
|
||||
from .admin_user import AdminUserBackend
|
||||
|
||||
|
||||
class NotSupportError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetUserManager:
|
||||
"""
|
||||
资产用户管理器
|
||||
"""
|
||||
ObjectDoesNotExist = ObjectDoesNotExist
|
||||
MultipleObjectsReturned = MultipleObjectsReturned
|
||||
NotSupportError = NotSupportError
|
||||
MSG_NOT_EXIST = '{} Object matching query does not exist'
|
||||
MSG_MULTIPLE = '{} get() returned more than one object ' \
|
||||
'-- it returned {}!'
|
||||
|
||||
backends = (
|
||||
('db', AuthBookBackend),
|
||||
('system_user', SystemUserBackend),
|
||||
('admin_user', AdminUserBackend),
|
||||
)
|
||||
|
||||
_prefer = "system_user"
|
||||
|
||||
def filter(self, username=None, assets=None, latest=True, prefer=None, prefer_id=None):
|
||||
if assets is not None and not assets:
|
||||
return AssetUserQuerySet([])
|
||||
|
||||
if prefer:
|
||||
self._prefer = prefer
|
||||
|
||||
instances_map = {}
|
||||
instances = []
|
||||
for name, backend in self.backends:
|
||||
if name != "db" and self._prefer != name:
|
||||
continue
|
||||
_instances = backend.filter(
|
||||
username=username, assets=assets, latest=latest,
|
||||
prefer=self._prefer, prefer_id=prefer_id,
|
||||
)
|
||||
instances_map[name] = _instances
|
||||
|
||||
# 如果不是获取最新版本,就不再merge
|
||||
if not latest:
|
||||
for _instances in instances_map.values():
|
||||
instances.extend(_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
# merge的顺序
|
||||
ordering = ["db"]
|
||||
if self._prefer == "system_user":
|
||||
ordering.extend(["system_user", "admin_user"])
|
||||
else:
|
||||
ordering.extend(["admin_user", "system_user"])
|
||||
# 根据prefer决定优先使用系统用户或管理用户谁的
|
||||
ordering_instances = [instances_map.get(i, []) for i in ordering]
|
||||
instances = self._merge_instances(*ordering_instances)
|
||||
return AssetUserQuerySet(instances)
|
||||
|
||||
def get(self, username, asset, **kwargs):
|
||||
instances = self.filter(username, assets=[asset], **kwargs)
|
||||
if len(instances) == 1:
|
||||
return instances[0]
|
||||
elif len(instances) == 0:
|
||||
self.raise_does_not_exist(self.__class__.__name__)
|
||||
else:
|
||||
self.raise_multiple_return(self.__class__.__name__, len(instances))
|
||||
|
||||
def raise_does_not_exist(self, name):
|
||||
raise self.ObjectDoesNotExist(self.MSG_NOT_EXIST.format(name))
|
||||
|
||||
def raise_multiple_return(self, name, length):
|
||||
raise self.MultipleObjectsReturned(self.MSG_MULTIPLE.format(name, length))
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs):
|
||||
instance = AuthBookBackend.create(**kwargs)
|
||||
return instance
|
||||
|
||||
def all(self):
|
||||
return self.filter()
|
||||
|
||||
def prefer(self, s):
|
||||
self._prefer = s
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def none():
|
||||
return AssetUserQuerySet()
|
||||
|
||||
@staticmethod
|
||||
def _merge_instances(*args):
|
||||
instances = list(args[0])
|
||||
keywords = [obj.keyword for obj in instances]
|
||||
|
||||
for _instances in args[1:]:
|
||||
need_merge_instances = [obj for obj in _instances if obj.keyword not in keywords]
|
||||
need_merge_keywords = [obj.keyword for obj in need_merge_instances]
|
||||
instances.extend(need_merge_instances)
|
||||
keywords.extend(need_merge_keywords)
|
||||
return instances
|
||||
30
apps/assets/backends/system_user.py
Normal file
30
apps/assets/backends/system_user.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import itertools
|
||||
|
||||
from assets.models import SystemUser
|
||||
from .asset_user import AssetUserBackend
|
||||
|
||||
|
||||
class SystemUserBackend(AssetUserBackend):
|
||||
model = SystemUser
|
||||
backend = 'SystemUser'
|
||||
|
||||
@classmethod
|
||||
def filter_queryset_more(cls, queryset):
|
||||
queryset = cls._distinct_system_users_by_username(queryset)
|
||||
return queryset
|
||||
|
||||
@classmethod
|
||||
def _distinct_system_users_by_username(cls, system_users):
|
||||
system_users = sorted(
|
||||
system_users,
|
||||
key=lambda su: (su.username, su.priority, su.date_updated),
|
||||
reverse=True,
|
||||
)
|
||||
results = itertools.groupby(system_users, key=lambda su: su.username)
|
||||
system_users = [next(result[1]) for result in results]
|
||||
return system_users
|
||||
|
||||
|
||||
16
apps/assets/backends/utils.py
Normal file
16
apps/assets/backends/utils.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
# from django.conf import settings
|
||||
|
||||
from .db import AuthBookBackend
|
||||
# from .vault import VaultBackend
|
||||
|
||||
|
||||
def get_backend():
|
||||
default_backend = AuthBookBackend
|
||||
|
||||
# if settings.BACKEND_ASSET_USER_AUTH_VAULT:
|
||||
# return VaultBackend
|
||||
|
||||
return default_backend
|
||||
11
apps/assets/backends/vault.py
Normal file
11
apps/assets/backends/vault.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from .base import BaseBackend
|
||||
|
||||
|
||||
class VaultBackend(BaseBackend):
|
||||
|
||||
@classmethod
|
||||
def filter(cls, username=None, asset=None, latest=True):
|
||||
pass
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
{
|
||||
@@ -10,7 +11,6 @@ UPDATE_ASSETS_HARDWARE_TASKS = [
|
||||
}
|
||||
]
|
||||
|
||||
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
|
||||
TEST_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
@@ -19,6 +19,14 @@ TEST_ADMIN_USER_CONN_TASKS = [
|
||||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ADMIN_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
|
||||
|
||||
@@ -31,8 +39,43 @@ TEST_SYSTEM_USER_CONN_TASKS = [
|
||||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_SYSTEM_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
TEST_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
TEST_WINDOWS_ASSET_USER_CONN_TASKS = [
|
||||
{
|
||||
"name": "ping",
|
||||
"action": {
|
||||
"module": "win_ping",
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
TASK_OPTIONS = {
|
||||
'timeout': 10,
|
||||
'forks': 10,
|
||||
}
|
||||
|
||||
CACHE_KEY_ASSET_BULK_UPDATE_ID_PREFIX = '_KEY_ASSET_BULK_UPDATE_ID_{}'
|
||||
CONN_UNREACHABLE, CONN_REACHABLE, CONN_UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(CONN_UNREACHABLE, _("Unreachable")),
|
||||
(CONN_REACHABLE, _('Reachable')),
|
||||
(CONN_UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
|
||||
@@ -6,21 +6,39 @@ from django.utils.translation import gettext_lazy as _
|
||||
from common.utils import get_logger
|
||||
from orgs.mixins import OrgModelForm
|
||||
|
||||
from ..models import Asset, AdminUser
|
||||
from ..models import Asset, Node
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
__all__ = ['AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm']
|
||||
__all__ = [
|
||||
'AssetCreateForm', 'AssetUpdateForm', 'AssetBulkUpdateForm', 'ProtocolForm',
|
||||
]
|
||||
|
||||
|
||||
class ProtocolForm(forms.Form):
|
||||
name = forms.ChoiceField(
|
||||
choices=Asset.PROTOCOL_CHOICES, label=_("Name"), initial='ssh',
|
||||
widget=forms.Select(attrs={'class': 'form-control protocol-name'})
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
max_value=65534, min_value=1, label=_("Port"), initial=22,
|
||||
widget=forms.TextInput(attrs={'class': 'form-control protocol-port'})
|
||||
)
|
||||
|
||||
|
||||
class AssetCreateForm(OrgModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
nodes_field = self.fields['nodes']
|
||||
nodes_field.choices = ((n.id, n.full_value) for n in
|
||||
Node.get_queryset())
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'public_ip', 'port', 'comment',
|
||||
'hostname', 'ip', 'public_ip', 'protocols', 'comment',
|
||||
'nodes', 'is_active', 'admin_user', 'labels', 'platform',
|
||||
'domain', 'protocol',
|
||||
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
@@ -32,7 +50,6 @@ class AssetCreateForm(OrgModelForm):
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
@@ -54,9 +71,9 @@ class AssetUpdateForm(OrgModelForm):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'hostname', 'ip', 'port', 'nodes', 'is_active', 'platform',
|
||||
'hostname', 'ip', 'protocols', 'nodes', 'is_active', 'platform',
|
||||
'public_ip', 'number', 'comment', 'admin_user', 'labels',
|
||||
'domain', 'protocol',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'nodes': forms.SelectMultiple(attrs={
|
||||
@@ -68,7 +85,6 @@ class AssetUpdateForm(OrgModelForm):
|
||||
'labels': forms.SelectMultiple(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Label')
|
||||
}),
|
||||
'port': forms.TextInput(),
|
||||
'domain': forms.Select(attrs={
|
||||
'class': 'select2', 'data-placeholder': _('Domain')
|
||||
}),
|
||||
@@ -97,24 +113,12 @@ class AssetBulkUpdateForm(OrgModelForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
port = forms.IntegerField(
|
||||
label=_('Port'), required=False, min_value=1, max_value=65535,
|
||||
)
|
||||
admin_user = forms.ModelChoiceField(
|
||||
required=False, queryset=AdminUser.objects,
|
||||
label=_("Admin user"),
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
'class': 'select2',
|
||||
'data-placeholder': _('Admin user')
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = [
|
||||
'assets', 'port', 'admin_user', 'labels', 'nodes', 'platform'
|
||||
'assets', 'admin_user', 'labels', 'platform',
|
||||
'domain',
|
||||
]
|
||||
widgets = {
|
||||
'labels': forms.SelectMultiple(
|
||||
@@ -125,6 +129,13 @@ class AssetBulkUpdateForm(OrgModelForm):
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# 重写其他字段为不再required
|
||||
for name, field in self.fields.items():
|
||||
if name != 'assets':
|
||||
field.required = False
|
||||
|
||||
def save(self, commit=True):
|
||||
changed_fields = []
|
||||
for field in self._meta.fields:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import re
|
||||
|
||||
from orgs.mixins import OrgModelForm
|
||||
from ..models import CommandFilter, CommandFilterRule
|
||||
@@ -15,6 +18,8 @@ class CommandFilterForm(OrgModelForm):
|
||||
|
||||
|
||||
class CommandFilterRuleForm(OrgModelForm):
|
||||
invalid_pattern = re.compile(r'[\.\*\+\[\\\?\{\}\^\$\|\(\)\#\<\>]')
|
||||
|
||||
class Meta:
|
||||
model = CommandFilterRule
|
||||
fields = [
|
||||
@@ -25,3 +30,11 @@ class CommandFilterRuleForm(OrgModelForm):
|
||||
'placeholder': 'eg:\r\nreboot\r\nrm -rf'
|
||||
}),
|
||||
}
|
||||
|
||||
def clean_content(self):
|
||||
content = self.cleaned_data.get("content")
|
||||
if self.invalid_pattern.search(content):
|
||||
invalid_char = self.invalid_pattern.pattern.replace('\\', '')
|
||||
msg = _("Content should not be contain: {}").format(invalid_char)
|
||||
raise ValidationError(msg)
|
||||
return content
|
||||
|
||||
@@ -64,8 +64,11 @@ class GatewayForm(PasswordAndKeyAuthForm, OrgModelForm):
|
||||
model = Gateway
|
||||
fields = [
|
||||
'name', 'ip', 'port', 'username', 'protocol', 'domain', 'password',
|
||||
'private_key_file', 'is_active', 'comment',
|
||||
'private_key', 'is_active', 'comment',
|
||||
]
|
||||
help_texts = {
|
||||
'protocol': _("SSH gateway support proxy SSH,RDP,VNC")
|
||||
}
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
|
||||
@@ -26,56 +26,50 @@ class PasswordAndKeyAuthForm(forms.ModelForm):
|
||||
label=_("Password"),
|
||||
)
|
||||
# Need use upload private key file except paste private key content
|
||||
private_key_file = forms.FileField(required=False, label=_("Private key"))
|
||||
private_key = forms.FileField(required=False, label=_("Private key"))
|
||||
|
||||
def clean_private_key_file(self):
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
def clean_private_key(self):
|
||||
private_key_f = self.cleaned_data['private_key']
|
||||
password = self.cleaned_data['password']
|
||||
|
||||
if private_key_file:
|
||||
key_string = private_key_file.read()
|
||||
private_key_file.seek(0)
|
||||
if private_key_f:
|
||||
key_string = private_key_f.read()
|
||||
private_key_f.seek(0)
|
||||
key_string = key_string.decode()
|
||||
|
||||
if not validate_ssh_private_key(key_string, password):
|
||||
raise forms.ValidationError(_('Invalid private key'))
|
||||
return private_key_file
|
||||
msg = _('Invalid private key, Only support '
|
||||
'RSA/DSA format key')
|
||||
raise forms.ValidationError(msg)
|
||||
return private_key_f
|
||||
|
||||
def validate_password_key(self):
|
||||
password = self.cleaned_data['password']
|
||||
private_key_file = self.cleaned_data.get('private_key_file', '')
|
||||
private_key_f = self.cleaned_data.get('private_key', '')
|
||||
|
||||
if not password and not private_key_file:
|
||||
if not password and not private_key_f:
|
||||
raise forms.ValidationError(_(
|
||||
'Password and private key file must be input one'
|
||||
))
|
||||
|
||||
def gen_keys(self):
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
private_key_file = self.cleaned_data['private_key_file']
|
||||
private_key_f = self.cleaned_data['private_key']
|
||||
public_key = private_key = None
|
||||
|
||||
if private_key_file:
|
||||
private_key = private_key_file.read().strip().decode('utf-8')
|
||||
if private_key_f:
|
||||
private_key = private_key_f.read().strip().decode('utf-8')
|
||||
public_key = ssh_pubkey_gen(private_key=private_key, password=password)
|
||||
return private_key, public_key
|
||||
|
||||
|
||||
class AdminUserForm(PasswordAndKeyAuthForm):
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
admin_user = super().save(commit=commit)
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
private_key, public_key = super().gen_keys()
|
||||
admin_user.set_auth(password=password, public_key=public_key, private_key=private_key)
|
||||
return admin_user
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.instance:
|
||||
super().validate_password_key()
|
||||
raise forms.ValidationError("Use api to save")
|
||||
|
||||
class Meta:
|
||||
model = AdminUser
|
||||
fields = ['name', 'username', 'password', 'private_key_file', 'comment']
|
||||
fields = ['name', 'username', 'password', 'private_key', 'comment']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'placeholder': _('Name')}),
|
||||
'username': forms.TextInput(attrs={'placeholder': _('Username')}),
|
||||
@@ -87,55 +81,13 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
||||
auto_generate_key = forms.BooleanField(initial=True, required=False)
|
||||
|
||||
def save(self, commit=True):
|
||||
# Because we define custom field, so we need rewrite :method: `save`
|
||||
system_user = super().save()
|
||||
password = self.cleaned_data.get('password', '') or None
|
||||
login_mode = self.cleaned_data.get('login_mode', '') or None
|
||||
protocol = self.cleaned_data.get('protocol') or None
|
||||
auto_generate_key = self.cleaned_data.get('auto_generate_key', False)
|
||||
private_key, public_key = super().gen_keys()
|
||||
|
||||
if login_mode == SystemUser.LOGIN_MANUAL or \
|
||||
protocol in [SystemUser.PROTOCOL_RDP,
|
||||
SystemUser.PROTOCOL_TELNET,
|
||||
SystemUser.PROTOCOL_VNC]:
|
||||
system_user.auto_push = 0
|
||||
auto_generate_key = False
|
||||
system_user.save()
|
||||
|
||||
if auto_generate_key:
|
||||
logger.info('Auto generate key and set system user auth')
|
||||
system_user.auto_gen_auth()
|
||||
else:
|
||||
system_user.set_auth(password=password, private_key=private_key,
|
||||
public_key=public_key)
|
||||
|
||||
return system_user
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
auto_generate = self.cleaned_data.get('auto_generate_key')
|
||||
if not self.instance and not auto_generate:
|
||||
super().validate_password_key()
|
||||
|
||||
def clean_username(self):
|
||||
username = self.data.get('username')
|
||||
login_mode = self.data.get('login_mode')
|
||||
protocol = self.data.get('protocol')
|
||||
|
||||
if username:
|
||||
return username
|
||||
if login_mode == SystemUser.LOGIN_AUTO and \
|
||||
protocol != SystemUser.PROTOCOL_VNC:
|
||||
msg = _('* Automatic login mode must fill in the username.')
|
||||
raise forms.ValidationError(msg)
|
||||
return username
|
||||
raise forms.ValidationError("Use api to save")
|
||||
|
||||
class Meta:
|
||||
model = SystemUser
|
||||
fields = [
|
||||
'name', 'username', 'protocol', 'auto_generate_key',
|
||||
'password', 'private_key_file', 'auto_push', 'sudo',
|
||||
'password', 'private_key', 'auto_push', 'sudo',
|
||||
'comment', 'shell', 'priority', 'login_mode', 'cmd_filters',
|
||||
]
|
||||
widgets = {
|
||||
@@ -150,5 +102,6 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
|
||||
'priority': _('1-100, High level will be using login asset as default, '
|
||||
'if user was granted more than 2 system user'),
|
||||
'login_mode': _('If you choose manual login mode, you do not '
|
||||
'need to fill in the username and password.')
|
||||
'need to fill in the username and password.'),
|
||||
'sudo': _("Use comma split multi command, ex: /bin/whoami,/bin/ifconfig")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,5 @@
|
||||
"""
|
||||
|
||||
|
||||
from common.permissions import AdminUserRequiredMixin
|
||||
from common.permissions import IsAppUser, IsOrgAdmin, IsValidUser, IsOrgAdminOrAppUser
|
||||
from users.models import User, UserGroup
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-05 10:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,158 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0002_auto_20180105_1807'), ('assets', '0003_auto_20180109_2331'), ('assets', '0004_auto_20180125_1218'), ('assets', '0005_auto_20180126_1637'), ('assets', '0006_auto_20180130_1502'), ('assets', '0007_auto_20180225_1815'), ('assets', '0008_auto_20180306_1804'), ('assets', '0009_auto_20180307_1212')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='adminuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Admin user'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='asset',
|
||||
options={'verbose_name': 'Asset'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='assetgroup',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Asset group'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cluster',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='systemuser',
|
||||
options={'ordering': ['name'], 'verbose_name': 'System user'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-09 15:31
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0002_auto_20180105_1807'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-25 04:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0003_auto_20180109_2331'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='assetgroup',
|
||||
name='created_by',
|
||||
field=models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by'),
|
||||
),
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-26 08:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0004_auto_20180125_1218'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Label',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('value', models.CharField(max_length=128, verbose_name='Value')),
|
||||
('category', models.CharField(choices=[('S', 'System'), ('U', 'User')], default='U', max_length=128, verbose_name='Category')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('comment', models.TextField(blank=True, null=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'assets_label',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together=set([('name', 'value')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='assets', to='assets.Label', verbose_name='Labels'),
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-01-30 07:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0005_auto_20180126_1637'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_no',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cabinet_pos',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='env',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='remote_card_ip',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='status',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='type',
|
||||
),
|
||||
]
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-02-25 10:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.asset
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0006_auto_20180130_1502'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Node',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=64, unique=True, verbose_name='Key')),
|
||||
('value', models.CharField(max_length=128, unique=True, verbose_name='Value')),
|
||||
('child_mark', models.IntegerField(default=0)),
|
||||
('date_create', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='groups',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='systemuser',
|
||||
name='cluster',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='nodes',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Node', verbose_name='Nodes'),
|
||||
),
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-06 10:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0007_auto_20180225_1815'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='created_by',
|
||||
field=models.CharField(max_length=128, null=True, verbose_name='Created by'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=128, verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 04:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0008_auto_20180306_1804'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-07 09:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,220 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-28 10:16
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
# Functions from the following migrations need manual copying.
|
||||
# Move them and any dependencies into this file, then update the
|
||||
# RunPython operations to refer to the local versions:
|
||||
# assets.migrations.0017_auto_20180702_1415
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('assets', '0010_auto_20180307_1749'), ('assets', '0011_auto_20180326_0957'), ('assets', '0012_auto_20180404_1302'), ('assets', '0013_auto_20180411_1135'), ('assets', '0014_auto_20180427_1245'), ('assets', '0015_auto_20180510_1235'), ('assets', '0016_auto_20180511_1203'), ('assets', '0017_auto_20180702_1415'), ('assets', '0018_auto_20180807_1116'), ('assets', '0019_auto_20180816_1320')]
|
||||
|
||||
dependencies = [
|
||||
('assets', '0009_auto_20180307_1212'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, unique=True, verbose_name='Value'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=migrate_win_to_ssh_protocol,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
||||
@@ -1,55 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-03-26 01:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import assets.models.utils
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0010_auto_20180307_1749'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Domain',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Gateway',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
|
||||
('username', models.CharField(max_length=128, verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('ip', models.GenericIPAddressField(db_index=True, verbose_name='IP')),
|
||||
('port', models.IntegerField(default=22, verbose_name='Port')),
|
||||
('protocol', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp')], default='ssh', max_length=16, verbose_name='Protocol')),
|
||||
('comment', models.CharField(blank=True, max_length=128, null=True, verbose_name='Comment')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Is active')),
|
||||
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Domain', verbose_name='Domain')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-04 05:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0011_auto_20180326_0957'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='domain',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.Domain', verbose_name='Domain'),
|
||||
),
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-11 03:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0012_auto_20180404_1302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='assets',
|
||||
field=models.ManyToManyField(blank=True, to='assets.Asset', verbose_name='Assets'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='sudo',
|
||||
field=models.TextField(default='/bin/whoami', verbose_name='Sudo'),
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-04-27 04:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0013_auto_20180411_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_-]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-10 04:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0014_auto_20180427_1245'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-05-11 04:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0015_auto_20180510_1235'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='node',
|
||||
name='value',
|
||||
field=models.CharField(max_length=128, verbose_name='Value'),
|
||||
),
|
||||
]
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2018-07-02 06:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_win_to_ssh_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
asset_model.objects.using(db_alias).filter(platform__startswith='Win').update(protocol='rdp')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0016_auto_20180511_1203'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=128, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='login_mode',
|
||||
field=models.CharField(choices=[('auto', 'Automatic login'), ('manual', 'Manually login')], default='auto', max_length=10, verbose_name='Login mode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='platform',
|
||||
field=models.CharField(choices=[('Linux', 'Linux'), ('Unix', 'Unix'), ('MacOS', 'MacOS'), ('BSD', 'BSD'), ('Windows', 'Windows'), ('Windows2016', 'Windows(2016)'), ('Other', 'Other')], default='Linux', max_length=128, verbose_name='Platform'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='protocol',
|
||||
field=models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)')], default='ssh', max_length=16, verbose_name='Protocol'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username'),
|
||||
),
|
||||
migrations.RunPython(migrate_win_to_ssh_protocol),
|
||||
]
|
||||
@@ -1,84 +0,0 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-07 03:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0017_auto_20180702_1415'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='adminuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='gateway',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='label',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='node',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='systemuser',
|
||||
name='org_id',
|
||||
field=models.CharField(blank=True, default=None, max_length=36, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=128, verbose_name='Hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='adminuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='asset',
|
||||
unique_together={('org_id', 'hostname')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='gateway',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='systemuser',
|
||||
unique_together={('name', 'org_id')},
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 2.0.7 on 2018-08-16 05:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0018_auto_20180807_1116'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='cpu_vcpus',
|
||||
field=models.IntegerField(null=True, verbose_name='CPU vcpus'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='label',
|
||||
unique_together={('name', 'value', 'org_id')},
|
||||
),
|
||||
]
|
||||
21
apps/assets/migrations/0025_auto_20190221_1902.py
Normal file
21
apps/assets/migrations/0025_auto_20190221_1902.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 2.1.7 on 2019-02-21 11:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0024_auto_20181219_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilter',
|
||||
options={'verbose_name': 'Command filter'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='commandfilterrule',
|
||||
options={'ordering': ('-priority', 'action'), 'verbose_name': 'Command filter rule'},
|
||||
),
|
||||
]
|
||||
43
apps/assets/migrations/0026_auto_20190325_2035.py
Normal file
43
apps/assets/migrations/0026_auto_20190325_2035.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 2.1.7 on 2019-03-25 12:35
|
||||
|
||||
import assets.models.utils
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0025_auto_20190221_1902'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuthBook',
|
||||
fields=[
|
||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('username', models.CharField(blank=True, max_length=32, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z_@\\-\\.]*$', 'Special char not allowed')], verbose_name='Username')),
|
||||
('_password', models.CharField(blank=True, max_length=256, null=True, verbose_name='Password')),
|
||||
('_private_key', models.TextField(blank=True, max_length=4096, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key')),
|
||||
('_public_key', models.TextField(blank=True, max_length=4096, verbose_name='SSH public key')),
|
||||
('comment', models.TextField(blank=True, verbose_name='Comment')),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_updated', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.CharField(max_length=128, null=True, verbose_name='Created by')),
|
||||
('is_latest', models.BooleanField(default=False, verbose_name='Latest version')),
|
||||
('version', models.IntegerField(default=1, verbose_name='Version')),
|
||||
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.Asset', verbose_name='Asset')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'AuthBook',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='node',
|
||||
options={'ordering': ['key'], 'verbose_name': 'Node'},
|
||||
),
|
||||
]
|
||||
23
apps/assets/migrations/0027_auto_20190521_1703.py
Normal file
23
apps/assets/migrations/0027_auto_20190521_1703.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-21 09:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0026_auto_20190325_2035'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='ip',
|
||||
field=models.CharField(db_index=True, max_length=128, verbose_name='IP'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='public_ip',
|
||||
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Public IP'),
|
||||
),
|
||||
]
|
||||
29
apps/assets/migrations/0028_protocol.py
Normal file
29
apps/assets/migrations/0028_protocol.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-22 02:58
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0027_auto_20190521_1703'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Protocol',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(choices=[('ssh', 'ssh'), ('rdp', 'rdp'), ('telnet', 'telnet (beta)'), ('vnc', 'vnc')], default='ssh', max_length=16, verbose_name='Name')),
|
||||
('port', models.IntegerField(default=22, validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(1)], verbose_name='Port')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=models.ManyToManyField(to='assets.Protocol',
|
||||
verbose_name='Protocol'),
|
||||
),
|
||||
]
|
||||
13
apps/assets/migrations/0029_auto_20190522_1114.py
Normal file
13
apps/assets/migrations/0029_auto_20190522_1114.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Generated by Django 2.1.7 on 2019-05-22 03:14
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0028_protocol'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
19
apps/assets/migrations/0030_auto_20190619_1135.py
Normal file
19
apps/assets/migrations/0030_auto_20190619_1135.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-19 03:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0029_auto_20190522_1114'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='asset',
|
||||
name='admin_user',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assets', to='assets.AdminUser', verbose_name='Admin user'),
|
||||
),
|
||||
]
|
||||
53
apps/assets/migrations/0031_auto_20190621_1332.py
Normal file
53
apps/assets/migrations/0031_auto_20190621_1332.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-21 05:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0030_auto_20190619_1135'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Date created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='date_updated',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='Date updated'),
|
||||
),
|
||||
]
|
||||
75
apps/assets/migrations/0032_auto_20190624_2108.py
Normal file
75
apps/assets/migrations/0032_auto_20190624_2108.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
import assets.models.utils
|
||||
import common.fields.model
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0031_auto_20190621_1332'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_password',
|
||||
field=common.fields.model.EncryptCharField(blank=True, max_length=256, null=True, verbose_name='Password'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, validators=[assets.models.utils.private_key_validator], verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='_public_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH public key'),
|
||||
),
|
||||
]
|
||||
73
apps/assets/migrations/0033_auto_20190624_2108.py
Normal file
73
apps/assets/migrations/0033_auto_20190624_2108.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Generated by Django 2.1.7 on 2019-06-24 13:08
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0032_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_private_key',
|
||||
new_name='private_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_public_key',
|
||||
new_name='public_key',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='adminuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='authbook',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='gateway',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='systemuser',
|
||||
old_name='_password',
|
||||
new_name='password',
|
||||
),
|
||||
]
|
||||
39
apps/assets/migrations/0034_auto_20190705_1348.py
Normal file
39
apps/assets/migrations/0034_auto_20190705_1348.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-05 05:48
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
from django.db.models import CharField, Value as V
|
||||
from django.db.models.functions import Concat
|
||||
|
||||
|
||||
def migrate_assets_protocol(apps, schema_editor):
|
||||
asset_model = apps.get_model("assets", "Asset")
|
||||
db_alias = schema_editor.connection.alias
|
||||
assets = asset_model.objects.using(db_alias).all().annotate(
|
||||
protocols_new=Concat(
|
||||
'protocol', V('/'), 'port',
|
||||
output_field=CharField(),
|
||||
),
|
||||
)
|
||||
assets.update(protocols=F('protocols_new'))
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0033_auto_20190624_2108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asset',
|
||||
name='protocols',
|
||||
field=CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
|
||||
),
|
||||
migrations.RunPython(migrate_assets_protocol),
|
||||
migrations.DeleteModel(name='Protocol'),
|
||||
]
|
||||
34
apps/assets/migrations/0035_auto_20190711_2018.py
Normal file
34
apps/assets/migrations/0035_auto_20190711_2018.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-11 12:18
|
||||
|
||||
import common.fields.model
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0034_auto_20190705_1348'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='adminuser',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='authbook',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='gateway',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='systemuser',
|
||||
name='private_key',
|
||||
field=common.fields.model.EncryptTextField(blank=True, null=True, verbose_name='SSH private key'),
|
||||
),
|
||||
]
|
||||
18
apps/assets/migrations/0036_auto_20190716_1535.py
Normal file
18
apps/assets/migrations/0036_auto_20190716_1535.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.7 on 2019-07-16 07:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0035_auto_20190711_2018'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='commandfilter',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
||||
@@ -1,9 +1,11 @@
|
||||
from .user import *
|
||||
from .asset import *
|
||||
from .label import Label
|
||||
from .user import *
|
||||
from .cluster import *
|
||||
from .group import *
|
||||
from .domain import *
|
||||
from .node import *
|
||||
from .asset import *
|
||||
from .cmd_filter import *
|
||||
from .authbook import *
|
||||
from .utils import *
|
||||
from .authbook import *
|
||||
|
||||
@@ -6,17 +6,16 @@ import uuid
|
||||
import logging
|
||||
import random
|
||||
from functools import reduce
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from collections import OrderedDict, defaultdict
|
||||
from django.core.cache import cache
|
||||
|
||||
from .user import AdminUser, SystemUser
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .utils import Connectivity
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
|
||||
__all__ = ['Asset']
|
||||
__all__ = ['Asset', 'ProtocolsMixin']
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -46,19 +45,12 @@ class AssetQuerySet(models.QuerySet):
|
||||
def valid(self):
|
||||
return self.active()
|
||||
|
||||
def has_protocol(self, name):
|
||||
return self.filter(protocols__contains=name)
|
||||
|
||||
class Asset(OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
class ProtocolsMixin:
|
||||
protocols = ''
|
||||
PROTOCOL_SSH = 'ssh'
|
||||
PROTOCOL_RDP = 'rdp'
|
||||
PROTOCOL_TELNET = 'telnet'
|
||||
@@ -70,21 +62,117 @@ class Asset(OrgModelMixin):
|
||||
(PROTOCOL_VNC, 'vnc'),
|
||||
)
|
||||
|
||||
@property
|
||||
def protocols_as_list(self):
|
||||
if not self.protocols:
|
||||
return []
|
||||
return self.protocols.split(' ')
|
||||
|
||||
@property
|
||||
def protocols_as_dict(self):
|
||||
d = OrderedDict()
|
||||
protocols = self.protocols_as_list
|
||||
for i in protocols:
|
||||
if '/' not in i:
|
||||
continue
|
||||
name, port = i.split('/')[:2]
|
||||
if not all([name, port]):
|
||||
continue
|
||||
d[name] = int(port)
|
||||
return d
|
||||
|
||||
@property
|
||||
def protocols_as_json(self):
|
||||
return [
|
||||
{"name": name, "port": port}
|
||||
for name, port in self.protocols_as_dict.items()
|
||||
]
|
||||
|
||||
def has_protocol(self, name):
|
||||
return name in self.protocols_as_dict
|
||||
|
||||
@property
|
||||
def ssh_port(self):
|
||||
return self.protocols_as_dict.get("ssh", 22)
|
||||
|
||||
|
||||
class NodesRelationMixin:
|
||||
NODES_CACHE_KEY = 'ASSET_NODES_{}'
|
||||
ALL_ASSET_NODES_CACHE_KEY = 'ALL_ASSETS_NODES'
|
||||
CACHE_TIME = 3600 * 24 * 7
|
||||
id = ""
|
||||
_all_nodes_keys = None
|
||||
|
||||
@classmethod
|
||||
def get_all_nodes_keys(cls):
|
||||
"""
|
||||
:return: {asset.id: [node.key, ]}
|
||||
"""
|
||||
from .node import Node
|
||||
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
assets = Asset.objects.all().only('id').prefetch_related(
|
||||
models.Prefetch('nodes', queryset=Node.objects.all().only('key'))
|
||||
)
|
||||
assets_nodes_keys = {}
|
||||
for asset in assets:
|
||||
assets_nodes_keys[asset.id] = [n.key for n in asset.nodes.all()]
|
||||
cache.set(cache_key, assets_nodes_keys, cls.CACHE_TIME)
|
||||
return assets_nodes_keys
|
||||
|
||||
@classmethod
|
||||
def expire_all_nodes_keys_cache(cls):
|
||||
cache_key = cls.ALL_ASSET_NODES_CACHE_KEY
|
||||
cache.delete(cache_key)
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
nodes = self.nodes.all() or [Node.root()]
|
||||
return nodes
|
||||
|
||||
def get_all_nodes(self, flat=False):
|
||||
nodes = []
|
||||
for node in self.get_nodes():
|
||||
_nodes = node.get_ancestor(with_self=True)
|
||||
nodes.append(_nodes)
|
||||
if flat:
|
||||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
|
||||
|
||||
class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
||||
# Important
|
||||
PLATFORM_CHOICES = (
|
||||
('Linux', 'Linux'),
|
||||
('Unix', 'Unix'),
|
||||
('MacOS', 'MacOS'),
|
||||
('BSD', 'BSD'),
|
||||
('Windows', 'Windows'),
|
||||
('Windows2016', 'Windows(2016)'),
|
||||
('Other', 'Other'),
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
ip = models.GenericIPAddressField(max_length=32, verbose_name=_('IP'), db_index=True)
|
||||
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
|
||||
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
|
||||
protocol = models.CharField(max_length=128, default=PROTOCOL_SSH, choices=PROTOCOL_CHOICES, verbose_name=_('Protocol'))
|
||||
protocol = models.CharField(max_length=128, default=ProtocolsMixin.PROTOCOL_SSH,
|
||||
choices=ProtocolsMixin.PROTOCOL_CHOICES,
|
||||
verbose_name=_('Protocol'))
|
||||
port = models.IntegerField(default=22, verbose_name=_('Port'))
|
||||
|
||||
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
|
||||
platform = models.CharField(max_length=128, choices=PLATFORM_CHOICES, default='Linux', verbose_name=_('Platform'))
|
||||
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
|
||||
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
|
||||
# Auth
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"))
|
||||
admin_user = models.ForeignKey('assets.AdminUser', on_delete=models.PROTECT, null=True, verbose_name=_("Admin user"), related_name='assets')
|
||||
|
||||
# Some information
|
||||
public_ip = models.GenericIPAddressField(max_length=32, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
|
||||
number = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Asset number'))
|
||||
|
||||
# Collect
|
||||
@@ -111,13 +199,7 @@ class Asset(OrgModelMixin):
|
||||
comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment'))
|
||||
|
||||
objects = OrgManager.from_queryset(AssetQuerySet)()
|
||||
CONNECTIVITY_CACHE_KEY = '_JMS_ASSET_CONNECTIVITY_{}'
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
_connectivity = None
|
||||
|
||||
def __str__(self):
|
||||
return '{0.hostname}({0.ip})'.format(self)
|
||||
@@ -127,50 +209,33 @@ class Asset(OrgModelMixin):
|
||||
warning = ''
|
||||
if not self.is_active:
|
||||
warning += ' inactive'
|
||||
else:
|
||||
return True, ''
|
||||
return False, warning
|
||||
if warning:
|
||||
return False, warning
|
||||
return True, warning
|
||||
|
||||
def support_ansible(self):
|
||||
if self.platform in ("Windows", "Windows2016", "Other"):
|
||||
return False
|
||||
if self.protocol != 'ssh':
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Windows2016"):
|
||||
def is_windows(self):
|
||||
if self.platform in ("Windows", "Windows2016"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_nodes(self):
|
||||
from .node import Node
|
||||
nodes = self.nodes.all() or [Node.root()]
|
||||
return nodes
|
||||
def is_unixlike(self):
|
||||
if self.platform not in ("Windows", "Windows2016", "Other"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_all_nodes(self, flat=False):
|
||||
nodes = []
|
||||
for node in self.get_nodes():
|
||||
_nodes = node.get_ancestor(with_self=True)
|
||||
_nodes.append(_nodes)
|
||||
if flat:
|
||||
nodes = list(reduce(lambda x, y: set(x) | set(y), nodes))
|
||||
return nodes
|
||||
def is_support_ansible(self):
|
||||
return self.has_protocol('ssh') and self.platform not in ("Other",)
|
||||
|
||||
@classmethod
|
||||
def get_queryset_by_fullname_list(cls, fullname_list):
|
||||
org_fullname_map = defaultdict(list)
|
||||
for fullname in fullname_list:
|
||||
hostname, org = cls.split_fullname(fullname)
|
||||
org_fullname_map[org].append(hostname)
|
||||
filter_arg = Q()
|
||||
for org, hosts in org_fullname_map.items():
|
||||
if org.is_real():
|
||||
filter_arg |= Q(hostname__in=hosts, org_id=org.id)
|
||||
else:
|
||||
filter_arg |= Q(Q(org_id__isnull=True) | Q(org_id=''), hostname__in=hosts)
|
||||
return Asset.objects.filter(filter_arg)
|
||||
@property
|
||||
def cpu_info(self):
|
||||
info = ""
|
||||
if self.cpu_model:
|
||||
info += self.cpu_model
|
||||
if self.cpu_count and self.cpu_cores:
|
||||
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
|
||||
return info
|
||||
|
||||
@property
|
||||
def hardware_info(self):
|
||||
@@ -184,25 +249,30 @@ class Asset(OrgModelMixin):
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
if not self.is_unixlike():
|
||||
return self.REACHABLE
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cached = cache.get(key, None)
|
||||
return cached if cached is not None else self.UNKNOWN
|
||||
if self._connectivity:
|
||||
return self._connectivity
|
||||
if not self.admin_user:
|
||||
return Connectivity.unknown()
|
||||
connectivity = self.admin_user.get_asset_connectivity(self)
|
||||
return connectivity
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
key = self.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
cache.set(key, value, 3600*2)
|
||||
if not self.admin_user:
|
||||
return
|
||||
self.admin_user.set_asset_connectivity(self, value)
|
||||
|
||||
def get_auth_info(self):
|
||||
if self.admin_user:
|
||||
return {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
'become': self.admin_user.become_info,
|
||||
}
|
||||
if not self.admin_user:
|
||||
return {}
|
||||
|
||||
self.admin_user.load_specific_asset_auth(self)
|
||||
info = {
|
||||
'username': self.admin_user.username,
|
||||
'password': self.admin_user.password,
|
||||
'private_key': self.admin_user.private_key_file,
|
||||
}
|
||||
return info
|
||||
|
||||
def as_node(self):
|
||||
from .node import Node
|
||||
@@ -214,34 +284,6 @@ class Asset(OrgModelMixin):
|
||||
fake_node.is_node = False
|
||||
return fake_node
|
||||
|
||||
def to_json(self):
|
||||
info = {
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
}
|
||||
if self.domain and self.domain.gateway_set.all():
|
||||
info["gateways"] = [d.id for d in self.domain.gateway_set.all()]
|
||||
return info
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""
|
||||
Ansible use it create inventory
|
||||
Todo: May be move to ops implements it
|
||||
"""
|
||||
data = self.to_json()
|
||||
if self.admin_user:
|
||||
admin_user = self.admin_user
|
||||
data.update({
|
||||
'username': admin_user.username,
|
||||
'password': admin_user.password,
|
||||
'private_key': admin_user.private_key_file,
|
||||
'become': admin_user.become_info,
|
||||
'groups': [node.value for node in self.nodes.all()],
|
||||
})
|
||||
return data
|
||||
|
||||
def as_tree_node(self, parent_node):
|
||||
from common.tree import TreeNode
|
||||
icon_skin = 'file'
|
||||
@@ -263,9 +305,8 @@ class Asset(OrgModelMixin):
|
||||
'id': self.id,
|
||||
'hostname': self.hostname,
|
||||
'ip': self.ip,
|
||||
'port': self.port,
|
||||
'protocols': self.protocols_as_list,
|
||||
'platform': self.platform,
|
||||
'protocol': self.protocol,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,21 +319,27 @@ class Asset(OrgModelMixin):
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
from .user import AdminUser, SystemUser
|
||||
from random import seed, choice
|
||||
import forgery_py
|
||||
from django.db import IntegrityError
|
||||
from .node import Node
|
||||
from orgs.utils import get_current_org
|
||||
from orgs.models import Organization
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
|
||||
nodes = list(Node.objects.all())
|
||||
seed()
|
||||
for i in range(count):
|
||||
ip = [str(i) for i in random.sample(range(255), 4)]
|
||||
asset = cls(ip='.'.join(ip),
|
||||
hostname=forgery_py.internet.user_name(True),
|
||||
hostname='.'.join(ip),
|
||||
admin_user=choice(AdminUser.objects.all()),
|
||||
port=22,
|
||||
created_by='Fake')
|
||||
try:
|
||||
asset.save()
|
||||
asset.protocols = 'ssh/22'
|
||||
if nodes and len(nodes) > 3:
|
||||
_nodes = random.sample(nodes, 3)
|
||||
else:
|
||||
|
||||
90
apps/assets/models/authbook.py
Normal file
90
apps/assets/models/authbook.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from orgs.mixins import OrgManager
|
||||
from .base import AssetUser
|
||||
|
||||
__all__ = ['AuthBook']
|
||||
|
||||
|
||||
class AuthBookQuerySet(models.QuerySet):
|
||||
|
||||
def latest_version(self):
|
||||
return self.filter(is_latest=True)
|
||||
|
||||
|
||||
class AuthBookManager(OrgManager):
|
||||
pass
|
||||
|
||||
|
||||
class AuthBook(AssetUser):
|
||||
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset'))
|
||||
is_latest = models.BooleanField(default=False, verbose_name=_('Latest version'))
|
||||
version = models.IntegerField(default=1, verbose_name=_('Version'))
|
||||
|
||||
objects = AuthBookManager.from_queryset(AuthBookQuerySet)()
|
||||
backend = "db"
|
||||
# 用于system user和admin_user的动态设置
|
||||
_connectivity = None
|
||||
CONN_CACHE_KEY = "ASSET_USER_CONN_{}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('AuthBook')
|
||||
|
||||
def _set_latest(self):
|
||||
self._remove_pre_obj_latest()
|
||||
self.is_latest = True
|
||||
self.save()
|
||||
|
||||
def _get_pre_obj(self):
|
||||
pre_obj = self.__class__.objects.filter(
|
||||
username=self.username, asset=self.asset
|
||||
).latest_version().first()
|
||||
return pre_obj
|
||||
|
||||
def _remove_pre_obj_latest(self):
|
||||
pre_obj = self._get_pre_obj()
|
||||
if pre_obj:
|
||||
pre_obj.is_latest = False
|
||||
pre_obj.save()
|
||||
|
||||
def _set_version(self):
|
||||
pre_obj = self._get_pre_obj()
|
||||
if pre_obj:
|
||||
self.version = pre_obj.version + 1
|
||||
else:
|
||||
self.version = 1
|
||||
self.save()
|
||||
|
||||
def set_version_and_latest(self):
|
||||
self._set_version()
|
||||
self._set_latest()
|
||||
|
||||
def get_related_assets(self):
|
||||
return [self.asset]
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
return self.get_asset_connectivity(self.asset)
|
||||
|
||||
@property
|
||||
def keyword(self):
|
||||
return '{}_#_{}'.format(self.username, str(self.asset.id))
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.asset.hostname
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.asset.ip
|
||||
|
||||
def __str__(self):
|
||||
return '{}@{}'.format(self.username, self.asset)
|
||||
|
||||
@@ -5,64 +5,47 @@ import uuid
|
||||
from hashlib import md5
|
||||
|
||||
import sshpubkeys
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from common.utils import get_signer, ssh_key_string_to_obj, ssh_key_gen
|
||||
from common.utils import (
|
||||
get_signer, ssh_key_string_to_obj, ssh_key_gen, get_logger
|
||||
)
|
||||
from common.validators import alphanumeric
|
||||
from common import fields
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from .utils import private_key_validator
|
||||
from .utils import private_key_validator, Connectivity
|
||||
|
||||
signer = get_signer()
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class AssetUser(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric])
|
||||
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
|
||||
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
|
||||
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
|
||||
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
|
||||
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_updated = models.DateTimeField(auto_now=True)
|
||||
date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
|
||||
date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
|
||||
created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))
|
||||
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
|
||||
CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
|
||||
ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
|
||||
ASSET_USER_CACHE_TIME = 3600 * 24
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
if self._password:
|
||||
return signer.unsign(self._password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@password.setter
|
||||
def password(self, password_raw):
|
||||
raise AttributeError("Using set_auth do that")
|
||||
# self._password = signer.sign(password_raw)
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
if self._private_key:
|
||||
return signer.unsign(self._private_key)
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key_raw):
|
||||
raise AttributeError("Using set_auth do that")
|
||||
# self._private_key = signer.sign(private_key_raw)
|
||||
_prefer = "system_user"
|
||||
|
||||
@property
|
||||
def private_key_obj(self):
|
||||
if self._private_key:
|
||||
key_str = signer.unsign(self._private_key)
|
||||
return ssh_key_string_to_obj(key_str, password=self.password)
|
||||
if self.private_key:
|
||||
return ssh_key_string_to_obj(self.private_key, password=self.password)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -72,22 +55,13 @@ class AssetUser(OrgModelMixin):
|
||||
return None
|
||||
project_dir = settings.PROJECT_DIR
|
||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||
key_str = signer.unsign(self._private_key)
|
||||
key_name = '.' + md5(key_str.encode('utf-8')).hexdigest()
|
||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||
key_path = os.path.join(tmp_dir, key_name)
|
||||
if not os.path.exists(key_path):
|
||||
self.private_key_obj.write_private_key_file(key_path)
|
||||
os.chmod(key_path, 0o400)
|
||||
return key_path
|
||||
|
||||
@property
|
||||
def public_key(self):
|
||||
key = signer.unsign(self._public_key)
|
||||
if key:
|
||||
return key
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def public_key_obj(self):
|
||||
if self.public_key:
|
||||
@@ -97,38 +71,153 @@ class AssetUser(OrgModelMixin):
|
||||
pass
|
||||
return None
|
||||
|
||||
@property
|
||||
def part_id(self):
|
||||
i = '-'.join(str(self.id).split('-')[:3])
|
||||
return i
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.assets.all()
|
||||
return assets
|
||||
|
||||
def set_auth(self, password=None, private_key=None, public_key=None):
|
||||
update_fields = []
|
||||
if password:
|
||||
self._password = signer.sign(password)
|
||||
update_fields.append('_password')
|
||||
self.password = password
|
||||
update_fields.append('password')
|
||||
if private_key:
|
||||
self._private_key = signer.sign(private_key)
|
||||
update_fields.append('_private_key')
|
||||
self.private_key = private_key
|
||||
update_fields.append('private_key')
|
||||
if public_key:
|
||||
self._public_key = signer.sign(public_key)
|
||||
update_fields.append('_public_key')
|
||||
self.public_key = public_key
|
||||
update_fields.append('public_key')
|
||||
|
||||
if update_fields:
|
||||
self.save(update_fields=update_fields)
|
||||
|
||||
def get_auth(self, asset=None):
|
||||
pass
|
||||
def set_connectivity(self, summary):
|
||||
unreachable = summary.get('dark', {}).keys()
|
||||
reachable = summary.get('contacted', {}).keys()
|
||||
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
for asset in assets:
|
||||
if asset.hostname in unreachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.unreachable())
|
||||
elif asset.hostname in reachable:
|
||||
self.set_asset_connectivity(asset, Connectivity.reachable())
|
||||
else:
|
||||
self.set_asset_connectivity(asset, Connectivity.unknown())
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
assets = self.get_related_assets()
|
||||
if not isinstance(assets, list):
|
||||
assets = assets.only('id', 'hostname', 'admin_user__id')
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
'unknown': [],
|
||||
}
|
||||
for asset in assets:
|
||||
connectivity = self.get_asset_connectivity(asset)
|
||||
if connectivity.is_reachable():
|
||||
data["reachable"].append(asset.hostname)
|
||||
elif connectivity.is_unreachable():
|
||||
data["unreachable"].append(asset.hostname)
|
||||
else:
|
||||
data["unknown"].append(asset.hostname)
|
||||
return data
|
||||
|
||||
@property
|
||||
def connectivity_amount(self):
|
||||
cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
|
||||
amount = cache.get(cache_key)
|
||||
if not amount:
|
||||
amount = {k: len(v) for k, v in self.connectivity.items()}
|
||||
cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
|
||||
return amount
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cached = cache.get(cache_key)
|
||||
if not cached:
|
||||
cached = self.get_related_assets().count()
|
||||
cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
|
||||
return cached
|
||||
|
||||
def expire_assets_amount(self):
|
||||
cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
|
||||
cache.delete(cache_key)
|
||||
|
||||
def get_asset_connectivity(self, asset):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
return Connectivity.get(key)
|
||||
|
||||
def get_asset_connectivity_key(self, asset):
|
||||
return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)
|
||||
|
||||
def set_asset_connectivity(self, asset, c):
|
||||
key = self.get_asset_connectivity_key(asset)
|
||||
Connectivity.set(key, c)
|
||||
|
||||
def get_asset_user(self, asset):
|
||||
from ..backends import AssetUserManager
|
||||
try:
|
||||
manager = AssetUserManager().prefer(self._prefer)
|
||||
other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
|
||||
return other
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
||||
def load_specific_asset_auth(self, asset):
|
||||
instance = self.get_asset_user(asset)
|
||||
if instance:
|
||||
self._merge_auth(instance)
|
||||
|
||||
def _merge_auth(self, other):
|
||||
if other.password:
|
||||
self.password = other.password
|
||||
if other.public_key:
|
||||
self.public_key = other.public_key
|
||||
if other.private_key:
|
||||
self.private_key = other.private_key
|
||||
|
||||
def clear_auth(self):
|
||||
self._password = ''
|
||||
self._private_key = ''
|
||||
self._public_key = ''
|
||||
self.password = ''
|
||||
self.private_key = ''
|
||||
self.public_key = ''
|
||||
self.save()
|
||||
|
||||
@staticmethod
|
||||
def gen_password():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@staticmethod
|
||||
def gen_key(username):
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=username
|
||||
)
|
||||
return private_key, public_key
|
||||
|
||||
def auto_gen_auth(self):
|
||||
password = str(uuid.uuid4())
|
||||
private_key, public_key = ssh_key_gen(
|
||||
username=self.username
|
||||
)
|
||||
self.set_auth(password=password,
|
||||
private_key=private_key,
|
||||
public_key=public_key)
|
||||
self.set_auth(
|
||||
password=password, private_key=private_key,
|
||||
public_key=public_key
|
||||
)
|
||||
|
||||
def auto_gen_auth_password(self):
|
||||
password = str(uuid.uuid4())
|
||||
self.set_auth(password=password)
|
||||
|
||||
def _to_secret_json(self):
|
||||
"""Push system user use it"""
|
||||
@@ -140,5 +229,26 @@ class AssetUser(OrgModelMixin):
|
||||
'private_key': self.private_key_file,
|
||||
}
|
||||
|
||||
def generate_id_with_asset(self, asset):
|
||||
user_id = [self.part_id]
|
||||
asset_id = str(asset.id).split('-')[3:]
|
||||
ids = user_id + asset_id
|
||||
return '-'.join(ids)
|
||||
|
||||
def construct_to_authbook(self, asset):
|
||||
from . import AuthBook
|
||||
fields = [
|
||||
'name', 'username', 'comment', 'org_id',
|
||||
'password', 'private_key', 'public_key',
|
||||
'date_created', 'date_updated', 'created_by'
|
||||
]
|
||||
i = self.generate_id_with_asset(asset)
|
||||
obj = AuthBook(id=i, asset=asset, version=0, is_latest=True)
|
||||
for field in fields:
|
||||
value = getattr(self, field)
|
||||
setattr(obj, field, value)
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ __all__ = [
|
||||
|
||||
class CommandFilter(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=64, verbose_name=_("Name"))
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_("Name"))
|
||||
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
|
||||
comment = models.TextField(blank=True, default='', verbose_name=_("Comment"))
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
@@ -27,6 +27,9 @@ class CommandFilter(OrgModelMixin):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Command filter")
|
||||
|
||||
|
||||
class CommandFilterRule(OrgModelMixin):
|
||||
TYPE_REGEX = 'regex'
|
||||
@@ -58,6 +61,7 @@ class CommandFilterRule(OrgModelMixin):
|
||||
|
||||
class Meta:
|
||||
ordering = ('-priority', 'action')
|
||||
verbose_name = _("Command filter rule")
|
||||
|
||||
@property
|
||||
def _pattern(self):
|
||||
|
||||
@@ -60,7 +60,9 @@ class Gateway(AssetUser):
|
||||
unique_together = [('name', 'org_id')]
|
||||
verbose_name = _("Gateway")
|
||||
|
||||
def test_connective(self):
|
||||
def test_connective(self, local_port=None):
|
||||
if local_port is None:
|
||||
local_port = self.port
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
proxy = paramiko.SSHClient()
|
||||
@@ -73,15 +75,15 @@ class Gateway(AssetUser):
|
||||
pkey=self.private_key_obj)
|
||||
except(paramiko.AuthenticationException,
|
||||
paramiko.BadAuthenticationType,
|
||||
paramiko.SSHException) as e:
|
||||
paramiko.SSHException,
|
||||
paramiko.ssh_exception.NoValidConnectionsError) as e:
|
||||
return False, str(e)
|
||||
|
||||
sock = proxy.get_transport().open_channel(
|
||||
'direct-tcpip', ('127.0.0.1', self.port), ('127.0.0.1', 0)
|
||||
)
|
||||
|
||||
try:
|
||||
client.connect("127.0.0.1", port=self.port,
|
||||
sock = proxy.get_transport().open_channel(
|
||||
'direct-tcpip', ('127.0.0.1', local_port), ('127.0.0.1', 0)
|
||||
)
|
||||
client.connect("127.0.0.1", port=local_port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
key_filename=self.private_key_file,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
@@ -8,49 +9,195 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext
|
||||
from django.core.cache import cache
|
||||
|
||||
from orgs.mixins import OrgModelMixin
|
||||
from orgs.mixins import OrgModelMixin, OrgManager
|
||||
from orgs.utils import set_current_org, get_current_org
|
||||
from orgs.models import Organization
|
||||
|
||||
__all__ = ['Node']
|
||||
|
||||
|
||||
class Node(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
class NodeQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
raise PermissionError("Bulk delete node deny")
|
||||
|
||||
|
||||
class FamilyMixin:
|
||||
_parents = None
|
||||
_children = None
|
||||
_all_children = None
|
||||
is_node = True
|
||||
_assets_amount = None
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.key == other.key
|
||||
|
||||
def __gt__(self, other):
|
||||
if self.is_root():
|
||||
return True
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
return self_key.__lt__(other_key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
def children(self):
|
||||
if self._children:
|
||||
return self._children
|
||||
pattern = r'^{0}:[0-9]+$'.format(self.key)
|
||||
return Node.objects.filter(key__regex=pattern)
|
||||
|
||||
@children.setter
|
||||
def children(self, value):
|
||||
self._children = value
|
||||
|
||||
@property
|
||||
def all_children(self):
|
||||
if self._all_children:
|
||||
return self._all_children
|
||||
pattern = r'^{0}:'.format(self.key)
|
||||
return Node.objects.filter(
|
||||
key__regex=pattern
|
||||
)
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
children = list(self.children)
|
||||
if with_self:
|
||||
children.append(self)
|
||||
return children
|
||||
|
||||
def get_all_children(self, with_self=False):
|
||||
children = self.all_children
|
||||
if with_self:
|
||||
children = list(children)
|
||||
children.append(self)
|
||||
return children
|
||||
|
||||
@property
|
||||
def parents(self):
|
||||
if self._parents:
|
||||
return self._parents
|
||||
ancestor_keys = self.get_ancestor_keys()
|
||||
ancestor = Node.objects.filter(
|
||||
key__in=ancestor_keys
|
||||
).order_by('key')
|
||||
return ancestor
|
||||
|
||||
@parents.setter
|
||||
def parents(self, value):
|
||||
self._parents = value
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
parents = self.parents
|
||||
if with_self:
|
||||
parents = list(parents)
|
||||
parents.append(self)
|
||||
return parents
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self._parents:
|
||||
return self._parents[0]
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = Node.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return Node.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_sibling(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = Node.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
ancestor = self.get_ancestor()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
parent_keys.append(":".join(key_list))
|
||||
key_list.pop()
|
||||
return parent_keys
|
||||
|
||||
def is_children(self, other):
|
||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(self.key))
|
||||
return pattern.match(other.key)
|
||||
|
||||
def is_parent(self, other):
|
||||
pattern = re.compile(r'^{0}:[0-9]+$'.format(other.key))
|
||||
return pattern.match(self.key)
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parents_keys(self, with_self=False):
|
||||
keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
keys.append(':'.join(key_list))
|
||||
key_list.pop()
|
||||
return keys
|
||||
|
||||
|
||||
class FullValueMixin:
|
||||
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||
_full_value = ''
|
||||
key = ''
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
if self._full_value:
|
||||
return self._full_value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
if self.is_root():
|
||||
return self.value
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
self.full_value = value
|
||||
return value
|
||||
|
||||
@full_value.setter
|
||||
def full_value(self, value):
|
||||
self._full_value = value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.set(key, value, 3600*24)
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_full_value(cls, nodes=None):
|
||||
key = cls._full_value_cache_key.format('*')
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
|
||||
class AssetsAmountMixin:
|
||||
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||
_assets_amount = None
|
||||
key = ''
|
||||
cache_time = 3600 * 24 * 7
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
@@ -65,53 +212,82 @@ class Node(OrgModelMixin):
|
||||
if cached is not None:
|
||||
return cached
|
||||
assets_amount = self.get_all_assets().count()
|
||||
cache.set(cache_key, assets_amount, 3600)
|
||||
self.assets_amount = assets_amount
|
||||
return assets_amount
|
||||
|
||||
@assets_amount.setter
|
||||
def assets_amount(self, value):
|
||||
self._assets_amount = value
|
||||
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||
cache.set(cache_key, value, self.cache_time)
|
||||
|
||||
def expire_assets_amount(self):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
|
||||
cache_keys = [self._assets_amount_cache_key.format(k) for k in
|
||||
ancestor_keys]
|
||||
cache.delete_many(cache_keys)
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_assets_amount(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_assets_amount()
|
||||
return
|
||||
key = cls._assets_amount_cache_key.format('*')
|
||||
cache.delete_pattern(key)
|
||||
|
||||
@property
|
||||
def full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cached = cache.get(key)
|
||||
if cached:
|
||||
return cached
|
||||
if self.is_root():
|
||||
return self.value
|
||||
parent_full_value = self.parent.full_value
|
||||
value = parent_full_value + ' / ' + self.value
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.set(key, value, 3600)
|
||||
return value
|
||||
|
||||
def expire_full_value(self):
|
||||
key = self._full_value_cache_key.format(self.key)
|
||||
cache.delete_pattern(key+'*')
|
||||
|
||||
@classmethod
|
||||
def expire_nodes_full_value(cls, nodes=None):
|
||||
if nodes:
|
||||
for node in nodes:
|
||||
node.expire_full_value()
|
||||
return
|
||||
key = cls._full_value_cache_key.format('*')
|
||||
cache.delete_pattern(key+'*')
|
||||
def refresh_nodes(cls):
|
||||
from ..utils import NodeUtil
|
||||
util = NodeUtil(with_assets_amount=True)
|
||||
util.set_assets_amount()
|
||||
util.set_full_value()
|
||||
|
||||
|
||||
class Node(OrgModelMixin, FamilyMixin, FullValueMixin, AssetsAmountMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
|
||||
value = models.CharField(max_length=128, verbose_name=_("Value"))
|
||||
child_mark = models.IntegerField(default=0)
|
||||
date_create = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
objects = OrgManager.from_queryset(NodeQuerySet)()
|
||||
is_node = True
|
||||
_parents = None
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Node")
|
||||
ordering = ['key']
|
||||
|
||||
def __str__(self):
|
||||
return self.full_value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
return False
|
||||
return self.id == other.id
|
||||
|
||||
def __gt__(self, other):
|
||||
# if self.is_root() and not other.is_root():
|
||||
# return False
|
||||
# elif not self.is_root() and other.is_root():
|
||||
# return True
|
||||
self_key = [int(k) for k in self.key.split(':')]
|
||||
other_key = [int(k) for k in other.key.split(':')]
|
||||
self_parent_key = self_key[:-1]
|
||||
other_parent_key = other_key[:-1]
|
||||
|
||||
if self_parent_key and other_parent_key and \
|
||||
self_parent_key == other_parent_key:
|
||||
return self.value > other.value
|
||||
# if len(self_parent_key) < len(other_parent_key):
|
||||
# return True
|
||||
# elif len(self_parent_key) > len(other_parent_key):
|
||||
# return False
|
||||
return self_key > other_key
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def level(self):
|
||||
@@ -134,39 +310,12 @@ class Node(OrgModelMixin):
|
||||
count = max(values) + 1 if values else 1
|
||||
return '{} {}'.format(name, count)
|
||||
|
||||
def create_child(self, value):
|
||||
def create_child(self, value, _id=None):
|
||||
with transaction.atomic():
|
||||
child_key = self.get_next_child_key()
|
||||
child = self.__class__.objects.create(key=child_key, value=value)
|
||||
child = self.__class__.objects.create(id=_id, key=child_key, value=value)
|
||||
return child
|
||||
|
||||
def get_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:[0-9]+$' if with_self else r'^{0}:[0-9]+$'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_all_children(self, with_self=False):
|
||||
pattern = r'^{0}$|^{0}:' if with_self else r'^{0}'
|
||||
return self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
|
||||
def get_sibling(self, with_self=False):
|
||||
key = ':'.join(self.key.split(':')[:-1])
|
||||
pattern = r'^{}:[0-9]+$'.format(key)
|
||||
sibling = self.__class__.objects.filter(
|
||||
key__regex=pattern.format(self.key)
|
||||
)
|
||||
if not with_self:
|
||||
sibling = sibling.exclude(key=self.key)
|
||||
return sibling
|
||||
|
||||
def get_family(self):
|
||||
ancestor = self.get_ancestor()
|
||||
children = self.get_all_children()
|
||||
return [*tuple(ancestor), self, *tuple(children)]
|
||||
|
||||
def get_assets(self):
|
||||
from .asset import Asset
|
||||
if self.is_default_node():
|
||||
@@ -194,7 +343,7 @@ class Node(OrgModelMixin):
|
||||
return self.get_all_assets().valid()
|
||||
|
||||
def is_default_node(self):
|
||||
return self.is_root() and self.key == '0'
|
||||
return self.is_root() and self.key == '1'
|
||||
|
||||
def is_root(self):
|
||||
if self.key.isdigit():
|
||||
@@ -202,52 +351,6 @@ class Node(OrgModelMixin):
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def parent_key(self):
|
||||
parent_key = ":".join(self.key.split(":")[:-1])
|
||||
return parent_key
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if self.is_root():
|
||||
return self
|
||||
try:
|
||||
parent = self.__class__.objects.get(key=self.parent_key)
|
||||
return parent
|
||||
except Node.DoesNotExist:
|
||||
return self.__class__.root()
|
||||
|
||||
@parent.setter
|
||||
def parent(self, parent):
|
||||
if not self.is_node:
|
||||
self.key = parent.key + ':fake'
|
||||
return
|
||||
children = self.get_all_children()
|
||||
old_key = self.key
|
||||
with transaction.atomic():
|
||||
self.key = parent.get_next_child_key()
|
||||
for child in children:
|
||||
child.key = child.key.replace(old_key, self.key, 1)
|
||||
child.save()
|
||||
self.save()
|
||||
|
||||
def get_ancestor_keys(self, with_self=False):
|
||||
parent_keys = []
|
||||
key_list = self.key.split(":")
|
||||
if not with_self:
|
||||
key_list.pop()
|
||||
for i in range(len(key_list)):
|
||||
parent_keys.append(":".join(key_list))
|
||||
key_list.pop()
|
||||
return parent_keys
|
||||
|
||||
def get_ancestor(self, with_self=False):
|
||||
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
|
||||
ancestor = self.__class__.objects.filter(
|
||||
key__in=ancestor_keys
|
||||
).order_by('key')
|
||||
return ancestor
|
||||
|
||||
@classmethod
|
||||
def create_root_node(cls):
|
||||
# 如果使用current_org 在set_current_org时会死循环
|
||||
@@ -275,13 +378,12 @@ class Node(OrgModelMixin):
|
||||
@classmethod
|
||||
def default_node(cls):
|
||||
defaults = {'value': 'Default'}
|
||||
return cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
|
||||
return obj
|
||||
|
||||
def as_tree_node(self):
|
||||
from common.tree import TreeNode
|
||||
from ..serializers import NodeSerializer
|
||||
name = '{} ({})'.format(self.value, self.assets_amount)
|
||||
node_serializer = NodeSerializer(instance=self)
|
||||
data = {
|
||||
'id': self.key,
|
||||
'name': name,
|
||||
@@ -290,18 +392,37 @@ class Node(OrgModelMixin):
|
||||
'isParent': True,
|
||||
'open': self.is_root(),
|
||||
'meta': {
|
||||
'node': node_serializer.data,
|
||||
'node': {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"value": self.value,
|
||||
"key": self.key,
|
||||
"assets_amount": self.assets_amount,
|
||||
},
|
||||
'type': 'node'
|
||||
}
|
||||
}
|
||||
tree_node = TreeNode(**data)
|
||||
return tree_node
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.children or self.get_assets():
|
||||
return
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls):
|
||||
from ..utils import NodeUtil
|
||||
util = NodeUtil()
|
||||
return sorted(util.nodes)
|
||||
|
||||
@classmethod
|
||||
def generate_fake(cls, count=100):
|
||||
import random
|
||||
org = get_current_org()
|
||||
if not org or not org.is_real():
|
||||
Organization.default().change_to()
|
||||
|
||||
for i in range(count):
|
||||
node = random.choice(cls.objects.all())
|
||||
node.create_child('Node {}'.format(i))
|
||||
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.cache import cache
|
||||
from functools import reduce
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
|
||||
from common.utils import get_signer
|
||||
from ..const import SYSTEM_USER_CONN_CACHE_KEY
|
||||
from .base import AssetUser
|
||||
from .asset import Asset
|
||||
|
||||
|
||||
__all__ = ['AdminUser', 'SystemUser']
|
||||
@@ -31,7 +32,8 @@ class AdminUser(AssetUser):
|
||||
become_method = models.CharField(choices=BECOME_METHOD_CHOICES, default='sudo', max_length=4)
|
||||
become_user = models.CharField(default='root', max_length=64)
|
||||
_become_pass = models.CharField(default='', max_length=128)
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_ADMIN_USER_CONNECTIVE_{}'
|
||||
CONNECTIVITY_CACHE_KEY = '_ADMIN_USER_CONNECTIVE_{}'
|
||||
_prefer = "admin_user"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -60,31 +62,6 @@ class AdminUser(AssetUser):
|
||||
info = None
|
||||
return info
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = self.asset_set.all()
|
||||
return assets
|
||||
|
||||
@property
|
||||
def assets_amount(self):
|
||||
return self.get_related_assets().count()
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
from .asset import Asset
|
||||
assets = self.get_related_assets().values_list('id', 'hostname', flat=True)
|
||||
data = {
|
||||
'unreachable': [],
|
||||
'reachable': [],
|
||||
}
|
||||
for asset_id, hostname in assets:
|
||||
key = Asset.CONNECTIVITY_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(key, Asset.UNKNOWN)
|
||||
if value == Asset.REACHABLE:
|
||||
data['reachable'].append(hostname)
|
||||
elif value == Asset.UNREACHABLE:
|
||||
data['unreachable'].append(hostname)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
unique_together = [('name', 'org_id')]
|
||||
@@ -140,78 +117,19 @@ class SystemUser(AssetUser):
|
||||
login_mode = models.CharField(choices=LOGIN_MODE_CHOICES, default=LOGIN_AUTO, max_length=10, verbose_name=_('Login mode'))
|
||||
cmd_filters = models.ManyToManyField('CommandFilter', related_name='system_users', verbose_name=_("Command filter"), blank=True)
|
||||
|
||||
SYSTEM_USER_CACHE_KEY = "__SYSTEM_USER_CACHED_{}"
|
||||
CONNECTIVE_CACHE_KEY = '_JMS_SYSTEM_USER_CONNECTIVE_{}'
|
||||
|
||||
def __str__(self):
|
||||
return '{0.name}({0.username})'.format(self)
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'username': self.username,
|
||||
'protocol': self.protocol,
|
||||
'priority': self.priority,
|
||||
'auto_push': self.auto_push,
|
||||
}
|
||||
|
||||
def get_related_assets(self):
|
||||
assets = set(self.assets.all())
|
||||
return assets
|
||||
|
||||
@property
|
||||
def connectivity(self):
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
value = cache.get(cache_key, None)
|
||||
if not value or 'unreachable' not in value:
|
||||
return {'unreachable': [], 'reachable': []}
|
||||
else:
|
||||
return value
|
||||
|
||||
@connectivity.setter
|
||||
def connectivity(self, value):
|
||||
data = self.connectivity
|
||||
unreachable = data['unreachable']
|
||||
reachable = data['reachable']
|
||||
|
||||
for host in value.get('dark', {}).keys():
|
||||
if host not in unreachable:
|
||||
unreachable.append(host)
|
||||
if host in reachable:
|
||||
reachable.remove(host)
|
||||
for host in value.get('contacted'):
|
||||
if host not in reachable:
|
||||
reachable.append(host)
|
||||
if host in unreachable:
|
||||
unreachable.remove(host)
|
||||
cache_key = self.CONNECTIVE_CACHE_KEY.format(str(self.id))
|
||||
cache.set(cache_key, data, 3600)
|
||||
|
||||
@property
|
||||
def assets_unreachable(self):
|
||||
return self.connectivity.get('unreachable')
|
||||
|
||||
@property
|
||||
def assets_reachable(self):
|
||||
return self.connectivity.get('reachable')
|
||||
|
||||
@property
|
||||
def login_mode_display(self):
|
||||
return self.get_login_mode_display()
|
||||
|
||||
def is_need_push(self):
|
||||
if self.auto_push and self.protocol == self.PROTOCOL_SSH:
|
||||
if self.auto_push and self.protocol in [self.PROTOCOL_SSH, self.PROTOCOL_RDP]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_cache(self):
|
||||
cache.set(self.SYSTEM_USER_CACHE_KEY.format(self.id), self, 3600)
|
||||
|
||||
def expire_cache(self):
|
||||
cache.delete(self.SYSTEM_USER_CACHE_KEY.format(self.id))
|
||||
|
||||
@property
|
||||
def cmd_filter_rules(self):
|
||||
from .cmd_filter import CommandFilterRule
|
||||
@@ -229,17 +147,18 @@ class SystemUser(AssetUser):
|
||||
return False, matched_cmd
|
||||
return True, None
|
||||
|
||||
@classmethod
|
||||
def get_system_user_by_id_or_cached(cls, sid):
|
||||
cached = cache.get(cls.SYSTEM_USER_CACHE_KEY.format(sid))
|
||||
if cached:
|
||||
return cached
|
||||
try:
|
||||
system_user = cls.objects.get(id=sid)
|
||||
system_user.set_cache()
|
||||
return system_user
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
def get_all_assets(self):
|
||||
args = [Q(systemuser=self)]
|
||||
pattern = set()
|
||||
nodes_keys = self.nodes.all().values_list('key', flat=True)
|
||||
for key in nodes_keys:
|
||||
pattern.add(r'^{0}$|^{0}:'.format(key))
|
||||
pattern = '|'.join(list(pattern))
|
||||
if pattern:
|
||||
args.append(Q(nodes__key__regex=pattern))
|
||||
args = reduce(lambda x, y: x | y, args)
|
||||
assets = Asset.objects.filter(args).distinct()
|
||||
return assets
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils import timezone
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.utils import validate_ssh_private_key
|
||||
|
||||
|
||||
__all__ = ['init_model', 'generate_fake']
|
||||
__all__ = [
|
||||
'init_model', 'generate_fake', 'private_key_validator', 'Connectivity',
|
||||
]
|
||||
|
||||
|
||||
def init_model():
|
||||
@@ -31,5 +37,72 @@ def private_key_validator(value):
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
class Connectivity:
|
||||
UNREACHABLE, REACHABLE, UNKNOWN = range(0, 3)
|
||||
CONNECTIVITY_CHOICES = (
|
||||
(UNREACHABLE, _("Unreachable")),
|
||||
(REACHABLE, _('Reachable')),
|
||||
(UNKNOWN, _("Unknown")),
|
||||
)
|
||||
|
||||
status = UNKNOWN
|
||||
datetime = timezone.now()
|
||||
|
||||
def __init__(self, status, datetime):
|
||||
self.status = status
|
||||
self.datetime = datetime
|
||||
|
||||
def display(self):
|
||||
return dict(self.__class__.CONNECTIVITY_CHOICES).get(self.status)
|
||||
|
||||
def is_reachable(self):
|
||||
return self.status == self.REACHABLE
|
||||
|
||||
def is_unreachable(self):
|
||||
return self.status == self.UNREACHABLE
|
||||
|
||||
def is_unknown(self):
|
||||
return self.status == self.UNKNOWN
|
||||
|
||||
@classmethod
|
||||
def unreachable(cls):
|
||||
return cls(cls.UNREACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def reachable(cls):
|
||||
return cls(cls.REACHABLE, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def unknown(cls):
|
||||
return cls(cls.UNKNOWN, timezone.now())
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, value, ttl=0):
|
||||
cache.set(key, value, ttl)
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
value = cache.get(key, cls.unknown())
|
||||
if not isinstance(value, cls):
|
||||
value = cls.unknown()
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def set_unreachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.unreachable(), ttl)
|
||||
|
||||
@classmethod
|
||||
def set_reachable(cls, key, ttl=0):
|
||||
cls.set(key, cls.reachable(), ttl)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.status == other.status
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.status > other.status
|
||||
|
||||
def __lt__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __str__(self):
|
||||
return self.display()
|
||||
|
||||
@@ -8,3 +8,4 @@ from .system_user import *
|
||||
from .node import *
|
||||
from .domain import *
|
||||
from .cmd_filter import *
|
||||
from .asset_user import *
|
||||
|
||||
@@ -1,49 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
|
||||
from ..models import Node, AdminUser
|
||||
from ..const import ADMIN_USER_CONN_CACHE_KEY
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
|
||||
from .base import AuthSerializer
|
||||
from .base import AuthSerializer, AuthSerializerMixin
|
||||
|
||||
|
||||
class AdminUserSerializer(serializers.ModelSerializer):
|
||||
class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
"""
|
||||
管理用户
|
||||
"""
|
||||
assets_amount = serializers.SerializerMethodField()
|
||||
unreachable_amount = serializers.SerializerMethodField()
|
||||
reachable_amount = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
model = AdminUser
|
||||
fields = '__all__'
|
||||
fields = [
|
||||
'id', 'name', 'username', 'password', 'private_key', 'public_key',
|
||||
'comment', 'assets_amount', 'date_created', 'date_updated', 'created_by',
|
||||
]
|
||||
read_only_fields = ['date_created', 'date_updated', 'created_by', 'assets_amount']
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
return [f for f in fields if not f.startswith('_')]
|
||||
|
||||
@staticmethod
|
||||
def get_unreachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('dark'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_reachable_amount(obj):
|
||||
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
|
||||
if data:
|
||||
return len(data.get('contacted'))
|
||||
else:
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_assets_amount(obj):
|
||||
return obj.assets_amount
|
||||
extra_kwargs = {
|
||||
'password': {"write_only": True},
|
||||
'private_key': {"write_only": True},
|
||||
'public_key': {"write_only": True},
|
||||
'assets_amount': {'label': _('Asset')},
|
||||
}
|
||||
|
||||
|
||||
class AdminUserAuthSerializer(AuthSerializer):
|
||||
|
||||
@@ -1,86 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework import serializers
|
||||
from rest_framework_bulk.serializers import BulkListSerializer
|
||||
from django.db.models import Prefetch
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.mixins import BulkSerializerMixin
|
||||
from ..models import Asset
|
||||
from .system_user import AssetSystemUserSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from ..models import Asset, Node, Label
|
||||
from .base import ConnectivitySerializer
|
||||
|
||||
__all__ = [
|
||||
'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer',
|
||||
'AssetAsNodeSerializer', 'AssetSimpleSerializer',
|
||||
'AssetSerializer', 'AssetSimpleSerializer',
|
||||
'ProtocolsField',
|
||||
]
|
||||
|
||||
|
||||
class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||
class ProtocolField(serializers.RegexField):
|
||||
protocols = '|'.join(dict(Asset.PROTOCOL_CHOICES).keys())
|
||||
default_error_messages = {
|
||||
'invalid': _('Protocol format should {}/{}'.format(protocols, '1-65535'))
|
||||
}
|
||||
regex = r'^(%s)/(\d{1,5})$' % protocols
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(self.regex, **kwargs)
|
||||
|
||||
|
||||
def validate_duplicate_protocols(values):
|
||||
errors = []
|
||||
names = []
|
||||
|
||||
for value in values:
|
||||
if not value or '/' not in value:
|
||||
continue
|
||||
name = value.split('/')[0]
|
||||
if name in names:
|
||||
errors.append(_("Protocol duplicate: {}").format(name))
|
||||
names.append(name)
|
||||
errors.append('')
|
||||
if any(errors):
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
|
||||
class ProtocolsField(serializers.ListField):
|
||||
default_validators = [validate_duplicate_protocols]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['child'] = ProtocolField()
|
||||
kwargs['allow_null'] = True
|
||||
kwargs['allow_empty'] = True
|
||||
kwargs['min_length'] = 1
|
||||
kwargs['max_length'] = 4
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def to_representation(self, value):
|
||||
if not value:
|
||||
return []
|
||||
return value.split(' ')
|
||||
|
||||
|
||||
class AssetSerializer(BulkOrgResourceModelSerializer):
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
class Meta:
|
||||
model = Asset
|
||||
list_serializer_class = BulkListSerializer
|
||||
fields = '__all__'
|
||||
validators = []
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
fields = [
|
||||
'id', 'ip', 'hostname', 'protocol', 'port',
|
||||
'protocols', 'platform', 'is_active', 'public_ip', 'domain',
|
||||
'admin_user', 'nodes', 'labels', 'number', 'vendor', 'model', 'sn',
|
||||
'cpu_model', 'cpu_count', 'cpu_cores', 'cpu_vcpus', 'memory',
|
||||
'disk_total', 'disk_info', 'os', 'os_version', 'os_arch',
|
||||
'hostname_raw', 'comment', 'created_by', 'date_created',
|
||||
'hardware_info', 'connectivity',
|
||||
]
|
||||
read_only_fields = (
|
||||
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
|
||||
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
|
||||
'os', 'os_version', 'os_arch', 'hostname_raw',
|
||||
'created_by', 'date_created',
|
||||
)
|
||||
extra_kwargs = {
|
||||
'protocol': {'write_only': True},
|
||||
'port': {'write_only': True},
|
||||
'hardware_info': {'label': _('Hardware info')},
|
||||
'org_name': {'label': _('Org name')}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.prefetch_related('labels', 'nodes')\
|
||||
.select_related('admin_user')
|
||||
queryset = queryset.prefetch_related(
|
||||
Prefetch('nodes', queryset=Node.objects.all().only('id')),
|
||||
Prefetch('labels', queryset=Label.objects.all().only('id')),
|
||||
).select_related('admin_user', 'domain')
|
||||
return queryset
|
||||
|
||||
def get_field_names(self, declared_fields, info):
|
||||
fields = super().get_field_names(declared_fields, info)
|
||||
fields.extend([
|
||||
'hardware_info', 'connectivity', 'org_name'
|
||||
])
|
||||
return fields
|
||||
def compatible_with_old_protocol(self, validated_data):
|
||||
protocols_data = validated_data.pop("protocols", [])
|
||||
|
||||
# 兼容老的api
|
||||
name = validated_data.get("protocol")
|
||||
port = validated_data.get("port")
|
||||
if not protocols_data and name and port:
|
||||
protocols_data.insert(0, '/'.join([name, str(port)]))
|
||||
elif not name and not port and protocols_data:
|
||||
protocol = protocols_data[0].split('/')
|
||||
validated_data["protocol"] = protocol[0]
|
||||
validated_data["port"] = int(protocol[1])
|
||||
if protocols_data:
|
||||
validated_data["protocols"] = ' '.join(protocols_data)
|
||||
|
||||
class AssetAsNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol']
|
||||
def create(self, validated_data):
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
instance = super().create(validated_data)
|
||||
return instance
|
||||
|
||||
|
||||
class AssetGrantedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
被授权资产的数据结构
|
||||
"""
|
||||
system_users_granted = AssetSystemUserSerializer(many=True, read_only=True)
|
||||
system_users_join = serializers.SerializerMethodField()
|
||||
# nodes = NodeTMPSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "ip", "port", "system_users_granted",
|
||||
"is_active", "system_users_join", "os", 'domain',
|
||||
"platform", "comment", "protocol", "org_id", "org_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_system_users_join(obj):
|
||||
system_users = [s.username for s in obj.system_users_granted]
|
||||
return ', '.join(system_users)
|
||||
|
||||
|
||||
class MyAssetGrantedSerializer(AssetGrantedSerializer):
|
||||
"""
|
||||
普通用户获取授权的资产定义的数据结构
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = (
|
||||
"id", "hostname", "system_users_granted",
|
||||
"is_active", "system_users_join", "org_name",
|
||||
"os", "platform", "comment", "org_id", "protocol"
|
||||
)
|
||||
def update(self, instance, validated_data):
|
||||
self.compatible_with_old_protocol(validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class AssetSimpleSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
|
||||
fields = ['id', 'hostname', 'ip', 'connectivity', 'port']
|
||||
|
||||
88
apps/assets/serializers/asset_user.py
Normal file
88
apps/assets/serializers/asset_user.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework import serializers
|
||||
|
||||
from common.serializers import AdaptedBulkListSerializer
|
||||
from orgs.mixins import BulkOrgResourceModelSerializer
|
||||
from ..models import AuthBook, Asset
|
||||
from ..backends import AssetUserManager
|
||||
from .base import ConnectivitySerializer, AuthSerializerMixin
|
||||
|
||||
|
||||
__all__ = [
|
||||
'AssetUserSerializer', 'AssetUserAuthInfoSerializer',
|
||||
'AssetUserExportSerializer', 'AssetUserPushSerializer',
|
||||
]
|
||||
|
||||
|
||||
class BasicAssetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = ['hostname', 'ip']
|
||||
|
||||
|
||||
class AssetUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
|
||||
hostname = serializers.CharField(read_only=True, label=_("Hostname"))
|
||||
ip = serializers.CharField(read_only=True, label=_("IP"))
|
||||
connectivity = ConnectivitySerializer(read_only=True, label=_("Connectivity"))
|
||||
|
||||
backend = serializers.CharField(read_only=True, label=_("Backend"))
|
||||
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
list_serializer_class = AdaptedBulkListSerializer
|
||||
read_only_fields = (
|
||||
'date_created', 'date_updated', 'created_by',
|
||||
'is_latest', 'version', 'connectivity',
|
||||
)
|
||||
fields = [
|
||||
"id", "hostname", "ip", "username", "password", "asset", "version",
|
||||
"is_latest", "connectivity", "backend",
|
||||
"date_created", "date_updated", "private_key", "public_key",
|
||||
]
|
||||
extra_kwargs = {
|
||||
'username': {'required': True},
|
||||
'password': {'write_only': True},
|
||||
'private_key': {'write_only': True},
|
||||
'public_key': {'write_only': True},
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
if not validated_data.get("name") and validated_data.get("username"):
|
||||
validated_data["name"] = validated_data["username"]
|
||||
instance = AssetUserManager.create(**validated_data)
|
||||
return instance
|
||||
|
||||
|
||||
class AssetUserExportSerializer(AssetUserSerializer):
|
||||
password = serializers.CharField(
|
||||
max_length=256, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Password')
|
||||
)
|
||||
public_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Public key')
|
||||
)
|
||||
private_key = serializers.CharField(
|
||||
max_length=4096, allow_blank=True, allow_null=True,
|
||||
required=False, label=_('Private key')
|
||||
)
|
||||
|
||||
|
||||
class AssetUserAuthInfoSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AuthBook
|
||||
fields = ['password', 'private_key', 'public_key']
|
||||
|
||||
|
||||
class AssetUserPushSerializer(serializers.Serializer):
|
||||
asset = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(), label=_("Asset"))
|
||||
username = serializers.CharField(max_length=1024)
|
||||
|
||||
def create(self, validated_data):
|
||||
pass
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user