Compare commits
595 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c999d40917 | ||
|
|
9d8ed5ea8d | ||
|
|
f041c2b703 | ||
|
|
14a7833512 | ||
|
|
94d3ece7a9 | ||
|
|
8981b97632 | ||
|
|
c75d26b618 | ||
|
|
13a13dd18f | ||
|
|
954b23d91f | ||
|
|
b9d35c3dc7 | ||
|
|
536f48b3c7 | ||
|
|
8cd768c7c2 | ||
|
|
b233d2e87d | ||
|
|
1f6d8c1458 | ||
|
|
ae05183aa3 | ||
|
|
8374103a15 | ||
|
|
dd9e03843a | ||
|
|
e38ce53ed5 | ||
|
|
0c21bcf847 | ||
|
|
1c6b94e640 | ||
|
|
ef1c47ab19 | ||
|
|
64ff16e895 | ||
|
|
89860d6770 | ||
|
|
91ff43a17f | ||
|
|
0f19ebc928 | ||
|
|
b48a1d5856 | ||
|
|
7776009a31 | ||
|
|
ff816035ce | ||
|
|
fe384b14f0 | ||
|
|
adeb84f44e | ||
|
|
dc2e17c5db | ||
|
|
f3d8b39ac5 | ||
|
|
3be1f2eac6 | ||
|
|
1146c8f5bf | ||
|
|
910bfb945d | ||
|
|
4e886c1c15 | ||
|
|
a4b85f1e30 | ||
|
|
7c85c9fddd | ||
|
|
68c964acaa | ||
|
|
1be64836f4 | ||
|
|
f2389189a3 | ||
|
|
bb4be6022b | ||
|
|
f85a0c5ea5 | ||
|
|
5afae04b1d | ||
|
|
d1b182d20b | ||
|
|
9e3d1caee4 | ||
|
|
9a78956b23 | ||
|
|
822655b944 | ||
|
|
6dfbe505d9 | ||
|
|
0809c2c104 | ||
|
|
e30000bd00 | ||
|
|
90a7601960 | ||
|
|
8a800c6d33 | ||
|
|
d0021c9306 | ||
|
|
97673c84da | ||
|
|
771a182235 | ||
|
|
857d725a77 | ||
|
|
25b3e0f691 | ||
|
|
d2ba7631b5 | ||
|
|
a893c78c74 | ||
|
|
5ff041aa7b | ||
|
|
096196fcd5 | ||
|
|
225073aa33 | ||
|
|
f8b26d82d8 | ||
|
|
6b98a46b94 | ||
|
|
13572b94ee | ||
|
|
999b790557 | ||
|
|
7c93d59a42 | ||
|
|
9bec2aa2f0 | ||
|
|
240e1d5813 | ||
|
|
d82f4d90c1 | ||
|
|
abc68e8ef9 | ||
|
|
660ee538ce | ||
|
|
a96144d6dc | ||
|
|
e43d192007 | ||
|
|
74a018edb8 | ||
|
|
07d0049183 | ||
|
|
5f5358ea0f | ||
|
|
36cc6552bf | ||
|
|
05b5fd2eb4 | ||
|
|
95f1e86509 | ||
|
|
378dd06274 | ||
|
|
314adeb164 | ||
|
|
cc4f8c9f8d | ||
|
|
35b0e81beb | ||
|
|
9136e3936b | ||
|
|
35aead6c0e | ||
|
|
615f3b82db | ||
|
|
baa441cb90 | ||
|
|
9ad683ca09 | ||
|
|
c2d1d12cd2 | ||
|
|
3b6bac7668 | ||
|
|
2be879548d | ||
|
|
033c346042 | ||
|
|
32a8e65fe8 | ||
|
|
b2d4d80181 | ||
|
|
56c1cb23a0 | ||
|
|
ba26f70d1a | ||
|
|
e5589e7664 | ||
|
|
4e82486784 | ||
|
|
bb1cdebaf4 | ||
|
|
01405f47c9 | ||
|
|
5e64dc9262 | ||
|
|
9c7cd943b3 | ||
|
|
7cf3166169 | ||
|
|
9bdb77a573 | ||
|
|
3b8ee5ec0d | ||
|
|
6e7e09064f | ||
|
|
dfcb450a8a | ||
|
|
b192c34c15 | ||
|
|
f813dbb690 | ||
|
|
16deafca76 | ||
|
|
647b087fa7 | ||
|
|
4bd1387b83 | ||
|
|
4e098462dc | ||
|
|
0b1c2ae72a | ||
|
|
5d3fa0a0d2 | ||
|
|
6097bca063 | ||
|
|
a6aafe7593 | ||
|
|
56d05af07a | ||
|
|
0d17345600 | ||
|
|
5df62b7422 | ||
|
|
868914feb1 | ||
|
|
1a9555d4af | ||
|
|
33c8f15e45 | ||
|
|
ed8dd01dbd | ||
|
|
2296e37e8f | ||
|
|
a0f33c7bdc | ||
|
|
f6b249836e | ||
|
|
0c92a97054 | ||
|
|
24ab152559 | ||
|
|
5f9f09d77c | ||
|
|
4d27d9e48d | ||
|
|
0b624b972a | ||
|
|
1889e12bac | ||
|
|
7648f73072 | ||
|
|
75d346ed85 | ||
|
|
dabfe7907d | ||
|
|
965976223f | ||
|
|
410f00c213 | ||
|
|
0d8b942ad4 | ||
|
|
5371015a58 | ||
|
|
2ead70e434 | ||
|
|
ffca14cb5f | ||
|
|
090d5e82df | ||
|
|
8893ddf0f7 | ||
|
|
997ec5a699 | ||
|
|
762818ee39 | ||
|
|
61c6ba8189 | ||
|
|
9cfa646bcb | ||
|
|
b4301c7d41 | ||
|
|
71b5f6a38a | ||
|
|
1c0052fe30 | ||
|
|
35862acb73 | ||
|
|
11cf64fcc7 | ||
|
|
2ab37b45cf | ||
|
|
7096fc830b | ||
|
|
39806b7d96 | ||
|
|
7a16b8cb0e | ||
|
|
2583068dbd | ||
|
|
e5d0b3a372 | ||
|
|
9a1caf1e7e | ||
|
|
af0e41e26c | ||
|
|
d3049164a9 | ||
|
|
bdfda6775d | ||
|
|
72e6e74a42 | ||
|
|
75832fbed9 | ||
|
|
811b3adb51 | ||
|
|
e710df3ba7 | ||
|
|
4ee0c3ccba | ||
|
|
036b934119 | ||
|
|
4bfd43bf4c | ||
|
|
55722d3c04 | ||
|
|
77043d8d66 | ||
|
|
002117a6e5 | ||
|
|
5aa8097cfd | ||
|
|
19e1049566 | ||
|
|
87bdc88e22 | ||
|
|
950e3ae91e | ||
|
|
a37532e1ad | ||
|
|
21ff5f311b | ||
|
|
7dd12cf0cb | ||
|
|
6f8df7a690 | ||
|
|
1ff0ef1f90 | ||
|
|
196fc10d80 | ||
|
|
7496de4cc6 | ||
|
|
02a6adf6a2 | ||
|
|
6c95575a8f | ||
|
|
39755e89a8 | ||
|
|
c5d3ca218e | ||
|
|
87a2a2a0e4 | ||
|
|
5904da2eb1 | ||
|
|
1ac0c81661 | ||
|
|
955711714d | ||
|
|
5848553a4b | ||
|
|
38758caac4 | ||
|
|
b41a1bdbf4 | ||
|
|
a400ab7f7d | ||
|
|
e5d0405882 | ||
|
|
f2137c02f7 | ||
|
|
4c61f465a3 | ||
|
|
6d22041eab | ||
|
|
752e26db6d | ||
|
|
094ec23f04 | ||
|
|
af8ff2901e | ||
|
|
628689c990 | ||
|
|
ab37221182 | ||
|
|
626892473f | ||
|
|
c621677852 | ||
|
|
6650b4848d | ||
|
|
c07c56f89b | ||
|
|
294590882f | ||
|
|
9151b9c2d6 | ||
|
|
5817468d09 | ||
|
|
31dd20999c | ||
|
|
4eb9c9bd4d | ||
|
|
150164534f | ||
|
|
2b2d8a9fab | ||
|
|
fb122cbbdb | ||
|
|
0b37857d29 | ||
|
|
15c1876687 | ||
|
|
473a6e391d | ||
|
|
13ad64e6f3 | ||
|
|
04e278249e | ||
|
|
e12c4ea1e2 | ||
|
|
acfa8632af | ||
|
|
f53c1f5605 | ||
|
|
f0f7f89ea8 | ||
|
|
059ff0647a | ||
|
|
d94e5b0620 | ||
|
|
3840bce6d7 | ||
|
|
71cd11eedf | ||
|
|
ecea04bc08 | ||
|
|
0575ee5507 | ||
|
|
8b2fa8405b | ||
|
|
7d36a50687 | ||
|
|
f7dd9d8d5b | ||
|
|
379b4f4612 | ||
|
|
4652668e1b | ||
|
|
4e02a8571e | ||
|
|
bc927a65ac | ||
|
|
90f1a1c115 | ||
|
|
c48acf6038 | ||
|
|
2640e8c890 | ||
|
|
094ec55e7f | ||
|
|
fe39bdac42 | ||
|
|
2c98d50c43 | ||
|
|
d374cff51c | ||
|
|
d9e7256804 | ||
|
|
0ef2d1523e | ||
|
|
05bad6f671 | ||
|
|
5a62cfcda1 | ||
|
|
634d38510d | ||
|
|
bf27872973 | ||
|
|
20bb5a4926 | ||
|
|
f63fb3ffa0 | ||
|
|
f11c32c606 | ||
|
|
0b4e22a952 | ||
|
|
a85dbff3a5 | ||
|
|
d2835dd577 | ||
|
|
62abc99b61 | ||
|
|
20de62cc79 | ||
|
|
731614279f | ||
|
|
2be751dc8e | ||
|
|
e0409941a3 | ||
|
|
e6a5a3c8c1 | ||
|
|
3f3590a223 | ||
|
|
c1f64d7b82 | ||
|
|
f90611c96f | ||
|
|
630e21f7c1 | ||
|
|
2e81642c0e | ||
|
|
39514b9550 | ||
|
|
2da82d5610 | ||
|
|
69f33a08b6 | ||
|
|
a05e9c7746 | ||
|
|
d8031e4f49 | ||
|
|
e6aa07ba5c | ||
|
|
173129014a | ||
|
|
8d4baa6d31 | ||
|
|
20463ce653 | ||
|
|
2617f96710 | ||
|
|
53b0614faf | ||
|
|
a01db9c448 | ||
|
|
84dc8e3696 | ||
|
|
e7b988c042 | ||
|
|
a06212af1a | ||
|
|
b217ac9456 | ||
|
|
ff8422d6c1 | ||
|
|
b51d279ba6 | ||
|
|
78e4601413 | ||
|
|
5900db10eb | ||
|
|
c9bd853032 | ||
|
|
976fcad098 | ||
|
|
5d8193c348 | ||
|
|
cc09b26818 | ||
|
|
b0247caa4c | ||
|
|
38316abeae | ||
|
|
0991dfa6bd | ||
|
|
88029663e9 | ||
|
|
3f5cb10db6 | ||
|
|
1c927722f0 | ||
|
|
c04354ba1c | ||
|
|
3335659c8d | ||
|
|
f0282b33f0 | ||
|
|
f69d461463 | ||
|
|
bcf16562fe | ||
|
|
d7412410f2 | ||
|
|
d5907477df | ||
|
|
9acfaff361 | ||
|
|
737274da59 | ||
|
|
7a0e0e6915 | ||
|
|
25847b9c83 | ||
|
|
07dd7bb93e | ||
|
|
bc9936bd92 | ||
|
|
e4428e3387 | ||
|
|
30ef0ce140 | ||
|
|
38c461ebe0 | ||
|
|
ead1776f90 | ||
|
|
0e944b2442 | ||
|
|
3bbd503308 | ||
|
|
a7b13b168e | ||
|
|
1cf8ae1bdf | ||
|
|
7da3c8e252 | ||
|
|
2edf3fb68d | ||
|
|
1e698ba81b | ||
|
|
e9034ea6fe | ||
|
|
08ebc1ce16 | ||
|
|
a80c95e1cf | ||
|
|
c3ab785d66 | ||
|
|
baef6210c1 | ||
|
|
74d4a6fa72 | ||
|
|
770e41a657 | ||
|
|
d04eb03b3c | ||
|
|
9a8224a8d6 | ||
|
|
bd072b727c | ||
|
|
93cedec32c | ||
|
|
3d8933e5f0 | ||
|
|
790f5a5f2f | ||
|
|
36bb24d580 | ||
|
|
3e08ba95cf | ||
|
|
e50f82175b | ||
|
|
5907c8b5ce | ||
|
|
9f9b5beff9 | ||
|
|
71dbd31477 | ||
|
|
9e4d17cc7c | ||
|
|
2ae14913cc | ||
|
|
a4e23eee83 | ||
|
|
0bc406b332 | ||
|
|
76a6b2407e | ||
|
|
f371067d56 | ||
|
|
d1507dffca | ||
|
|
c11cc0b979 | ||
|
|
7cc941cc84 | ||
|
|
a847339d72 | ||
|
|
46827cdaa0 | ||
|
|
183b54f9b8 | ||
|
|
e51a70f7b9 | ||
|
|
f83e97abfc | ||
|
|
92a566e73e | ||
|
|
59ade0d2ba | ||
|
|
7da368d555 | ||
|
|
e00fd648a6 | ||
|
|
f74c8420fb | ||
|
|
d2fbb8aba9 | ||
|
|
ba8aee2148 | ||
|
|
018dfd8865 | ||
|
|
be40ee3100 | ||
|
|
35f9b5ed17 | ||
|
|
8c4450246b | ||
|
|
537e7d10e6 | ||
|
|
124c7288b9 | ||
|
|
3224648bc7 | ||
|
|
9b8a11b65e | ||
|
|
6b9d72f4a2 | ||
|
|
8d50697345 | ||
|
|
f8e9d2b73a | ||
|
|
99ed78a2e6 | ||
|
|
346dab2c26 | ||
|
|
28582b2b0c | ||
|
|
2bda7e5f65 | ||
|
|
8849f6c513 | ||
|
|
6d50856c07 | ||
|
|
bdec0f9493 | ||
|
|
ab31beb1ae | ||
|
|
014d995284 | ||
|
|
69783d3606 | ||
|
|
55a0078332 | ||
|
|
346e0194d4 | ||
|
|
512f222819 | ||
|
|
006c6b3312 | ||
|
|
8c80a52ce2 | ||
|
|
5d8bf54f2f | ||
|
|
70c1eb0623 | ||
|
|
447b674469 | ||
|
|
7cb2147569 | ||
|
|
327de4d714 | ||
|
|
9b57955d72 | ||
|
|
e63198e130 | ||
|
|
03eac9006f | ||
|
|
d3402cca5e | ||
|
|
076b320e70 | ||
|
|
44180f95a5 | ||
|
|
e23ebf13b7 | ||
|
|
e0fd1a2e93 | ||
|
|
c39986b37c | ||
|
|
1e0029dc15 | ||
|
|
63bc5e4161 | ||
|
|
8c8b4da595 | ||
|
|
1a7536270a | ||
|
|
d92e3c8d7b | ||
|
|
c30aedf491 | ||
|
|
0e96a462ee | ||
|
|
3ef60cb3a0 | ||
|
|
34b7638e11 | ||
|
|
63ef932469 | ||
|
|
e09163fc72 | ||
|
|
6a737f6d7d | ||
|
|
ccc7b7b213 | ||
|
|
1e98bc6430 | ||
|
|
c63a945057 | ||
|
|
a13d9fa00f | ||
|
|
1518d5eafc | ||
|
|
c74800fd8c | ||
|
|
9e282efe28 | ||
|
|
26efc9158d | ||
|
|
e2d3d35d71 | ||
|
|
c115bf84f0 | ||
|
|
5b56d32492 | ||
|
|
2fe294ad8a | ||
|
|
1343897fa9 | ||
|
|
9b2b0e4ea6 | ||
|
|
489e775b08 | ||
|
|
0d76a45181 | ||
|
|
4787f9a462 | ||
|
|
b228e12c81 | ||
|
|
0e28100ec6 | ||
|
|
d2c1697144 | ||
|
|
b924ea8dd1 | ||
|
|
30936d3ad1 | ||
|
|
289589dce9 | ||
|
|
48734b8109 | ||
|
|
2e69a41943 | ||
|
|
f414a3ac0a | ||
|
|
81833b134c | ||
|
|
5a5b74a0d5 | ||
|
|
5a7a230193 | ||
|
|
bc5ab6252e | ||
|
|
0e4d5e0973 | ||
|
|
08e254e34c | ||
|
|
2c20796ea1 | ||
|
|
8b9d7a7e2e | ||
|
|
1a0709b02c | ||
|
|
0f386ee8d2 | ||
|
|
c0b41155e9 | ||
|
|
ddd1165728 | ||
|
|
651dbe59c8 | ||
|
|
aeb6f28f9a | ||
|
|
012eefad4e | ||
|
|
b05346d4a3 | ||
|
|
854e6e84fe | ||
|
|
20436bf07d | ||
|
|
4110e4b8cb | ||
|
|
e179edc2e0 | ||
|
|
133ef7eb16 | ||
|
|
5a98982fc2 | ||
|
|
f6e92bf597 | ||
|
|
af4a8d5ae8 | ||
|
|
c3704bbb67 | ||
|
|
8620f2f80d | ||
|
|
e0124474cf | ||
|
|
ac21b78225 | ||
|
|
dca69a5428 | ||
|
|
6b1fa0f50c | ||
|
|
3ab35dc4e0 | ||
|
|
2aa1fc1acb | ||
|
|
e6643fa4cb | ||
|
|
4848dd8502 | ||
|
|
4b08d7b4b2 | ||
|
|
839189c772 | ||
|
|
a06ff45c21 | ||
|
|
c598efcb9f | ||
|
|
878476d195 | ||
|
|
82e599fc13 | ||
|
|
79b76687a0 | ||
|
|
af5349c4cb | ||
|
|
3de35b481e | ||
|
|
008bb308e7 | ||
|
|
9b44fc4a35 | ||
|
|
3d374fc792 | ||
|
|
b51c842d18 | ||
|
|
95b595d5bf | ||
|
|
445c3f220a | ||
|
|
9644d95b9b | ||
|
|
eef9f151ef | ||
|
|
6c05a4ec25 | ||
|
|
2ea71e1feb | ||
|
|
294404750d | ||
|
|
ada957c7a3 | ||
|
|
cda747a1f5 | ||
|
|
3f9db95eba | ||
|
|
18d77f5ff5 | ||
|
|
e7e8cfe8da | ||
|
|
7abc791050 | ||
|
|
913098916e | ||
|
|
cbf1f3bd9f | ||
|
|
a0a1fe6737 | ||
|
|
cb2a702829 | ||
|
|
65f171bde0 | ||
|
|
85531e2133 | ||
|
|
17e058dce2 | ||
|
|
566f187a3d | ||
|
|
85c528ce27 | ||
|
|
9a87c8bffb | ||
|
|
ae931b2dde | ||
|
|
183be70d44 | ||
|
|
eb6e5d7a75 | ||
|
|
e1101bdd62 | ||
|
|
954cf2c0ff | ||
|
|
17c7806850 | ||
|
|
2594773a12 | ||
|
|
a0025e933a | ||
|
|
aea11afb5a | ||
|
|
e40ee1e3a8 | ||
|
|
1632af3e22 | ||
|
|
fd3793d3d7 | ||
|
|
fef65c5209 | ||
|
|
173ab8cf91 | ||
|
|
9d6cb6e044 | ||
|
|
27c5909d7f | ||
|
|
91b0943a8b | ||
|
|
a8b741bb22 | ||
|
|
4cd1a8a6a0 | ||
|
|
db501f9f72 | ||
|
|
95d98181fc | ||
|
|
3981cb95fc | ||
|
|
c80fa93515 | ||
|
|
5e2895ce33 | ||
|
|
9c5e0df719 | ||
|
|
da73fcd973 | ||
|
|
f8ae050556 | ||
|
|
d53c1211cf | ||
|
|
905ebd76ae | ||
|
|
179687490c | ||
|
|
85aff3e75a | ||
|
|
388a69fe4e | ||
|
|
4e9086a042 | ||
|
|
d6707488c8 | ||
|
|
bbcc65f149 | ||
|
|
e74b99ec65 | ||
|
|
125de3e35e | ||
|
|
9689a63bdc | ||
|
|
89b468e443 | ||
|
|
db6b6fe8fa | ||
|
|
e5718dd742 | ||
|
|
39622fe263 | ||
|
|
1d723000a9 | ||
|
|
36101fd7dd | ||
|
|
b8bb6cb55c | ||
|
|
995249e421 | ||
|
|
4533dd72ae | ||
|
|
f5c12d9ea6 | ||
|
|
ce8d58199a | ||
|
|
5556a57685 | ||
|
|
39f760e135 | ||
|
|
e8351c3246 | ||
|
|
35bf1f0f77 | ||
|
|
87039fa784 | ||
|
|
dea8e48895 | ||
|
|
4145de7662 | ||
|
|
2800c2f077 | ||
|
|
e7787ea95c | ||
|
|
e7809b405d | ||
|
|
b04b3ef924 | ||
|
|
6876b905cd | ||
|
|
371101bf69 | ||
|
|
e17ecf967d | ||
|
|
b0705a911d | ||
|
|
99c7f619e0 | ||
|
|
c4a37b2a85 | ||
|
|
c12f40ecf7 | ||
|
|
fe6055c402 | ||
|
|
d2b36c4d23 | ||
|
|
fdd29190a4 | ||
|
|
6d31d0a60e | ||
|
|
a4cb908390 | ||
|
|
b8d3c35e34 | ||
|
|
48bde677bc | ||
|
|
cafb7da93e | ||
|
|
ad6c3cb132 | ||
|
|
e1e532ed91 | ||
|
|
2afbeb1c10 | ||
|
|
598db5c83b | ||
|
|
ceddb83d2d | ||
|
|
4e70378b46 | ||
|
|
5cd7f3568e |
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Set default charset
|
||||||
|
[*.{js,ts,scss,html}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
9
.eslintignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dist
|
||||||
|
build
|
||||||
|
build-cli
|
||||||
|
jslib
|
||||||
|
webpack.cli.js
|
||||||
|
webpack.main.js
|
||||||
|
webpack.renderer.js
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
32
.eslintrc.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": ["./jslib/shared/eslintrc.json"],
|
||||||
|
"rules": {
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"alphabetize": {
|
||||||
|
"order": "asc"
|
||||||
|
},
|
||||||
|
"newlines-between": "always",
|
||||||
|
"pathGroups": [
|
||||||
|
{
|
||||||
|
"pattern": "jslib-*/**",
|
||||||
|
"group": "external",
|
||||||
|
"position": "after"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "src/**/*",
|
||||||
|
"group": "parent",
|
||||||
|
"position": "before"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pathGroupsExcludedImportTypes": ["builtin"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Apply Prettier https://github.com/bitwarden/directory-connector/pull/194
|
||||||
|
096196fcd512944d1c3d9c007647a1319b032639
|
||||||
33
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## Type of change
|
||||||
|
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature development
|
||||||
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
- [ ] Build/deploy pipeline (DevOps)
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
||||||
|
|
||||||
|
## Code changes
|
||||||
|
|
||||||
|
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
|
||||||
|
<!--Also refer to any related changes or PRs in other repositories-->
|
||||||
|
|
||||||
|
- **file.ext:** Description of what was changed and why
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<!--Required for any UI changes. Delete if not applicable-->
|
||||||
|
|
||||||
|
## Testing requirements
|
||||||
|
|
||||||
|
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||||
|
|
||||||
|
## Before you submit
|
||||||
|
|
||||||
|
- [ ] I have checked for **linting** errors (`npm run lint`) (required)
|
||||||
|
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||||
|
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||||
|
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||||
BIN
.github/secrets/devid-app-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/devid-installer-cert.p12.gpg
vendored
Normal file
BIN
.github/secrets/macdev-cert.p12.gpg
vendored
Normal file
698
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
---
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'l10n_master'
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/workflows/**'
|
||||||
|
workflow_dispatch: {}
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cloc:
|
||||||
|
name: CLOC
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up CLOC
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt -y install cloc
|
||||||
|
|
||||||
|
- name: Print lines of code
|
||||||
|
run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git
|
||||||
|
|
||||||
|
|
||||||
|
setup:
|
||||||
|
name: Setup
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
outputs:
|
||||||
|
package_version: ${{ steps.retrieve-version.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Get Package Version
|
||||||
|
id: retrieve-version
|
||||||
|
run: |
|
||||||
|
PKG_VERSION=$(jq -r .version src/package.json)
|
||||||
|
echo "::set-output name=package_version::$PKG_VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
linux-cli:
|
||||||
|
name: Build Linux CLI
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||||
|
_PKG_FETCH_VERSION: 3.2
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Get pkg-fetch
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-linux-x64"
|
||||||
|
|
||||||
|
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
|
||||||
|
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-linux-x64"
|
||||||
|
|
||||||
|
- name: Keytar
|
||||||
|
run: |
|
||||||
|
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||||
|
keytarTar="keytar-v$keytarVersion-napi-v3-linux-x64.tar"
|
||||||
|
|
||||||
|
keytarTarGz="$keytarTar.gz"
|
||||||
|
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||||
|
|
||||||
|
mkdir -p ./keytar/linux
|
||||||
|
wget $keytarUrl -O ./keytar/linux/$keytarTarGz
|
||||||
|
tar -xvf ./keytar/linux/$keytarTarGz -C ./keytar/linux
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Package CLI
|
||||||
|
run: npm run dist:cli:lin
|
||||||
|
|
||||||
|
- name: Zip
|
||||||
|
run: zip -j ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip ./dist-cli/linux/bwdc ./keytar/linux/build/Release/keytar.node
|
||||||
|
|
||||||
|
- name: Create checksums
|
||||||
|
run: sha256sum ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-linux-sha256-$_PACKAGE_VERSION.txt
|
||||||
|
|
||||||
|
- name: Version Test
|
||||||
|
run: |
|
||||||
|
sudo apt install libsecret-1-0 dbus-x11 gnome-keyring
|
||||||
|
eval $(dbus-launch --sh-syntax)
|
||||||
|
|
||||||
|
eval $(echo -n "" | /usr/bin/gnome-keyring-daemon --login)
|
||||||
|
eval $(/usr/bin/gnome-keyring-daemon --components=secrets --start)
|
||||||
|
|
||||||
|
mkdir -p test/linux
|
||||||
|
unzip ./dist-cli/bwdc-linux-$_PACKAGE_VERSION.zip -d ./test/linux
|
||||||
|
|
||||||
|
testVersion=$(./test/linux/bwdc -v)
|
||||||
|
|
||||||
|
echo "version: $_PACKAGE_VERSION"
|
||||||
|
echo "testVersion: $testVersion"
|
||||||
|
|
||||||
|
if [ "$testVersion" != "$_PACKAGE_VERSION" ]; then
|
||||||
|
echo "Version test failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Linux Zip to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Linux checksum to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-linux-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
macos-cli:
|
||||||
|
name: Build Mac CLI
|
||||||
|
runs-on: macos-11
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
_PKG_FETCH_NODE_VERSION: 16.13.0
|
||||||
|
_PKG_FETCH_VERSION: 3.2
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Get pkg-fetch
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
fetchedUrl="https://github.com/vercel/pkg-fetch/releases/download/v$_PKG_FETCH_VERSION/node-v$_PKG_FETCH_NODE_VERSION-macos-x64"
|
||||||
|
|
||||||
|
mkdir -p .pkg-cache/v$_PKG_FETCH_VERSION
|
||||||
|
wget $fetchedUrl -O "./.pkg-cache/v$_PKG_FETCH_VERSION/fetched-v$_PKG_FETCH_NODE_VERSION-macos-x64"
|
||||||
|
|
||||||
|
- name: Keytar
|
||||||
|
run: |
|
||||||
|
keytarVersion=$(cat src/package.json | jq -r '.dependencies.keytar')
|
||||||
|
keytarTar="keytar-v$keytarVersion-napi-v3-darwin-x64.tar"
|
||||||
|
|
||||||
|
keytarTarGz="$keytarTar.gz"
|
||||||
|
keytarUrl="https://github.com/atom/node-keytar/releases/download/v$keytarVersion/$keytarTarGz"
|
||||||
|
|
||||||
|
mkdir -p ./keytar/macos
|
||||||
|
wget $keytarUrl -O ./keytar/macos/$keytarTarGz
|
||||||
|
tar -xvf ./keytar/macos/$keytarTarGz -C ./keytar/macos
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Package CLI
|
||||||
|
run: npm run dist:cli:mac
|
||||||
|
|
||||||
|
- name: Zip
|
||||||
|
run: zip -j ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip ./dist-cli/macos/bwdc ./keytar/macos/build/Release/keytar.node
|
||||||
|
|
||||||
|
- name: Create checksums
|
||||||
|
run: sha256sum ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip | cut -d " " -f 1 > ./dist-cli/bwdc-macos-sha256-$_PACKAGE_VERSION.txt
|
||||||
|
|
||||||
|
- name: Version Test
|
||||||
|
run: |
|
||||||
|
mkdir -p test/macos
|
||||||
|
unzip ./dist-cli/bwdc-macos-$_PACKAGE_VERSION.zip -d ./test/macos
|
||||||
|
|
||||||
|
testVersion=$(./test/macos/bwdc -v)
|
||||||
|
|
||||||
|
echo "version: $_PACKAGE_VERSION"
|
||||||
|
echo "testVersion: $testVersion"
|
||||||
|
|
||||||
|
if [ "$testVersion" != "$_PACKAGE_VERSION" ]; then
|
||||||
|
echo "Version test failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Mac Zip to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Mac checksum to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-macos-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
windows-cli:
|
||||||
|
name: Build Windows CLI
|
||||||
|
runs-on: windows-2019
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
_WIN_PKG_FETCH_VERSION: 16.13.0
|
||||||
|
_WIN_PKG_VERSION: 3.2
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Setup Windows builder
|
||||||
|
run: |
|
||||||
|
choco install checksum --no-progress
|
||||||
|
choco install reshack --no-progress
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Get pkg-fetch
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
cd $HOME
|
||||||
|
$fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache
|
||||||
|
New-Item -ItemType directory -Path ./.pkg-cache/v$env:_WIN_PKG_VERSION
|
||||||
|
Invoke-RestMethod -Uri $fetchedUrl `
|
||||||
|
-OutFile "./.pkg-cache/v$env:_WIN_PKG_VERSION/fetched-v$env:_WIN_PKG_FETCH_VERSION-win-x64"
|
||||||
|
|
||||||
|
- name: Keytar
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$keytarVersion = (Get-Content -Raw -Path ./src/package.json | ConvertFrom-Json).dependencies.keytar
|
||||||
|
$keytarTar = "keytar-v${keytarVersion}-napi-v3-{0}-x64.tar"
|
||||||
|
$keytarTarGz = "${keytarTar}.gz"
|
||||||
|
$keytarUrl = "https://github.com/atom/node-keytar/releases/download/v${keytarVersion}/${keytarTarGz}"
|
||||||
|
|
||||||
|
New-Item -ItemType directory -Path ./keytar/windows | Out-Null
|
||||||
|
|
||||||
|
Invoke-RestMethod -Uri $($keytarUrl -f "win32") -OutFile "./keytar/windows/$($keytarTarGz -f "win32")"
|
||||||
|
|
||||||
|
7z e "./keytar/windows/$($keytarTarGz -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
7z e "./keytar/windows/$($keytarTar -f "win32")" -o"./keytar/windows"
|
||||||
|
|
||||||
|
- name: Setup Version Info
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$major, $minor, $patch = $env:_PACKAGE_VERSION.split('.')
|
||||||
|
|
||||||
|
$versionInfo = @"
|
||||||
|
|
||||||
|
1 VERSIONINFO
|
||||||
|
FILEVERSION $major,$minor,$patch,0
|
||||||
|
PRODUCTVERSION $major,$minor,$patch,0
|
||||||
|
FILEOS 0x40004
|
||||||
|
FILETYPE 0x1
|
||||||
|
{
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
{
|
||||||
|
BLOCK "040904b0"
|
||||||
|
{
|
||||||
|
VALUE "CompanyName", "Bitwarden Inc."
|
||||||
|
VALUE "ProductName", "Bitwarden"
|
||||||
|
VALUE "FileDescription", "Bitwarden Directory Connector CLI"
|
||||||
|
VALUE "FileVersion", "$env:_PACKAGE_VERSION"
|
||||||
|
VALUE "ProductVersion", "$env:_PACKAGE_VERSION"
|
||||||
|
VALUE "OriginalFilename", "bwdc.exe"
|
||||||
|
VALUE "InternalName", "bwdc"
|
||||||
|
VALUE "LegalCopyright", "Copyright Bitwarden Inc."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
{
|
||||||
|
VALUE "Translation", 0x0409 0x04B0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
$versionInfo | Out-File ./version-info.rc
|
||||||
|
|
||||||
|
- name: Resource Hacker
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
set PATH=%PATH%;C:\Program Files (x86)\Resource Hacker
|
||||||
|
set WIN_PKG=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\fetched-v%_WIN_PKG_FETCH_VERSION%-win-x64
|
||||||
|
set WIN_PKG_BUILT=C:\Users\runneradmin\.pkg-cache\v%_WIN_PKG_VERSION%\built-v%_WIN_PKG_FETCH_VERSION%-win-x64
|
||||||
|
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action delete -mask ICONGROUP,1,
|
||||||
|
ResourceHacker -open version-info.rc -save version-info.res -action compile
|
||||||
|
ResourceHacker -open %WIN_PKG% -save %WIN_PKG% -action addoverwrite -resource version-info.res
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Package CLI
|
||||||
|
run: npm run dist:cli:win
|
||||||
|
|
||||||
|
- name: Zip
|
||||||
|
shell: cmd
|
||||||
|
run: 7z a ./dist-cli/bwdc-windows-%_PACKAGE_VERSION%.zip ./dist-cli/windows/bwdc.exe ./keytar/windows/keytar.node
|
||||||
|
|
||||||
|
- name: Version Test
|
||||||
|
run: |
|
||||||
|
Expand-Archive -Path "./dist-cli/bwdc-windows-${env:_PACKAGE_VERSION}.zip" -DestinationPath "./test/windows"
|
||||||
|
$testVersion = Invoke-Expression '& ./test/windows/bwdc.exe -v'
|
||||||
|
echo "version: $env:_PACKAGE_VERSION"
|
||||||
|
echo "testVersion: $testVersion"
|
||||||
|
if($testVersion -ne $env:_PACKAGE_VERSION) {
|
||||||
|
Throw "Version test failed."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Create checksums
|
||||||
|
run: |
|
||||||
|
checksum -f="./dist-cli/bwdc-windows-${env:_PACKAGE_VERSION}.zip" `
|
||||||
|
-t sha256 | Out-File ./dist-cli/bwdc-windows-sha256-${env:_PACKAGE_VERSION}.txt
|
||||||
|
|
||||||
|
- name: Upload Windows Zip to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Windows checksum to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
path: ./dist-cli/bwdc-windows-sha256-${{ env._PACKAGE_VERSION }}.txt
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
windows-gui:
|
||||||
|
name: Build Windows GUI
|
||||||
|
runs-on: windows-2019
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up .NET
|
||||||
|
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829
|
||||||
|
with:
|
||||||
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
dotnet --version
|
||||||
|
|
||||||
|
- name: Install AST
|
||||||
|
uses: bitwarden/gh-actions/install-ast@f135c42c8596cb535c5bcb7523c0b2eef89709ac
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
# - name: Run linter
|
||||||
|
# run: npm run lint
|
||||||
|
|
||||||
|
- name: Build & Sign
|
||||||
|
run: npm run dist:win
|
||||||
|
env:
|
||||||
|
ELECTRON_BUILDER_SIGN: 1
|
||||||
|
SIGNING_VAULT_URL: ${{ secrets.SIGNING_VAULT_URL }}
|
||||||
|
SIGNING_CLIENT_ID: ${{ secrets.SIGNING_CLIENT_ID }}
|
||||||
|
SIGNING_TENANT_ID: ${{ secrets.SIGNING_TENANT_ID }}
|
||||||
|
SIGNING_CLIENT_SECRET: ${{ secrets.SIGNING_CLIENT_SECRET }}
|
||||||
|
SIGNING_CERT_NAME: ${{ secrets.SIGNING_CERT_NAME }}
|
||||||
|
|
||||||
|
- name: Upload Portable Executable to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||||
|
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Installer Executable to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||||
|
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Installer Executable Blockmap to GitHub
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||||
|
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload latest auto-update artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: latest.yml
|
||||||
|
path: ./dist/latest.yml
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
linux-gui:
|
||||||
|
name: Build Linux GUI
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up environment
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install pkg-config libxss-dev libsecret-1-dev
|
||||||
|
sudo apt-get -y install rpm
|
||||||
|
|
||||||
|
- name: NPM Install
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: NPM Rebuild
|
||||||
|
run: npm run rebuild
|
||||||
|
|
||||||
|
- name: NPM Package
|
||||||
|
run: npm run dist:lin
|
||||||
|
|
||||||
|
- name: Upload AppImage
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload latest auto-update artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: latest-linux.yml
|
||||||
|
path: ./dist/latest-linux.yml
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
macos-gui:
|
||||||
|
name: Build MacOS GUI
|
||||||
|
runs-on: macos-11
|
||||||
|
needs: setup
|
||||||
|
env:
|
||||||
|
_PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0
|
||||||
|
with:
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: '**/package-lock.json'
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Update NPM
|
||||||
|
run: |
|
||||||
|
npm install -g node-gyp
|
||||||
|
node-gyp install $(node -v)
|
||||||
|
|
||||||
|
- name: Set Node options
|
||||||
|
run: echo "NODE_OPTIONS=--max_old_space_size=4096" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
echo "GitHub ref: $GITHUB_REF"
|
||||||
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Decrypt secrets
|
||||||
|
env:
|
||||||
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/secrets
|
||||||
|
|
||||||
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
|
--output "$HOME/secrets/devid-app-cert.p12" \
|
||||||
|
"$GITHUB_WORKSPACE/.github/secrets/devid-app-cert.p12.gpg"
|
||||||
|
|
||||||
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
|
--output "$HOME/secrets/devid-installer-cert.p12" \
|
||||||
|
"$GITHUB_WORKSPACE/.github/secrets/devid-installer-cert.p12.gpg"
|
||||||
|
|
||||||
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
|
--output "$HOME/secrets/macdev-cert.p12" \
|
||||||
|
"$GITHUB_WORKSPACE/.github/secrets/macdev-cert.p12.gpg"
|
||||||
|
|
||||||
|
- name: Set up keychain
|
||||||
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
|
DEVID_CERT_PASSWORD: ${{ secrets.DEVID_CERT_PASSWORD }}
|
||||||
|
MACDEV_CERT_PASSWORD: ${{ secrets.MACDEV_CERT_PASSWORD }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
security set-keychain-settings -lut 1200 build.keychain
|
||||||
|
security import "$HOME/secrets/devid-app-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
|
||||||
|
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security import "$HOME/secrets/devid-installer-cert.p12" -k build.keychain -P $DEVID_CERT_PASSWORD \
|
||||||
|
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security import "$HOME/secrets/macdev-cert.p12" -k build.keychain -P $MACDEV_CERT_PASSWORD \
|
||||||
|
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
|
||||||
|
|
||||||
|
- name: Load package version
|
||||||
|
run: |
|
||||||
|
$rootPath = $env:GITHUB_WORKSPACE;
|
||||||
|
$packageVersion = (Get-Content -Raw -Path $rootPath\src\package.json | ConvertFrom-Json).version;
|
||||||
|
|
||||||
|
Write-Output "Setting package version to $packageVersion";
|
||||||
|
Write-Output "PACKAGE_VERSION=$packageVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append;
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Install Node dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
# - name: Run linter
|
||||||
|
# run: npm run lint
|
||||||
|
|
||||||
|
- name: Build application
|
||||||
|
run: npm run dist:mac
|
||||||
|
env:
|
||||||
|
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||||
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Rename Zip Artifact
|
||||||
|
run: |
|
||||||
|
cd dist
|
||||||
|
mv "Bitwarden Directory Connector-${{ env._PACKAGE_VERSION }}-mac.zip" \
|
||||||
|
"Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip"
|
||||||
|
|
||||||
|
- name: Upload .zip artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload .dmg artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload .dmg Blockmap artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||||
|
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload latest auto-update artifact
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: latest-mac.yml
|
||||||
|
path: ./dist/latest-mac.yml
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|
||||||
|
check-failures:
|
||||||
|
name: Check for failures
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs:
|
||||||
|
- cloc
|
||||||
|
- setup
|
||||||
|
- linux-cli
|
||||||
|
- macos-cli
|
||||||
|
- windows-cli
|
||||||
|
- windows-gui
|
||||||
|
- linux-gui
|
||||||
|
- macos-gui
|
||||||
|
steps:
|
||||||
|
- name: Check if any job failed
|
||||||
|
if: ${{ (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/rc') }}
|
||||||
|
env:
|
||||||
|
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||||
|
SETUP_STATUS: ${{ needs.setup.result }}
|
||||||
|
LINUX_CLI_STATUS: ${{ needs.linux-cli.result }}
|
||||||
|
MACOS_CLI_STATUS: ${{ needs.macos-cli.result }}
|
||||||
|
WINDOWS_CLI_STATUS: ${{ needs.windows-cli.result }}
|
||||||
|
WINDOWS_GUI_STATUS: ${{ needs.windows-gui.result }}
|
||||||
|
LINUX_GUI_STATUS: ${{ needs.linux-gui.result }}
|
||||||
|
MACOS_GUI_STATUS: ${{ needs.macos-gui.result }}
|
||||||
|
run: |
|
||||||
|
if [ "$CLOC_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$SETUP_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$LINUX_CLI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$MACOS_CLI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$WINDOWS_CLI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$WINDOWS_GUI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$LINUX_GUI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
elif [ "$MACOS_GUI_STATUS" = "failure" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Login to Azure - Prod Subscription
|
||||||
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||||
|
|
||||||
|
- name: Retrieve secrets
|
||||||
|
id: retrieve-secrets
|
||||||
|
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
keyvault: "bitwarden-prod-kv"
|
||||||
|
secrets: "devops-alerts-slack-webhook-url"
|
||||||
|
|
||||||
|
- name: Notify Slack on failure
|
||||||
|
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
||||||
|
if: failure()
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||||
|
with:
|
||||||
|
status: ${{ job.status }}
|
||||||
16
.github/workflows/enforce-labels.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: Enforce PR labels
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||||
|
jobs:
|
||||||
|
enforce-label:
|
||||||
|
name: EnforceLabel
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Enforce Label
|
||||||
|
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
|
||||||
|
with:
|
||||||
|
BANNED_LABELS: "hold"
|
||||||
|
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"
|
||||||
97
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type:
|
||||||
|
description: 'Release Options'
|
||||||
|
required: true
|
||||||
|
default: 'Initial Release'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- Initial Release
|
||||||
|
- Redeploy
|
||||||
|
- Dry Run
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
name: Setup
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Branch check
|
||||||
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
|
run: |
|
||||||
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||||
|
echo "==================================="
|
||||||
|
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
|
||||||
|
echo "==================================="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
|
||||||
|
- name: Retrieve Directory Connector release version
|
||||||
|
id: retrieve-version
|
||||||
|
run: |
|
||||||
|
PKG_VERSION=$(jq -r .version src/package.json)
|
||||||
|
echo "::set-output name=package_version::$PKG_VERSION"
|
||||||
|
|
||||||
|
- name: Check to make sure Mobile release version has been bumped
|
||||||
|
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
latest_ver=$(hub release -L 1 -f '%T')
|
||||||
|
latest_ver=${latest_ver:1}
|
||||||
|
echo "Latest version: $latest_ver"
|
||||||
|
ver=${{ steps.retrieve-version.outputs.package_version }}
|
||||||
|
echo "Version: $ver"
|
||||||
|
if [ "$latest_ver" = "$ver" ]; then
|
||||||
|
echo "Version has not been bumped!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get branch name
|
||||||
|
id: branch
|
||||||
|
run: |
|
||||||
|
BRANCH_NAME=$(basename ${{ github.ref }})
|
||||||
|
echo "::set-output name=branch-name::$BRANCH_NAME"
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: bitwarden/gh-actions/download-artifacts@c1fa8e09871a860862d6bbe36184b06d2c7e35a8
|
||||||
|
with:
|
||||||
|
workflow: build.yml
|
||||||
|
workflow_conclusion: success
|
||||||
|
branch: ${{ steps.branch.outputs.branch-name }}
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
|
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
||||||
|
env:
|
||||||
|
PKG_VERSION: ${{ steps.retrieve-version.outputs.package_version }}
|
||||||
|
with:
|
||||||
|
artifacts: "./bwdc-windows-${{ env.PKG_VERSION }}.zip,
|
||||||
|
./bwdc-macos-${{ env.PKG_VERSION }}.zip,
|
||||||
|
./bwdc-linux-${{ env.PKG_VERSION }}.zip,
|
||||||
|
./bwdc-windows-sha256-${{ env.PKG_VERSION }}.txt,
|
||||||
|
./bwdc-macos-sha256-${{ env.PKG_VERSION }}.txt,
|
||||||
|
./bwdc-linux-sha256-${{ env.PKG_VERSION }}.txt,
|
||||||
|
./Bitwarden-Connector-Portable-${{ env.PKG_VERSION }}.exe,
|
||||||
|
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe,
|
||||||
|
./Bitwarden-Connector-Installer-${{ env.PKG_VERSION }}.exe.blockmap,
|
||||||
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}-x86_64.AppImage,
|
||||||
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}-mac.zip,
|
||||||
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg,
|
||||||
|
./Bitwarden-Connector-${{ env.PKG_VERSION }}.dmg.blockmap,
|
||||||
|
./latest-linux.yml,
|
||||||
|
./latest-mac.yml,
|
||||||
|
./latest.yml"
|
||||||
|
commit: ${{ github.sha }}
|
||||||
|
tag: v${{ env.PKG_VERSION }}
|
||||||
|
name: Version ${{ env.PKG_VERSION }}
|
||||||
|
body: "<insert release notes here>"
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
draft: true
|
||||||
65
.github/workflows/version-bump.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
name: Version Bump
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version_number:
|
||||||
|
description: "New Version"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump_version:
|
||||||
|
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout Branch
|
||||||
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
|
||||||
|
- name: Create Version Branch
|
||||||
|
run: |
|
||||||
|
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Checkout Version Branch
|
||||||
|
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||||
|
with:
|
||||||
|
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Bump Version - Package
|
||||||
|
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.version_number }}
|
||||||
|
file_path: "./src/package.json"
|
||||||
|
|
||||||
|
- name: Commit files
|
||||||
|
run: |
|
||||||
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
|
- name: Create Version PR
|
||||||
|
env:
|
||||||
|
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
BASE_BRANCH: master
|
||||||
|
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
||||||
|
run: |
|
||||||
|
gh pr create --title "$TITLE" \
|
||||||
|
--base "$BASE" \
|
||||||
|
--head "$PR_BRANCH" \
|
||||||
|
--label "version update" \
|
||||||
|
--label "automated pr" \
|
||||||
|
--body "
|
||||||
|
## Type of change
|
||||||
|
- [ ] Bug fix
|
||||||
|
- [ ] New feature development
|
||||||
|
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
||||||
|
- [ ] Build/deploy pipeline (DevOps)
|
||||||
|
- [X] Other
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Automated version bump to ${{ github.event.inputs.version_number }}"
|
||||||
11
.github/workflows/workflow-linter.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: Workflow Linter
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
call-workflow:
|
||||||
|
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||||
276
.gitignore
vendored
@@ -1,261 +1,17 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
.vs
|
||||||
## files generated by popular Visual Studio add-ons.
|
.idea
|
||||||
|
node_modules
|
||||||
# User-specific files
|
npm-debug.log
|
||||||
*.suo
|
vwd.webinfo
|
||||||
*.user
|
dist/
|
||||||
*.userosscache
|
dist-cli/
|
||||||
*.sln.docstates
|
css/
|
||||||
|
*.crx
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
*.pem
|
||||||
*.userprefs
|
build-cli/
|
||||||
|
build/
|
||||||
# Build results
|
yarn-error.log
|
||||||
[Dd]ebug/
|
.DS_Store
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# DNX
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
#*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
*.nupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
*.provisionprofile
|
||||||
**/packages/*
|
*.env
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignoreable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
node_modules/
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|||||||
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "jslib"]
|
||||||
|
path = jslib
|
||||||
|
url = https://github.com/bitwarden/jslib.git
|
||||||
|
branch = master
|
||||||
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_
|
||||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
12
.prettierignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Build directories
|
||||||
|
build
|
||||||
|
build-cli
|
||||||
|
dist
|
||||||
|
|
||||||
|
jslib
|
||||||
|
|
||||||
|
# External libraries / auto synced locales
|
||||||
|
src/locales
|
||||||
|
|
||||||
|
# Github Workflows
|
||||||
|
.github/workflows
|
||||||
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
40
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Electron: Main",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"cwd": "${workspaceRoot}/build",
|
||||||
|
"runtimeArgs": ["--remote-debugging-port=9223", "."],
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"sourceMaps": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Electron: Renderer",
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 9223,
|
||||||
|
"webRoot": "${workspaceFolder}/build",
|
||||||
|
"sourceMaps": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug CLI",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"program": "${workspaceFolder}/build-cli/bwdc.js",
|
||||||
|
"args": ["sync"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Electron: All",
|
||||||
|
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||||
|
|
||||||
|
# Localization (l10n)
|
||||||
|
|
||||||
|
[](https://crowdin.com/project/bitwarden-desktop)
|
||||||
|
|
||||||
|
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||||
|
|
||||||
|
If you are interested in helping translate the Bitwarden desktop app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-desktop
|
||||||
|
|
||||||
|
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/mail/compose/kspearrin).
|
||||||
|
|
||||||
|
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||||
5
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<!--
|
||||||
|
Please do not submit feature requests. The [Community Forums][1] has a
|
||||||
|
section for submitting, voting for, and discussing product feature requests.
|
||||||
|
[1]: https://community.bitwarden.com
|
||||||
|
-->
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 19 November 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,15 +7,17 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
The GNU General Public License is a free, copyleft license for
|
||||||
software and other kinds of works, specifically designed to ensure
|
software and other kinds of works.
|
||||||
cooperation with the community in the case of network server software.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users.
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
Developers that use our General Public Licenses protect your rights
|
To protect your rights, we need to prevent others from denying you
|
||||||
with two steps: (1) assert copyright on the software, and (2) offer
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
you this License which gives you legal permission to copy, distribute
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
and/or modify the software.
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
A secondary benefit of defending all users' freedom is that
|
For example, if you distribute copies of such a program, whether
|
||||||
improvements made in alternate versions of the program, if they
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
receive widespread use, become available for other developers to
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
incorporate. Many developers of free software are heartened and
|
or can get the source code. And you must show them these terms so they
|
||||||
encouraged by the resulting cooperation. However, in the case of
|
know their rights.
|
||||||
software used on network servers, this result may fail to come about.
|
|
||||||
The GNU General Public License permits making a modified version and
|
|
||||||
letting the public access it on a server without ever releasing its
|
|
||||||
source code to the public.
|
|
||||||
|
|
||||||
The GNU Affero General Public License is designed specifically to
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
ensure that, in such cases, the modified source code becomes available
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
to the community. It requires the operator of a network server to
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
provide the source code of the modified version running there to the
|
|
||||||
users of that server. Therefore, public use of a modified version, on
|
|
||||||
a publicly accessible server, gives the public access to the source
|
|
||||||
code of the modified version.
|
|
||||||
|
|
||||||
An older license, called the Affero General Public License and
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
published by Affero, was designed to accomplish similar goals. This is
|
that there is no warranty for this free software. For both users' and
|
||||||
a different license, not a version of the Affero GPL, but Affero has
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
released a new version of the Affero GPL which permits relicensing under
|
changed, so that their problems will not be attributed erroneously to
|
||||||
this license.
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -60,7 +72,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, if you modify the
|
|
||||||
Program, your modified version must prominently offer all users
|
|
||||||
interacting with it remotely through a computer network (if your version
|
|
||||||
supports such interaction) an opportunity to receive the Corresponding
|
|
||||||
Source of your version by providing access to the Corresponding Source
|
|
||||||
from a network server at no charge, through some standard or customary
|
|
||||||
means of facilitating copying of software. This Corresponding Source
|
|
||||||
shall include the Corresponding Source for any work covered by version 3
|
|
||||||
of the GNU General Public License that is incorporated pursuant to the
|
|
||||||
following paragraph.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU General Public License into a single
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the work with which it is combined will remain governed by version
|
but the special requirements of the GNU Affero General Public License,
|
||||||
3 of the GNU General Public License.
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU Affero General Public License from time to time. Such new versions
|
the GNU General Public License from time to time. Such new versions will
|
||||||
will be similar in spirit to the present version, but may differ in detail to
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU Affero General
|
Program specifies that a certain numbered version of the GNU General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU Affero General Public License, you may choose any version ever published
|
GNU General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU Affero General Public License can be used, that proxy's
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -629,33 +631,44 @@ to attach them to the start of each source file to most effectively
|
|||||||
state the exclusion of warranty; and each file should have at least
|
state the exclusion of warranty; and each file should have at least
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as published
|
it under the terms of the GNU General Public License as published by
|
||||||
by the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU Affero General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If your software can interact with users remotely through a computer
|
If the program does terminal interaction, make it output a short
|
||||||
network, you should also make sure that it provides a way for users to
|
notice like this when it starts in an interactive mode:
|
||||||
get its source. For example, if your program is a web application, its
|
|
||||||
interface could display a "Source" link that leads users to an archive
|
{project} Copyright (C) {year} {fullname}
|
||||||
of the code. There are many ways you could offer source, and different
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
solutions will be better for different programs; see section 13 for the
|
This is free software, and you are welcome to redistribute it
|
||||||
specific requirements.
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<http://www.gnu.org/licenses/>.
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
100
README.md
@@ -1,29 +1,105 @@
|
|||||||
[](https://ci.appveyor.com/project/bitwarden/directory-connector)
|

|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# bitwarden Directory Connector
|
# Bitwarden Directory Connector
|
||||||
|
|
||||||
The bitwarden Directory Connector is a command line application used to connect your bitwarden enterprise organization to an existing directory of users and groups.
|
The Bitwarden Directory Connector is a a desktop application used to sync your Bitwarden enterprise organization to an existing directory of users and groups.
|
||||||
It is written in C# with the .NET Framework. It consists of a console application and an optional windows service to run syncs in the background on a specified interval.
|
|
||||||
|
|
||||||
Supported directories:
|
Supported directories:
|
||||||
|
|
||||||
- Active Directory
|
- Active Directory
|
||||||
- Azure Active Directory
|
|
||||||
- GSuite (Google)
|
|
||||||
- Any other LDAP-based directory
|
- Any other LDAP-based directory
|
||||||
|
- Azure Active Directory
|
||||||
|
- G Suite (Google)
|
||||||
|
- Okta
|
||||||
|
|
||||||
<img src="https://i.imgur.com/IdqS0se.png" alt="" width="680" height="479" />
|
The application is written using Electron with Angular and installs on Windows, macOS, and Linux distributions.
|
||||||
|
|
||||||
# Build/Run
|
[](https://bitwarden.com/help/directory-sync/#download-and-install)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Command-line Interface
|
||||||
|
|
||||||
|
A command-line interface tool is also available for the Bitwarden Directory Connector. The Directory Connector CLI (`bwdc`) is written with TypeScript and Node.js and can also be run on Windows, macOS, and Linux distributions.
|
||||||
|
|
||||||
|
## CLI Documentation
|
||||||
|
|
||||||
|
The Bitwarden Directory Connector CLI is self-documented with `--help` content and examples for every command. You should start exploring the CLI by using the global `--help` option:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bwdc --help
|
||||||
|
```
|
||||||
|
|
||||||
|
This option will list all available commands that you can use with the Directory Connector CLI.
|
||||||
|
|
||||||
|
Additionally, you can run the `--help` option on a specific command to learn more about it:
|
||||||
|
|
||||||
|
```
|
||||||
|
bwdc test --help
|
||||||
|
bwdc config --help
|
||||||
|
```
|
||||||
|
|
||||||
|
**Detailed Documentation**
|
||||||
|
|
||||||
|
We provide detailed documentation and examples for using the Directory Connector CLI in our help center at https://bitwarden.com/help/directory-sync-cli/.
|
||||||
|
|
||||||
|
## Build/Run
|
||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
- [Visual Studio](https://www.visualstudio.com/)
|
- [Node.js](https://nodejs.org) v16.13.1 (LTS)
|
||||||
|
- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
|
||||||
|
|
||||||
Open `bitwarden-directory-connector.sln` or `bitwarden-directory-connector-noinstaller.sln`. After restoring the nuget packages, you can build and run the application.
|
**Run the app**
|
||||||
|
|
||||||
# Contribute
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run reset # Only necessary if you have previously run the CLI app
|
||||||
|
npm run rebuild
|
||||||
|
npm run electron
|
||||||
|
```
|
||||||
|
|
||||||
Code contributions are welcome! Visual Studio is required to work on this project. Please commit any pull requests against the `master` branch.
|
**Run the CLI**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run reset # Only necessary if you have previously run the desktop app
|
||||||
|
npm run build:cli:watch
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then run commands from the `./build-cli` folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ./build-cli/bwdc.js --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## We're Hiring!
|
||||||
|
|
||||||
|
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
||||||
|
|
||||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||||
|
|
||||||
|
### Prettier
|
||||||
|
|
||||||
|
We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
|
||||||
|
|
||||||
|
1. Check out your local Branch
|
||||||
|
2. Run `git merge 225073aa335d33ad905877b68336a9288e89ea10`
|
||||||
|
3. Resolve any merge conflicts, commit.
|
||||||
|
4. Run `npm run prettier`
|
||||||
|
5. Commit
|
||||||
|
6. Run `git merge -Xours 096196fcd512944d1c3d9c007647a1319b032639`
|
||||||
|
7. Push
|
||||||
|
|
||||||
|
#### Git blame
|
||||||
|
|
||||||
|
We also recommend that you configure git to ignore the prettier revision using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
```
|
||||||
|
|||||||
48
SECURITY.md
@@ -1,45 +1,21 @@
|
|||||||
bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||||
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
|
||||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
|
||||||
|
|
||||||
# Disclosure Policy
|
# Disclosure Policy
|
||||||
|
|
||||||
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
|
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
|
||||||
effort to quickly resolve the issue.
|
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
||||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
|
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
|
||||||
third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||||
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
|
|
||||||
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
|
||||||
account holder.
|
|
||||||
- If you would like to encrypt your report, please use the PGP key with long ID
|
|
||||||
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
|
||||||
|
|
||||||
# In-scope
|
|
||||||
|
|
||||||
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
|
||||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
|
||||||
code is available at https://github.com/bitwarden.
|
|
||||||
|
|
||||||
# Exclusions
|
|
||||||
|
|
||||||
The following bug classes are out-of scope:
|
|
||||||
|
|
||||||
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
|
||||||
or that we already know of. Note that some of our issue tracking is private.
|
|
||||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
|
||||||
upstream maintainer.
|
|
||||||
- Attacks requiring physical access to a user's device.
|
|
||||||
- Self-XSS
|
|
||||||
- Issues related to software or protocols not under bitwarden's control
|
|
||||||
- Vulnerabilities in outdated versions of bitwarden
|
|
||||||
- Missing security best practices that do not directly lead to a vulnerability
|
|
||||||
- Issues that do not have any impact on the general public
|
|
||||||
|
|
||||||
While researching, we'd like to ask you to refrain from:
|
While researching, we'd like to ask you to refrain from:
|
||||||
|
|
||||||
- Denial of service
|
- Denial of service
|
||||||
- Spamming
|
- Spamming
|
||||||
- Social engineering (including phishing) of bitwarden staff or contractors
|
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||||
- Any physical attempts against bitwarden property or data centers
|
- Any physical attempts against Bitwarden property or data centers
|
||||||
|
|
||||||
Thank you for helping keep bitwarden and our users safe!
|
# We want to help you!
|
||||||
|
|
||||||
|
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
|
||||||
|
|
||||||
|
Thank you for helping keep Bitwarden and our users safe!
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 15
|
|
||||||
VisualStudioVersion = 15.0.26730.10
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "src\Console\Console.csproj", "{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{AE082484-A34C-4B3A-A69F-49E5EF298B27}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "src\Service\Service.csproj", "{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {52CB25C1-56A6-4FBE-977C-E842EA0AFAD7}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio 15
|
|
||||||
VisualStudioVersion = 15.0.27004.2009
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Console", "src\Console\Console.csproj", "{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{AE082484-A34C-4B3A-A69F-49E5EF298B27}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Service", "src\Service\Service.csproj", "{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}"
|
|
||||||
EndProject
|
|
||||||
Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup", "src\Setup\Setup.vdproj", "{4D852DF8-9327-43D0-93AB-FA68D4F3414B}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{DD4E5CD2-C9DD-4912-9A25-1600A07BF8C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AE082484-A34C-4B3A-A69F-49E5EF298B27}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{A8FD8CED-5510-4EBD-AACE-5D3CBB7516DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4D852DF8-9327-43D0-93AB-FA68D4F3414B}.Debug|Any CPU.ActiveCfg = Debug
|
|
||||||
{4D852DF8-9327-43D0-93AB-FA68D4F3414B}.Release|Any CPU.ActiveCfg = Release
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {AB7EF6F7-C38B-4E66-BBF9-1F6915896CB0}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
1
jslib
Submodule
20619
package-lock.json
generated
Normal file
220
package.json
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
{
|
||||||
|
"name": "@bitwarden/directory-connector",
|
||||||
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"keywords": [
|
||||||
|
"bitwarden",
|
||||||
|
"password",
|
||||||
|
"vault",
|
||||||
|
"password manager"
|
||||||
|
],
|
||||||
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
|
"homepage": "https://bitwarden.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitwarden/directory-connector"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"sub:init": "git submodule update --init --recursive",
|
||||||
|
"sub:update": "git submodule update --remote",
|
||||||
|
"sub:pull": "git submodule foreach git pull origin master",
|
||||||
|
"sub:commit": "npm run sub:pull && git commit -am \"update submodule\"",
|
||||||
|
"preinstall": "npm run sub:init",
|
||||||
|
"symlink:win": "rm -rf ./jslib && cmd /c mklink /J .\\jslib ..\\jslib",
|
||||||
|
"symlink:mac": "npm run symlink:lin",
|
||||||
|
"symlink:lin": "rm -rf ./jslib && ln -s ../jslib ./jslib",
|
||||||
|
"rebuild": "electron-rebuild",
|
||||||
|
"reset": "rimraf ./node_modules/keytar/* && npm install",
|
||||||
|
"lint": "eslint . && prettier --check .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"build": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\"",
|
||||||
|
"build:main": "webpack --config webpack.main.js",
|
||||||
|
"build:renderer": "webpack --config webpack.renderer.js",
|
||||||
|
"build:renderer:watch": "webpack --config webpack.renderer.js --watch",
|
||||||
|
"build:dist": "npm run reset && npm run rebuild && npm run build",
|
||||||
|
"build:cli": "webpack --config webpack.cli.js",
|
||||||
|
"build:cli:watch": "webpack --config webpack.cli.js --watch",
|
||||||
|
"build:cli:prod": "cross-env NODE_ENV=production webpack --config webpack.cli.js",
|
||||||
|
"build:cli:prod:watch": "cross-env NODE_ENV=production webpack --config webpack.cli.js --watch",
|
||||||
|
"electron": "npm run build:main && concurrently -k -n Main,Rend -c yellow,cyan \"electron --inspect=5858 ./build --watch\" \"npm run build:renderer:watch\"",
|
||||||
|
"clean:dist": "rimraf ./dist/*",
|
||||||
|
"clean:dist:cli": "rimraf ./dist-cli/*",
|
||||||
|
"pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never",
|
||||||
|
"pack:mac": "npm run clean:dist && electron-builder --mac -p never",
|
||||||
|
"pack:win": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
|
"pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --ia32 -p never",
|
||||||
|
"pack:cli": "npm run pack:cli:win | npm run pack:cli:mac | npm run pack:cli:lin",
|
||||||
|
"pack:cli:win": "pkg ./src-cli --targets win-x64 --output ./dist-cli/windows/bwdc.exe",
|
||||||
|
"pack:cli:mac": "pkg ./src-cli --targets macos-x64 --output ./dist-cli/macos/bwdc",
|
||||||
|
"pack:cli:lin": "pkg ./src-cli --targets linux-x64 --output ./dist-cli/linux/bwdc",
|
||||||
|
"dist:lin": "npm run build:dist && npm run pack:lin",
|
||||||
|
"dist:mac": "npm run build:dist && npm run pack:mac",
|
||||||
|
"dist:win": "npm run build:dist && npm run pack:win",
|
||||||
|
"dist:win:ci": "npm run build && npm run pack:win:ci",
|
||||||
|
"dist:cli": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli",
|
||||||
|
"dist:cli:win": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:win",
|
||||||
|
"dist:cli:mac": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:mac",
|
||||||
|
"dist:cli:lin": "npm run build:cli:prod && npm run clean:dist:cli && npm run pack:cli:lin",
|
||||||
|
"publish:lin": "npm run build:dist && npm run clean:dist && electron-builder --linux --x64 -p always",
|
||||||
|
"publish:mac": "npm run build:dist && npm run clean:dist && electron-builder --mac -p always",
|
||||||
|
"publish:win": "npm run build:dist && npm run clean:dist && electron-builder --win --x64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"",
|
||||||
|
"prettier": "prettier --write .",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"extraMetadata": {
|
||||||
|
"name": "bitwarden-directory-connector"
|
||||||
|
},
|
||||||
|
"productName": "Bitwarden Directory Connector",
|
||||||
|
"appId": "com.bitwarden.directory-connector",
|
||||||
|
"copyright": "Copyright © 2015-2022 Bitwarden Inc.",
|
||||||
|
"directories": {
|
||||||
|
"buildResources": "resources",
|
||||||
|
"output": "dist",
|
||||||
|
"app": "build"
|
||||||
|
},
|
||||||
|
"afterSign": "scripts/notarize.js",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"entitlements": "resources/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "resources/entitlements.mac.plist",
|
||||||
|
"target": [
|
||||||
|
"dmg",
|
||||||
|
"zip"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"portable",
|
||||||
|
"nsis"
|
||||||
|
],
|
||||||
|
"sign": "scripts/sign.js"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"category": "Utility",
|
||||||
|
"synopsis": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"target": [
|
||||||
|
"AppImage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dmg": {
|
||||||
|
"artifactName": "Bitwarden-Connector-${version}.${ext}",
|
||||||
|
"icon": "dmg.icns",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"x": 150,
|
||||||
|
"y": 185,
|
||||||
|
"type": "file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 390,
|
||||||
|
"y": 180,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/Applications"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"width": 540,
|
||||||
|
"height": 380
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"perMachine": true,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"artifactName": "Bitwarden-Connector-Installer-${version}.${ext}",
|
||||||
|
"uninstallDisplayName": "${productName}",
|
||||||
|
"deleteAppDataOnUninstall": true
|
||||||
|
},
|
||||||
|
"portable": {
|
||||||
|
"artifactName": "Bitwarden-Connector-Portable-${version}.${ext}"
|
||||||
|
},
|
||||||
|
"appImage": {
|
||||||
|
"artifactName": "Bitwarden-Connector-${version}-${arch}.${ext}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/compiler-cli": "^12.2.13",
|
||||||
|
"@microsoft/microsoft-graph-types": "^1.4.0",
|
||||||
|
"@ngtools/webpack": "^12.2.13",
|
||||||
|
"@types/ldapjs": "^1.0.10",
|
||||||
|
"@types/node": "^16.11.12",
|
||||||
|
"@types/proper-lockfile": "^4.1.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||||
|
"@typescript-eslint/parser": "^5.12.1",
|
||||||
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"concurrently": "^6.0.2",
|
||||||
|
"copy-webpack-plugin": "^10.0.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"css-loader": "^6.5.1",
|
||||||
|
"electron-builder": "^22.14.5",
|
||||||
|
"electron-notarize": "^1.1.1",
|
||||||
|
"electron-rebuild": "^3.2.5",
|
||||||
|
"electron-reload": "^1.5.0",
|
||||||
|
"eslint": "^8.9.0",
|
||||||
|
"eslint-config-prettier": "^8.4.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
|
"eslint-plugin-import": "^2.25.4",
|
||||||
|
"html-loader": "^3.0.1",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"husky": "^7.0.4",
|
||||||
|
"lint-staged": "^12.1.3",
|
||||||
|
"mini-css-extract-plugin": "^2.4.5",
|
||||||
|
"node-loader": "^2.0.0",
|
||||||
|
"pkg": "^5.5.1",
|
||||||
|
"prebuild-install": "^5.0.0",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"sass": "^1.32.11",
|
||||||
|
"sass-loader": "^12.4.0",
|
||||||
|
"tapable": "^1.1.3",
|
||||||
|
"ts-loader": "^9.2.5",
|
||||||
|
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||||
|
"typescript": "4.3.5",
|
||||||
|
"webpack": "^5.64.4",
|
||||||
|
"webpack-cli": "^4.9.1",
|
||||||
|
"webpack-merge": "^5.8.0",
|
||||||
|
"webpack-node-externals": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^12.2.13",
|
||||||
|
"@angular/cdk": "^12.2.13",
|
||||||
|
"@angular/common": "^12.2.13",
|
||||||
|
"@angular/compiler": "^12.2.13",
|
||||||
|
"@angular/core": "^12.2.13",
|
||||||
|
"@angular/forms": "^12.2.13",
|
||||||
|
"@angular/platform-browser": "^12.2.13",
|
||||||
|
"@angular/platform-browser-dynamic": "^12.2.13",
|
||||||
|
"@angular/router": "^12.2.13",
|
||||||
|
"@bitwarden/jslib-angular": "file:jslib/angular",
|
||||||
|
"@bitwarden/jslib-common": "file:jslib/common",
|
||||||
|
"@bitwarden/jslib-electron": "file:jslib/electron",
|
||||||
|
"@bitwarden/jslib-node": "file:jslib/node",
|
||||||
|
"@microsoft/microsoft-graph-client": "^2.2.1",
|
||||||
|
"bootstrap": "^4.6.0",
|
||||||
|
"chalk": "^4.1.1",
|
||||||
|
"commander": "^7.2.0",
|
||||||
|
"core-js": "^3.11.0",
|
||||||
|
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"googleapis": "^73.0.0",
|
||||||
|
"inquirer": "8.0.0",
|
||||||
|
"ldapjs": "2.3.1",
|
||||||
|
"lunr": "^2.3.9",
|
||||||
|
"ngx-toastr": "14.1.4",
|
||||||
|
"open": "^8.0.6",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
|
"rxjs": "^7.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "~16",
|
||||||
|
"npm": "~8"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"./!(jslib)**": "prettier --ignore-unknown --write",
|
||||||
|
"*.ts": "eslint --fix"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
resources/background.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
resources/dmg.icns
Normal file
BIN
resources/dmg.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
resources/dmg.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
resources/dmg.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 692 B |
BIN
resources/dmg.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/dmg.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
resources/dmg.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
resources/dmg.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/dmg.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/dmg.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
resources/dmg.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 492 KiB |
10
resources/entitlements.mac.plist
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
resources/icon.icns
Normal file
BIN
resources/icon.ico
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
resources/icon.iconset/icon_128x128.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/icon.iconset/icon_128x128@2x.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icon.iconset/icon_16x16.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
resources/icon.iconset/icon_16x16@2x.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icon.iconset/icon_256x256.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icon.iconset/icon_256x256@2x.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icon.iconset/icon_32x32.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icon.iconset/icon_32x32@2x.png
Normal file
|
After Width: | Height: | Size: 823 B |
BIN
resources/icon.iconset/icon_512x512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icon.iconset/icon_512x512@2x.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
resources/icon.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
resources/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
resources/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
resources/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 507 B |
BIN
resources/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 681 B |
BIN
resources/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
resources/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 961 B |
BIN
resources/installerSidebar.bmp
Normal file
|
After Width: | Height: | Size: 151 KiB |
19
scripts/notarize.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
require("dotenv").config();
|
||||||
|
const { notarize } = require("electron-notarize");
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
|
const { electronPlatformName, appOutDir } = context;
|
||||||
|
if (electronPlatformName !== "darwin") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const appleId = process.env.APPLE_ID_USERNAME || process.env.APPLEID;
|
||||||
|
const appleIdPassword = process.env.APPLE_ID_PASSWORD || `@keychain:AC_PASSWORD`;
|
||||||
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
return await notarize({
|
||||||
|
appBundleId: "com.bitwarden.directory-connector",
|
||||||
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
|
appleId: appleId,
|
||||||
|
appleIdPassword: appleIdPassword,
|
||||||
|
});
|
||||||
|
};
|
||||||
21
scripts/sign.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||||
|
exports.default = async function (configuration) {
|
||||||
|
if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") {
|
||||||
|
console.log(`[*] Signing file: ${configuration.path}`);
|
||||||
|
require("child_process").execSync(
|
||||||
|
`azuresigntool sign ` +
|
||||||
|
`-kvu ${process.env.SIGNING_VAULT_URL} ` +
|
||||||
|
`-kvi ${process.env.SIGNING_CLIENT_ID} ` +
|
||||||
|
`-kvt ${process.env.SIGNING_TENANT_ID} ` +
|
||||||
|
`-kvs ${process.env.SIGNING_CLIENT_SECRET} ` +
|
||||||
|
`-kvc ${process.env.SIGNING_CERT_NAME} ` +
|
||||||
|
`-fd ${configuration.hash} ` +
|
||||||
|
`-du ${configuration.site} ` +
|
||||||
|
`-tr http://timestamp.digicert.com ` +
|
||||||
|
`"${configuration.path}"`,
|
||||||
|
{
|
||||||
|
stdio: "inherit",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
1006
src-cli/package-lock.json
generated
Normal file
24
src-cli/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "@bitwarden/directory-connector",
|
||||||
|
"productName": "Bitwarden Directory Connector",
|
||||||
|
"description": "Sync your user directory to your Bitwarden organization.",
|
||||||
|
"version": "2.9.5",
|
||||||
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
|
"homepage": "https://bitwarden.com",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"main": "main.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/bitwarden/directory-connector"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"bwdc": "../build-cli/bwdc.js"
|
||||||
|
},
|
||||||
|
"pkg": {
|
||||||
|
"assets": "../build-cli/**/*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"browser-hrtime": "^1.1.8",
|
||||||
|
"keytar": "^7.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net461</TargetFramework>
|
|
||||||
<RootNamespace>Bit.Console</RootNamespace>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Core\Core.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net461</TargetFramework>
|
|
||||||
<RootNamespace>Bit.Core</RootNamespace>
|
|
||||||
<AssemblyName>Core</AssemblyName>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Google.Apis.Admin.Directory.directory_v1" Version="1.31.0.1061" />
|
|
||||||
<PackageReference Include="Microsoft.Graph" Version="1.6.2" />
|
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.17.2" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
|
||||||
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
|
|
||||||
<Reference Include="System.DirectoryServices" />
|
|
||||||
<Reference Include="System.ServiceProcess" />
|
|
||||||
<Reference Include="System.Security" />
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum DirectoryType : byte
|
|
||||||
{
|
|
||||||
ActiveDirectory = 0,
|
|
||||||
AzureActiveDirectory = 1,
|
|
||||||
Other = 2,
|
|
||||||
GSuite = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum OrganizationUserStatusType : byte
|
|
||||||
{
|
|
||||||
Invited = 0,
|
|
||||||
Accepted = 1,
|
|
||||||
Confirmed = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum OrganizationUserType : byte
|
|
||||||
{
|
|
||||||
Owner = 0,
|
|
||||||
Admin = 1,
|
|
||||||
User = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
public enum TwoFactorProviderType : byte
|
|
||||||
{
|
|
||||||
Authenticator = 0,
|
|
||||||
Email = 1,
|
|
||||||
Duo = 2,
|
|
||||||
YubiKey = 3,
|
|
||||||
U2f = 4,
|
|
||||||
Remember = 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Enums
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum UserAccountControl : int
|
|
||||||
{
|
|
||||||
AccountDisabled = 0x00000002,
|
|
||||||
LockOut = 0x00000010,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ApiError
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ApiResult<T>
|
|
||||||
{
|
|
||||||
private List<ApiError> m_errors = new List<ApiError>();
|
|
||||||
|
|
||||||
public bool Succeeded { get; private set; }
|
|
||||||
public T Result { get; set; }
|
|
||||||
public IEnumerable<ApiError> Errors => m_errors;
|
|
||||||
public HttpStatusCode StatusCode { get; private set; }
|
|
||||||
|
|
||||||
public static ApiResult<T> Success(T result, HttpStatusCode statusCode)
|
|
||||||
{
|
|
||||||
return new ApiResult<T>
|
|
||||||
{
|
|
||||||
Succeeded = true,
|
|
||||||
Result = result,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiResult<T> Failed(HttpStatusCode statusCode, params ApiError[] errors)
|
|
||||||
{
|
|
||||||
var result = new ApiResult<T>
|
|
||||||
{
|
|
||||||
Succeeded = false,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if(errors != null)
|
|
||||||
{
|
|
||||||
result.m_errors.AddRange(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ApiResult
|
|
||||||
{
|
|
||||||
private List<ApiError> m_errors = new List<ApiError>();
|
|
||||||
|
|
||||||
public bool Succeeded { get; private set; }
|
|
||||||
public IEnumerable<ApiError> Errors => m_errors;
|
|
||||||
public HttpStatusCode StatusCode { get; private set; }
|
|
||||||
|
|
||||||
public static ApiResult Success(HttpStatusCode statusCode)
|
|
||||||
{
|
|
||||||
return new ApiResult
|
|
||||||
{
|
|
||||||
Succeeded = true,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiResult Failed(HttpStatusCode statusCode, params ApiError[] errors)
|
|
||||||
{
|
|
||||||
var result = new ApiResult
|
|
||||||
{
|
|
||||||
Succeeded = false,
|
|
||||||
StatusCode = statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if(errors != null)
|
|
||||||
{
|
|
||||||
result.m_errors.AddRange(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class AzureConfiguration
|
|
||||||
{
|
|
||||||
public string Tenant { get; set; } = "yourcompany.onmicrosoft.com";
|
|
||||||
public string Id { get; set; }
|
|
||||||
public EncryptedData Secret { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class EncryptedData
|
|
||||||
{
|
|
||||||
public EncryptedData() { }
|
|
||||||
|
|
||||||
public EncryptedData(byte[] plainValue)
|
|
||||||
{
|
|
||||||
IV = RandomBytes();
|
|
||||||
#if NET461
|
|
||||||
Value = ProtectedData.Protect(plainValue, IV, DataProtectionScope.LocalMachine);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncryptedData(string plainValue)
|
|
||||||
{
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(plainValue);
|
|
||||||
IV = RandomBytes();
|
|
||||||
#if NET461
|
|
||||||
Value = ProtectedData.Protect(bytes, IV, DataProtectionScope.LocalMachine);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Value { get; set; }
|
|
||||||
public byte[] IV { get; set; }
|
|
||||||
|
|
||||||
public byte[] Decrypt()
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
return ProtectedData.Unprotect(Value, IV, DataProtectionScope.LocalMachine);
|
|
||||||
#else
|
|
||||||
return new byte[0];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DecryptToString()
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
var bytes = ProtectedData.Unprotect(Value, IV, DataProtectionScope.LocalMachine);
|
|
||||||
#else
|
|
||||||
var bytes = new byte[0];
|
|
||||||
#endif
|
|
||||||
return Encoding.UTF8.GetString(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] RandomBytes()
|
|
||||||
{
|
|
||||||
var entropy = new byte[16];
|
|
||||||
new RNGCryptoServiceProvider().GetBytes(entropy);
|
|
||||||
return entropy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public abstract class Entry
|
|
||||||
{
|
|
||||||
public string ReferenceId { get; set; }
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public DateTime? CreationDate { get; set; }
|
|
||||||
public DateTime? RevisionDate { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GroupEntry : Entry
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public HashSet<string> UserMemberExternalIds { get; set; } = new HashSet<string>();
|
|
||||||
public HashSet<string> GroupMemberReferenceIds { get; set; } = new HashSet<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UserEntry : Entry
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public bool Disabled { get; set; }
|
|
||||||
public bool Deleted { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ErrorResponse
|
|
||||||
{
|
|
||||||
public string Message { get; set; }
|
|
||||||
public Dictionary<string, IEnumerable<string>> ValidationErrors { get; set; }
|
|
||||||
// For use in development environments.
|
|
||||||
public string ExceptionMessage { get; set; }
|
|
||||||
public string ExceptionStackTrace { get; set; }
|
|
||||||
public string InnerExceptionMessage { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class GSuiteConfiguration
|
|
||||||
{
|
|
||||||
public string SecretFile { get; set; } = "client_secret.json";
|
|
||||||
public string Customer { get; set; }
|
|
||||||
public string Domain { get; set; } = "yourcompany.com";
|
|
||||||
public string AdminUser { get; set; } = "adminuser@yourcompany.com";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ImportRequest
|
|
||||||
{
|
|
||||||
public ImportRequest(List<GroupEntry> groups, List<UserEntry> users)
|
|
||||||
{
|
|
||||||
Groups = groups?.Select(g => new Group(g)).ToArray() ?? new Group[] { };
|
|
||||||
Users = users?.Select(u => new User(u)).ToArray() ?? new User[] { };
|
|
||||||
}
|
|
||||||
|
|
||||||
public Group[] Groups { get; set; }
|
|
||||||
public User[] Users { get; set; }
|
|
||||||
|
|
||||||
public class Group
|
|
||||||
{
|
|
||||||
public Group(GroupEntry entry)
|
|
||||||
{
|
|
||||||
Name = entry.Name;
|
|
||||||
ExternalId = entry.ExternalId;
|
|
||||||
Users = entry.UserMemberExternalIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public IEnumerable<string> Users { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
public User(UserEntry entry)
|
|
||||||
{
|
|
||||||
Email = entry.Email;
|
|
||||||
Deleted = (SettingsService.Instance.Sync.RemoveDisabledUsers && entry.Disabled) || entry.Deleted;
|
|
||||||
ExternalId = entry.ExternalId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ExternalId { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public bool Deleted { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
using Bit.Core.Services;
|
|
||||||
using System;
|
|
||||||
#if NET461
|
|
||||||
using System.DirectoryServices;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class LdapConfiguration
|
|
||||||
{
|
|
||||||
public string Address { get; set; }
|
|
||||||
public string Port { get; set; } = "389";
|
|
||||||
public string Path { get; set; }
|
|
||||||
public string Username { get; set; }
|
|
||||||
public EncryptedData Password { get; set; }
|
|
||||||
public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
|
|
||||||
|
|
||||||
#if NET461
|
|
||||||
public DirectoryEntry GetUserDirectoryEntry()
|
|
||||||
{
|
|
||||||
return GetPathedDirectoryEntry(SettingsService.Instance.Sync.Ldap.UserPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetGroupDirectoryEntry()
|
|
||||||
{
|
|
||||||
return GetPathedDirectoryEntry(SettingsService.Instance.Sync.Ldap.GroupPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetPathedDirectoryEntry(string pathPrefix = null)
|
|
||||||
{
|
|
||||||
var path = Path;
|
|
||||||
if(!string.IsNullOrWhiteSpace(pathPrefix))
|
|
||||||
{
|
|
||||||
path = string.Concat(pathPrefix, ",", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetDirectoryEntry(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetBasePathDirectoryEntry()
|
|
||||||
{
|
|
||||||
var path = Path.Substring(Path.IndexOf("dc=", StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
return GetDirectoryEntry(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DirectoryEntry GetDirectoryEntry(string path = null)
|
|
||||||
{
|
|
||||||
if(Password == null && string.IsNullOrWhiteSpace(Username))
|
|
||||||
{
|
|
||||||
return new DirectoryEntry(ServerPath(path));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new DirectoryEntry(ServerPath(path), Username, Password.DecryptToString(), AuthenticationTypes.None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private string ServerPath(string path)
|
|
||||||
{
|
|
||||||
return $"LDAP://{Address}:{Port}/{path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class LoginResult
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
|
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public List<Organization> Organizations { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class Organization
|
|
||||||
{
|
|
||||||
public Organization() { }
|
|
||||||
|
|
||||||
public Organization(ProfileOrganizationResponseModel org)
|
|
||||||
{
|
|
||||||
Name = org.Name;
|
|
||||||
Id = org.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ProfileOrganizationResponseModel
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Key { get; set; }
|
|
||||||
public OrganizationUserStatusType Status { get; set; }
|
|
||||||
public OrganizationUserType Type { get; set; }
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ProfileResponse
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHint { get; set; }
|
|
||||||
public string Culture { get; set; }
|
|
||||||
public bool TwoFactorEnabled { get; set; }
|
|
||||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class ServerConfiguration
|
|
||||||
{
|
|
||||||
public Enums.DirectoryType Type { get; set; } = Enums.DirectoryType.ActiveDirectory;
|
|
||||||
public LdapConfiguration Ldap { get; set; }
|
|
||||||
public AzureConfiguration Azure { get; set; }
|
|
||||||
public GSuiteConfiguration GSuite { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class SyncConfiguration
|
|
||||||
{
|
|
||||||
public SyncConfiguration() { }
|
|
||||||
|
|
||||||
public SyncConfiguration(DirectoryType type)
|
|
||||||
{
|
|
||||||
Ldap = new LdapSyncConfiguration(type);
|
|
||||||
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case DirectoryType.ActiveDirectory:
|
|
||||||
break;
|
|
||||||
case DirectoryType.AzureActiveDirectory:
|
|
||||||
break;
|
|
||||||
case DirectoryType.Other:
|
|
||||||
break;
|
|
||||||
case DirectoryType.GSuite:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Depending on what server type you are using, filters are be one of the following:
|
|
||||||
*
|
|
||||||
* 1. ActiveDirectory or Other
|
|
||||||
* - LDAP query/filter syntax
|
|
||||||
* - Read more at: http://bit.ly/2qyLpzW
|
|
||||||
* - ex. "(&(givenName=John)(|(l=Dallas)(l=Austin)))"
|
|
||||||
*
|
|
||||||
* 2. AzureActiveDirectory
|
|
||||||
* - OData syntax for a Microsoft Graph query parameter '$filter'
|
|
||||||
* - Read more at http://bit.ly/2q3FOOD
|
|
||||||
* - ex. "startswith(displayName,'J')"
|
|
||||||
*
|
|
||||||
* 3. G Suite
|
|
||||||
* - Group Filter
|
|
||||||
* - Custom filtering syntax that allows you to exclude or include a comma separated list of group names.
|
|
||||||
* - ex. "include:Group A,Sales People,My Other Group"
|
|
||||||
* or "exclude:Group C,Developers,Some Other Group"
|
|
||||||
* - User Filter
|
|
||||||
* - Custom filtering syntax that allows you to exclude or include a comma separated list of group names.
|
|
||||||
* - Allows you to concatenate a G Suite Admin API user search query to the end of the filter after delimiting
|
|
||||||
* the include/exclude filter with a pipe (|).
|
|
||||||
* - Read more at http://bit.ly/2rlTskX
|
|
||||||
* - ex.
|
|
||||||
* or "include:joe@company.com,bill@company.com,tom@company.com"
|
|
||||||
* or "exclude:john@company.com,bill@company.com|orgName=Engineering orgTitle:Manager"
|
|
||||||
* or "|orgName=Engineering orgTitle:Manager"
|
|
||||||
*/
|
|
||||||
|
|
||||||
public string GroupFilter { get; set; }
|
|
||||||
public string UserFilter { get; set; }
|
|
||||||
|
|
||||||
public bool SyncGroups { get; set; } = true;
|
|
||||||
public bool SyncUsers { get; set; } = true;
|
|
||||||
public int IntervalMinutes { get; set; } = 5;
|
|
||||||
public bool RemoveDisabledUsers { get; set; }
|
|
||||||
public LdapSyncConfiguration Ldap { get; set; } = new LdapSyncConfiguration();
|
|
||||||
|
|
||||||
public class LdapSyncConfiguration
|
|
||||||
{
|
|
||||||
public LdapSyncConfiguration() { }
|
|
||||||
|
|
||||||
public LdapSyncConfiguration(DirectoryType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case DirectoryType.ActiveDirectory:
|
|
||||||
CreationDateAttribute = "whenCreated";
|
|
||||||
RevisionDateAttribute = "whenChanged";
|
|
||||||
UserEmailPrefixAttribute = "sAMAccountName";
|
|
||||||
UserPath = "CN=Users";
|
|
||||||
GroupPath = "CN=Users";
|
|
||||||
break;
|
|
||||||
case DirectoryType.Other:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UserPath { get; set; }
|
|
||||||
public string GroupPath { get; set; }
|
|
||||||
public string UserObjectClass { get; set; } = "person";
|
|
||||||
public string GroupObjectClass { get; set; } = "group";
|
|
||||||
public string MemberAttribute { get; set; } = "member";
|
|
||||||
public string GroupNameAttribute { get; set; } = "name";
|
|
||||||
public string UserEmailAttribute { get; set; } = "mail";
|
|
||||||
public bool EmailPrefixSuffix { get; set; } = false;
|
|
||||||
public string UserEmailPrefixAttribute { get; set; } = "cn";
|
|
||||||
public string UserEmailSuffix { get; set; } = "@companyname.com";
|
|
||||||
public string CreationDateAttribute { get; set; }
|
|
||||||
public string RevisionDateAttribute { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class SyncResult
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string ErrorMessage { get; set; }
|
|
||||||
public List<GroupEntry> Groups { get; set; } = new List<GroupEntry>();
|
|
||||||
public List<UserEntry> Users { get; set; } = new List<UserEntry>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TokenRequest
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
public string Token { get; set; }
|
|
||||||
public TwoFactorProviderType? Provider { get; set; }
|
|
||||||
public bool Remember { get; set; }
|
|
||||||
|
|
||||||
public IDictionary<string, string> ToIdentityTokenRequest()
|
|
||||||
{
|
|
||||||
var dict = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "grant_type", "password" },
|
|
||||||
{ "username", Email },
|
|
||||||
{ "password", MasterPasswordHash },
|
|
||||||
{ "scope", "api offline_access" },
|
|
||||||
{ "client_id", "connector" }
|
|
||||||
};
|
|
||||||
|
|
||||||
if(Token != null && Provider.HasValue)
|
|
||||||
{
|
|
||||||
dict.Add("TwoFactorToken", Token);
|
|
||||||
dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
|
|
||||||
dict.Add("TwoFactorRemember", Remember ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TokenResponse
|
|
||||||
{
|
|
||||||
[JsonProperty("access_token")]
|
|
||||||
public string AccessToken { get; set; }
|
|
||||||
[JsonProperty("expires_in")]
|
|
||||||
public long ExpiresIn { get; set; }
|
|
||||||
[JsonProperty("refresh_token")]
|
|
||||||
public string RefreshToken { get; set; }
|
|
||||||
[JsonProperty("token_type")]
|
|
||||||
public string TokenType { get; set; }
|
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
|
|
||||||
public string PrivateKey { get; set; }
|
|
||||||
public string TwoFactorToken { get; set; }
|
|
||||||
public string Key { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace Bit.Core.Models
|
|
||||||
{
|
|
||||||
public class TwoFactorEmailRequest
|
|
||||||
{
|
|
||||||
public string Email { get; set; }
|
|
||||||
public string MasterPasswordHash { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class ApiService
|
|
||||||
{
|
|
||||||
private static ApiService _instance;
|
|
||||||
|
|
||||||
private ApiService()
|
|
||||||
{
|
|
||||||
Client = new HttpClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new ApiService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HttpClient Client { get; private set; }
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj)
|
|
||||||
{
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.IdentityBaseUrl, "/connect/token")),
|
|
||||||
Content = new FormUrlEncodedContent(requestObj.ToIdentityTokenRequest())
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var errorResponse = JObject.Parse(responseContent);
|
|
||||||
if(errorResponse["TwoFactorProviders2"] != null)
|
|
||||||
{
|
|
||||||
return ApiResult<TokenResponse>.Success(new TokenResponse
|
|
||||||
{
|
|
||||||
TwoFactorProviders2 = errorResponse["TwoFactorProviders2"]
|
|
||||||
.ToObject<Dictionary<TwoFactorProviderType, Dictionary<string, object>>>()
|
|
||||||
}, response.StatusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await HandleErrorAsync<TokenResponse>(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseObj = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
|
||||||
return ApiResult<TokenResponse>.Success(responseObj, response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException<TokenResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult> PostImportAsync(ImportRequest requestObj)
|
|
||||||
{
|
|
||||||
var tokenStateResponse = await HandleTokenStateAsync();
|
|
||||||
if(!tokenStateResponse.Succeeded)
|
|
||||||
{
|
|
||||||
return tokenStateResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stringContent = JsonConvert.SerializeObject(requestObj);
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/organizations/",
|
|
||||||
SettingsService.Instance.Organization.Id, "/import")),
|
|
||||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json"),
|
|
||||||
};
|
|
||||||
|
|
||||||
requestMessage.Headers.Add("Authorization", $"Bearer {TokenService.Instance.AccessToken}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResult.Success(response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult<ProfileResponse>> GetProfileAsync()
|
|
||||||
{
|
|
||||||
var tokenStateResponse = await HandleTokenStateAsync<ProfileResponse>();
|
|
||||||
if(!tokenStateResponse.Succeeded)
|
|
||||||
{
|
|
||||||
return tokenStateResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Get,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/accounts/profile")),
|
|
||||||
};
|
|
||||||
|
|
||||||
requestMessage.Headers.Add("Authorization", $"Bearer {TokenService.Instance.AccessToken}");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync<ProfileResponse>(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
var responseObj = JsonConvert.DeserializeObject<ProfileResponse>(responseContent);
|
|
||||||
return ApiResult<ProfileResponse>.Success(responseObj, response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException<ProfileResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<ApiResult> PostTwoFactorSendEmailLoginAsync(TwoFactorEmailRequest requestObj)
|
|
||||||
{
|
|
||||||
var stringContent = JsonConvert.SerializeObject(requestObj);
|
|
||||||
|
|
||||||
var requestMessage = new HttpRequestMessage()
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.ApiBaseUrl, "/two-factor/send-email-login")),
|
|
||||||
Content = new StringContent(stringContent, Encoding.UTF8, "application/json")
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await HandleErrorAsync(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResult.Success(response.StatusCode);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return HandledWebException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ApiResult HandledWebException()
|
|
||||||
{
|
|
||||||
return ApiResult.Failed(HttpStatusCode.BadGateway,
|
|
||||||
new ApiError { Message = "There is a problem connecting to the server." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ApiResult<T> HandledWebException<T>()
|
|
||||||
{
|
|
||||||
return ApiResult<T>.Failed(HttpStatusCode.BadGateway,
|
|
||||||
new ApiError { Message = "There is a problem connecting to the server." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult> HandleTokenStateAsync()
|
|
||||||
{
|
|
||||||
return await HandleTokenStateAsync(
|
|
||||||
() => ApiResult.Success(HttpStatusCode.OK),
|
|
||||||
() => HandledWebException(),
|
|
||||||
(r) => HandleErrorAsync(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult<T>> HandleTokenStateAsync<T>()
|
|
||||||
{
|
|
||||||
return await HandleTokenStateAsync(
|
|
||||||
() => ApiResult<T>.Success(default(T), HttpStatusCode.OK),
|
|
||||||
() => HandledWebException<T>(),
|
|
||||||
(r) => HandleErrorAsync<T>(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<T> HandleTokenStateAsync<T>(Func<T> success, Func<T> webException,
|
|
||||||
Func<HttpResponseMessage, Task<T>> error)
|
|
||||||
{
|
|
||||||
if(TokenService.Instance.AccessTokenNeedsRefresh && !string.IsNullOrWhiteSpace(TokenService.Instance.RefreshToken))
|
|
||||||
{
|
|
||||||
var requestMessage = new HttpRequestMessage
|
|
||||||
{
|
|
||||||
Method = HttpMethod.Post,
|
|
||||||
RequestUri = new Uri(string.Concat(SettingsService.Instance.IdentityBaseUrl, "/connect/token")),
|
|
||||||
Content = new FormUrlEncodedContent(
|
|
||||||
new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{ "grant_type", "refresh_token" },
|
|
||||||
{ "client_id", "connector" },
|
|
||||||
{ "refresh_token", TokenService.Instance.RefreshToken }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Client.SendAsync(requestMessage).ConfigureAwait(false);
|
|
||||||
if(!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
if(response.StatusCode == HttpStatusCode.BadRequest)
|
|
||||||
{
|
|
||||||
response.StatusCode = HttpStatusCode.Unauthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await error.Invoke(response).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(responseContent);
|
|
||||||
TokenService.Instance.AccessToken = tokenResponse.AccessToken;
|
|
||||||
TokenService.Instance.RefreshToken = tokenResponse.RefreshToken;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return webException.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult<T>> HandleErrorAsync<T>(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var errors = await ParseErrorsAsync(response).ConfigureAwait(false);
|
|
||||||
return ApiResult<T>.Failed(response.StatusCode, errors.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{ }
|
|
||||||
|
|
||||||
return ApiResult<T>.Failed(response.StatusCode,
|
|
||||||
new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<ApiResult> HandleErrorAsync(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var errors = await ParseErrorsAsync(response).ConfigureAwait(false);
|
|
||||||
return ApiResult.Failed(response.StatusCode, errors.ToArray());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{ }
|
|
||||||
|
|
||||||
return ApiResult.Failed(response.StatusCode,
|
|
||||||
new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<ApiError>> ParseErrorsAsync(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
var errors = new List<ApiError>();
|
|
||||||
var statusCode = (int)response.StatusCode;
|
|
||||||
if(statusCode >= 400 && statusCode <= 500)
|
|
||||||
{
|
|
||||||
ErrorResponse errorResponseModel = null;
|
|
||||||
|
|
||||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
if(!string.IsNullOrWhiteSpace(responseContent))
|
|
||||||
{
|
|
||||||
var errorResponse = JObject.Parse(responseContent);
|
|
||||||
if(errorResponse["ErrorModel"] != null && errorResponse["ErrorModel"]["Message"] != null)
|
|
||||||
{
|
|
||||||
errorResponseModel = errorResponse["ErrorModel"].ToObject<ErrorResponse>();
|
|
||||||
}
|
|
||||||
else if(errorResponse["Message"] != null)
|
|
||||||
{
|
|
||||||
errorResponseModel = errorResponse.ToObject<ErrorResponse>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errorResponseModel != null)
|
|
||||||
{
|
|
||||||
if((errorResponseModel.ValidationErrors?.Count ?? 0) > 0)
|
|
||||||
{
|
|
||||||
foreach(var valError in errorResponseModel.ValidationErrors)
|
|
||||||
{
|
|
||||||
foreach(var errorMessage in valError.Value)
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = errorMessage });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = errorResponseModel.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(errors.Count == 0)
|
|
||||||
{
|
|
||||||
errors.Add(new ApiError { Message = "An unknown error has occurred." });
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class AuthService
|
|
||||||
{
|
|
||||||
private static AuthService _instance;
|
|
||||||
|
|
||||||
private AuthService() { }
|
|
||||||
|
|
||||||
public static AuthService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new AuthService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Authenticated => !string.IsNullOrWhiteSpace(TokenService.Instance.AccessToken);
|
|
||||||
public bool OrganizationSet => SettingsService.Instance.Organization != null;
|
|
||||||
|
|
||||||
public void LogOut()
|
|
||||||
{
|
|
||||||
TokenService.Instance.AccessToken = null;
|
|
||||||
TokenService.Instance.RefreshToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInAsync(string email, string masterPassword)
|
|
||||||
{
|
|
||||||
var normalizedEmail = email.Trim().ToLower();
|
|
||||||
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
|
|
||||||
|
|
||||||
var request = new TokenRequest
|
|
||||||
{
|
|
||||||
Email = normalizedEmail,
|
|
||||||
MasterPasswordHash = Crypto.HashPasswordBase64(key, masterPassword)
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostTokenAsync(request);
|
|
||||||
|
|
||||||
masterPassword = null;
|
|
||||||
key = null;
|
|
||||||
|
|
||||||
var result = new LoginResult();
|
|
||||||
if(!response.Succeeded)
|
|
||||||
{
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Success = true;
|
|
||||||
if(response.Result.TwoFactorProviders2 != null && response.Result.TwoFactorProviders2.Count > 0)
|
|
||||||
{
|
|
||||||
result.TwoFactorProviders = response.Result.TwoFactorProviders2;
|
|
||||||
result.MasterPasswordHash = request.MasterPasswordHash;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ProcessLogInSuccessAsync(response.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInTwoFactorAsync(TwoFactorProviderType type, string token, string email,
|
|
||||||
string masterPassword)
|
|
||||||
{
|
|
||||||
var normalizedEmail = email.Trim().ToLower();
|
|
||||||
var key = Crypto.MakeKeyFromPassword(masterPassword, normalizedEmail);
|
|
||||||
|
|
||||||
var result = await LogInTwoFactorWithHashAsync(type, token, email, Crypto.HashPasswordBase64(key, masterPassword));
|
|
||||||
|
|
||||||
key = null;
|
|
||||||
masterPassword = null;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginResult> LogInTwoFactorWithHashAsync(TwoFactorProviderType type, string token, string email,
|
|
||||||
string masterPasswordHash)
|
|
||||||
{
|
|
||||||
if(type == TwoFactorProviderType.Email || type == TwoFactorProviderType.Authenticator)
|
|
||||||
{
|
|
||||||
token = token.Trim().Replace(" ", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new TokenRequest
|
|
||||||
{
|
|
||||||
Email = email.Trim().ToLower(),
|
|
||||||
MasterPasswordHash = masterPasswordHash,
|
|
||||||
Token = token,
|
|
||||||
Provider = type,
|
|
||||||
Remember = false
|
|
||||||
};
|
|
||||||
|
|
||||||
var response = await ApiService.Instance.PostTokenAsync(request);
|
|
||||||
|
|
||||||
if(!response.Succeeded)
|
|
||||||
{
|
|
||||||
var result = new LoginResult();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = response.Errors.FirstOrDefault()?.Message;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await ProcessLogInSuccessAsync(response.Result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<LoginResult> ProcessLogInSuccessAsync(TokenResponse response)
|
|
||||||
{
|
|
||||||
TokenService.Instance.AccessToken = response.AccessToken;
|
|
||||||
TokenService.Instance.RefreshToken = response.RefreshToken;
|
|
||||||
|
|
||||||
var result = new LoginResult();
|
|
||||||
|
|
||||||
var profile = await ApiService.Instance.GetProfileAsync();
|
|
||||||
if(profile.Succeeded)
|
|
||||||
{
|
|
||||||
var adminOrgs = profile.Result.Organizations.Where(o =>
|
|
||||||
o.Status == OrganizationUserStatusType.Confirmed &&
|
|
||||||
o.Type != OrganizationUserType.User);
|
|
||||||
if(!adminOrgs.Any())
|
|
||||||
{
|
|
||||||
LogOut();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = "You are not an admin of any organizations.";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Organizations = adminOrgs.Select(o => new Organization(o)).ToList();
|
|
||||||
if(result.Organizations.Count == 1)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.Organization = new Organization(adminOrgs.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Success = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogOut();
|
|
||||||
result.Success = false;
|
|
||||||
result.ErrorMessage = "Could not load profile.";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.Graph;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class AzureDirectoryService : IDirectoryService
|
|
||||||
{
|
|
||||||
private static AzureDirectoryService _instance;
|
|
||||||
private static GraphServiceClient _graphClient;
|
|
||||||
|
|
||||||
private AzureDirectoryService()
|
|
||||||
{
|
|
||||||
_graphClient = new GraphServiceClient(new AzureAuthenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IDirectoryService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new AzureDirectoryService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not logged in or have an org set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserEntry> users = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
users = await GetUsersAsync(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GroupEntry> groups = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.GroupFilter);
|
|
||||||
groups = await GetGroupsAsync(force || (users?.Any(u => !u.Deleted && !u.Disabled) ?? false), filter);
|
|
||||||
|
|
||||||
if(filter != null && users != null)
|
|
||||||
{
|
|
||||||
users = users.Where(u => u.Disabled || u.Deleted ||
|
|
||||||
groups.Any(g => g.UserMemberExternalIds.Contains(u.ExternalId))).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<List<GroupEntry>> GetGroupsAsync(bool force, Tuple<bool, HashSet<string>> filter)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync groups.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = new List<GroupEntry>();
|
|
||||||
var changedGroupIds = new List<string>();
|
|
||||||
var getFullResults = SettingsService.Instance.GroupDeltaToken == null || force;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var delataRequest = _graphClient.Groups.Delta().Request();
|
|
||||||
if(!getFullResults)
|
|
||||||
{
|
|
||||||
delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.GroupDeltaToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupsDelta = await delataRequest.GetAsync();
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
if(getFullResults)
|
|
||||||
{
|
|
||||||
foreach(var group in groupsDelta)
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, group.DisplayName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = await BuildGroupAsync(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
changedGroupIds.AddRange(groupsDelta.Select(g => g.Id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groupsDelta.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
object deltaLink;
|
|
||||||
if(groupsDelta.AdditionalData.TryGetValue("@odata.deltaLink", out deltaLink))
|
|
||||||
{
|
|
||||||
var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString();
|
|
||||||
if(deltaUriQuery["$deltatoken"] != null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.GroupDeltaToken = deltaUriQuery["$deltatoken"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
groupsDelta = await groupsDelta.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
if(getFullResults || !changedGroupIds.Any())
|
|
||||||
{
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
var groups = await _graphClient.Groups.Request().GetAsync();
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
foreach(var group in groups)
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, group.DisplayName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = await BuildGroupAsync(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(groups.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
groups = await groups.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<GroupEntry> BuildGroupAsync(Group group)
|
|
||||||
{
|
|
||||||
var entry = new GroupEntry
|
|
||||||
{
|
|
||||||
ReferenceId = group.Id,
|
|
||||||
ExternalId = group.Id,
|
|
||||||
Name = group.DisplayName
|
|
||||||
};
|
|
||||||
|
|
||||||
var members = await _graphClient.Groups[group.Id].Members.Request().Select("id").GetAsync();
|
|
||||||
foreach(var member in members)
|
|
||||||
{
|
|
||||||
if(member is User)
|
|
||||||
{
|
|
||||||
entry.UserMemberExternalIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
else if(member is Group)
|
|
||||||
{
|
|
||||||
entry.GroupMemberReferenceIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async static Task<List<UserEntry>> GetUsersAsync(bool force)
|
|
||||||
{
|
|
||||||
if(!SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not configured to sync users.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.Azure == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!AuthService.Instance.Authenticated)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not authenticated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = new List<UserEntry>();
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
|
|
||||||
var userRequest = _graphClient.Users.Delta();
|
|
||||||
IUserDeltaCollectionPage users = null;
|
|
||||||
|
|
||||||
if(!force && SettingsService.Instance.UserDeltaToken != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var delataRequest = userRequest.Request();
|
|
||||||
delataRequest.QueryOptions.Add(new QueryOption("$deltatoken", SettingsService.Instance.UserDeltaToken));
|
|
||||||
users = await delataRequest.GetAsync();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
users = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(users == null)
|
|
||||||
{
|
|
||||||
users = await userRequest.Request().GetAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
foreach(var user in users)
|
|
||||||
{
|
|
||||||
var entry = new UserEntry
|
|
||||||
{
|
|
||||||
ReferenceId = user.Id,
|
|
||||||
ExternalId = user.Id,
|
|
||||||
Email = user.Mail ?? user.UserPrincipalName,
|
|
||||||
Disabled = !user.AccountEnabled.GetValueOrDefault(true)
|
|
||||||
};
|
|
||||||
|
|
||||||
if(FilterOutResult(filter, entry.Email))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(user.AdditionalData.TryGetValue("@removed", out object deleted) && deleted.ToString().Contains("changed"))
|
|
||||||
{
|
|
||||||
entry.Deleted = true;
|
|
||||||
}
|
|
||||||
else if(!entry.Disabled && (entry?.Email?.Contains("#") ?? true))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(users.NextPageRequest == null)
|
|
||||||
{
|
|
||||||
if(users.AdditionalData.TryGetValue("@odata.deltaLink", out object deltaLink))
|
|
||||||
{
|
|
||||||
var deltaUriQuery = new Uri(deltaLink.ToString()).ParseQueryString();
|
|
||||||
if(deltaUriQuery["$deltatoken"] != null)
|
|
||||||
{
|
|
||||||
SettingsService.Instance.UserDeltaToken = deltaUriQuery["$deltatoken"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
users = await users.NextPageRequest.GetAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Tuple<bool, HashSet<string>> CreateSetFromFilter(string filter)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(filter))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = filter.Split(':');
|
|
||||||
if(parts.Length != 2)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var exclude = true;
|
|
||||||
if(string.Equals(parts[0].Trim(), "include", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = false;
|
|
||||||
}
|
|
||||||
else if(string.Equals(parts[0].Trim(), "exclude", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new HashSet<string>(parts[1].Split(',').Select(p => p.Trim()));
|
|
||||||
return new Tuple<bool, HashSet<string>>(exclude, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool FilterOutResult(Tuple<bool, HashSet<string>> filter, string result)
|
|
||||||
{
|
|
||||||
if(filter != null)
|
|
||||||
{
|
|
||||||
// excluded
|
|
||||||
if(filter.Item1 && filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// included
|
|
||||||
else if(!filter.Item1 && !filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
using Bit.Core.Utilities;
|
|
||||||
#if NET461
|
|
||||||
using System.ServiceProcess;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class ControllerService
|
|
||||||
{
|
|
||||||
private static ControllerService _instance;
|
|
||||||
|
|
||||||
private ControllerService()
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
Controller = new ServiceController(Constants.ProgramName);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ControllerService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new ControllerService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if NET461
|
|
||||||
public ServiceController Controller { get; private set; }
|
|
||||||
public ServiceControllerStatus Status
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
Controller.Refresh();
|
|
||||||
return Controller.Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool Running => Status == ServiceControllerStatus.Running;
|
|
||||||
public bool Paused => Status == ServiceControllerStatus.Paused;
|
|
||||||
public bool Stopped => Status == ServiceControllerStatus.Stopped;
|
|
||||||
public bool Pending =>
|
|
||||||
Status == ServiceControllerStatus.ContinuePending ||
|
|
||||||
Status == ServiceControllerStatus.PausePending ||
|
|
||||||
Status == ServiceControllerStatus.StartPending ||
|
|
||||||
Status == ServiceControllerStatus.StopPending;
|
|
||||||
#endif
|
|
||||||
public string StatusString
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
return Controller == null ? "Unavailable" : Status.ToString();
|
|
||||||
#else
|
|
||||||
return "Unavailable";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public bool Start()
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
if(Controller == null || !Stopped)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller.Start();
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
throw new System.Exception("Controller unavailable.");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Stop()
|
|
||||||
{
|
|
||||||
#if NET461
|
|
||||||
if(Controller == null || !Controller.CanStop)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller.Stop();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
throw new System.Exception("Controller unavailable.");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
using Bit.Core.Models;
|
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Google.Apis.Admin.Directory.directory_v1;
|
|
||||||
using Google.Apis.Services;
|
|
||||||
using Google.Apis.Auth.OAuth2;
|
|
||||||
using System.IO;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Linq;
|
|
||||||
using Google.Apis.Admin.Directory.directory_v1.Data;
|
|
||||||
using Google.Apis.Requests;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
|
||||||
{
|
|
||||||
public class GSuiteDirectoryService : IDirectoryService
|
|
||||||
{
|
|
||||||
private static GSuiteDirectoryService _instance;
|
|
||||||
private static DirectoryService _service;
|
|
||||||
|
|
||||||
private GSuiteDirectoryService()
|
|
||||||
{
|
|
||||||
ICredential creds;
|
|
||||||
|
|
||||||
var secretFilePath = Path.Combine(Constants.BaseStoragePath, SettingsService.Instance.Server.GSuite.SecretFile);
|
|
||||||
using(var stream = new FileStream(secretFilePath, FileMode.Open, FileAccess.Read))
|
|
||||||
{
|
|
||||||
var scopes = new List<string>
|
|
||||||
{
|
|
||||||
DirectoryService.Scope.AdminDirectoryUserReadonly,
|
|
||||||
DirectoryService.Scope.AdminDirectoryGroupReadonly,
|
|
||||||
DirectoryService.Scope.AdminDirectoryGroupMemberReadonly
|
|
||||||
};
|
|
||||||
|
|
||||||
creds = GoogleCredential.FromStream(stream)
|
|
||||||
.CreateScoped(scopes)
|
|
||||||
.CreateWithUser(SettingsService.Instance.Server.GSuite.AdminUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
_service = new DirectoryService(new BaseClientService.Initializer
|
|
||||||
{
|
|
||||||
HttpClientInitializer = creds,
|
|
||||||
ApplicationName = Constants.ProgramName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IDirectoryService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new GSuiteDirectoryService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Tuple<List<GroupEntry>, List<UserEntry>>> GetEntriesAsync(bool force = false)
|
|
||||||
{
|
|
||||||
if(!AuthService.Instance.Authenticated || !AuthService.Instance.OrganizationSet)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Not logged in or have an org set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Server?.GSuite == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for directory server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SettingsService.Instance.Sync == null)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("No configuration for sync.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserEntry> users = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncUsers)
|
|
||||||
{
|
|
||||||
users = await GetUsersAsync(force);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GroupEntry> groups = null;
|
|
||||||
if(SettingsService.Instance.Sync.SyncGroups)
|
|
||||||
{
|
|
||||||
groups = await GetGroupsAsync(force || (users?.Any(u => !u.Deleted && !u.Disabled) ?? false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<List<GroupEntry>, List<UserEntry>>(groups, users);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<List<GroupEntry>> GetGroupsAsync(bool force)
|
|
||||||
{
|
|
||||||
var entries = new List<GroupEntry>();
|
|
||||||
|
|
||||||
var request = _service.Groups.List();
|
|
||||||
request.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
request.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
|
|
||||||
var pageStreamer = new PageStreamer<Group, GroupsResource.ListRequest, Groups, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.GroupsValue);
|
|
||||||
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.GroupFilter);
|
|
||||||
foreach(var group in pageStreamer.Fetch(request))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, group.Name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildGroup(group);
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GroupEntry BuildGroup(Group group)
|
|
||||||
{
|
|
||||||
var entry = new GroupEntry
|
|
||||||
{
|
|
||||||
ReferenceId = group.Id,
|
|
||||||
ExternalId = group.Id,
|
|
||||||
Name = group.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
var memberRequest = _service.Members.List(group.Id);
|
|
||||||
var pageStreamer = new PageStreamer<Member, MembersResource.ListRequest, Members, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.MembersValue);
|
|
||||||
|
|
||||||
foreach(var member in pageStreamer.Fetch(memberRequest))
|
|
||||||
{
|
|
||||||
if(!member.Role.Equals("member", StringComparison.InvariantCultureIgnoreCase) ||
|
|
||||||
!member.Status.Equals("active", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(member.Type.Equals("user", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
entry.UserMemberExternalIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
else if(member.Type.Equals("group", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
entry.GroupMemberReferenceIds.Add(member.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<List<UserEntry>> GetUsersAsync(bool force)
|
|
||||||
{
|
|
||||||
var entries = new List<UserEntry>();
|
|
||||||
var query = CreateGSuiteQueryFromFilter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
|
|
||||||
var request = _service.Users.List();
|
|
||||||
request.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
request.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
request.Query = query;
|
|
||||||
|
|
||||||
var pageStreamer = new PageStreamer<User, UsersResource.ListRequest, Users, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.UsersValue);
|
|
||||||
|
|
||||||
var filter = CreateSetFromFilter(SettingsService.Instance.Sync.UserFilter);
|
|
||||||
foreach(var user in pageStreamer.Fetch(request))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, user.PrimaryEmail))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildUser(user, false);
|
|
||||||
if(entry != null)
|
|
||||||
{
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var deletedRequest = _service.Users.List();
|
|
||||||
deletedRequest.Domain = SettingsService.Instance.Server.GSuite.Domain;
|
|
||||||
deletedRequest.Customer = SettingsService.Instance.Server.GSuite.Customer;
|
|
||||||
deletedRequest.Query = query;
|
|
||||||
deletedRequest.ShowDeleted = "true";
|
|
||||||
|
|
||||||
var deletedPageStreamer = new PageStreamer<User, UsersResource.ListRequest, Users, string>(
|
|
||||||
(req, token) => req.PageToken = token,
|
|
||||||
res => res.NextPageToken,
|
|
||||||
res => res.UsersValue);
|
|
||||||
|
|
||||||
foreach(var user in deletedPageStreamer.Fetch(deletedRequest))
|
|
||||||
{
|
|
||||||
if(FilterOutResult(filter, user.PrimaryEmail))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = BuildUser(user, true);
|
|
||||||
if(entry != null)
|
|
||||||
{
|
|
||||||
entries.Add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserEntry BuildUser(User user, bool deleted)
|
|
||||||
{
|
|
||||||
var entry = new UserEntry
|
|
||||||
{
|
|
||||||
ReferenceId = user.Id,
|
|
||||||
ExternalId = user.Id,
|
|
||||||
Email = user.PrimaryEmail,
|
|
||||||
Disabled = user.Suspended.GetValueOrDefault(false),
|
|
||||||
Deleted = deleted,
|
|
||||||
CreationDate = user.CreationTime
|
|
||||||
};
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(entry.Email) && !entry.Deleted)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CreateGSuiteQueryFromFilter(string filter)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(filter))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainParts = filter.Split('|');
|
|
||||||
if(mainParts.Count() < 2 || string.IsNullOrWhiteSpace(mainParts[1]))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainParts[1].Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tuple<bool, HashSet<string>> CreateSetFromFilter(string filter)
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(filter))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mainParts = filter.Split('|');
|
|
||||||
if(mainParts.Count() < 1 || string.IsNullOrWhiteSpace(mainParts[0]))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = mainParts[0].Split(':');
|
|
||||||
if(parts.Count() != 2)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var exclude = true;
|
|
||||||
if(string.Equals(parts[0].Trim(), "include", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = false;
|
|
||||||
}
|
|
||||||
else if(string.Equals(parts[0].Trim(), "exclude", StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
exclude = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new HashSet<string>(parts[1].Split(',').Select(p => p.Trim()));
|
|
||||||
return new Tuple<bool, HashSet<string>>(exclude, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FilterOutResult(Tuple<bool, HashSet<string>> filter, string result)
|
|
||||||
{
|
|
||||||
if(filter != null)
|
|
||||||
{
|
|
||||||
// excluded
|
|
||||||
if(filter.Item1 && filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// included
|
|
||||||
else if(!filter.Item1 && !filter.Item2.Contains(result, StringComparer.InvariantCultureIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||