mirror of
https://github.com/bitwarden/android.git
synced 2026-05-11 10:38:43 -05:00
Compare commits
1347 Commits
v2024.11.5
...
v2025.7.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a721744a6b | ||
|
|
37af6a1773 | ||
|
|
557c5b46a5 | ||
|
|
390ef34398 | ||
|
|
d2f7d52132 | ||
|
|
0feac46711 | ||
|
|
bc50c0d873 | ||
|
|
fb3b9c9ea7 | ||
|
|
9a81e18cb4 | ||
|
|
f9914e5b46 | ||
|
|
e193661f5f | ||
|
|
532fcbb40e | ||
|
|
187d50faa2 | ||
|
|
8f5376c2de | ||
|
|
56192a7e8b | ||
|
|
70350746ce | ||
|
|
febfc82a53 | ||
|
|
5f5c71979f | ||
|
|
ba49a3e91f | ||
|
|
965ab67e58 | ||
|
|
2932ed831b | ||
|
|
2ff3f3e23d | ||
|
|
eb5893dde4 | ||
|
|
1165e7002b | ||
|
|
5fa7239130 | ||
|
|
fd9bdfa228 | ||
|
|
7db8f040e4 | ||
|
|
790331e058 | ||
|
|
d0640b7e20 | ||
|
|
5429e27228 | ||
|
|
917aaac3a6 | ||
|
|
0b7209b3c9 | ||
|
|
a7b3201015 | ||
|
|
348e14e52d | ||
|
|
ef9dda5159 | ||
|
|
b0309e876e | ||
|
|
59a49355fd | ||
|
|
901184db45 | ||
|
|
a2507c317d | ||
|
|
f608852dc7 | ||
|
|
e44d63229c | ||
|
|
f7b876f204 | ||
|
|
1268afaef8 | ||
|
|
3f1c1dec17 | ||
|
|
5eea55f173 | ||
|
|
1a8cf4055a | ||
|
|
defdf8eb58 | ||
|
|
9940c8cf9e | ||
|
|
e1058f5021 | ||
|
|
986cd2ee30 | ||
|
|
eae870cb3a | ||
|
|
79493a55bd | ||
|
|
18bafaba8a | ||
|
|
896be911a4 | ||
|
|
85a86106f6 | ||
|
|
edb7996c28 | ||
|
|
a806109380 | ||
|
|
4f5c28e248 | ||
|
|
b22f06cbf9 | ||
|
|
1070c9d46e | ||
|
|
b1dc894fe8 | ||
|
|
c76945161a | ||
|
|
789cd80eba | ||
|
|
9482890102 | ||
|
|
ed2d6ca585 | ||
|
|
d279f6acae | ||
|
|
6ebcab7b86 | ||
|
|
3ee74d3ec5 | ||
|
|
288efb3611 | ||
|
|
bbdf8552c9 | ||
|
|
44ef598df3 | ||
|
|
73a8e241d4 | ||
|
|
4d6260ea02 | ||
|
|
569bb4f110 | ||
|
|
ffc71371a9 | ||
|
|
8d0b23d166 | ||
|
|
5f525d9d95 | ||
|
|
b94d59ba6b | ||
|
|
4ff1a9ba94 | ||
|
|
9c1673f603 | ||
|
|
ddc099f727 | ||
|
|
fbfcfcd683 | ||
|
|
1234898786 | ||
|
|
182e6475c0 | ||
|
|
f27590a4d6 | ||
|
|
807c76f8ec | ||
|
|
3877c4bd64 | ||
|
|
8c88fd9d53 | ||
|
|
b92493611e | ||
|
|
9235f92206 | ||
|
|
a3610c22dd | ||
|
|
1e4fc31ed4 | ||
|
|
ac1a9a2dc0 | ||
|
|
fe0e6bc67b | ||
|
|
419e5ca918 | ||
|
|
be1a6e2097 | ||
|
|
4fe989ce68 | ||
|
|
8be7410302 | ||
|
|
16225f0d68 | ||
|
|
08679a8973 | ||
|
|
4d3e782b69 | ||
|
|
4d8fe722d1 | ||
|
|
c5600c1d84 | ||
|
|
9816321d93 | ||
|
|
56e8acf81f | ||
|
|
08b07a0050 | ||
|
|
25d7c1e72c | ||
|
|
e311a4f618 | ||
|
|
0eea6b07a3 | ||
|
|
c52e769327 | ||
|
|
292a28d155 | ||
|
|
6c41c358ac | ||
|
|
e7cf5a7efa | ||
|
|
f64364c1b8 | ||
|
|
d42b8ecd2d | ||
|
|
a6f7b1e176 | ||
|
|
d56b9fc0ff | ||
|
|
f290ae411b | ||
|
|
508566f06f | ||
|
|
95f146fb3e | ||
|
|
469df4495a | ||
|
|
053dfc1647 | ||
|
|
7de770ca03 | ||
|
|
861a4281fa | ||
|
|
265014fd64 | ||
|
|
5d32fe9caf | ||
|
|
44ba0f548a | ||
|
|
694443f2e1 | ||
|
|
0ade60025c | ||
|
|
5adccca823 | ||
|
|
3c86bb425b | ||
|
|
3474e0b608 | ||
|
|
0f2476bebf | ||
|
|
edffb8dd6f | ||
|
|
2dc6c170f5 | ||
|
|
2a8a16ab3f | ||
|
|
76995a28ad | ||
|
|
7e146800a8 | ||
|
|
7a2f1c294f | ||
|
|
44d4926300 | ||
|
|
e4c160d1e0 | ||
|
|
0f9f9d9dce | ||
|
|
a0c2600517 | ||
|
|
c60df56648 | ||
|
|
9cdfe0c5d6 | ||
|
|
d822be62e1 | ||
|
|
7adbfdcc84 | ||
|
|
beb4c533c8 | ||
|
|
e1cd813445 | ||
|
|
f769900976 | ||
|
|
a0ff94195f | ||
|
|
9853f137d2 | ||
|
|
b591534bd9 | ||
|
|
d2c329264c | ||
|
|
a9791c3f9f | ||
|
|
a59eaf5d40 | ||
|
|
d2129cf507 | ||
|
|
903c260ad1 | ||
|
|
09a8c01824 | ||
|
|
a1a4c217de | ||
|
|
7fbe3510b5 | ||
|
|
0892e0ff1f | ||
|
|
0934d47159 | ||
|
|
caf1c2eed5 | ||
|
|
803d519c24 | ||
|
|
a3d2e51c8e | ||
|
|
891def5e32 | ||
|
|
00ded69a84 | ||
|
|
c5f597aedb | ||
|
|
f43367ebfa | ||
|
|
997769bb1c | ||
|
|
65d1a4f12a | ||
|
|
f7c1278805 | ||
|
|
aa3602a5ce | ||
|
|
af18848159 | ||
|
|
f3b7d0f732 | ||
|
|
e250a8dc1e | ||
|
|
ab2ac60957 | ||
|
|
b877487ce1 | ||
|
|
ef68879778 | ||
|
|
a4e4d1488b | ||
|
|
cf8578f3ef | ||
|
|
bdd0660e2b | ||
|
|
294ef674bc | ||
|
|
b9a897a9fc | ||
|
|
6b12b9757f | ||
|
|
61411ca73c | ||
|
|
3908827a14 | ||
|
|
e553d7a015 | ||
|
|
3015b768c6 | ||
|
|
21b8ef92ba | ||
|
|
0e3e6069fa | ||
|
|
8d8dee5171 | ||
|
|
97b6bccd72 | ||
|
|
2a6813e4a2 | ||
|
|
29e7899525 | ||
|
|
4cd603006f | ||
|
|
1a091e198c | ||
|
|
3551e75596 | ||
|
|
6d976bea4c | ||
|
|
d5c04123d9 | ||
|
|
000a7d141e | ||
|
|
c9d4d35f07 | ||
|
|
4216f3f5a0 | ||
|
|
c14545107d | ||
|
|
d1a8cbf59f | ||
|
|
1acc1a87a6 | ||
|
|
fd73360539 | ||
|
|
178625222a | ||
|
|
3ea17eb71c | ||
|
|
6ccb035ffd | ||
|
|
5c3008d080 | ||
|
|
54efc74907 | ||
|
|
34aed2ac65 | ||
|
|
3d152f5c36 | ||
|
|
4c8e5602dd | ||
|
|
6e44ee2eb0 | ||
|
|
4895f2a18a | ||
|
|
fc4f02c4d5 | ||
|
|
6719534494 | ||
|
|
183584f678 | ||
|
|
046bb0fa39 | ||
|
|
9508b4ba90 | ||
|
|
07e4e6a806 | ||
|
|
4d142a6a5c | ||
|
|
f02a3a249b | ||
|
|
28149532a0 | ||
|
|
7f5426dea0 | ||
|
|
d7d703c977 | ||
|
|
7fda5d799f | ||
|
|
3c1a0a352a | ||
|
|
cb0b135429 | ||
|
|
7422efd07a | ||
|
|
27a9fc52b7 | ||
|
|
c105c102a3 | ||
|
|
c83bd8f4a8 | ||
|
|
d820b3345a | ||
|
|
b71b01d48d | ||
|
|
8a0f67c0e9 | ||
|
|
b4d85e07ba | ||
|
|
dfd58822b7 | ||
|
|
f14a1404e3 | ||
|
|
f1950600a1 | ||
|
|
7d6b6a5959 | ||
|
|
ea70191429 | ||
|
|
db956b9b91 | ||
|
|
119812507a | ||
|
|
a97c962428 | ||
|
|
456adf3158 | ||
|
|
62cb962298 | ||
|
|
7f4e65d7e4 | ||
|
|
860a2e265f | ||
|
|
6d68c3ae24 | ||
|
|
97b8c51ab3 | ||
|
|
a2449a2f19 | ||
|
|
1d73bbd440 | ||
|
|
da62244000 | ||
|
|
11b767c98f | ||
|
|
cd4db467e3 | ||
|
|
7fdf165273 | ||
|
|
e49bab637c | ||
|
|
14ac194cb7 | ||
|
|
578f96a944 | ||
|
|
2c71ab7d27 | ||
|
|
c5ee389231 | ||
|
|
5037af07c7 | ||
|
|
d6d1e8e97f | ||
|
|
652168f946 | ||
|
|
d4d5d2c2a8 | ||
|
|
ed148c2089 | ||
|
|
564304616d | ||
|
|
0d0b8d6780 | ||
|
|
472e41f6bc | ||
|
|
fb9c68755a | ||
|
|
d7671f47ea | ||
|
|
9c7270df69 | ||
|
|
733290569c | ||
|
|
cbaa8a329e | ||
|
|
f968d7698a | ||
|
|
68cd08b069 | ||
|
|
84683894a6 | ||
|
|
ed2d9ecb80 | ||
|
|
8f4d46954e | ||
|
|
a9fc6ff589 | ||
|
|
5c8f5670e4 | ||
|
|
eec88d4924 | ||
|
|
82da193e55 | ||
|
|
76fb85ac1f | ||
|
|
625ac0ea5f | ||
|
|
4e88833737 | ||
|
|
ecea2ef7c1 | ||
|
|
0eccc7197e | ||
|
|
5dd34afe81 | ||
|
|
5abcc5b1f7 | ||
|
|
6fec95cb84 | ||
|
|
1d68c1fdf6 | ||
|
|
0c2de427dc | ||
|
|
f932682949 | ||
|
|
e1f432ea5d | ||
|
|
31de7fc331 | ||
|
|
07469672ba | ||
|
|
1a2beea770 | ||
|
|
639ca02739 | ||
|
|
186bea2d1d | ||
|
|
3dc187da87 | ||
|
|
69708c1285 | ||
|
|
ad1566f4b0 | ||
|
|
32d0ca7bcd | ||
|
|
0353f0c153 | ||
|
|
7436122953 | ||
|
|
23ef5b38fe | ||
|
|
fe1fe770c7 | ||
|
|
240bca3c2f | ||
|
|
8c7cc27c5d | ||
|
|
a4aa9837a6 | ||
|
|
b901de9ddf | ||
|
|
96df23f0af | ||
|
|
0eb149941d | ||
|
|
6f44e64375 | ||
|
|
cda86b842e | ||
|
|
e1608b426d | ||
|
|
b6017baf54 | ||
|
|
0f6d15d6a6 | ||
|
|
cd11164544 | ||
|
|
1b9d2bfab4 | ||
|
|
373b789fbb | ||
|
|
985e576a82 | ||
|
|
b11e4481f9 | ||
|
|
5ac0f2b111 | ||
|
|
37a0d19efc | ||
|
|
54983bc92e | ||
|
|
36989875a6 | ||
|
|
88b0fe59bb | ||
|
|
e4d0c48eed | ||
|
|
bd364a1108 | ||
|
|
39b88d6064 | ||
|
|
da709e039b | ||
|
|
31311964d0 | ||
|
|
8cbd7369c5 | ||
|
|
2a1669cf87 | ||
|
|
4c4007a734 | ||
|
|
70dc82d1b6 | ||
|
|
021ece138b | ||
|
|
bee09de972 | ||
|
|
33da0d8138 | ||
|
|
d5d8da2410 | ||
|
|
3722a45359 | ||
|
|
f23079b5ac | ||
|
|
524ddb6d0c | ||
|
|
0d40d1e569 | ||
|
|
4f65044179 | ||
|
|
d67e74e48b | ||
|
|
b760b58669 | ||
|
|
36e6fbc14c | ||
|
|
0be26c1eda | ||
|
|
3311086dfc | ||
|
|
c912a3f12a | ||
|
|
e67790438e | ||
|
|
ff72efe0ed | ||
|
|
9dd71eaea2 | ||
|
|
2d416eade5 | ||
|
|
83de8b888d | ||
|
|
e35be360d7 | ||
|
|
899689ba7b | ||
|
|
71237cb3a7 | ||
|
|
18c7333cf3 | ||
|
|
b6d9bee266 | ||
|
|
868f8091d2 | ||
|
|
dcd2d26d6c | ||
|
|
548c9ee092 | ||
|
|
186741e052 | ||
|
|
d989c61bc2 | ||
|
|
8f311861e3 | ||
|
|
942b95927f | ||
|
|
f087b6aac7 | ||
|
|
87ef0e2961 | ||
|
|
b60f30d8a1 | ||
|
|
c553d2caec | ||
|
|
d1ba730012 | ||
|
|
944eb64562 | ||
|
|
12ce1e5229 | ||
|
|
b72da1ba69 | ||
|
|
ed97695228 | ||
|
|
05b1266b1b | ||
|
|
8153b25d89 | ||
|
|
2fd5ab6f8d | ||
|
|
392695cb54 | ||
|
|
d7f771990e | ||
|
|
dd0f08d759 | ||
|
|
dd9ca853a7 | ||
|
|
2ef0ca3620 | ||
|
|
97ef0ec004 | ||
|
|
9d7df2bc10 | ||
|
|
cb05787256 | ||
|
|
8767f2dd24 | ||
|
|
bf1ed690a4 | ||
|
|
a12f325815 | ||
|
|
c2872b6b9b | ||
|
|
622d68e40c | ||
|
|
6ebece1b1e | ||
|
|
c540d3ef47 | ||
|
|
385f5efac5 | ||
|
|
6668af58d2 | ||
|
|
c5e216783e | ||
|
|
acf222d151 | ||
|
|
1df96fdb62 | ||
|
|
942f6e2475 | ||
|
|
2176b61cd3 | ||
|
|
4a63a709b8 | ||
|
|
62cfcbbd72 | ||
|
|
538f1feb2e | ||
|
|
70e42a27db | ||
|
|
4d9a19f43c | ||
|
|
dda8237ce5 | ||
|
|
c4f54ee93c | ||
|
|
cac22346dd | ||
|
|
3f35ace6e9 | ||
|
|
d1e4078c5a | ||
|
|
241a89fe13 | ||
|
|
4df1b245e8 | ||
|
|
853069ee1c | ||
|
|
1149e91dd5 | ||
|
|
d5de173431 | ||
|
|
f6d5302a73 | ||
|
|
0ea7d00e93 | ||
|
|
ebdf5f816a | ||
|
|
85109a2e4b | ||
|
|
5017e935d7 | ||
|
|
ce0aa7adda | ||
|
|
111d141fac | ||
|
|
4676f4bf8c | ||
|
|
321a764f20 | ||
|
|
5d4df86bc9 | ||
|
|
291c568583 | ||
|
|
7938c8c2bb | ||
|
|
1fecd4af5f | ||
|
|
1e6f896328 | ||
|
|
e67a143474 | ||
|
|
a6862bb791 | ||
|
|
d70e658c8b | ||
|
|
f43702cb83 | ||
|
|
20bda929b3 | ||
|
|
ce139623d6 | ||
|
|
8e7de92609 | ||
|
|
b82b1ad570 | ||
|
|
56cce8ffdd | ||
|
|
7abae5e86a | ||
|
|
862384db3a | ||
|
|
01c4a3db03 | ||
|
|
e5ddeb44fd | ||
|
|
a476436bff | ||
|
|
d3e14e8f52 | ||
|
|
36fa907d87 | ||
|
|
a4ee8017ae | ||
|
|
aa35e2f93c | ||
|
|
40179429bf | ||
|
|
bdf50fd1a6 | ||
|
|
445d6ec3b8 | ||
|
|
c304a4306f | ||
|
|
f82bda5bce | ||
|
|
a2f4e4f3b5 | ||
|
|
d4c079140d | ||
|
|
f1c82eb027 | ||
|
|
932eced64e | ||
|
|
1eced037a4 | ||
|
|
a4fb50d3d8 | ||
|
|
9a50399116 | ||
|
|
5d5bc25a45 | ||
|
|
af528fdd82 | ||
|
|
7d119fb552 | ||
|
|
81b43d13b0 | ||
|
|
cd86413ff6 | ||
|
|
05094cf6e7 | ||
|
|
75af4868e2 | ||
|
|
b7948948f0 | ||
|
|
efec5cb4ca | ||
|
|
6369b20f18 | ||
|
|
2e11c81f45 | ||
|
|
3926fbca7f | ||
|
|
29838b19e0 | ||
|
|
66dae199c8 | ||
|
|
f698d09b43 | ||
|
|
9486f4c4e2 | ||
|
|
6664ccc53f | ||
|
|
d62e3164dc | ||
|
|
b0729e8cd2 | ||
|
|
c51f61c585 | ||
|
|
6340c2dd04 | ||
|
|
42df9733c8 | ||
|
|
cfa753cb12 | ||
|
|
71250a28fa | ||
|
|
5279e6d18c | ||
|
|
8dd5a9df9f | ||
|
|
346961856f | ||
|
|
4cae0823f2 | ||
|
|
98b6f68821 | ||
|
|
a6d1f210c8 | ||
|
|
c0707ae08c | ||
|
|
3e15b60178 | ||
|
|
6081b7e932 | ||
|
|
bc76e46185 | ||
|
|
b4b4f753ca | ||
|
|
22376bfe4b | ||
|
|
ab270cd243 | ||
|
|
8ed9b97805 | ||
|
|
27e4c6a2b4 | ||
|
|
a6ed702a95 | ||
|
|
0792f44b6b | ||
|
|
94a91702cc | ||
|
|
21af60f4de | ||
|
|
1add57d56c | ||
|
|
b0421c774b | ||
|
|
7d846ae383 | ||
|
|
29371bdcb5 | ||
|
|
3eed1c1abe | ||
|
|
ad6bc883b8 | ||
|
|
f4f669683e | ||
|
|
475a82e0fb | ||
|
|
f22156389b | ||
|
|
4f09f5dae4 | ||
|
|
3934bc9ae2 | ||
|
|
72c9149d27 | ||
|
|
a040a38ce8 | ||
|
|
ef3b7730d0 | ||
|
|
ad8d8d271a | ||
|
|
4954e57007 | ||
|
|
6f50fffd17 | ||
|
|
44c5755301 | ||
|
|
b20eece3aa | ||
|
|
869a3b00a5 | ||
|
|
7f8e848c46 | ||
|
|
6c784d28eb | ||
|
|
dfcdc72499 | ||
|
|
db287ddce5 | ||
|
|
9ea85917b1 | ||
|
|
6db4165c4c | ||
|
|
18ce45e7e5 | ||
|
|
6fe9eba620 | ||
|
|
b084987758 | ||
|
|
5d0593026f | ||
|
|
47abeb7843 | ||
|
|
e2779e4edb | ||
|
|
90cc9f77c5 | ||
|
|
40760f270a | ||
|
|
8a773141a4 | ||
|
|
0c149abdd9 | ||
|
|
f540f86b19 | ||
|
|
ca64ce2176 | ||
|
|
da63c9e36b | ||
|
|
e16ad44d5e | ||
|
|
d26a2ee52a | ||
|
|
1eb741ab58 | ||
|
|
e10ca9a6ec | ||
|
|
3fca61ad3e | ||
|
|
409529b9ca | ||
|
|
4568dd53d4 | ||
|
|
b9b90165bf | ||
|
|
778a630012 | ||
|
|
4809066ad7 | ||
|
|
d03c6c243d | ||
|
|
d19ab498ff | ||
|
|
efc3d21fde | ||
|
|
35e585a60e | ||
|
|
39787f9bf0 | ||
|
|
3940997ef9 | ||
|
|
ce482e744d | ||
|
|
b0157d10e2 | ||
|
|
cf3c2fb56d | ||
|
|
a88a173e00 | ||
|
|
ac6ff98041 | ||
|
|
ec030f2c2e | ||
|
|
be08c1a536 | ||
|
|
84edf4ead0 | ||
|
|
915aac561c | ||
|
|
2027a66f02 | ||
|
|
ef6d9bc68c | ||
|
|
3ceda9e40a | ||
|
|
3f1a6e97fd | ||
|
|
4448ab05ce | ||
|
|
d584391843 | ||
|
|
e0d91d7682 | ||
|
|
e8a98dd3ed | ||
|
|
b6163cf53c | ||
|
|
a24c6f2719 | ||
|
|
b3219d4040 | ||
|
|
27043e28a8 | ||
|
|
7c36b7cb82 | ||
|
|
fcfcf48cee | ||
|
|
757994ec18 | ||
|
|
548de56d60 | ||
|
|
71cd917328 | ||
|
|
01188c21b2 | ||
|
|
5c076871ab | ||
|
|
dca7284230 | ||
|
|
07d3849c4b | ||
|
|
33c3fd28e9 | ||
|
|
88609c2f5b | ||
|
|
af8cfcd2f0 | ||
|
|
7cc8108498 | ||
|
|
a31c499b15 | ||
|
|
d7d099477f | ||
|
|
0ba240852f | ||
|
|
727d943fae | ||
|
|
234f49a92c | ||
|
|
4f49d3d504 | ||
|
|
aba8344df1 | ||
|
|
6da8e2c47b | ||
|
|
78d5965271 | ||
|
|
6953d5e132 | ||
|
|
7073124495 | ||
|
|
0cc7067808 | ||
|
|
9e920f1cf5 | ||
|
|
537e743891 | ||
|
|
60da236f3e | ||
|
|
7804d8430f | ||
|
|
2893c3871f | ||
|
|
d04ac5e672 | ||
|
|
55e03565a6 | ||
|
|
768f7a3fd9 | ||
|
|
1d02737093 | ||
|
|
dab06b0ed4 | ||
|
|
fb792a668b | ||
|
|
64da29ffaa | ||
|
|
675cbb7c4f | ||
|
|
6b63218839 | ||
|
|
30a1bba796 | ||
|
|
c2d9e4858b | ||
|
|
d8e42083b7 | ||
|
|
ac7fbfd129 | ||
|
|
25dfa74bdf | ||
|
|
85a98e86c4 | ||
|
|
078d80d3f6 | ||
|
|
00eb78f02e | ||
|
|
eadfac5ea8 | ||
|
|
a651d9b1fc | ||
|
|
6308aed34a | ||
|
|
011d637f7c | ||
|
|
0b03d2c0d5 | ||
|
|
bb7e4061cc | ||
|
|
d1308cb936 | ||
|
|
892f817b2a | ||
|
|
86e5789f30 | ||
|
|
80bd1bfde2 | ||
|
|
4943df24b3 | ||
|
|
b7464b87d9 | ||
|
|
3fb7904a36 | ||
|
|
9d9f9e3e72 | ||
|
|
a061cbb1d3 | ||
|
|
ad03f8c996 | ||
|
|
aac2345a64 | ||
|
|
61c48bf673 | ||
|
|
77b631b021 | ||
|
|
c59a28a9df | ||
|
|
1349165156 | ||
|
|
65cc0a0dd8 | ||
|
|
0ba67f5887 | ||
|
|
87f64d7aba | ||
|
|
063003b4aa | ||
|
|
f2eb524da4 | ||
|
|
e929ca8a7d | ||
|
|
acc5e30b7a | ||
|
|
26d4a397a6 | ||
|
|
ba9a0d8884 | ||
|
|
bb2e98eac6 | ||
|
|
133d8548e8 | ||
|
|
9f19a99eb9 | ||
|
|
22931bbd38 | ||
|
|
359dedc9d7 | ||
|
|
be416f87de | ||
|
|
6a3a534304 | ||
|
|
09254a2285 | ||
|
|
f0d6599eb3 | ||
|
|
d7a50b092b | ||
|
|
9f616b67c9 | ||
|
|
1198de2a74 | ||
|
|
3db7b17e3a | ||
|
|
631be3fca5 | ||
|
|
e8e6040318 | ||
|
|
5790397a27 | ||
|
|
8b98e8f461 | ||
|
|
f3482bd3e2 | ||
|
|
14ef7351bd | ||
|
|
0820061466 | ||
|
|
c5616b9e38 | ||
|
|
0538becf7f | ||
|
|
2ae3d5e100 | ||
|
|
2aa371a972 | ||
|
|
72ac8d3699 | ||
|
|
00f30c922d | ||
|
|
a68b370cba | ||
|
|
60a6b2a545 | ||
|
|
2c63c1ab94 | ||
|
|
fdc92712c4 | ||
|
|
4eb4d5f256 | ||
|
|
6fdb39026f | ||
|
|
ac6c90eb7a | ||
|
|
eb3bc838d6 | ||
|
|
30e882d7d1 | ||
|
|
3adc80fdf8 | ||
|
|
e70b21c951 | ||
|
|
571b8368e1 | ||
|
|
fd26472f71 | ||
|
|
6ffe9df353 | ||
|
|
7e0fc07a7c | ||
|
|
d27ab1d31a | ||
|
|
7b9cbb7bef | ||
|
|
a1a3d55656 | ||
|
|
c672bff18c | ||
|
|
7ab5972893 | ||
|
|
13aa3251d5 | ||
|
|
a5f6864512 | ||
|
|
c3506c1c25 | ||
|
|
1710b563c0 | ||
|
|
01b646afa7 | ||
|
|
445bd90f67 | ||
|
|
de7416d96e | ||
|
|
5e29df5f30 | ||
|
|
5f48d7bebd | ||
|
|
b342f21cbb | ||
|
|
4b3ec1d918 | ||
|
|
5f52c49a68 | ||
|
|
efe1475fbf | ||
|
|
ffe5ddb4a6 | ||
|
|
6f82251332 | ||
|
|
76e780f813 | ||
|
|
161d8517a4 | ||
|
|
0786ab98a7 | ||
|
|
3d12c98969 | ||
|
|
07fe6e53ea | ||
|
|
2bbadf8726 | ||
|
|
f004e10d41 | ||
|
|
38c9d6cfbc | ||
|
|
62d26491d5 | ||
|
|
925db01b44 | ||
|
|
2b79cc9a17 | ||
|
|
f141465b41 | ||
|
|
ecc5d4ce30 | ||
|
|
de558bf94d | ||
|
|
9b3bb32c87 | ||
|
|
6f5cb0df96 | ||
|
|
2195d07f78 | ||
|
|
c09b02f1d0 | ||
|
|
76c7f8c41d | ||
|
|
6a1f37b243 | ||
|
|
590fc21820 | ||
|
|
4575fa96c1 | ||
|
|
1a7ddbef7a | ||
|
|
555d30645f | ||
|
|
80d04fdf9c | ||
|
|
4ea92223c0 | ||
|
|
f8e41caaf2 | ||
|
|
3963623407 | ||
|
|
bbb2d8dc05 | ||
|
|
db035bbd7d | ||
|
|
69f33ddca9 | ||
|
|
2eca28d571 | ||
|
|
d9ac445149 | ||
|
|
fe06bf48e7 | ||
|
|
3f1f9983e3 | ||
|
|
a681402956 | ||
|
|
3c7262d2b3 | ||
|
|
7a25aafc23 | ||
|
|
b2c4fbb593 | ||
|
|
e2dec1f2fe | ||
|
|
5f1ef71b3b | ||
|
|
b3550bc933 | ||
|
|
09b898d655 | ||
|
|
92b92403c6 | ||
|
|
b84d393208 | ||
|
|
464f8de5f5 | ||
|
|
3a6db38172 | ||
|
|
dbefcfe342 | ||
|
|
e2d2d7fd7d | ||
|
|
31ccf490ae | ||
|
|
50ae0902f0 | ||
|
|
873f416fec | ||
|
|
70e72da404 | ||
|
|
d573ce6e10 | ||
|
|
e9159cc8c1 | ||
|
|
bf60f8f9e3 | ||
|
|
2787edbf45 | ||
|
|
bb5aeaaf15 | ||
|
|
f68f44615c | ||
|
|
66ff5942e4 | ||
|
|
a11d8eff4a | ||
|
|
b233505c24 | ||
|
|
ad564f98ef | ||
|
|
d488e7d5dd | ||
|
|
2d66fb5702 | ||
|
|
2b94e01c56 | ||
|
|
08e51fde98 | ||
|
|
e25743e3f0 | ||
|
|
055c598491 | ||
|
|
a185a94d56 | ||
|
|
f1ee9c89c0 | ||
|
|
5b81c4dc7c | ||
|
|
e54d9ab5a9 | ||
|
|
f9fc61ecf5 | ||
|
|
a0eadc282b | ||
|
|
412649ed9e | ||
|
|
be4014962c | ||
|
|
5840bcdf85 | ||
|
|
2672f426dc | ||
|
|
20f9421ea4 | ||
|
|
c1bb58cf17 | ||
|
|
f92d748de3 | ||
|
|
21597ba746 | ||
|
|
efbb959ecc | ||
|
|
b128d5de0a | ||
|
|
41d9e96406 | ||
|
|
ac6bb35d76 | ||
|
|
1316882ef4 | ||
|
|
7fdb669786 | ||
|
|
f595cee7f6 | ||
|
|
43844739a2 | ||
|
|
a3096c04f1 | ||
|
|
0684011bda | ||
|
|
eef0b1cbbb | ||
|
|
542d2ad1e9 | ||
|
|
e63a549485 | ||
|
|
3e552564dc | ||
|
|
0a8d1fa0f5 | ||
|
|
f2c87d1f66 | ||
|
|
2f2db1a03f | ||
|
|
0493710cb4 | ||
|
|
b5d73c98fe | ||
|
|
6b6e95aa3f | ||
|
|
f35ee76c95 | ||
|
|
bb66150b5c | ||
|
|
9c2a902b51 | ||
|
|
69da467b7c | ||
|
|
c8d3f341a8 | ||
|
|
3717e33d00 | ||
|
|
7ab72543a3 | ||
|
|
80f31cdff9 | ||
|
|
40056f3181 | ||
|
|
b0e9703d9f | ||
|
|
0ca97de1b2 | ||
|
|
b958734946 | ||
|
|
2acede98f3 | ||
|
|
fb2edf43be | ||
|
|
484faedcc5 | ||
|
|
8bde8741dd | ||
|
|
d5a02e6285 | ||
|
|
a35ec8cf3c | ||
|
|
ae8db9256c | ||
|
|
688dd3a39b | ||
|
|
6223f362c3 | ||
|
|
1148e4821c | ||
|
|
f32eecc0d7 | ||
|
|
2ba516f50f | ||
|
|
c4c7af54ff | ||
|
|
ae0bf6318d | ||
|
|
3be2242431 | ||
|
|
d9c0911238 | ||
|
|
5aa8369ac5 | ||
|
|
35e8cecdcf | ||
|
|
a7939414ae | ||
|
|
6c355ae5b7 | ||
|
|
843247b02d | ||
|
|
efbb8446e3 | ||
|
|
a279a2b1a3 | ||
|
|
3329dfaf20 | ||
|
|
b615bfa664 | ||
|
|
f6bd467ec8 | ||
|
|
8548c74d58 | ||
|
|
e2b93ec08c | ||
|
|
e565a5a118 | ||
|
|
3a41138f39 | ||
|
|
889457ae96 | ||
|
|
be88cdf42e | ||
|
|
e37cefeb1d | ||
|
|
ae20e55b1a | ||
|
|
bd29e1738c | ||
|
|
f28f5ee688 | ||
|
|
c92c334e03 | ||
|
|
fb8f260a94 | ||
|
|
9635bd9b43 | ||
|
|
9f5e97b8c9 | ||
|
|
8dd9ba65d0 | ||
|
|
1aec94ee7d | ||
|
|
d4b153107a | ||
|
|
d405a0d04b | ||
|
|
86587258c9 | ||
|
|
7571d35fb3 | ||
|
|
c7bef56639 | ||
|
|
d5a75c2d53 | ||
|
|
ad90785c39 | ||
|
|
e11b0c7839 | ||
|
|
a6929f2d8d | ||
|
|
9b064eea90 | ||
|
|
d9ef87e21f | ||
|
|
7b3ad98698 | ||
|
|
ea1a4e4710 | ||
|
|
2f678ba32e | ||
|
|
c00cdc7407 | ||
|
|
4bab5a59fc | ||
|
|
4932353003 | ||
|
|
5997579330 | ||
|
|
7abb52b42d | ||
|
|
5b0b3b6c70 | ||
|
|
65d361625e | ||
|
|
8d09d9d604 | ||
|
|
41cf116e6d | ||
|
|
ddfd9bd0d8 | ||
|
|
5abdf1e4b0 | ||
|
|
4234008337 | ||
|
|
7b7c2da67c | ||
|
|
12dd865cec | ||
|
|
ff09bdd110 | ||
|
|
ec788f2472 | ||
|
|
0c53fa6c0b | ||
|
|
b2eae2c33f | ||
|
|
7da1e48aa5 | ||
|
|
10b6f533b2 | ||
|
|
6a77dbe8b5 | ||
|
|
6f7aedbcd2 | ||
|
|
97285f463e | ||
|
|
6e643cb43b | ||
|
|
6cb734fb94 | ||
|
|
df846374f5 | ||
|
|
65ff843ada | ||
|
|
26a7876525 | ||
|
|
718cece22e | ||
|
|
00ce3c038b | ||
|
|
d4e7211156 | ||
|
|
1b3e254a3f | ||
|
|
52748ba66f | ||
|
|
86057b5923 | ||
|
|
ef8223bd8b | ||
|
|
7d1e2fec90 | ||
|
|
382597f356 | ||
|
|
45200d0480 | ||
|
|
1534fb598b | ||
|
|
819cc625a1 | ||
|
|
7e82b6e400 | ||
|
|
02c44f514a | ||
|
|
6ef5d4b6aa | ||
|
|
cb8c1341e2 | ||
|
|
b2391dd66a | ||
|
|
bca9f5e859 | ||
|
|
b654ef1b43 | ||
|
|
348abcefe9 | ||
|
|
a96fcd944e | ||
|
|
05aa52b032 | ||
|
|
cce9befe8c | ||
|
|
8e7ec7af4c | ||
|
|
2e1845887c | ||
|
|
c1be5be188 | ||
|
|
76b6853f90 | ||
|
|
89935ac42b | ||
|
|
050b3b3007 | ||
|
|
249dbdaaf8 | ||
|
|
dbb006d745 | ||
|
|
5d4197076c | ||
|
|
1e223b1a2a | ||
|
|
b19e7e1495 | ||
|
|
1ef7e2173b | ||
|
|
bef6ffc094 | ||
|
|
06b08d595b | ||
|
|
4fb031d76c | ||
|
|
c4f13fc8bd | ||
|
|
8a534d11d2 | ||
|
|
57ea58fc3c | ||
|
|
245fcd7502 | ||
|
|
dbeb00ba1c | ||
|
|
cbfd7ad1b1 | ||
|
|
d4033a7705 | ||
|
|
96bd25eae5 | ||
|
|
ec8e934bf4 | ||
|
|
3092ba1fc6 | ||
|
|
5ea17700b3 | ||
|
|
d418444dc0 | ||
|
|
531b003347 | ||
|
|
dca88a58e7 | ||
|
|
da878a9fab | ||
|
|
95552a7a55 | ||
|
|
90b638eff0 | ||
|
|
ccd4fd9aba | ||
|
|
5e94b1b689 | ||
|
|
f3f51cf244 | ||
|
|
5f109b5085 | ||
|
|
2d15c4864f | ||
|
|
b183f7af42 | ||
|
|
2ece0856d4 | ||
|
|
429c76ce03 | ||
|
|
a0cc8a8a3d | ||
|
|
5b53b50b01 | ||
|
|
506d0f13c7 | ||
|
|
6a0a7d70bc | ||
|
|
3742940290 | ||
|
|
1cb647b7ae | ||
|
|
ffeae93728 | ||
|
|
e90bd136f6 | ||
|
|
30eb11b85e | ||
|
|
a04598c77a | ||
|
|
cad2df79b6 | ||
|
|
089136552b | ||
|
|
1b0bc13903 | ||
|
|
d125fab0b7 | ||
|
|
13210343db | ||
|
|
3c4ac8b01a | ||
|
|
0a888a72c8 | ||
|
|
40f33dff89 | ||
|
|
5938e38070 | ||
|
|
31bc171d6b | ||
|
|
0967234ad8 | ||
|
|
911c9e4704 | ||
|
|
072c3a992c | ||
|
|
1e0e4831b8 | ||
|
|
e804dbd48e | ||
|
|
9a5aa217e6 | ||
|
|
c6beaec102 | ||
|
|
5a9944f79d | ||
|
|
89c267aa5d | ||
|
|
f33296b44f | ||
|
|
a8416b073e | ||
|
|
fd4a7c5716 | ||
|
|
771e719963 | ||
|
|
abbf6565d4 | ||
|
|
5263329ab5 | ||
|
|
c3d2c17830 | ||
|
|
29213421ec | ||
|
|
c5293715e1 | ||
|
|
2c40a7f105 | ||
|
|
a3ed2bc068 | ||
|
|
16cc70f344 | ||
|
|
6dd783051f | ||
|
|
1bb85d0fa0 | ||
|
|
dfeb87be10 | ||
|
|
dae50a7b88 | ||
|
|
63324fcec1 | ||
|
|
49642f5a1d | ||
|
|
016d0f889c | ||
|
|
5184be0e98 | ||
|
|
0643ecc00b | ||
|
|
7e5dcd3814 | ||
|
|
fe84feb184 | ||
|
|
54d3b34876 | ||
|
|
b6dfc3d17b | ||
|
|
96c6b9c214 | ||
|
|
27666c193e | ||
|
|
b76f7202a4 | ||
|
|
7ccba88780 | ||
|
|
87d324b063 | ||
|
|
e397c036e4 | ||
|
|
29384596d4 | ||
|
|
88a741c93a | ||
|
|
db3490f61a | ||
|
|
4930c1032e | ||
|
|
202b4de5ca | ||
|
|
6763bfd997 | ||
|
|
aeba03a769 | ||
|
|
8f9585e4bc | ||
|
|
e5e0464929 | ||
|
|
7f25fa07a4 | ||
|
|
c2537f329d | ||
|
|
b7ffa3966d | ||
|
|
9240fb82e4 | ||
|
|
2eb41e932b | ||
|
|
51e299998f | ||
|
|
8b00773c84 | ||
|
|
b42ec0ae13 | ||
|
|
78d7865207 | ||
|
|
2f6578fd5a | ||
|
|
0844939eca | ||
|
|
8f2d55c146 | ||
|
|
3e5e6ce3ab | ||
|
|
4831750ffd | ||
|
|
7d7380d622 | ||
|
|
a0b9e92ae9 | ||
|
|
ce180f1bbb | ||
|
|
c99e5ce2de | ||
|
|
5ac5e31dd2 | ||
|
|
eaa7923d1f | ||
|
|
bcc7e6756e | ||
|
|
56367cc14e | ||
|
|
6e0ce3b742 | ||
|
|
aa42e0ad18 | ||
|
|
c0abc1d647 | ||
|
|
5de6dc3473 | ||
|
|
fab018782c | ||
|
|
0211729525 | ||
|
|
540ece5a40 | ||
|
|
bb6255b6a4 | ||
|
|
78e7adfbc1 | ||
|
|
6f26ae50ea | ||
|
|
a5e57f1836 | ||
|
|
9e5fefa3ee | ||
|
|
8b16135955 | ||
|
|
a1108889cb | ||
|
|
150c8e0312 | ||
|
|
f3916b4ef6 | ||
|
|
8df4292e08 | ||
|
|
05c768610e | ||
|
|
21a5242abe | ||
|
|
deb9eb8d9b | ||
|
|
4a91d87d9d | ||
|
|
408d01d546 | ||
|
|
3b9e42a256 | ||
|
|
6d500e52e7 | ||
|
|
3f72b49286 | ||
|
|
8b9f13e9e7 | ||
|
|
064db9fb6a | ||
|
|
c47f8606cd | ||
|
|
3e2f10a5b9 | ||
|
|
b060b70a6b | ||
|
|
7ea7d78e66 | ||
|
|
b64175ff6e | ||
|
|
164cc09f19 | ||
|
|
0960f61c37 | ||
|
|
f8bf864fc9 | ||
|
|
93aece75cf | ||
|
|
5159258de5 | ||
|
|
68a834ac14 | ||
|
|
33a430419c | ||
|
|
eb4ffebba0 | ||
|
|
53d4c4c03e | ||
|
|
e80585f77e | ||
|
|
0ff2fe6d6a | ||
|
|
b0885ff60a | ||
|
|
a55fbca16a | ||
|
|
fcd69e3e6f | ||
|
|
28e87fe216 | ||
|
|
1d2095b0b3 | ||
|
|
5fdfb26950 | ||
|
|
5d7656323b | ||
|
|
932c26dfc6 | ||
|
|
5c38522ec6 | ||
|
|
ab4d9b9984 | ||
|
|
74b1bc8303 | ||
|
|
25e01c86bc | ||
|
|
b127021701 | ||
|
|
123d227c13 | ||
|
|
88b96812de | ||
|
|
8abe62e53e | ||
|
|
d07a9dcf81 | ||
|
|
1820073b65 | ||
|
|
1669fa4044 | ||
|
|
d3c1f8e26a | ||
|
|
a78f81014e | ||
|
|
4c74f720eb | ||
|
|
03251abe88 | ||
|
|
43fb630393 | ||
|
|
54e59cc61b | ||
|
|
d6ae7da44c | ||
|
|
280aa35b73 | ||
|
|
f3bce23942 | ||
|
|
1015654d27 | ||
|
|
d8c80f7e28 | ||
|
|
3aaa93675d | ||
|
|
af0f894dd3 | ||
|
|
6e7f5a31f1 | ||
|
|
79b7866e30 | ||
|
|
d8da7a8cc2 | ||
|
|
e36e9e878f | ||
|
|
93423d4b13 | ||
|
|
ee165491c9 | ||
|
|
8ad9bc5eed | ||
|
|
e2e49a4555 | ||
|
|
a92fd5b35d | ||
|
|
5e5b29677a | ||
|
|
9f4a85d373 | ||
|
|
b0edb7cf3b | ||
|
|
0d5bbc177c | ||
|
|
f7c14890f1 | ||
|
|
924ff87979 | ||
|
|
713bb51974 | ||
|
|
6faf720076 | ||
|
|
a13a7c363f | ||
|
|
9d6192ba1f | ||
|
|
ddeece0a5c | ||
|
|
3445191a05 | ||
|
|
2d75679586 | ||
|
|
d676a6c76c | ||
|
|
26eeb39f88 | ||
|
|
49c2e3030c | ||
|
|
ea267a0298 | ||
|
|
6e0063c977 | ||
|
|
1f89314bbb | ||
|
|
afd7097b53 | ||
|
|
087cd5a093 | ||
|
|
06d742bd16 | ||
|
|
05bcaf9dbc | ||
|
|
d37fa652bb | ||
|
|
170e74db55 | ||
|
|
b75133516d | ||
|
|
e90c41a3b7 | ||
|
|
5758b34dcf | ||
|
|
b49d8a18a2 | ||
|
|
0d77e7085b | ||
|
|
d0203eedc4 | ||
|
|
3c74a342d1 | ||
|
|
8503610b0b | ||
|
|
ae1225b948 | ||
|
|
959361adc2 | ||
|
|
f9ccb766c2 | ||
|
|
e5e5d3c67c | ||
|
|
ea1813a1b6 | ||
|
|
b1f0a10a55 | ||
|
|
1af16bfbef | ||
|
|
524e0941e4 | ||
|
|
bcea5a6b25 | ||
|
|
b2a256e2fc | ||
|
|
83c8db6867 | ||
|
|
96e2985aed | ||
|
|
5e544ff1f9 | ||
|
|
58c31937a2 | ||
|
|
01ac1f3f93 | ||
|
|
f93d11fe36 | ||
|
|
44a53f18d3 | ||
|
|
72218b643d | ||
|
|
d00a77e247 | ||
|
|
6e9f9d62a1 | ||
|
|
48f30f022d | ||
|
|
2f526a7725 | ||
|
|
43855ab555 | ||
|
|
9fdeb8e639 | ||
|
|
a19523a3f8 | ||
|
|
023e5dd2fc | ||
|
|
0af9bb887a | ||
|
|
1e1beec6e4 | ||
|
|
9a72c0c8e5 | ||
|
|
cff5c7b9a2 | ||
|
|
be96f1b9d4 | ||
|
|
b8511ec4f8 | ||
|
|
e5f7488a7c | ||
|
|
a2b7132cf3 | ||
|
|
8be57a6ef7 | ||
|
|
49a5ff34df | ||
|
|
53ac8ac49c | ||
|
|
2b721ac52c | ||
|
|
31f45ae5a4 | ||
|
|
f912e00d21 | ||
|
|
7ca1eab4b2 | ||
|
|
cbb469e5ab | ||
|
|
17c9008f95 | ||
|
|
2640d28468 | ||
|
|
f695254fc1 | ||
|
|
18e5b711bb | ||
|
|
9912123293 | ||
|
|
e737f3260d | ||
|
|
be8562b4db | ||
|
|
dc0413b416 | ||
|
|
d1b5f3078e | ||
|
|
c8194545a4 | ||
|
|
c67a5d1a8d | ||
|
|
00b35bd3ab | ||
|
|
a40f3b91db | ||
|
|
9537bfaf6f | ||
|
|
e9fb28d374 | ||
|
|
1161c3c446 | ||
|
|
d3e4b56d30 | ||
|
|
f9dd295188 | ||
|
|
6fae5b8b77 | ||
|
|
2529ed3fb9 | ||
|
|
a1e7e92d6d | ||
|
|
15251c840c | ||
|
|
663265d7b3 | ||
|
|
f2365771ff | ||
|
|
97e6758449 | ||
|
|
91e197db2c | ||
|
|
8e10170347 | ||
|
|
74b6fc7e2e | ||
|
|
6b4f959b12 | ||
|
|
578efd3b7f | ||
|
|
31669cf457 | ||
|
|
d9e29d3c81 | ||
|
|
5c38d62a61 | ||
|
|
9b5449f657 | ||
|
|
6da31f797d | ||
|
|
f546bd2640 | ||
|
|
d32ed06516 | ||
|
|
26c22295fc | ||
|
|
de9aebbda9 | ||
|
|
f7ea3a7972 | ||
|
|
51c3f2b04a | ||
|
|
61d80793e7 | ||
|
|
b59814aa83 | ||
|
|
5c46e913a2 | ||
|
|
1a592273bf | ||
|
|
b597a2fb62 | ||
|
|
ea55cbc914 | ||
|
|
86d8a2ed8d | ||
|
|
4bfe787f01 | ||
|
|
c0b04694d9 | ||
|
|
dd4a0a502d | ||
|
|
1eb4a9770e | ||
|
|
165ae77113 | ||
|
|
dad060e8c1 | ||
|
|
75fb200eaf | ||
|
|
2bd0df2910 | ||
|
|
2b6fe1b28a | ||
|
|
f39669e615 | ||
|
|
1f0881f74e | ||
|
|
cb158b0947 | ||
|
|
3adc43683a | ||
|
|
3ae4c72b46 | ||
|
|
7e93c1a74b | ||
|
|
f191f02296 | ||
|
|
9e86332c5e | ||
|
|
d85904748e | ||
|
|
5167b2c491 | ||
|
|
6b88bcf02f | ||
|
|
eb97a33e8e | ||
|
|
e829e1e2ca | ||
|
|
015cbdd37a | ||
|
|
66d834c7e9 | ||
|
|
066d5c5628 | ||
|
|
34c547a431 | ||
|
|
af1ab4c953 | ||
|
|
186766960f | ||
|
|
de02a6999d | ||
|
|
736761ee93 | ||
|
|
7bad184849 | ||
|
|
612c8e8aa3 | ||
|
|
8da98f95e1 | ||
|
|
6d4df646af | ||
|
|
d98db6ee67 | ||
|
|
12e5314c61 | ||
|
|
b6a165aef5 | ||
|
|
6b025832d7 | ||
|
|
ea58313e31 | ||
|
|
a27002af0f | ||
|
|
8ad7c184f1 | ||
|
|
168e662a38 | ||
|
|
a00452faf1 | ||
|
|
32b0c90f17 | ||
|
|
cd992a6994 | ||
|
|
38a92042de | ||
|
|
65263ebad9 | ||
|
|
50530b57c6 | ||
|
|
04a565e5a2 | ||
|
|
9aa091818d | ||
|
|
6a226f21bd | ||
|
|
67d641572e | ||
|
|
a92ed17d65 | ||
|
|
580a0eecdd | ||
|
|
d28a754ee9 | ||
|
|
5fd8593095 | ||
|
|
bcaf00dc97 | ||
|
|
c012e3cb7e | ||
|
|
4575f52fe0 | ||
|
|
c01a101f83 | ||
|
|
8553fb3995 | ||
|
|
3636da5c4e | ||
|
|
8bd430b793 |
@@ -8,4 +8,4 @@ checkmarx:
|
||||
configs:
|
||||
sast:
|
||||
# Exclude test directories
|
||||
filter: "!app/src/test/**"
|
||||
filter: "**/test/**,!**/androidTest/**,!**/commonTest/**,!**/jvmTest/**,!**/jsTest/**,!**/iosTest/**"
|
||||
|
||||
129
.editorconfig
129
.editorconfig
@@ -12,127 +12,20 @@ end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
guidelines = 120
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 4
|
||||
|
||||
# Xml project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
|
||||
# Xml config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
|
||||
# JSON files
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
# JS files
|
||||
[*.{js,ts,scss,html}]
|
||||
# Kotlin files
|
||||
# noinspection EditorConfigKeyCorrectness
|
||||
[*.{kt,kts}]
|
||||
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-declaration-site
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-declaration-site
|
||||
trailing-comma-on-declaration-site = true
|
||||
# https://pinterest.github.io/ktlint/1.0.1/rules/configuration-ktlint/#trailing-comma-on-call-site
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
|
||||
[*.{scss,yml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{ts}]
|
||||
quote_type = single
|
||||
|
||||
[*.{scss,yml,csproj}]
|
||||
indent_size = 2
|
||||
|
||||
[*.sln]
|
||||
indent_style = tab
|
||||
|
||||
# Dotnet code style settings:
|
||||
[*.{cs,vb}]
|
||||
# Sort using and Import directives with System.* appearing first
|
||||
dotnet_sort_system_directives_first = true
|
||||
# Avoid "this." and "Me." if not necessary
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# Use language keywords instead of framework type names for type references
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# Suggest more modern language features when available
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
|
||||
# Prefix private members with underscore
|
||||
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
|
||||
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
|
||||
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
|
||||
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
||||
|
||||
dotnet_naming_style.prefix_underscore.capitalization = camel_case
|
||||
dotnet_naming_style.prefix_underscore.required_prefix = _
|
||||
|
||||
# Async methods should have "Async" suffix
|
||||
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
|
||||
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
|
||||
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
|
||||
|
||||
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
|
||||
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.any_async_methods.required_modifiers = async
|
||||
|
||||
dotnet_naming_style.end_in_async.required_prefix =
|
||||
dotnet_naming_style.end_in_async.required_suffix = Async
|
||||
dotnet_naming_style.end_in_async.capitalization = pascal_case
|
||||
dotnet_naming_style.end_in_async.word_separator =
|
||||
|
||||
# Obsolete warnings, this should be removed or changed to warning once we address some of the obsolete items.
|
||||
dotnet_diagnostic.CS0618.severity = suggestion
|
||||
|
||||
# Obsolete warnings, this should be removed or changed to warning once we address some of the obsolete items.
|
||||
dotnet_diagnostic.CS0612.severity = suggestion
|
||||
|
||||
# Remove unnecessary using directives https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0005
|
||||
dotnet_diagnostic.IDE0005.severity = warning
|
||||
|
||||
# CSharp code style settings:
|
||||
[*.cs]
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Prefer method-like constructs to have a expression-body
|
||||
csharp_style_expression_bodied_methods = true:none
|
||||
csharp_style_expression_bodied_constructors = true:none
|
||||
csharp_style_expression_bodied_operators = true:none
|
||||
|
||||
# Prefer property-like constructs to have an expression-body
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Suggest more modern language features when available
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Newline settings
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
|
||||
# Namespace settings
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
||||
# Switch expression
|
||||
dotnet_diagnostic.CS8509.severity = error # missing switch case for named enum value
|
||||
dotnet_diagnostic.CS8524.severity = none # missing switch case for unnamed enum value
|
||||
|
||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -5,10 +5,10 @@
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Default file owners.
|
||||
* @bitwarden/team-android @brian-livefront @david-livefront @dseverns-livefront @ahaisting-livefront
|
||||
* @bitwarden/team-android @brian-livefront @david-livefront
|
||||
|
||||
# Actions and workflow changes.
|
||||
.github/workflows @bitwarden/dept-development-mobile
|
||||
.github/ @bitwarden/dept-development-mobile
|
||||
|
||||
# Auth
|
||||
# app/src/main/java/com/x8bit/bitwarden/data/auth @bitwarden/team-auth-dev
|
||||
|
||||
84
.github/ISSUE_TEMPLATE/bug-bwa.yml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/bug-bwa.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Authenticator Android App Bug Report
|
||||
description: File a bug report
|
||||
labels: [ "app:authenticator", "bug" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: How can we reproduce the behavior.
|
||||
value: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. Click on '...'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Result
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual Result
|
||||
description: A clear and concise description of what is happening.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots or Videos
|
||||
description: If applicable, add screenshots and/or a short video to help explain your problem.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any other context about the problem here.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Build Version
|
||||
description: What version of our software are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: server-region
|
||||
attributes:
|
||||
label: What server are you connecting to?
|
||||
options:
|
||||
- US
|
||||
- EU
|
||||
- Self-host
|
||||
- N/A
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-version
|
||||
attributes:
|
||||
label: Self-host Server Version
|
||||
description: If self-hosting, what version of Bitwarden Server are you running?
|
||||
- type: textarea
|
||||
id: environment-details
|
||||
attributes:
|
||||
label: Environment Details
|
||||
placeholder: |
|
||||
- Device: [e.g. Pixel Tablet, Samsung Galaxy S24 ]
|
||||
- OS Version: [e.g. API 32, Tiramisu ]
|
||||
- type: checkboxes
|
||||
id: issue-tracking-info
|
||||
attributes:
|
||||
label: Issue Tracking Info
|
||||
description: |
|
||||
Issue tracking information
|
||||
options:
|
||||
- label: I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
|
||||
64
.github/ISSUE_TEMPLATE/bug-passkey.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bug-passkey.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Passkey Bug Report
|
||||
description: File a Passkey / FIDO2 related bug report
|
||||
labels: [ "app:password-manager", "bug-passkey" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this Passkey-related bug report!
|
||||
|
||||
Please provide as much detail as possible to help us investigate the issue.
|
||||
|
||||
- type: dropdown
|
||||
id: origin
|
||||
attributes:
|
||||
label: Origin
|
||||
description: Are you using a web browser or a native application?
|
||||
options:
|
||||
- Web (Browser)
|
||||
- Native Application (non-browser app)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: rp-id
|
||||
attributes:
|
||||
label: Web URL or App name
|
||||
description: The website domain or app name you were trying to use the Passkey with
|
||||
placeholder: "e.g. example.com or ExampleApp"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: operation-type
|
||||
attributes:
|
||||
label: Passkey Action
|
||||
description: What passkey related action(s) were you trying to perform?
|
||||
options:
|
||||
- label: Creating new passkey (Registration)
|
||||
- label: Signing in (Authentication)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: build-info
|
||||
attributes:
|
||||
label: Build Information
|
||||
description: Please retrieve the build information from the About screen by tapping the Version number field
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-info
|
||||
attributes:
|
||||
label: Additional Information
|
||||
description: Any additional context, steps to reproduce, error messages, or relevant information about the issue
|
||||
|
||||
- type: checkboxes
|
||||
id: issue-tracking-info
|
||||
attributes:
|
||||
label: Issue Tracking Info
|
||||
description: |
|
||||
Issue tracking information
|
||||
options:
|
||||
- label: I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
|
||||
32
.github/ISSUE_TEMPLATE/bug.yml
vendored
32
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,25 +1,13 @@
|
||||
name: Android Beta Bug Report
|
||||
name: Password Manager Android App Bug Report
|
||||
description: File a bug report
|
||||
labels: [ bug ]
|
||||
labels: [ "app:password-manager", "bug" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
> [!WARNING]
|
||||
> This is the new native Bitwarden Beta app repository. For the publicly available apps in App Store / Play Store, submit your report in [bitwarden/mobile](https://github.com/bitwarden/mobile)
|
||||
|
||||
|
||||
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
|
||||
- type: checkboxes
|
||||
id: beta
|
||||
attributes:
|
||||
label: Bitwarden Beta
|
||||
options:
|
||||
- label: "I'm using the new native Bitwarden Beta app and I'm aware that legacy .NET app bugs should be reported in [bitwarden/mobile](https://github.com/bitwarden/mobile)"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
@@ -63,6 +51,22 @@ body:
|
||||
description: What version of our software are you running?
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: server-region
|
||||
attributes:
|
||||
label: What server are you connecting to?
|
||||
options:
|
||||
- US
|
||||
- EU
|
||||
- Self-host
|
||||
- N/A
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server-version
|
||||
attributes:
|
||||
label: Self-host Server Version
|
||||
description: If self-hosting, what version of Bitwarden Server are you running?
|
||||
- type: textarea
|
||||
id: environment-details
|
||||
attributes:
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Legacy Android Bug Reports
|
||||
url: https://github.com/bitwarden/mobile/issues
|
||||
about: Bugs found in the publicly available .NET MAUI app should be reported in [bitwarden/mobile](https://github.com/bitwarden/mobile)
|
||||
- name: Feature Requests
|
||||
url: https://community.bitwarden.com/c/feature-requests/
|
||||
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
|
||||
@@ -15,3 +12,5 @@ contact_links:
|
||||
- name: Security Issues
|
||||
url: https://hackerone.com/bitwarden
|
||||
about: We use HackerOne to manage security disclosures.
|
||||
- name: Report mobile autofill failure
|
||||
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
|
||||
|
||||
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -15,10 +15,11 @@
|
||||
- Contributor guidelines followed
|
||||
- All formatters and local linters executed and passed
|
||||
- Written new unit and / or integration tests where applicable
|
||||
- Protected functional changes with optionality (feature flags)
|
||||
- Used internationalization (i18n) for all UI strings
|
||||
- CI builds passed
|
||||
- Communicated to DevOps any deployment requirements
|
||||
- Updated any necessary documentation or informed the documentation team
|
||||
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team
|
||||
|
||||
## 🦮 Reviewer guidelines
|
||||
|
||||
@@ -27,8 +28,7 @@
|
||||
- 👍 (`:+1:`) or similar for great changes
|
||||
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
|
||||
- ❓ (`:question:`) for questions
|
||||
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed
|
||||
issue and could potentially benefit from discussion
|
||||
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
|
||||
- 🎨 (`:art:`) for suggestions / improvements
|
||||
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
|
||||
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
|
||||
|
||||
2
.github/codecov.yml
vendored
Normal file
2
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "src/test/**" # Tests
|
||||
50
.github/renovate.json
vendored
50
.github/renovate.json
vendored
@@ -1,32 +1,56 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>bitwarden/renovate-config"],
|
||||
"enabledManagers": ["github-actions", "gradle", "bundler"],
|
||||
"extends": [
|
||||
"github>bitwarden/renovate-config"
|
||||
],
|
||||
"enabledManagers": [
|
||||
"github-actions",
|
||||
"gradle",
|
||||
"bundler"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "gh minor",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "gradle minor",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchManagers": ["gradle"]
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "kotlin",
|
||||
"description": "Kotlin and Compose dependencies that must be updated together to maintain compatibility.",
|
||||
"matchPackagePatterns": [
|
||||
"androidx.compose:compose-bom",
|
||||
"org.jetbrains.kotlin.*",
|
||||
"com.google.devtools.ksp"
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
],
|
||||
"matchManagers": ["gradle"]
|
||||
"matchPackageNames": [
|
||||
"/androidx.compose:compose-bom/",
|
||||
"/androidx.lifecycle:*/",
|
||||
"/org.jetbrains.kotlin.*/",
|
||||
"/com.google.devtools.ksp/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "bundler minor",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchManagers": ["bundler"]
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"matchManagers": [
|
||||
"bundler"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
133
.github/scripts/jira-get-release-notes/README.md
vendored
Normal file
133
.github/scripts/jira-get-release-notes/README.md
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# Get Release Notes from Jira script
|
||||
|
||||
Fetches release notes from Jira issues.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python dev environment - use [uv](https://github.com/astral-sh/uv)
|
||||
- Jira API token. Generate one at: https://id.atlassian.com/manage-profile/security/api-tokens
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
uv pip install -r pyproject.toml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./jira_release_notes.py RELEASE-1762 example@example.com T0k3n123
|
||||
```
|
||||
|
||||
# Output Format
|
||||
|
||||
The script retrieves the content from a custom field and handles two types of Jira release notes formats:
|
||||
|
||||
1. Bullet Points:
|
||||
```
|
||||
• Point 1
|
||||
• Point 2
|
||||
• Point 3
|
||||
```
|
||||
|
||||
2. Single Line:
|
||||
```
|
||||
Single line of release notes text
|
||||
```
|
||||
|
||||
## Jira JSON format example
|
||||
|
||||
### Single line
|
||||
|
||||
```json
|
||||
...
|
||||
"customfield_10335": {
|
||||
"type": "doc",
|
||||
"version": 1,
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Single line release notes"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### Bullet points
|
||||
|
||||
```json
|
||||
...
|
||||
"customfield_10335": {
|
||||
"type": "doc",
|
||||
"version": 1,
|
||||
"content": [
|
||||
{
|
||||
"type": "bulletList",
|
||||
"content": [
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
70
.github/scripts/jira-get-release-notes/jira_release_notes.py
vendored
Executable file
70
.github/scripts/jira-get-release-notes/jira_release_notes.py
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import base64
|
||||
import json
|
||||
import requests
|
||||
|
||||
def extract_text_from_content(content):
|
||||
if isinstance(content, list):
|
||||
texts = [extract_text_from_content(item) for item in content]
|
||||
return '\n'.join(text for text in texts if text.strip())
|
||||
|
||||
if isinstance(content, dict):
|
||||
if content.get('type') == 'text':
|
||||
return content.get('text', '')
|
||||
elif content.get('type') == 'paragraph':
|
||||
return extract_text_from_content(content.get('content', []))
|
||||
elif content.get('type') == 'bulletList':
|
||||
return extract_text_from_content(content.get('content', []))
|
||||
elif content.get('type') == 'listItem':
|
||||
item_text = extract_text_from_content(content.get('content', []))
|
||||
return f"* {item_text.strip()}"
|
||||
|
||||
return ''
|
||||
|
||||
def parse_release_notes(response_json):
|
||||
try:
|
||||
fields = response_json.get('fields', {})
|
||||
release_notes_field = fields.get('customfield_10335', {})
|
||||
|
||||
if not release_notes_field or not release_notes_field.get('content'):
|
||||
return ''
|
||||
|
||||
release_notes = extract_text_from_content(release_notes_field.get('content', []))
|
||||
return release_notes
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing release notes: {str(e)}", file=sys.stderr)
|
||||
return ''
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(f"Usage: {sys.argv[0]} <issue_id> <jira_email> <jira_api_token>")
|
||||
sys.exit(1)
|
||||
|
||||
jira_issue_id = sys.argv[1]
|
||||
jira_email = sys.argv[2]
|
||||
jira_api_token = sys.argv[3]
|
||||
jira_base_url = "https://bitwarden.atlassian.net"
|
||||
|
||||
auth = base64.b64encode(f"{jira_email}:{jira_api_token}".encode()).decode()
|
||||
headers = {
|
||||
"Authorization": f"Basic {auth}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{jira_base_url}/rest/api/3/issue/{jira_issue_id}",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error fetching Jira issue: {response.status_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
release_notes = parse_release_notes(response.json())
|
||||
print(release_notes)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
.github/scripts/jira-get-release-notes/pyproject.toml
vendored
Normal file
9
.github/scripts/jira-get-release-notes/pyproject.toml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "jira-get-release-notes"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
91
.github/scripts/jira-get-release-notes/uv.lock
generated
vendored
Normal file
91
.github/scripts/jira-get-release-notes/uv.lock
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.4.26"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jira-get-release-notes"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "requests", specifier = ">=2.32.3" }]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
||||
]
|
||||
1
.github/scripts/validate-json/.python-version
vendored
Normal file
1
.github/scripts/validate-json/.python-version
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
39
.github/scripts/validate-json/README.md
vendored
Normal file
39
.github/scripts/validate-json/README.md
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# JSON Validation Scripts
|
||||
|
||||
Utility scripts for validating JSON files and checking for duplicate package names between Google and Community privileged browser lists.
|
||||
|
||||
## Usage
|
||||
|
||||
### Validate a JSON file
|
||||
|
||||
```bash
|
||||
python validate_json.py validate <json_file>
|
||||
```
|
||||
|
||||
### Check for duplicates between two JSON files
|
||||
|
||||
```bash
|
||||
python validate_json.py duplicates <json_file1> <json_file2> [output_file]
|
||||
```
|
||||
|
||||
If `output_file` is not specified, duplicates will be saved to `duplicates.txt`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
python -m unittest test_validate_json.py
|
||||
|
||||
# Run the invalid JSON test individually
|
||||
python -m unittest test_validate_json.TestValidateJson.test_validate_json_invalid
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Validate Google privileged browsers list
|
||||
python validate_json.py validate ../../app/src/main/assets/fido2_privileged_google.json
|
||||
|
||||
# Check for duplicates between Google and Community lists
|
||||
python validate_json.py duplicates ../../app/src/main/assets/fido2_privileged_google.json ../../app/src/main/assets/fido2_privileged_community.json duplicates.txt
|
||||
```
|
||||
20
.github/scripts/validate-json/fixtures/sample-invalid.json
vendored
Normal file
20
.github/scripts/validate-json/fixtures/sample-invalid.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"apps": [
|
||||
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
.github/scripts/validate-json/fixtures/sample-valid1.json
vendored
Normal file
48
.github/scripts/validate-json/fixtures/sample-valid1.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.chrome.dev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.chrome.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
.github/scripts/validate-json/fixtures/sample-valid2.json
vendored
Normal file
20
.github/scripts/validate-json/fixtures/sample-valid2.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.chromium.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
.github/scripts/validate-json/test_validate_json.py
vendored
Normal file
48
.github/scripts/validate-json/test_validate_json.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import os
|
||||
import json
|
||||
from validate_json import validate_json, find_duplicates, get_package_names
|
||||
from unittest.mock import patch
|
||||
import io
|
||||
|
||||
|
||||
class TestValidateJson(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.valid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid1.json")
|
||||
self.valid_file2 = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid2.json")
|
||||
self.invalid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-invalid.json")
|
||||
|
||||
# Suppress stdout
|
||||
self.stdout_patcher = patch('sys.stdout', new=io.StringIO())
|
||||
self.stdout_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.stdout_patcher.stop()
|
||||
|
||||
def test_validate_json_valid(self):
|
||||
"""Test validation of valid JSON file"""
|
||||
self.assertTrue(validate_json(self.valid_file))
|
||||
|
||||
def test_validate_json_invalid(self):
|
||||
"""Test validation of invalid JSON file"""
|
||||
self.assertFalse(validate_json(self.invalid_file))
|
||||
|
||||
def test_find_duplicates(self):
|
||||
"""Test when using the same file (should find duplicates)"""
|
||||
expected_package_names = get_package_names(self.valid_file)
|
||||
|
||||
duplicates = find_duplicates(self.valid_file, self.valid_file)
|
||||
|
||||
self.assertEqual(len(duplicates), len(expected_package_names))
|
||||
for package_name in expected_package_names:
|
||||
self.assertIn(package_name, duplicates)
|
||||
|
||||
def test_find_duplicates_returns_empty_list_when_no_duplicates(self):
|
||||
"""Test when using different files (should not find duplicates)"""
|
||||
duplicates = find_duplicates(self.valid_file, self.valid_file2)
|
||||
self.assertEqual(len(duplicates), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
145
.github/scripts/validate-json/validate_json.py
vendored
Normal file
145
.github/scripts/validate-json/validate_json.py
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from typing import List, Dict, Any, Set
|
||||
|
||||
|
||||
def get_package_names(file_path: str) -> Set[str]:
|
||||
"""
|
||||
Extracts package names from a JSON file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the JSON file
|
||||
|
||||
Returns:
|
||||
Set of package names
|
||||
"""
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
package_names = set()
|
||||
for app in data["apps"]:
|
||||
package_names.add(app["info"]["package_name"])
|
||||
|
||||
return package_names
|
||||
|
||||
|
||||
def validate_json(file_path: str) -> bool:
|
||||
"""
|
||||
Validates if a JSON file is correctly formatted by attempting to deserialize it.
|
||||
|
||||
Args:
|
||||
file_path: Path to the JSON file to validate
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Error: File {file_path} does not exist")
|
||||
return False
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
json.load(f)
|
||||
print(f"✅ JSON file {file_path} is valid")
|
||||
return True
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ Invalid JSON in {file_path}: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error validating {file_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def find_duplicates(file1_path: str, file2_path: str) -> List[str]:
|
||||
"""
|
||||
Checks for duplicate package_name entries between two JSON files.
|
||||
|
||||
Args:
|
||||
file1_path: Path to the first JSON file
|
||||
file2_path: Path to the second JSON file
|
||||
|
||||
Returns:
|
||||
List of duplicate package names, empty list if none found
|
||||
"""
|
||||
try:
|
||||
# Get package names from both files
|
||||
packages1 = get_package_names(file1_path)
|
||||
packages2 = get_package_names(file2_path)
|
||||
|
||||
# Find duplicates
|
||||
duplicates = list(packages1.intersection(packages2))
|
||||
|
||||
if duplicates:
|
||||
print(f"❌ Found {len(duplicates)} duplicate package names between {file1_path} and {file2_path}:")
|
||||
for dup in duplicates:
|
||||
print(f" - {dup}")
|
||||
return duplicates
|
||||
else:
|
||||
print(f"✅ No duplicate package names found between {file1_path} and {file2_path}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking duplicates: {str(e)}")
|
||||
return []
|
||||
|
||||
|
||||
def save_duplicates_to_file(duplicates: List[str], output_file: str) -> None:
|
||||
"""
|
||||
Saves the list of duplicates to a file.
|
||||
|
||||
Args:
|
||||
duplicates: List of duplicate package names
|
||||
output_file: Path to save the list of duplicates
|
||||
"""
|
||||
try:
|
||||
with open(output_file, 'w') as f:
|
||||
for dup in duplicates:
|
||||
f.write(f"{dup}\n")
|
||||
print(f"Duplicates saved to {output_file}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error saving duplicates to file: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print(" Validate JSON: python validate_json.py validate <json_file>")
|
||||
print(" Check duplicates: python validate_json.py duplicates <json_file1> <json_file2> [output_file]")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
match command:
|
||||
case "validate":
|
||||
if len(sys.argv) < 3:
|
||||
print("Error: Missing JSON file path")
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[2]
|
||||
success = validate_json(file_path)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
case "duplicates":
|
||||
if len(sys.argv) < 4:
|
||||
print("Error: Missing JSON file paths")
|
||||
sys.exit(1)
|
||||
|
||||
file1_path = sys.argv[2]
|
||||
file2_path = sys.argv[3]
|
||||
output_file = sys.argv[4] if len(sys.argv) > 4 else "duplicates.txt"
|
||||
|
||||
duplicates = find_duplicates(file1_path, file2_path)
|
||||
if duplicates:
|
||||
save_duplicates_to_file(duplicates, output_file)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
case _:
|
||||
print(f"Unknown command: {command}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
302
.github/workflows/build-authenticator.yml
vendored
Normal file
302
.github/workflows/build-authenticator.yml
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
name: Build Authenticator
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version-name:
|
||||
description: "Optional. Version string to use, in X.Y.Z format. Overrides default in the project."
|
||||
required: false
|
||||
type: string
|
||||
version-code:
|
||||
description: "Optional. Build number to use. Overrides default of GitHub run number."
|
||||
required: false
|
||||
type: number
|
||||
distribute-to-firebase:
|
||||
description: "Optional. Distribute artifacts to Firebase."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
publish-to-play-store:
|
||||
description: "Optional. Deploy bundle artifact to Google Play Store"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Authenticator
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Check Authenticator
|
||||
run: bundle exec fastlane check
|
||||
|
||||
- name: Build Authenticator
|
||||
run: bundle exec fastlane buildAuthenticatorDebug
|
||||
|
||||
publish_playstore:
|
||||
name: Publish Authenticator Play Store artifacts
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ["aab", "apk"]
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/secrets
|
||||
mkdir -p ${{ github.workspace }}/keystores
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name authenticator_apk-keystore.jks --file ${{ github.workspace }}/keystores/authenticator_apk-keystore.jks --output none
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name authenticator_aab-keystore.jks --file ${{ github.workspace }}/keystores/authenticator_aab-keystore.jks --output none
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name com.bitwarden.authenticator-google-services.json --file ${{ github.workspace }}/authenticator/src/google-services.json --output none
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name com.bitwarden.authenticator.dev-google-services.json --file ${{ github.workspace }}/authenticator/src/debug/google-services.json --output none
|
||||
|
||||
- name: Download Firebase credentials
|
||||
if : ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/secrets
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name authenticator_play_firebase-creds.json --file ${{ github.workspace }}/secrets/authenticator_play_firebase-creds.json --output none
|
||||
|
||||
- name: Download Play Store credentials
|
||||
if: ${{ inputs.publish-to-play-store }}
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/secrets
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name authenticator_play_store-creds.json --file ${{ github.workspace }}/secrets/authenticator_play_store-creds.json --output none
|
||||
|
||||
- name: Verify Play Store credentials
|
||||
if: ${{ inputs.publish-to-play-store }}
|
||||
run: |
|
||||
bundle exec fastlane run validate_play_store_json_key \
|
||||
json_key:${{ github.workspace }}/secrets/authenticator_play_store-creds.json }}
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Increment version
|
||||
run: |
|
||||
DEFAULT_VERSION_CODE=$GITHUB_RUN_NUMBER
|
||||
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
|
||||
bundle exec fastlane setAuthenticatorBuildVersionInfo \
|
||||
versionCode:$VERSION_CODE \
|
||||
versionName:${{ inputs.version-name || '' }}
|
||||
|
||||
regex='versionName = "([^"]+)"'
|
||||
if [[ "$(cat authenticator/build.gradle.kts)" =~ $regex ]]; then
|
||||
VERSION_NAME="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
echo "Version Name: ${VERSION_NAME}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Version Number: $VERSION_CODE" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Generate release Play Store bundle
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
run: |
|
||||
bundle exec fastlane bundleAuthenticatorRelease \
|
||||
storeFile:${{ github.workspace }}/keystores/authenticator_aab-keystore.jks \
|
||||
storePassword:'${{ secrets.BWA_AAB_KEYSTORE_STORE_PASSWORD }}' \
|
||||
keyAlias:authenticatorupload \
|
||||
keyPassword:'${{ secrets.BWA_AAB_KEYSTORE_KEY_PASSWORD }}'
|
||||
|
||||
- name: Generate release Play Store APK
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
run: |
|
||||
bundle exec fastlane buildAuthenticatorRelease \
|
||||
storeFile:${{ github.workspace }}/keystores/authenticator_apk-keystore.jks \
|
||||
storePassword:'${{ secrets.BWA_APK_KEYSTORE_STORE_PASSWORD }}' \
|
||||
keyAlias:bitwardenauthenticator \
|
||||
keyPassword:'${{ secrets.BWA_APK_KEYSTORE_KEY_PASSWORD }}'
|
||||
|
||||
- name: Upload release Play Store .aab artifact
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.bitwarden.authenticator.aab
|
||||
path: authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload release .apk artifact
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.bitwarden.authenticator.apk
|
||||
path: authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum file for Release AAB
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
run: |
|
||||
sha256sum "authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab" \
|
||||
> ./authenticator-android-aab-sha256.txt
|
||||
|
||||
- name: Create checksum for release .apk artifact
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
run: |
|
||||
sha256sum "authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk" \
|
||||
> ./authenticator-android-apk-sha256.txt
|
||||
|
||||
- name: Upload .apk SHA file for release
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: authenticator-android-apk-sha256.txt
|
||||
path: ./authenticator-android-apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .aab SHA file for release
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: authenticator-android-aab-sha256.txt
|
||||
path: ./authenticator-android-aab-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install Firebase app distribution plugin
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
run: bundle exec fastlane add_plugin firebase_app_distribution
|
||||
|
||||
- name: Publish release bundle to Firebase
|
||||
if: ${{ matrix.variant == 'aab' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
env:
|
||||
FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/authenticator_play_firebase-creds.json
|
||||
run: |
|
||||
bundle exec fastlane distributeAuthenticatorReleaseBundleToFirebase \
|
||||
serviceCredentialsFile:${{ env.FIREBASE_CREDS_PATH }}
|
||||
|
||||
# Only publish bundles to Play Store when `publish-to-play-store` is true while building
|
||||
# bundles
|
||||
- name: Publish release bundle to Google Play Store
|
||||
if: ${{ inputs.publish-to-play-store && matrix.variant == 'aab' }}
|
||||
env:
|
||||
PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/authenticator_play_store-creds.json
|
||||
run: |
|
||||
bundle exec fastlane publishAuthenticatorReleaseToGooglePlayStore \
|
||||
serviceCredentialsFile:${{ env.PLAY_STORE_CREDS_FILE }} \
|
||||
146
.github/workflows/build.yml
vendored
146
.github/workflows/build.yml
vendored
@@ -30,20 +30,33 @@ env:
|
||||
JAVA_VERSION: 17
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -53,7 +66,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -62,13 +75,13 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -85,7 +98,7 @@ jobs:
|
||||
run: bundle exec fastlane assembleDebugApks
|
||||
|
||||
- name: Upload test reports on failure
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: failure()
|
||||
with:
|
||||
name: test-reports
|
||||
@@ -103,10 +116,10 @@ jobs:
|
||||
artifact: ["apk", "aab"]
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -117,7 +130,7 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -157,10 +170,10 @@ jobs:
|
||||
--name app_play_prod_firebase-creds.json --file ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json --output none
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -170,7 +183,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -179,14 +192,23 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Update app CI Build info
|
||||
run: |
|
||||
./scripts/update_app_ci_build_info.sh \
|
||||
$GITHUB_REPOSITORY \
|
||||
$GITHUB_REF_NAME \
|
||||
$GITHUB_SHA \
|
||||
$GITHUB_RUN_ID \
|
||||
$GITHUB_RUN_ATTEMPT
|
||||
|
||||
- name: Increment version
|
||||
run: |
|
||||
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
||||
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
|
||||
bundle exec fastlane setBuildVersionInfo \
|
||||
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
|
||||
versionName:${{ inputs.version-name }}
|
||||
@@ -244,78 +266,78 @@ jobs:
|
||||
|
||||
- name: Upload release Play Store .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab
|
||||
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab
|
||||
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload beta Play Store .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.aab
|
||||
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab
|
||||
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload release .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk
|
||||
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk
|
||||
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload beta .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.apk
|
||||
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk
|
||||
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk
|
||||
if-no-files-found: error
|
||||
|
||||
# When building variants other than 'prod'
|
||||
- name: Upload debug .apk artifact
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk
|
||||
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for release .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk" \
|
||||
sha256sum "app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk" \
|
||||
> ./com.x8bit.bitwarden.apk-sha256.txt
|
||||
|
||||
- name: Create checksum for beta .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk" \
|
||||
sha256sum "app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk" \
|
||||
> ./com.x8bit.bitwarden.beta.apk-sha256.txt
|
||||
|
||||
- name: Create checksum for release .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
run: |
|
||||
sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab" \
|
||||
sha256sum "app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab" \
|
||||
> ./com.x8bit.bitwarden.aab-sha256.txt
|
||||
|
||||
- name: Create checksum for beta .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
run: |
|
||||
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab" \
|
||||
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab" \
|
||||
> ./com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
|
||||
- name: Create checksum for Debug .apk artifact
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk" \
|
||||
sha256sum "app/build/outputs/apk/standard/debug/com.x8bit.bitwarden.dev.apk" \
|
||||
> ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
|
||||
|
||||
- name: Upload .apk SHA file for release
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.apk-sha256.txt
|
||||
@@ -323,7 +345,7 @@ jobs:
|
||||
|
||||
- name: Upload .apk SHA file for beta
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta.apk-sha256.txt
|
||||
@@ -331,7 +353,7 @@ jobs:
|
||||
|
||||
- name: Upload .aab SHA file for release
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.aab-sha256.txt
|
||||
@@ -339,7 +361,7 @@ jobs:
|
||||
|
||||
- name: Upload .aab SHA file for beta
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
@@ -347,7 +369,7 @@ jobs:
|
||||
|
||||
- name: Upload .apk SHA file for debug
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
|
||||
@@ -393,10 +415,10 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -407,7 +429,7 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -433,10 +455,10 @@ jobs:
|
||||
--name app_fdroid_firebase-creds.json --file ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json --output none
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -446,7 +468,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -455,19 +477,35 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Update app CI Build info
|
||||
run: |
|
||||
./scripts/update_app_ci_build_info.sh \
|
||||
$GITHUB_REPOSITORY \
|
||||
$GITHUB_REF_NAME \
|
||||
$GITHUB_SHA \
|
||||
$GITHUB_RUN_ID \
|
||||
$GITHUB_RUN_ATTEMPT
|
||||
|
||||
# Start from 11000 to prevent collisions with mobile build version codes
|
||||
- name: Increment version
|
||||
run: |
|
||||
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
||||
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
|
||||
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
|
||||
bundle exec fastlane setBuildVersionInfo \
|
||||
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
|
||||
versionCode:$VERSION_CODE \
|
||||
versionName:${{ inputs.version-name || '' }}
|
||||
|
||||
regex='versionName = "([^"]+)"'
|
||||
if [[ "$(cat app/build.gradle.kts)" =~ $regex ]]; then
|
||||
VERSION_NAME="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
echo "Version Name: ${VERSION_NAME}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Version Number: $VERSION_CODE" >> $GITHUB_STEP_SUMMARY
|
||||
- name: Generate F-Droid artifacts
|
||||
env:
|
||||
FDROID_STORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
|
||||
@@ -490,49 +528,49 @@ jobs:
|
||||
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk
|
||||
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for F-Droid artifact
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk" \
|
||||
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk" \
|
||||
> ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
|
||||
|
||||
- name: Upload F-Droid SHA file
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden-fdroid.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload F-Droid Beta .apk artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta-fdroid.apk
|
||||
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk
|
||||
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for F-Droid Beta artifact
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk" \
|
||||
sha256sum "app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden.beta-fdroid.apk" \
|
||||
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
|
||||
- name: Upload F-Droid Beta SHA file
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install Firebase app distribution plugin
|
||||
if: ${{ inputs.distribute_to_firebase || github.event_name == 'push' }}
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
run: bundle exec fastlane add_plugin firebase_app_distribution
|
||||
|
||||
- name: Publish release F-Droid artifacts to Firebase
|
||||
if: ${{ inputs.distribute_to_firebase || github.event_name == 'push' }}
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
env:
|
||||
APP_FDROID_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json
|
||||
run: |
|
||||
|
||||
98
.github/workflows/cron-sync-google-priviledged-browsers.yml
vendored
Normal file
98
.github/workflows/cron-sync-google-priviledged-browsers.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
name: Cron / Sync Google Privileged Browsers List
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Monday at 00:00 UTC
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
SOURCE_URL: https://www.gstatic.com/gpm-passkeys-privileged-apps/apps.json
|
||||
GOOGLE_FILE: app/src/main/assets/fido2_privileged_google.json
|
||||
COMMUNITY_FILE: app/src/main/assets/fido2_privileged_community.json
|
||||
|
||||
jobs:
|
||||
sync-privileged-browsers:
|
||||
name: Sync Google Privileged Browsers List
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
|
||||
|
||||
- name: Download Google Privileged Browsers List
|
||||
run: curl -s $SOURCE_URL -o $GOOGLE_FILE
|
||||
|
||||
- name: Check for changes
|
||||
id: check-changes
|
||||
run: |
|
||||
if git diff --quiet -- $GOOGLE_FILE; then
|
||||
echo "👀 No changes detected, skipping..."
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "👀 Changes detected, validating fido2_privileged_google.json..."
|
||||
|
||||
python .github/scripts/validate-json/validate_json.py validate $GOOGLE_FILE
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "::error::JSON validation failed for $GOOGLE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "👀 fido2_privileged_google.json is valid, checking for duplicates..."
|
||||
|
||||
# Check for duplicates between Google and Community files
|
||||
python .github/scripts/validate-json/validate_json.py duplicates $GOOGLE_FILE $COMMUNITY_FILE duplicates.txt
|
||||
|
||||
if [ -f duplicates.txt ]; then
|
||||
echo "::warning::Duplicate package names found between Google and Community files."
|
||||
echo "duplicates_found=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✅ No duplicate package names found between Google and Community files"
|
||||
echo "duplicates_found=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create branch and commit
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "👀 Committing fido2_privileged_google.json..."
|
||||
|
||||
BRANCH_NAME="cron-sync-privileged-browsers/$GITHUB_RUN_NUMBER-sync"
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.email "actions@github.com"
|
||||
git checkout -b $BRANCH_NAME
|
||||
git add $GOOGLE_FILE
|
||||
git commit -m "Update Google privileged browsers list"
|
||||
git push origin $BRANCH_NAME
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
|
||||
echo "🌱 Branch created: $BRANCH_NAME"
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.check-changes.outputs.has_changes == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DUPLICATES_FOUND: ${{ steps.check-changes.outputs.duplicates_found }}
|
||||
BASE_PR_URL: ${{ github.server_url }}/${{ github.repository }}/pull/
|
||||
run: |
|
||||
PR_BODY="Updates the Google privileged browsers list with the latest data from $SOURCE_URL"
|
||||
|
||||
if [ "$DUPLICATES_FOUND" = "true" ]; then
|
||||
PR_BODY="$PR_BODY\n\n> [!WARNING]\n> :suspect: The following package(s) appear in both Google and Community files:"
|
||||
while IFS= read -r line; do
|
||||
PR_BODY="$PR_BODY\n> - $line"
|
||||
done < duplicates.txt
|
||||
fi
|
||||
|
||||
# Use echo -e to interpret escape sequences and pipe to gh pr create
|
||||
PR_URL=$(echo -e "$PR_BODY" | gh pr create \
|
||||
--title "Update Google privileged browsers list" \
|
||||
--body-file - \
|
||||
--base main \
|
||||
--head $BRANCH_NAME \
|
||||
--label "automated-pr" \
|
||||
--label "t:ci")
|
||||
54
.github/workflows/crowdin-pull.yml
vendored
54
.github/workflows/crowdin-pull.yml
vendored
@@ -1,23 +1,35 @@
|
||||
name: Crowdin Sync
|
||||
name: Cron / Crowdin Pull
|
||||
run-name: Crowdin Pull - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: { }
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
name: Autosync
|
||||
name: Crowdin Pull - ${{ matrix.name }} - ${{ github.event_name }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: Password Manager
|
||||
project_id: 269690
|
||||
config: crowdin-bwpm.yml
|
||||
branch: crowdin-pull-bwpm
|
||||
- name: Authenticator
|
||||
project_id: 673718
|
||||
config: crowdin-bwa.yml
|
||||
branch: crowdin-pull-bwa
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
@@ -28,22 +40,30 @@ jobs:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@95d6e895e871c3c7acf0cfb962f296baa41e63c6 # v2.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
|
||||
id: app-token
|
||||
with:
|
||||
config: crowdin.yml
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Download translations for ${{ matrix.name }}
|
||||
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
_CROWDIN_PROJECT_ID: ${{ matrix.project_id }}
|
||||
with:
|
||||
config: ${{ matrix.config }}
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
github_user_name: "bitwarden-devops-bot"
|
||||
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||
commit_message: "Autosync the updated translations"
|
||||
localization_branch_name: crowdin-auto-sync
|
||||
commit_message: "Crowdin Pull - ${{ matrix.name }}"
|
||||
localization_branch_name: ${{ matrix.branch }}
|
||||
create_pull_request: true
|
||||
pull_request_title: "Autosync Crowdin Translations"
|
||||
pull_request_body: "Autosync the updated translations"
|
||||
pull_request_title: "Crowdin Pull - ${{ matrix.name }}"
|
||||
pull_request_body: ":inbox_tray: New translations for ${{ matrix.name }} received!"
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
|
||||
35
.github/workflows/crowdin-push.yml
vendored
35
.github/workflows/crowdin-push.yml
vendored
@@ -1,39 +1,52 @@
|
||||
name: Crowdin Push
|
||||
name: CI / Crowdin Push
|
||||
run-name: Crowdin Push - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'CI' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- main
|
||||
|
||||
jobs:
|
||||
crowdin-push:
|
||||
name: Crowdin Push
|
||||
name: Crowdin Push - ${{ github.event_name }}
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.6.1
|
||||
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@2bd1450c2cdb2a8ac886232b8589696f22794229 # v0.2.0
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Upload sources
|
||||
uses: crowdin/github-action@95d6e895e871c3c7acf0cfb962f296baa41e63c6 # v2.2.0
|
||||
- name: Upload sources for Password Manager
|
||||
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
with:
|
||||
config: crowdin.yml
|
||||
config: crowdin-bwpm.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
- name: Upload sources for Authenticator
|
||||
uses: crowdin/github-action@b8012bd5491b8aa8578b73ab5b5f5e7c94aaa6e2 # v2.7.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
_CROWDIN_PROJECT_ID: "673718"
|
||||
with:
|
||||
config: crowdin-bwa.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
220
.github/workflows/github-release.yml
vendored
Normal file
220
.github/workflows/github-release.yml
vendored
Normal file
@@ -0,0 +1,220 @@
|
||||
name: Create GitHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
artifact-run-id:
|
||||
description: 'GitHub Action Run ID containing artifacts'
|
||||
required: true
|
||||
type: string
|
||||
release-ticket-id:
|
||||
description: 'Release Ticket ID - e.g. RELEASE-1762'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
ARTIFACTS_PATH: artifacts
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get branch from workflow run
|
||||
id: get_release_branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
run: |
|
||||
workflow_data=$(gh run view $ARTIFACT_RUN_ID --json headBranch,workflowName)
|
||||
release_branch=$(echo "$workflow_data" | jq -r .headBranch)
|
||||
workflow_name=$(echo "$workflow_data" | jq -r .workflowName)
|
||||
|
||||
# branch protection check
|
||||
if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then
|
||||
echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "release_branch=$release_branch" >> $GITHUB_OUTPUT
|
||||
echo "workflow_name=$workflow_name" >> $GITHUB_OUTPUT
|
||||
|
||||
case "$workflow_name" in
|
||||
*"Password Manager"* | "Build")
|
||||
echo "app_name=Password Manager" >> $GITHUB_OUTPUT
|
||||
echo "app_name_suffix=bwpm" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
*"Authenticator"*)
|
||||
echo "app_name=Authenticator" >> $GITHUB_OUTPUT
|
||||
echo "app_name_suffix=bwa" >> $GITHUB_OUTPUT
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown workflow name: $workflow_name"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Get version info from run logs and set release tag name
|
||||
id: get_release_info
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }}
|
||||
run: |
|
||||
workflow_log=$(gh run view $ARTIFACT_RUN_ID --log)
|
||||
|
||||
version_number_with_trailing_dot=$(grep -m 1 "Setting version code to" <<< "$workflow_log" | sed 's/.*Setting version code to //')
|
||||
version_number=${version_number_with_trailing_dot%.} # remove trailing dot
|
||||
|
||||
version_name_with_trailing_dot=$(grep -m 1 "Setting version name to" <<< "$workflow_log" | sed 's/.*Setting version name to //')
|
||||
version_name=${version_name_with_trailing_dot%.} # remove trailing dot
|
||||
|
||||
if [[ -z "$version_name" ]]; then
|
||||
echo "::warning::Version name not found. Using default value - 0.0.0"
|
||||
version_name="0.0.0"
|
||||
else
|
||||
echo "✅ Found version name: $version_name"
|
||||
fi
|
||||
|
||||
if [[ -z "$version_number" ]]; then
|
||||
echo "::warning::Version number not found. Using default value - 0"
|
||||
version_number="0"
|
||||
else
|
||||
echo "✅ Found version number: $version_number"
|
||||
fi
|
||||
|
||||
echo "version_number=$version_number" >> $GITHUB_OUTPUT
|
||||
echo "version_name=$version_name" >> $GITHUB_OUTPUT
|
||||
|
||||
tag_name="v$version_name-$_APP_NAME_SUFFIX" # e.g. v2025.6.0-bwpm
|
||||
echo "🔖 New tag name: $tag_name"
|
||||
echo "tag_name=$tag_name" >> $GITHUB_OUTPUT
|
||||
|
||||
last_release_tag=$(git tag -l --sort=-authordate | grep "$APP_NAME_SUFFIX" | head -n 1)
|
||||
echo "🔖 Last release tag: $last_release_tag"
|
||||
echo "last_release_tag=$last_release_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
run: |
|
||||
gh run download $ARTIFACT_RUN_ID -D $ARTIFACTS_PATH
|
||||
file_count=$(find $ARTIFACTS_PATH -type f | wc -l)
|
||||
echo "Downloaded $file_count file(s)."
|
||||
if [ "$file_count" -gt 0 ]; then
|
||||
echo "Downloaded files:"
|
||||
find $ARTIFACTS_PATH -type f
|
||||
fi
|
||||
|
||||
- name: Get product release notes
|
||||
id: get_release_notes
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_RELEASE_TICKET_ID: ${{ inputs.release-ticket-id }}
|
||||
_JIRA_API_EMAIL: ${{ secrets.JIRA_API_EMAIL }}
|
||||
_JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
|
||||
run: |
|
||||
echo "Getting product release notes"
|
||||
product_release_notes=$(python3 .github/scripts/jira-get-release-notes/jira_release_notes.py $_RELEASE_TICKET_ID $_JIRA_API_EMAIL $_JIRA_API_TOKEN)
|
||||
|
||||
if [[ -z "$product_release_notes" || $product_release_notes == "Error checking"* ]]; then
|
||||
echo "::warning::Failed to fetch release notes from Jira. Output: $product_release_notes"
|
||||
product_release_notes="<insert product release notes here>"
|
||||
else
|
||||
echo "✅ Product release notes:"
|
||||
echo "$product_release_notes"
|
||||
fi
|
||||
|
||||
echo "$product_release_notes" > product_release_notes.txt
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
_APP_NAME: ${{ steps.get_release_branch.outputs.app_name }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
|
||||
_TARGET_COMMIT: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||
_TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }}
|
||||
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
|
||||
run: |
|
||||
echo "⌛️ Creating release for $_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER) on $_TARGET_COMMIT"
|
||||
release_url=$(gh release create "$_TAG_NAME" \
|
||||
--title "$_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER)" \
|
||||
--target "$_TARGET_COMMIT" \
|
||||
--generate-notes \
|
||||
--notes-start-tag "$_LAST_RELEASE_TAG" \
|
||||
--draft \
|
||||
$ARTIFACTS_PATH/*/*)
|
||||
|
||||
echo "✅ Release created: $release_url"
|
||||
|
||||
# Get release info for outputs
|
||||
release_data=$(gh release view "$_TAG_NAME" --json id)
|
||||
release_id=$(echo "$release_data" | jq -r .id)
|
||||
|
||||
echo "id=$release_id" >> $GITHUB_OUTPUT
|
||||
echo "url=$release_url" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update Release Description
|
||||
id: update_release_description
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }}
|
||||
run: |
|
||||
echo "Getting current release body. Tag: $_TAG_NAME"
|
||||
current_body=$(gh release view "$_TAG_NAME" --json body --jq .body)
|
||||
|
||||
product_release_notes=$(cat product_release_notes.txt)
|
||||
|
||||
# Update release description with product release notes and builds source
|
||||
updated_body="# Overview
|
||||
${product_release_notes}
|
||||
|
||||
${current_body}
|
||||
**Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID"
|
||||
|
||||
new_release_url=$(gh release edit "$_TAG_NAME" --notes "$updated_body")
|
||||
|
||||
# draft release links change after editing
|
||||
echo "release_url=$new_release_url" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Add Release Summary
|
||||
env:
|
||||
_RELEASE_TAG: ${{ steps.get_release_info.outputs.tag_name }}
|
||||
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
|
||||
_RELEASE_BRANCH: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||
_RELEASE_URL: ${{ steps.update_release_description.outputs.release_url }}
|
||||
run: |
|
||||
echo "# :fish_cake: Release ready at:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$_RELEASE_URL" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "$_VERSION_NAME" == "0.0.0" || "$_VERSION_NUMBER" == "0" ]]; then
|
||||
echo "> [!CAUTION]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Version name or number wasn't previously found and a default value was used. You'll need to manually update the release Title, Tag and Description, specifically, the "Full Changelog" link." >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ":clipboard: Confirm that the defined GitHub Release options are correct:" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :bookmark: New tag name: \`$_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :palm_tree: Target branch: \`$_RELEASE_BRANCH\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :ocean: Previous tag set in the description \"Full Changelog\" link: \`$_LAST_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :white_check_mark: Description has automated release notes and they match the commits in the release branch" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> [!NOTE]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Commits directly pushed to branches without a Pull Request won't appear in the automated release notes." >> $GITHUB_STEP_SUMMARY
|
||||
14
.github/workflows/publish-github-release.yml
vendored
Normal file
14
.github/workflows/publish-github-release.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Publish GitHub Release as newest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stub:
|
||||
runs-on: ubuntu-24.04
|
||||
name: Stub
|
||||
steps:
|
||||
- name: Stub
|
||||
run: echo "This is a stub job to trigger the workflow."
|
||||
16
.github/workflows/publish-store.yml
vendored
Normal file
16
.github/workflows/publish-store.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-24.04
|
||||
name: Promote build to Production in Play Store
|
||||
|
||||
steps:
|
||||
- name: TEST STEP
|
||||
run: exit 0
|
||||
58
.github/workflows/release-branch.yml
vendored
Normal file
58
.github/workflows/release-branch.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Cut Release Branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: 'Release Type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- RC
|
||||
- Hotfix
|
||||
|
||||
jobs:
|
||||
create-release-branch:
|
||||
name: Create Release Branch
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create RC Branch
|
||||
if: inputs.release_type == 'RC'
|
||||
env:
|
||||
RC_PREFIX_DATE: "true" # replace with input if needed
|
||||
run: |
|
||||
if [ "$RC_PREFIX_DATE" = "true" ]; then
|
||||
current_date=$(date +'%Y.%m')
|
||||
branch_name="release/${current_date}-rc${{ github.run_number }}"
|
||||
else
|
||||
branch_name="release/rc${{ github.run_number }}"
|
||||
fi
|
||||
git switch main
|
||||
git switch -c $branch_name
|
||||
git push origin $branch_name
|
||||
echo "# :cherry_blossom: RC branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Create Hotfix Branch
|
||||
if: inputs.release_type == 'Hotfix'
|
||||
run: |
|
||||
latest_tag=$(git tag -l --sort=-creatordate | head -n 1)
|
||||
if [ -z "$latest_tag" ]; then
|
||||
echo "::error::No tags found in the repository"
|
||||
exit 1
|
||||
fi
|
||||
branch_name="release/hotfix-${latest_tag}"
|
||||
echo "🌿 branch name: $branch_name"
|
||||
if git show-ref --verify --quiet "refs/remotes/origin/$branch_name"; then
|
||||
echo "# :fire: :warning: Hotfix branch already exists: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
git switch -c $branch_name $latest_tag
|
||||
git push origin $branch_name
|
||||
echo "# :fire: Hotfix branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
||||
60
.github/workflows/scan-ci.yml
vendored
Normal file
60
.github/workflows/scan-ci.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Scan Protected Branches On Push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
sast:
|
||||
name: SAST scan
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@ef93013c95adc60160bc22060875e90800d3ecfc # 2.3.19
|
||||
with:
|
||||
project_name: ${{ github.repository }}
|
||||
cx_tenant: ${{ secrets.CHECKMARX_TENANT }}
|
||||
base_uri: https://ast.checkmarx.net/
|
||||
cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }}
|
||||
cx_client_secret: ${{ secrets.CHECKMARX_SECRET }}
|
||||
additional_params: |
|
||||
--report-format sarif \
|
||||
--filter "state=TO_VERIFY;PROPOSED_NOT_EXPLOITABLE;CONFIRMED;URGENT" \
|
||||
--output-path .
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
with:
|
||||
sarif_file: cx_result.sarif
|
||||
|
||||
quality:
|
||||
name: Quality scan
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Scan with SonarCloud
|
||||
uses: sonarsource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
with:
|
||||
args: >
|
||||
-Dsonar.organization=${{ github.repository_owner }}
|
||||
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}
|
||||
35
.github/workflows/scan.yml
vendored
35
.github/workflows/scan.yml
vendored
@@ -1,21 +1,24 @@
|
||||
name: Scan
|
||||
name: Scan Pull Requests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- main
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
sast:
|
||||
name: SAST scan
|
||||
@@ -28,12 +31,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36
|
||||
uses: checkmarx/ast-github-action@ef93013c95adc60160bc22060875e90800d3ecfc # 2.3.19
|
||||
env:
|
||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||
with:
|
||||
@@ -48,9 +51,11 @@ jobs:
|
||||
--output-path . ${{ env.INCREMENTAL }}
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
|
||||
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
with:
|
||||
sarif_file: cx_result.sarif
|
||||
sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
|
||||
ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
|
||||
|
||||
quality:
|
||||
name: Quality scan
|
||||
@@ -62,17 +67,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with SonarCloud
|
||||
uses: sonarsource/sonarcloud-github-action@383f7e52eae3ab0510c3cb0e7d9d150bbaeab838 # v3.1.0
|
||||
uses: sonarsource/sonarqube-scan-action@aa494459d7c39c106cc77b166de8b4250a32bb97 # v5.1.0
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
args: >
|
||||
-Dsonar.organization=${{ github.repository_owner }}
|
||||
-Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }}
|
||||
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
|
||||
|
||||
16
.github/workflows/test-device.yml
vendored
Normal file
16
.github/workflows/test-device.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Test Device
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test Device
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Placeholder step
|
||||
run: echo "Placeholder workflow step"
|
||||
75
.github/workflows/test.yml
vendored
75
.github/workflows/test.yml
vendored
@@ -6,42 +6,34 @@ on:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
type: [checks_requested]
|
||||
types: [checks_requested]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
_JAVA_VERSION: 17
|
||||
_GITHUB_ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
|
||||
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-24.04
|
||||
needs: check-run
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
packages: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -51,7 +43,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -60,15 +52,15 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0
|
||||
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
java-version: ${{ env._JAVA_VERSION }}
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
@@ -77,19 +69,48 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Build and test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Used in settings.gradle.kts to download the SDK from GitHub Maven Packages
|
||||
run: |
|
||||
bundle exec fastlane check
|
||||
|
||||
- name: Upload test reports on failure
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
if: failure()
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: always()
|
||||
with:
|
||||
name: test-reports
|
||||
path: app/build/reports/tests/
|
||||
path: |
|
||||
build/reports/kover/reportMergedCoverage.xml
|
||||
app/build/reports/tests/
|
||||
authenticator/build/reports/tests/
|
||||
authenticatorbridge/build/reports/tests/
|
||||
core/build/reports/tests/
|
||||
data/build/reports/tests/
|
||||
network/build/reports/tests/
|
||||
ui/build/reports/tests/
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
||||
id: upload-to-codecov
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
with:
|
||||
file: app/build/reports/kover/reportStandardDebug.xml
|
||||
os: linux
|
||||
files: build/reports/kover/reportMergedCoverage.xml
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
|
||||
- name: Comment PR if tests failed
|
||||
if: steps.upload-to-codecov.outcome == 'failure' && (github.event_name == 'push' || github.event_name == 'pull_request')
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RUN_ACTOR: ${{ github.triggering_actor }}
|
||||
run: |
|
||||
echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Uploading code coverage report failed. Please check the \"Upload to codecov.io\" step of \"Process Test Reports\" job for more details." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
message=$'> [!WARNING]\n> @'$RUN_ACTOR' Uploading code coverage report failed. Please check the "Upload to codecov.io" step of [Process Test Reports job]('$_GITHUB_ACTION_RUN_URL') for more details.'
|
||||
gh pr comment --repo $GITHUB_REPOSITORY $PR_NUMBER --body "$message"
|
||||
fi
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -25,5 +25,13 @@ user.properties
|
||||
|
||||
# Secrets
|
||||
/keystores/*.jks
|
||||
/app/src/standardDebug/google-services.json
|
||||
/app/src/standardBeta/google-services.json
|
||||
/app/src/standardRelease/google-services.json
|
||||
/authenticator/src/google-services.json
|
||||
|
||||
# Python
|
||||
.python-version
|
||||
__pycache__/
|
||||
|
||||
# Generated by .github/scripts/validate-json/validate-json.py
|
||||
duplicates.txt
|
||||
|
||||
1
.husky/pre-commit
Executable file
1
.husky/pre-commit
Executable file
@@ -0,0 +1 @@
|
||||
npx lint-staged
|
||||
91
Gemfile.lock
91
Gemfile.lock
@@ -9,32 +9,34 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.989.0)
|
||||
aws-sdk-core (3.209.1)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1125.0)
|
||||
aws-sdk-core (3.226.2)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.94.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
logger
|
||||
aws-sdk-kms (1.106.0)
|
||||
aws-sdk-core (~> 3, >= 3.225.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.167.0)
|
||||
aws-sdk-core (~> 3, >= 3.207.0)
|
||||
aws-sdk-s3 (1.192.0)
|
||||
aws-sdk-core (~> 3, >= 3.225.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.0)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
base64 (0.3.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
date (3.3.4)
|
||||
date (3.4.1)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
@@ -56,11 +58,11 @@ GEM
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
@@ -68,8 +70,8 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.224.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -85,6 +87,7 @@ GEM
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
@@ -108,11 +111,13 @@ GEM
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-plugin-firebase_app_distribution (0.9.1)
|
||||
fastlane-plugin-firebase_app_distribution (0.10.1)
|
||||
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
|
||||
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
@@ -134,12 +139,12 @@ GEM
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
@@ -155,36 +160,39 @@ GEM
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.7)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.7.2)
|
||||
jwt (2.9.3)
|
||||
json (2.12.2)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.5.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (6.0.1)
|
||||
rake (13.2.1)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.3.8)
|
||||
rouge (2.0.7)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
signet (0.20.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -192,10 +200,11 @@ GEM
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
time (0.4.0)
|
||||
time (0.4.1)
|
||||
date
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
@@ -205,15 +214,15 @@ GEM
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.25.1)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
@@ -229,4 +238,4 @@ RUBY VERSION
|
||||
ruby 3.3.1p55
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.6.6
|
||||
|
||||
61
README-bwa.md
Normal file
61
README-bwa.md
Normal file
@@ -0,0 +1,61 @@
|
||||
[](https://github.com/bitwarden/authenticator-android/actions/workflows/build-authenticator.yml?query=branch:main)
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
# Bitwarden Authenticator Android App
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.bitwarden.authenticator" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a>
|
||||
|
||||
Bitwarden Authenticator allows you easily store and generate two-factor authentication codes on your device. The Bitwarden Authenticator Android application is written in Kotlin.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/authenticator-android-codes.png" alt="" width="325" height="650" />
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Minimum SDK**: 28
|
||||
- **Target SDK**: 34
|
||||
- **Device Types Supported**: Phone and Tablet
|
||||
- **Orientations Supported**: Portrait and Landscape
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/bitwarden/authenticator-android
|
||||
```
|
||||
|
||||
2. Create a `user.properties` file in the root directory of the project and add the following properties:
|
||||
|
||||
- `gitHubToken`: A "classic" Github Personal Access Token (PAT) with the `read:packages` scope (ex: `gitHubToken=gph_xx...xx`). These can be generated by going to the [Github tokens page](https://github.com/settings/tokens). See [the Github Packages user documentation concerning authentication](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#authenticating-to-github-packages) for more details.
|
||||
|
||||
3. Setup the code style formatter:
|
||||
|
||||
All code must follow the guidelines described in the [Code Style Guidelines document](docs/STYLE_AND_BEST_PRACTICES.md). To aid in adhering to these rules, all contributors should apply `docs/bitwarden-style.xml` as their code style scheme. In IntelliJ / Android Studio:
|
||||
|
||||
- Navigate to `Preferences > Editor > Code Style`.
|
||||
- Hit the `Manage` button next to `Scheme`.
|
||||
- Select `Import`.
|
||||
- Find the `bitwarden-style.xml` file in the project's `docs/` directory.
|
||||
- Import "from" `BitwardenStyle` "to" `BitwardenStyle`.
|
||||
- Hit `Apply` and `OK` to save the changes and exit Preferences.
|
||||
|
||||
Note that in some cases you may need to restart Android Studio for the changes to take effect.
|
||||
|
||||
All code should be formatted before submitting a pull request. This can be done manually but it can also be helpful to create a macro with a custom keyboard binding to auto-format when saving. In Android Studio on OS X:
|
||||
|
||||
- Select `Edit > Macros > Start Macro Recording`
|
||||
- Select `Code > Optimize Imports`
|
||||
- Select `Code > Reformat Code`
|
||||
- Select `File > Save All`
|
||||
- Select `Edit > Macros > Stop Macro Recording`
|
||||
|
||||
This can then be mapped to a set of keys by navigating to `Android Studio > Preferences` and editing the macro under `Keymap` (ex : shift + command + s).
|
||||
|
||||
Please avoid mixing formatting and logical changes in the same commit/PR. When possible, fix any large formatting issues in a separate PR before opening one to make logical changes to the same code. This helps others focus on the meaningful code changes when reviewing the code.
|
||||
|
||||
## Contribute
|
||||
|
||||
Code contributions are welcome! Please commit any pull requests against the `main` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
|
||||
|
||||
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.
|
||||
91
README.md
91
README.md
@@ -1,24 +1,21 @@
|
||||
# Bitwarden Android (BETA)
|
||||
|
||||
> [!TIP]
|
||||
> This repo has the new native Android app, currently in [Beta](https://community.bitwarden.com/t/about-the-beta-program/39185). Looking for the legacy .NET MAUI apps? Head on over to [bitwarden/mobile](https://github.com/bitwarden/mobile)
|
||||
# Bitwarden Android
|
||||
|
||||
## Contents
|
||||
|
||||
- [Compatibility](#compatibility)
|
||||
- [Setup](#setup)
|
||||
- [Theme](#theme)
|
||||
- [Dependencies](#dependencies)
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Minimum SDK**: 29
|
||||
- **Target SDK**: 34
|
||||
- **Target SDK**: 35
|
||||
- **Device Types Supported**: Phone and Tablet
|
||||
- **Orientations Supported**: Portrait and Landscape
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
@@ -55,12 +52,46 @@
|
||||
|
||||
Please avoid mixing formatting and logical changes in the same commit/PR. When possible, fix any large formatting issues in a separate PR before opening one to make logical changes to the same code. This helps others focus on the meaningful code changes when reviewing the code.
|
||||
|
||||
4. Setup JDK `Version` `17`:
|
||||
|
||||
- Navigate to `Preferences > Build, Execution, Deployment > Build Tools > Gradle`.
|
||||
- Hit the selected Gradle JDK next to `Gradle JDK:`.
|
||||
- Select a `17.x` version or hit `Download JDK...` if not present.
|
||||
- Select `Version` `17`.
|
||||
- Select your preferred `Vendor`.
|
||||
- Hit `Download`.
|
||||
- Hit `Apply`.
|
||||
|
||||
## Theme
|
||||
|
||||
### Icons & Illustrations
|
||||
|
||||
The app supports light mode, dark mode and dynamic colors. Most icons in the app will display correctly using tinting but multi-tonal icons and illustrations require extra processing in order to be displayed properly with dynamic colors.
|
||||
|
||||
All illustrations and multi-tonal icons require the svg paths to be tagged with the `name` attribute in order for each individual path to be tinted the appropriate color. Any untagged path will not be tinted and the resulting image will be incorrect.
|
||||
|
||||
The supported tags are as follows:
|
||||
|
||||
* outline
|
||||
* primary
|
||||
* secondary
|
||||
* tertiary
|
||||
* accent
|
||||
* logo
|
||||
* navigation
|
||||
* navigationActiveAccent
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Application Dependencies
|
||||
|
||||
The following is a list of all third-party dependencies included as part of the application beyond the standard Android SDK.
|
||||
|
||||
- **AndroidX Activity**
|
||||
- https://developer.android.com/jetpack/androidx/releases/activity
|
||||
- Purpose: Allows access composable APIs built on top of Activity.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Appcompat**
|
||||
- https://developer.android.com/jetpack/androidx/releases/appcompat
|
||||
- Purpose: Allows access to new APIs on older API versions.
|
||||
@@ -81,7 +112,7 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Displays webpages with the user's default browser.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX CameraX Camera2**
|
||||
- **AndroidX Camera**
|
||||
- https://developer.android.com/jetpack/androidx/releases/camera
|
||||
- Purpose: Display and capture images for barcode scanning.
|
||||
- License: Apache 2.0
|
||||
@@ -91,9 +122,9 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: A Kotlin-based declarative UI framework.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Core SplashScreen**
|
||||
- **AndroidX Core**
|
||||
- https://developer.android.com/jetpack/androidx/releases/core
|
||||
- Purpose: Backwards compatible SplashScreen API implementation.
|
||||
- Purpose: Backwards compatible platform features and APIs.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Credentials**
|
||||
@@ -106,6 +137,11 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Lifecycle aware components and tooling.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Navigation**
|
||||
- https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
- Purpose: Provides a consistent API for navigating between Android components.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Room**
|
||||
- https://developer.android.com/jetpack/androidx/releases/room
|
||||
- Purpose: A convenient SQLite-based persistence layer for Android.
|
||||
@@ -126,16 +162,6 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Dependency injection framework.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Cloud Messaging**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: Allows for push notification support. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Crashlytics**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: SDK for crash and non-fatal error reporting. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Glide**
|
||||
- https://github.com/bumptech/glide
|
||||
- Purpose: Image loading and caching.
|
||||
@@ -156,11 +182,6 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: JSON serialization library for Kotlin.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **kotlinx.serialization converter**
|
||||
- https://github.com/square/retrofit/tree/trunk/retrofit-converters/kotlinx-serialization
|
||||
- Purpose: Converter for Retrofit 2 and kotlinx.serialization.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **OkHttp 3**
|
||||
- https://github.com/square/okhttp
|
||||
- Purpose: An HTTP client used by the library to intercept and log traffic.
|
||||
@@ -176,16 +197,28 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Extensible logging library for Android.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **zxcvbn4j**
|
||||
- https://github.com/nulab/zxcvbn4j
|
||||
- Purpose: Password strength estimation.
|
||||
- License: MIT
|
||||
|
||||
- **ZXing**
|
||||
- https://github.com/zxing/zxing
|
||||
- Purpose: Barcode scanning and generation.
|
||||
- License: Apache 2.0
|
||||
|
||||
The following is an additional list of third-party dependencies that are only included in the non-F-Droid build variants of the application.
|
||||
|
||||
- **Firebase Cloud Messaging**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: Allows for push notification support.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Crashlytics**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: SDK for crash and non-fatal error reporting.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Google Play Reviews**
|
||||
- https://developer.android.com/reference/com/google/android/play/core/release-notes
|
||||
- Purpose: On standard builds provide an interface to add a review for the password manager application in Google Play.
|
||||
- License: Apache 2.0
|
||||
|
||||
### Development Environment Dependencies
|
||||
|
||||
The following is a list of additional third-party dependencies used as part of the local development environment. This includes test-related artifacts as well as tools related to code quality and linting. These are not present in the final packaged application.
|
||||
|
||||
31
SECURITY.md
31
SECURITY.md
@@ -1,21 +1,32 @@
|
||||
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!
|
||||
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!
|
||||
|
||||
# Disclosure Policy
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- 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).
|
||||
- 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.
|
||||
- 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.
|
||||
- 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).
|
||||
|
||||
While researching, we'd like to ask you to refrain from:
|
||||
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
|
||||
# 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.
|
||||
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
annotation/.gitignore
vendored
Normal file
1
annotation/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
42
annotation/build.gradle.kts
Normal file
42
annotation/build.gradle.kts
Normal file
@@ -0,0 +1,42 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.bitwarden.annotation"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.minSdkBwa.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility(libs.versions.jvmTarget.get())
|
||||
targetCompatibility(libs.versions.jvmTarget.get())
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
testFixtures {
|
||||
enable = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.x8bit.bitwarden.data.platform.annotation
|
||||
package com.bitwarden.annotation
|
||||
|
||||
/**
|
||||
* Used to omit the annotated class from test coverage reporting. This should be used sparingly and
|
||||
@@ -1,25 +1,26 @@
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import com.android.utils.cxx.io.removeExtensionIfPresent
|
||||
import com.google.firebase.crashlytics.buildtools.gradle.tasks.InjectMappingFileIdTask
|
||||
import com.google.firebase.crashlytics.buildtools.gradle.tasks.UploadMappingFileTask
|
||||
import com.google.gms.googleservices.GoogleServicesTask
|
||||
import dagger.hilt.android.plugin.util.capitalize
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.androidx.room)
|
||||
// Crashlytics is enabled for all builds initially but removed for FDroid builds in gradle and
|
||||
// standardDebug builds in the merged manifest.
|
||||
alias(libs.plugins.crashlytics)
|
||||
alias(libs.plugins.detekt)
|
||||
alias(libs.plugins.hilt)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose.compiler)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinx.kover)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.google.services)
|
||||
alias(libs.plugins.sonarqube)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,25 +33,40 @@ val userProperties = Properties().apply {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads CI-specific build properties that are not checked into source control.
|
||||
*/
|
||||
val ciProperties = Properties().apply {
|
||||
val ciPropsFile = File(rootDir, "ci.properties")
|
||||
if (ciPropsFile.exists()) {
|
||||
FileInputStream(ciPropsFile).use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.x8bit.bitwarden"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.x8bit.bitwarden"
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "2024.9.0"
|
||||
versionName = "2025.4.0"
|
||||
|
||||
setProperty("archivesBaseName", "com.x8bit.bitwarden")
|
||||
|
||||
ksp {
|
||||
// The location in which the generated Room Database Schemas will be stored in the repo.
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "CI_INFO",
|
||||
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}",
|
||||
)
|
||||
}
|
||||
|
||||
androidResources {
|
||||
@@ -83,9 +99,11 @@ android {
|
||||
applicationIdSuffix = ".beta"
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
matchingFallbacks += listOf("release")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
|
||||
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
|
||||
@@ -94,9 +112,10 @@ android {
|
||||
release {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
|
||||
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
|
||||
@@ -115,6 +134,39 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
val bundlesDir = "${layout.buildDirectory.get()}/outputs/bundle"
|
||||
outputs
|
||||
.mapNotNull { it as? BaseVariantOutputImpl }
|
||||
.forEach { output ->
|
||||
val fileNameWithoutExtension = when (flavorName) {
|
||||
"fdroid" -> "$applicationId-$flavorName"
|
||||
"standard" -> "$applicationId"
|
||||
else -> output.outputFileName.removeExtensionIfPresent(".apk")
|
||||
}
|
||||
|
||||
// Set the APK output filename.
|
||||
output.outputFileName = "$fileNameWithoutExtension.apk"
|
||||
|
||||
val variantName = name
|
||||
val renameTaskName = "rename${variantName.capitalize()}AabFiles"
|
||||
tasks.register(renameTaskName) {
|
||||
group = "build"
|
||||
description = "Renames the bundle files for $variantName variant"
|
||||
doLast {
|
||||
renameFile(
|
||||
"$bundlesDir/$variantName/$namespace-$flavorName-${buildType.name}.aab",
|
||||
"$fileNameWithoutExtension.aab",
|
||||
)
|
||||
}
|
||||
}
|
||||
// Force renaming task to execute after the variant is built.
|
||||
tasks
|
||||
.getByName("bundle${variantName.capitalize()}")
|
||||
.finalizedBy(renameTaskName)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility(libs.versions.jvmTarget.get())
|
||||
targetCompatibility(libs.versions.jvmTarget.get())
|
||||
@@ -128,20 +180,22 @@ android {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
testOptions {
|
||||
// Required for Robolectric
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
lint {
|
||||
disable.add("MissingTranslation")
|
||||
disable += listOf(
|
||||
"MissingTranslation",
|
||||
"ExtraTranslation",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
|
||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +213,13 @@ dependencies {
|
||||
add("standardImplementation", dependencyNotation)
|
||||
}
|
||||
|
||||
// TODO: this should use a versioned AAR instead of referencing a local AAR BITAU-94
|
||||
implementation(files("libs/authenticatorbridge-0.1.0-SNAPSHOT-release.aar"))
|
||||
implementation(files("libs/authenticatorbridge-1.0.1-release.aar"))
|
||||
|
||||
implementation(project(":annotation"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":network"))
|
||||
implementation(project(":ui"))
|
||||
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
@@ -173,6 +232,7 @@ dependencies {
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.animation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.material3.adaptive)
|
||||
implementation(libs.androidx.compose.runtime)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
@@ -197,7 +257,6 @@ dependencies {
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.kotlinx.serialization)
|
||||
implementation(libs.nulab.zxcvbn4j)
|
||||
implementation(libs.square.okhttp)
|
||||
implementation(libs.square.okhttp.logging)
|
||||
implementation(platform(libs.square.retrofit.bom))
|
||||
@@ -214,9 +273,17 @@ dependencies {
|
||||
standardImplementation(libs.google.firebase.cloud.messaging)
|
||||
standardImplementation(platform(libs.google.firebase.bom))
|
||||
standardImplementation(libs.google.firebase.crashlytics)
|
||||
standardImplementation(libs.google.play.review)
|
||||
|
||||
// Pull in test fixtures from other modules
|
||||
testImplementation(testFixtures(project(":data")))
|
||||
testImplementation(testFixtures(project(":network")))
|
||||
testImplementation(testFixtures(project(":ui")))
|
||||
|
||||
testImplementation(libs.androidx.compose.ui.test)
|
||||
testImplementation(libs.google.hilt.android.testing)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testImplementation(libs.junit.junit5)
|
||||
testImplementation(libs.junit.vintage)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
@@ -224,117 +291,38 @@ dependencies {
|
||||
testImplementation(libs.robolectric.robolectric)
|
||||
testImplementation(libs.square.okhttp.mockwebserver)
|
||||
testImplementation(libs.square.turbine)
|
||||
|
||||
detektPlugins(libs.detekt.detekt.formatting)
|
||||
detektPlugins(libs.detekt.detekt.rules)
|
||||
}
|
||||
|
||||
detekt {
|
||||
autoCorrect = true
|
||||
config.from(files("$rootDir/detekt-config.yml"))
|
||||
}
|
||||
|
||||
kover {
|
||||
currentProject {
|
||||
sources {
|
||||
excludeJava = true
|
||||
}
|
||||
}
|
||||
reports {
|
||||
filters {
|
||||
excludes {
|
||||
androidGeneratedClasses()
|
||||
annotatedBy(
|
||||
// Compose previews
|
||||
"androidx.compose.ui.tooling.preview.Preview",
|
||||
"androidx.compose.ui.tooling.preview.PreviewScreenSizes",
|
||||
// Manually excluded classes/files/etc.
|
||||
"com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage",
|
||||
)
|
||||
classes(
|
||||
// Navigation helpers
|
||||
"*.*NavigationKt*",
|
||||
// Composable singletons
|
||||
"*.*ComposableSingletons*",
|
||||
// Generated classes related to interfaces with default values
|
||||
"*.*DefaultImpls*",
|
||||
// Databases
|
||||
"*.database.*Database*",
|
||||
"*.dao.*Dao*",
|
||||
// Dagger Hilt
|
||||
"dagger.hilt.*",
|
||||
"hilt_aggregated_deps.*",
|
||||
"*_Factory",
|
||||
"*_Factory\$*",
|
||||
"*_*Factory",
|
||||
"*_*Factory\$*",
|
||||
"*.Hilt_*",
|
||||
"*_HiltModules",
|
||||
"*_HiltModules\$*",
|
||||
"*_Impl",
|
||||
"*_Impl\$*",
|
||||
"*_MembersInjector",
|
||||
)
|
||||
packages(
|
||||
// Dependency injection
|
||||
"*.di",
|
||||
// Models
|
||||
"*.model",
|
||||
// Custom UI components
|
||||
"com.x8bit.bitwarden.ui.platform.components",
|
||||
// Theme-related code
|
||||
"com.x8bit.bitwarden.ui.platform.theme",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
getByName("check") {
|
||||
// Add detekt with type resolution to check
|
||||
dependsOn("detekt")
|
||||
}
|
||||
|
||||
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
|
||||
jvmTarget = libs.versions.jvmTarget.get()
|
||||
}
|
||||
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>().configureEach {
|
||||
jvmTarget = libs.versions.jvmTarget.get()
|
||||
}
|
||||
|
||||
withType<Test> {
|
||||
useJUnitPlatform()
|
||||
maxHeapSize = "2g"
|
||||
maxParallelForks = Runtime.getRuntime().availableProcessors()
|
||||
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC"
|
||||
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC" + "-Duser.country=US"
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// Disable Fdroid-specific tasks that we want to exclude
|
||||
val tasks = tasks.withType<GoogleServicesTask>() +
|
||||
val fdroidTasksToDisable = tasks.withType<GoogleServicesTask>() +
|
||||
tasks.withType<InjectMappingFileIdTask>() +
|
||||
tasks.withType<UploadMappingFileTask>()
|
||||
tasks
|
||||
fdroidTasksToDisable
|
||||
.filter { it.name.contains("Fdroid") }
|
||||
.forEach { it.enabled = false }
|
||||
}
|
||||
|
||||
sonar {
|
||||
properties {
|
||||
property("sonar.projectKey", "bitwarden_android")
|
||||
property("sonar.organization", "bitwarden")
|
||||
property("sonar.host.url", "https://sonarcloud.io")
|
||||
property("sonar.sources", "app/src/")
|
||||
property("sonar.tests", "app/src/")
|
||||
property("sonar.test.inclusions", "app/src/test/")
|
||||
property("sonar.exclusions", "app/src/test/")
|
||||
private fun renameFile(path: String, newName: String) {
|
||||
val originalFile = File(path)
|
||||
if (!originalFile.exists()) {
|
||||
println("File $originalFile does not exist!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
getByName("sonar") {
|
||||
dependsOn("check")
|
||||
val newFile = File(originalFile.parentFile, newName)
|
||||
if (originalFile.renameTo(newFile)) {
|
||||
println("Renamed $originalFile to $newFile")
|
||||
} else {
|
||||
throw RuntimeException("Failed to rename $originalFile to $newFile")
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
app/libs/authenticatorbridge-1.0.1-release.aar
Normal file
BIN
app/libs/authenticatorbridge-1.0.1-release.aar
Normal file
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "ce40856ec88770d11b7afb587c7deabc",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "privileged_apps",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` TEXT NOT NULL, PRIMARY KEY(`package_name`, `signature`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"package_name",
|
||||
"signature"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ce40856ec88770d11b7afb587c7deabc')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "f28200334a5c94feed1d9712e04ff01b",
|
||||
"identityHash": "f7906c69e0a2c065d4d3be140fc721b6",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "ciphers",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "collections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -97,6 +97,12 @@
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@@ -120,7 +126,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "domains",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT NOT NULL, PRIMARY KEY(`user_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
@@ -132,7 +138,7 @@
|
||||
"fieldPath": "domainsJson",
|
||||
"columnName": "domains_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@@ -244,7 +250,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f28200334a5c94feed1d9712e04ff01b')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f7906c69e0a2c065d4d3be140fc721b6')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "f28200334a5c94feed1d9712e04ff01b",
|
||||
"identityHash": "ee697e71290c92fe5b607d0b7665481b",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "ciphers",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "collections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -97,6 +97,12 @@
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@@ -244,7 +250,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f28200334a5c94feed1d9712e04ff01b')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee697e71290c92fe5b607d0b7665481b')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 6,
|
||||
"identityHash": "ee158c483edfe5102504670f3d9845d4",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "ciphers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherType",
|
||||
"columnName": "cipher_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherJson",
|
||||
"columnName": "cipher_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ciphers_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "collections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationId",
|
||||
"columnName": "organization_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "shouldHidePasswords",
|
||||
"columnName": "should_hide_passwords",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalId",
|
||||
"columnName": "external_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isReadOnly",
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_collections_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_collections_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "domains",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domainsJson",
|
||||
"columnName": "domains_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "folders",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `name` TEXT, `revision_date` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "revisionDate",
|
||||
"columnName": "revision_date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folders_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_folders_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sends",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `send_type` TEXT NOT NULL, `send_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendType",
|
||||
"columnName": "send_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendJson",
|
||||
"columnName": "send_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_sends_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_sends_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee158c483edfe5102504670f3d9845d4')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "4c6ad1f5268d7e8add7407201788aa2e",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "ciphers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `has_totp` INTEGER NOT NULL DEFAULT 1, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasTotp",
|
||||
"columnName": "has_totp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherType",
|
||||
"columnName": "cipher_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherJson",
|
||||
"columnName": "cipher_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ciphers_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "collections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationId",
|
||||
"columnName": "organization_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "shouldHidePasswords",
|
||||
"columnName": "should_hide_passwords",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalId",
|
||||
"columnName": "external_id",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isReadOnly",
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_collections_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_collections_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "domains",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domainsJson",
|
||||
"columnName": "domains_json",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "folders",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `name` TEXT, `revision_date` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "revisionDate",
|
||||
"columnName": "revision_date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folders_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_folders_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "sends",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `send_type` TEXT NOT NULL, `send_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendType",
|
||||
"columnName": "send_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendJson",
|
||||
"columnName": "send_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_sends_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_sends_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4c6ad1f5268d7e8add7407201788aa2e')"
|
||||
]
|
||||
}
|
||||
}
|
||||
21
app/src/beta/AndroidManifest.xml
Normal file
21
app/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application tools:ignore="MissingApplicationIcon">
|
||||
<activity
|
||||
android:name=".MainActivity">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
27
app/src/beta/res/xml/shortcuts.xml
Normal file
27
app/src/beta/res/xml/shortcuts.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_generator_shortcut"
|
||||
android:shortcutId="bitwarden_password_generator"
|
||||
android:shortcutLongLabel="@string/password_generator"
|
||||
android:shortcutShortLabel="@string/password_generator">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="bitwarden://password_generator"
|
||||
android:targetClass="com.x8bit.bitwarden.MainActivity"
|
||||
android:targetPackage="com.x8bit.bitwarden.beta" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_vault_shortcut"
|
||||
android:shortcutId="bitwarden_my_vault"
|
||||
android:shortcutLongLabel="@string/my_vault"
|
||||
android:shortcutShortLabel="@string/my_vault">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="bitwarden://my_vault"
|
||||
android:targetClass="com.x8bit.bitwarden.MainActivity"
|
||||
android:targetPackage="com.x8bit.bitwarden.beta" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -7,6 +7,20 @@
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.x8bit.bitwarden.BuildConfig
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.x8bit.bitwarden.ui.platform.manager.review
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
/**
|
||||
* No-op implementation of [AppReviewManager] for F-Droid builds.
|
||||
*/
|
||||
class AppReviewManagerImpl(
|
||||
activity: Activity,
|
||||
) : AppReviewManager {
|
||||
override fun promptForReview() = Unit
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
|
||||
<!-- Protect access to AuthenticatorBridgeService using this custom permission.
|
||||
|
||||
Note that each build type uses a different value for knownCerts.
|
||||
@@ -37,15 +37,18 @@
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:intentMatchingFlags="enforceIntentFilter"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/LaunchTheme"
|
||||
tools:ignore="CredentialDependency"
|
||||
tools:replace="appComponentFactory"
|
||||
tools:targetApi="33">
|
||||
tools:targetApi="36">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="@integer/launchModeAPIlevel"
|
||||
android:theme="@style/LaunchTheme"
|
||||
@@ -76,16 +79,14 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:host="vault.bitwarden.com" />
|
||||
<data android:host="vault.bitwarden.eu" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<data android:host="*.bitwarden.com" />
|
||||
<data android:host="*.bitwarden.eu" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.fido2.ACTION_GET_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.fido2.ACTION_UNLOCK_ACCOUNT" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_UNLOCK_ACCOUNT" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
@@ -310,6 +311,14 @@
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.AUTHENTICATOR_BRIDGE_SERVICE" />
|
||||
|
||||
<!-- Firebase SDK initOrder is 100. We use a higher order to initialize first -->
|
||||
<provider
|
||||
android:name=".data.platform.contentprovider.UncaughtErrorLoggingContentProvider"
|
||||
android:authorities="${applicationId}"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="false"
|
||||
android:initOrder="101" />
|
||||
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
@@ -320,6 +329,19 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent>
|
||||
<!-- To Query Privileged Apps -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
<!-- To Query Chrome Beta: -->
|
||||
<package android:name="com.chrome.beta" />
|
||||
|
||||
<!-- To Query Chrome Stable: -->
|
||||
<package android:name="com.android.chrome" />
|
||||
|
||||
<!-- To Query Brave Stable: -->
|
||||
<package android:name="com.brave.browser" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.github.forkmaintainers.iceraven",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:0D:22:37:9F:48:7B:70:A4:F9:F8:BE:C0:17:3C:F9:1A:16:44:F0:8F:93:38:5B:5B:78:2C:E3:76:60:BA:81"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
@@ -24,6 +36,18 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.ironfoxoss.ironfox",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C5:E2:91:B5:A5:71:F9:C8:CD:9A:97:99:C2:C9:4E:02:EC:97:03:94:88:93:F2:CA:75:6D:67:B9:42:04:F9:04"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
@@ -35,46 +59,6 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "us.spotco.fennec_dos",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "26:0E:0A:49:67:8C:78:B7:0C:02:D6:53:7A:DD:3B:6D:C0:A1:71:71:BB:DE:8C:E7:5F:D4:02:6A:8A:3E:18:D2"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "FF:81:F5:BE:56:39:65:94:EE:E7:0F:EF:28:32:25:6E:15:21:41:22:E2:BA:9C:ED:D2:60:05:FF:D4:BC:AA:A8"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "us.spotco.mulch",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "26:0E:0A:49:67:8C:78:B7:0C:02:D6:53:7A:DD:3B:6D:C0:A1:71:71:BB:DE:8C:E7:5F:D4:02:6A:8A:3E:18:D2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.github.forkmaintainers.iceraven",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:0D:22:37:9F:48:7B:70:A4:F9:F8:BE:C0:17:3C:F9:1A:16:44:F0:8F:93:38:5B:5B:78:2C:E3:76:60:BA:81"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -160,6 +160,78 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fenix",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "50:04:77:90:88:E7:F9:88:D5:BC:5C:C5:F8:79:8F:EB:F4:F8:CD:08:4A:1B:2A:46:EF:D4:C8:EE:4A:EA:F2:11"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fenix.debug",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus.nightly",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.klar",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.reference.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "B0:09:90:E3:0F:9D:81:5D:2E:BC:7B:9B:B2:21:CE:47:E5:C9:D5:17:AA:C7:0E:7F:D5:95:B1:E5:3E:9A:4B:14"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
@@ -571,6 +643,178 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.Island",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D9:C3:39:AC:9C:3A:EE:E1:75:1D:85:8C:35:D9:BA:C5:CC:87:B3:CE:76:30:93:F0:F5:10:64:F5:A2:F6:9B:04"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandCanary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "90:17:13:23:45:6E:6F:39:CB:FD:CF:B2:56:BE:1D:CF:F3:BC:1C:59:8A:15:93:30:E4:97:73:D0:4C:B9:C9:05"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandBeta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "35:31:83:1A:9E:2B:21:1D:E6:AA:C3:69:4B:45:83:6E:56:09:B9:D7:D0:04:C3:1B:21:87:40:FB:77:17:38:D1"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandDev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C2:38:24:15:41:20:A0:8F:C3:95:42:AC:D8:2A:E9:24:94:78:80:1E:47:FD:6C:66:2B:18:1C:28:CA:7E:59:4E"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.canary.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1E:16:74:BB:79:EA:09:FB:37:CF:9F:1B:07:1B:1D:51:8D:46:03:0E:D3:EE:F2:C1:4E:AD:93:9E:C6:EE:3A:4C"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.beta.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D2:5E:AD:F6:1C:E6:36:6C:A4:23:A4:7F:C4:DB:9B:8C:9C:8A:35:B4:B0:19:E8:D9:82:FB:D0:8A:D9:DB:49:5A"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.dev.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "net.quetta.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "BE:FE:E7:31:12:6A:A5:6E:7E:FD:AE:AF:5E:F3:FA:EA:44:1C:19:CC:E0:CA:EC:42:6B:65:BB:F8:2C:59:46:80"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "F1:38:00:4F:38:04:51:D4:8A:05:2B:B3:A3:EF:17:24:23:D4:B0:D0:C8:A3:AA:DD:FB:DB:66:30:31:48:EC:A4"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "cz.seznam.sbrowser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DB:95:40:66:10:78:83:6E:4E:B1:66:F6:9E:F4:07:30:9E:8D:AE:33:34:68:5E:C8:F6:FA:2F:13:81:B9:AC:F6"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.opera.mini.native",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "57:AC:BC:52:5F:1B:2E:BD:19:19:6C:D6:F0:14:39:7C:C9:10:FD:18:84:1E:0A:E8:50:FE:BC:3E:1E:59:3F:F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.opera.mini.native.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "57:AC:BC:52:5F:1B:2E:BD:19:19:6C:D6:F0:14:39:7C:C9:10:FD:18:84:1E:0A:E8:50:FE:BC:3E:1E:59:3F:F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Primary entry point for the application.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val mainViewModel: MainViewModel by viewModels()
|
||||
|
||||
@Inject
|
||||
lateinit var accessibilityActivityManager: AccessibilityActivityManager
|
||||
|
||||
@Inject
|
||||
lateinit var autofillActivityManager: AutofillActivityManager
|
||||
|
||||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var accessibilityCompletionManager: AccessibilityCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var debugLaunchManager: DebugMenuLaunchManager
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
var shouldShowSplashScreen = true
|
||||
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mainViewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = intent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Within the app the language will change dynamically and will be managed
|
||||
// by the OS, but we need to ensure we properly set the language when
|
||||
// upgrading from older versions that handle this differently.
|
||||
settingsRepository.appLanguage.localeName?.let { localeName ->
|
||||
val localeList = LocaleListCompat.forLanguageTags(localeName)
|
||||
AppCompatDelegate.setApplicationLocales(localeList)
|
||||
}
|
||||
setContent {
|
||||
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val navController = rememberNavController()
|
||||
EventsEffect(viewModel = mainViewModel) { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAccessibilityAutofill -> {
|
||||
handleCompleteAccessibilityAutofill(event)
|
||||
}
|
||||
|
||||
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
||||
MainEvent.Recreate -> handleRecreate()
|
||||
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
|
||||
is MainEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(
|
||||
baseContext,
|
||||
event.message.invoke(resources),
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
|
||||
LocalManagerProvider {
|
||||
BitwardenTheme(theme = state.theme) {
|
||||
RootNavScreen(
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
navController = navController,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
mainViewModel.trySendAction(
|
||||
action = MainAction.ReceiveNewIntent(
|
||||
intent = intent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// In some scenarios on an emulator the Activity can leak when recreated
|
||||
// if we don't first clear focus anytime we exit and return to the app.
|
||||
currentFocus?.clearFocus()
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(event: MotionEvent): Boolean = debugLaunchManager
|
||||
.actionOnInputEvent(event = event, action = ::sendOpenDebugMenuEvent)
|
||||
.takeIf { it }
|
||||
?: super.dispatchTouchEvent(event)
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean = debugLaunchManager
|
||||
.actionOnInputEvent(event = event, action = ::sendOpenDebugMenuEvent)
|
||||
.takeIf { it }
|
||||
?: super.dispatchKeyEvent(event)
|
||||
|
||||
private fun sendOpenDebugMenuEvent() {
|
||||
mainViewModel.trySendAction(MainAction.OpenDebugMenu)
|
||||
}
|
||||
|
||||
private fun handleCompleteAccessibilityAutofill(
|
||||
event: MainEvent.CompleteAccessibilityAutofill,
|
||||
) {
|
||||
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
||||
autofillCompletionManager.completeAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleRecreate() {
|
||||
recreate()
|
||||
}
|
||||
|
||||
private fun updateScreenCapture(isScreenCaptureAllowed: Boolean) {
|
||||
if (isScreenCaptureAllowed) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Container for the user's API tokens.
|
||||
*
|
||||
* @property requestId The ID of the pending Auth Request.
|
||||
* @property requestPrivateKey The private of the pending Auth Request.
|
||||
*/
|
||||
@Serializable
|
||||
data class PendingAuthRequestJson(
|
||||
@SerialName("Id")
|
||||
val requestId: String,
|
||||
|
||||
@SerialName("PrivateKey")
|
||||
val requestPrivateKey: String,
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /organizations API.
|
||||
*/
|
||||
interface UnauthenticatedOrganizationApi {
|
||||
/**
|
||||
* Checks for the claimed domain organization of an email for SSO purposes.
|
||||
*/
|
||||
@POST("/organizations/domain/sso/details")
|
||||
suspend fun getClaimedDomainOrganizationDetails(
|
||||
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
||||
): NetworkResult<OrganizationDomainSsoDetailsResponseJson>
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides network dependencies in the auth package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AuthNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAccountService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): AccountsService = AccountsServiceImpl(
|
||||
unauthenticatedAccountsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
authenticatedAccountsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedKeyConnectorApi = retrofits.createStaticRetrofit().create(),
|
||||
authenticatedKeyConnectorApi = retrofits
|
||||
.createStaticRetrofit(isAuthenticated = true)
|
||||
.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAuthRequestsService(
|
||||
retrofits: Retrofits,
|
||||
): AuthRequestsService = AuthRequestsServiceImpl(
|
||||
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDevicesService(
|
||||
retrofits: Retrofits,
|
||||
): DevicesService = DevicesServiceImpl(
|
||||
authenticatedDevicesApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedDevicesApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIdentityService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): IdentityService = IdentityServiceImpl(
|
||||
unauthenticatedIdentityApi = retrofits.unauthenticatedIdentityRetrofit.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesHaveIBeenPwnedService(
|
||||
retrofits: Retrofits,
|
||||
): HaveIBeenPwnedService = HaveIBeenPwnedServiceImpl(
|
||||
api = retrofits
|
||||
.createStaticRetrofit(baseUrl = "https://api.pwnedpasswords.com")
|
||||
.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNewAuthRequestService(
|
||||
retrofits: Retrofits,
|
||||
): NewAuthRequestService = NewAuthRequestServiceImpl(
|
||||
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedAuthRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOrganizationService(
|
||||
retrofits: Retrofits,
|
||||
): OrganizationService = OrganizationServiceImpl(
|
||||
authenticatedOrganizationApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedOrganizationApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Response object returned when requesting organization domain SSO details.
|
||||
*
|
||||
* @property isSsoAvailable Whether or not SSO is available for this domain.
|
||||
* @property organizationIdentifier The organization's identifier.
|
||||
*/
|
||||
@Serializable
|
||||
data class OrganizationDomainSsoDetailsResponseJson(
|
||||
@SerialName("ssoAvailable") val isSsoAvailable: Boolean,
|
||||
@SerialName("organizationIdentifier") val organizationIdentifier: String,
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Response body from the SSO prevalidate request.
|
||||
*/
|
||||
@Serializable
|
||||
data class PrevalidateSsoResponseJson(
|
||||
@SerialName("token") val token: String?,
|
||||
)
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Models result of logging in.
|
||||
*/
|
||||
sealed class LoginResult {
|
||||
/**
|
||||
* Login succeeded.
|
||||
*/
|
||||
data object Success : LoginResult()
|
||||
|
||||
/**
|
||||
* Captcha verification is required.
|
||||
*/
|
||||
data class CaptchaRequired(val captchaId: String) : LoginResult()
|
||||
|
||||
/**
|
||||
* Two-factor verification is required.
|
||||
*/
|
||||
data object TwoFactorRequired : LoginResult()
|
||||
|
||||
/**
|
||||
* There was an error logging in.
|
||||
*/
|
||||
data class Error(val errorMessage: String?) : LoginResult()
|
||||
|
||||
/**
|
||||
* There was an error while logging into an unofficial Bitwarden server.
|
||||
*/
|
||||
data object UnofficialServerError : LoginResult()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Response types when checking for an email's claimed domain organization.
|
||||
*/
|
||||
sealed class OrganizationDomainSsoDetailsResult {
|
||||
/**
|
||||
* The request was successful.
|
||||
*
|
||||
* @property isSsoAvailable Indicates if SSO is available for the email address.
|
||||
* @property organizationIdentifier The claimed organization identifier for the email address.
|
||||
*/
|
||||
data class Success(
|
||||
val isSsoAvailable: Boolean,
|
||||
val organizationIdentifier: String,
|
||||
) : OrganizationDomainSsoDetailsResult()
|
||||
|
||||
/**
|
||||
* The request failed.
|
||||
*/
|
||||
data object Failure : OrganizationDomainSsoDetailsResult()
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.JwtTokenDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Internal, generally basic [Json] instance for JWT parsing purposes.
|
||||
*/
|
||||
private val json: Json by lazy {
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [JwtTokenDataJson] from the given [jwtToken], or `null` if this parsing is not possible.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
fun parseJwtTokenDataOrNull(jwtToken: String): JwtTokenDataJson? {
|
||||
val parts = jwtToken.split(".")
|
||||
if (parts.size != 3) return null
|
||||
|
||||
val dataJson = parts[1]
|
||||
val decodedDataJson = dataJson.base64UrlDecodeOrNull() ?: return null
|
||||
|
||||
return try {
|
||||
json.decodeFromString<JwtTokenDataJson>(decodedDataJson)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
/**
|
||||
* Provides dependencies within the accessibility package scoped to the activity.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
object ActivityAccessibilityModule {
|
||||
@ActivityScoped
|
||||
@Provides
|
||||
fun providesAccessibilityActivityManager(
|
||||
@ApplicationContext context: Context,
|
||||
accessibilityEnabledManager: AccessibilityEnabledManager,
|
||||
appForegroundManager: AppForegroundManager,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
): AccessibilityActivityManager =
|
||||
AccessibilityActivityManagerImpl(
|
||||
context = context,
|
||||
accessibilityEnabledManager = accessibilityEnabledManager,
|
||||
appForegroundManager = appForegroundManager,
|
||||
lifecycleScope = lifecycleScope,
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
/**
|
||||
* A helper for dealing with accessibility configuration that must be scoped to a specific
|
||||
* [Activity]. In particular, this should be injected into an [Activity] to ensure that the
|
||||
* [AccessibilityEnabledManager] reports correct values.
|
||||
*/
|
||||
interface AccessibilityActivityManager
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
/**
|
||||
* The default implementation of the [AccessibilityActivityManager].
|
||||
*/
|
||||
class AccessibilityActivityManagerImpl(
|
||||
private val context: Context,
|
||||
private val accessibilityEnabledManager: AccessibilityEnabledManager,
|
||||
appForegroundManager: AppForegroundManager,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
) : AccessibilityActivityManager {
|
||||
init {
|
||||
appForegroundManager
|
||||
.appForegroundStateFlow
|
||||
.onEach {
|
||||
accessibilityEnabledManager.isAccessibilityEnabled =
|
||||
context.isAccessibilityServiceEnabled
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.accessibility.processor
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
|
||||
/**
|
||||
* A class to handle accessibility event processing. This only includes fill requests.
|
||||
*/
|
||||
interface BitwardenAccessibilityProcessor {
|
||||
/**
|
||||
* Processes the [AccessibilityNodeInfo] for autofill options.
|
||||
*/
|
||||
fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.accessibility.processor
|
||||
|
||||
import android.content.Context
|
||||
import android.os.PowerManager
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.widget.Toast
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.util.fillTextField
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.util.isSystemPackage
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.util.shouldSkipPackage
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent
|
||||
|
||||
/**
|
||||
* The default implementation of the [BitwardenAccessibilityProcessor].
|
||||
*/
|
||||
class BitwardenAccessibilityProcessorImpl(
|
||||
private val context: Context,
|
||||
private val accessibilityParser: AccessibilityParser,
|
||||
private val accessibilityAutofillManager: AccessibilityAutofillManager,
|
||||
private val launcherPackageNameManager: LauncherPackageNameManager,
|
||||
private val powerManager: PowerManager,
|
||||
) : BitwardenAccessibilityProcessor {
|
||||
override fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?) {
|
||||
val rootNode = rootAccessibilityNodeInfo ?: return
|
||||
// Ignore the event when the phone is inactive
|
||||
if (!powerManager.isInteractive) return
|
||||
// We skip if the system package
|
||||
if (rootNode.isSystemPackage) return
|
||||
// We skip any package that is a launcher or unsupported
|
||||
if (rootNode.shouldSkipPackage ||
|
||||
launcherPackageNameManager.launcherPackages.any { it == rootNode.packageName }
|
||||
) {
|
||||
// Clear the action since this event needs to be ignored completely
|
||||
accessibilityAutofillManager.accessibilityAction = null
|
||||
return
|
||||
}
|
||||
|
||||
// Only process the event if the tile was clicked
|
||||
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return
|
||||
accessibilityAutofillManager.accessibilityAction = null
|
||||
|
||||
when (accessibilityAction) {
|
||||
is AccessibilityAction.AttemptFill -> {
|
||||
handleAttemptFill(rootNode = rootNode, attemptFill = accessibilityAction)
|
||||
}
|
||||
|
||||
AccessibilityAction.AttemptParseUri -> handleAttemptParseUri(rootNode = rootNode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) {
|
||||
accessibilityParser
|
||||
.parseForUriOrPackageName(rootNode = rootNode)
|
||||
?.let { uri ->
|
||||
context.startActivity(
|
||||
createAutofillSelectionIntent(
|
||||
context = context,
|
||||
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
uri = uri.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
?: run {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
R.string.autofill_tile_uri_not_found,
|
||||
Toast.LENGTH_LONG,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAttemptFill(
|
||||
rootNode: AccessibilityNodeInfo,
|
||||
attemptFill: AccessibilityAction.AttemptFill,
|
||||
) {
|
||||
val loginView = attemptFill.cipherView.login ?: return
|
||||
val fields = accessibilityParser.parseForFillableFields(
|
||||
rootNode = rootNode,
|
||||
uri = attemptFill.uri,
|
||||
)
|
||||
fields.usernameField?.fillTextField(value = loginView.username)
|
||||
fields.passwordFields.forEach { passwordField ->
|
||||
passwordField.fillTextField(value = loginView.password)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
/**
|
||||
* Defines calls to an RP digital asset link file.
|
||||
*/
|
||||
interface DigitalAssetLinkApi {
|
||||
|
||||
/**
|
||||
* Attempts to download the asset links file from the RP.
|
||||
*/
|
||||
@GET
|
||||
suspend fun getDigitalAssetLinks(
|
||||
@Url url: String,
|
||||
): NetworkResult<List<DigitalAssetLinkResponseJson>>
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.di
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides network dependencies in the fido2 package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object Fido2NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDigitalAssetLinkService(
|
||||
retrofits: Retrofits,
|
||||
): DigitalAssetLinkService =
|
||||
DigitalAssetLinkServiceImpl(
|
||||
digitalAssetLinkApi = retrofits
|
||||
.createStaticRetrofit()
|
||||
.create(),
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Models a response from an RP digital asset link request.
|
||||
*/
|
||||
@Serializable
|
||||
data class DigitalAssetLinkResponseJson(
|
||||
@SerialName("relation")
|
||||
val relation: List<String>,
|
||||
|
||||
@SerialName("target")
|
||||
val target: Target,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents targets for an asset link statement.
|
||||
*/
|
||||
@Serializable
|
||||
data class Target(
|
||||
@SerialName("namespace")
|
||||
val namespace: String,
|
||||
|
||||
@SerialName("package_name")
|
||||
val packageName: String?,
|
||||
|
||||
@SerialName("sha256_cert_fingerprints")
|
||||
val sha256CertFingerprints: List<String>?,
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||
|
||||
/**
|
||||
* Provides an API for querying digital asset links.
|
||||
*/
|
||||
interface DigitalAssetLinkService {
|
||||
|
||||
/**
|
||||
* Attempt to retrieve the asset links file from the provided [relyingParty].
|
||||
*/
|
||||
suspend fun getDigitalAssetLinkForRp(
|
||||
scheme: String = "https://",
|
||||
relyingParty: String,
|
||||
): Result<List<DigitalAssetLinkResponseJson>>
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api.DigitalAssetLinkApi
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||
|
||||
/**
|
||||
* Primary implementation of [DigitalAssetLinkService].
|
||||
*/
|
||||
class DigitalAssetLinkServiceImpl(
|
||||
private val digitalAssetLinkApi: DigitalAssetLinkApi,
|
||||
) : DigitalAssetLinkService {
|
||||
|
||||
override suspend fun getDigitalAssetLinkForRp(
|
||||
scheme: String,
|
||||
relyingParty: String,
|
||||
): Result<List<DigitalAssetLinkResponseJson>> =
|
||||
digitalAssetLinkApi
|
||||
.getDigitalAssetLinks(
|
||||
url = "$scheme$relyingParty/.well-known/assetlinks.json",
|
||||
)
|
||||
.toResult()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.di
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessor
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.processor.Fido2ProviderProcessorImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.AssetManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Clock
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides dependencies within the fido2 package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object Fido2ProviderModule {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCredentialProviderProcessor(
|
||||
@ApplicationContext context: Context,
|
||||
authRepository: AuthRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
fido2CredentialStore: Fido2CredentialStore,
|
||||
fido2CredentialManager: Fido2CredentialManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
intentManager: IntentManager,
|
||||
clock: Clock,
|
||||
): Fido2ProviderProcessor =
|
||||
Fido2ProviderProcessorImpl(
|
||||
context,
|
||||
authRepository,
|
||||
vaultRepository,
|
||||
fido2CredentialStore,
|
||||
fido2CredentialManager,
|
||||
intentManager,
|
||||
clock,
|
||||
dispatcherManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFido2CredentialManager(
|
||||
assetManager: AssetManager,
|
||||
digitalAssetLinkService: DigitalAssetLinkService,
|
||||
vaultSdkSource: VaultSdkSource,
|
||||
fido2CredentialStore: Fido2CredentialStore,
|
||||
json: Json,
|
||||
): Fido2CredentialManager =
|
||||
Fido2CredentialManagerImpl(
|
||||
assetManager = assetManager,
|
||||
digitalAssetLinkService = digitalAssetLinkService,
|
||||
vaultSdkSource = vaultSdkSource,
|
||||
fido2CredentialStore = fido2CredentialStore,
|
||||
json = json,
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.manager
|
||||
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
|
||||
|
||||
/**
|
||||
* Responsible for managing FIDO 2 credential registration and authentication.
|
||||
*/
|
||||
interface Fido2CredentialManager {
|
||||
/**
|
||||
* Returns true when the user has performed an explicit verification action. E.g., biometric
|
||||
* verification, device credential verification, or vault unlock.
|
||||
*/
|
||||
var isUserVerified: Boolean
|
||||
|
||||
/**
|
||||
* The number of times the user has attempted to authenticate with their password or PIN
|
||||
* for the FIDO 2 user verification flow.
|
||||
*/
|
||||
var authenticationAttempts: Int
|
||||
|
||||
/**
|
||||
* Attempt to validate the RP and origin of the provided [callingAppInfo] and [relyingPartyId].
|
||||
*/
|
||||
suspend fun validateOrigin(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
relyingPartyId: String,
|
||||
): Fido2ValidateOriginResult
|
||||
|
||||
/**
|
||||
* Attempt to extract FIDO 2 passkey attestation options from the system [requestJson], or null.
|
||||
*/
|
||||
fun getPasskeyAttestationOptionsOrNull(
|
||||
requestJson: String,
|
||||
): PasskeyAttestationOptions?
|
||||
|
||||
/**
|
||||
* Attempt to extract FIDO 2 passkey assertion options from the system [requestJson], or null.
|
||||
*/
|
||||
fun getPasskeyAssertionOptionsOrNull(
|
||||
requestJson: String,
|
||||
): PasskeyAssertionOptions?
|
||||
|
||||
/**
|
||||
* Register a new FIDO 2 credential to a users vault.
|
||||
*/
|
||||
suspend fun registerFido2Credential(
|
||||
userId: String,
|
||||
fido2CredentialRequest: Fido2CredentialRequest,
|
||||
selectedCipherView: CipherView,
|
||||
): Fido2RegisterCredentialResult
|
||||
|
||||
/**
|
||||
* Authenticate a FIDO credential against a cipher in the users vault.
|
||||
*/
|
||||
suspend fun authenticateFido2Credential(
|
||||
userId: String,
|
||||
request: Fido2CredentialAssertionRequest,
|
||||
selectedCipherView: CipherView,
|
||||
): Fido2CredentialAssertionResult
|
||||
|
||||
/**
|
||||
* Whether or not the user has authentication attempts remaining.
|
||||
*/
|
||||
fun hasAuthenticationAttemptsRemaining(): Boolean
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.manager
|
||||
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import com.bitwarden.fido.ClientData
|
||||
import com.bitwarden.fido.Origin
|
||||
import com.bitwarden.fido.UnverifiedAssetLink
|
||||
import com.bitwarden.sdk.Fido2CredentialStore
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service.DigitalAssetLinkService
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2RegisterCredentialResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2ValidateOriginResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAssertionOptions
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.PasskeyAttestationOptions
|
||||
import com.x8bit.bitwarden.data.platform.manager.AssetManager
|
||||
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
|
||||
import com.x8bit.bitwarden.data.platform.util.getAppOrigin
|
||||
import com.x8bit.bitwarden.data.platform.util.getAppSigningSignatureFingerprint
|
||||
import com.x8bit.bitwarden.data.platform.util.getSignatureFingerprintAsHexString
|
||||
import com.x8bit.bitwarden.data.platform.util.validatePrivilegedApp
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.AuthenticateFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.RegisterFido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidAttestationResponse
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.util.toAndroidFido2PublicKeyCredential
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val GOOGLE_ALLOW_LIST_FILE_NAME = "fido2_privileged_google.json"
|
||||
private const val COMMUNITY_ALLOW_LIST_FILE_NAME = "fido2_privileged_community.json"
|
||||
|
||||
/**
|
||||
* Primary implementation of [Fido2CredentialManager].
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class Fido2CredentialManagerImpl(
|
||||
private val assetManager: AssetManager,
|
||||
private val digitalAssetLinkService: DigitalAssetLinkService,
|
||||
private val vaultSdkSource: VaultSdkSource,
|
||||
private val fido2CredentialStore: Fido2CredentialStore,
|
||||
private val json: Json,
|
||||
) : Fido2CredentialManager,
|
||||
Fido2CredentialStore by fido2CredentialStore {
|
||||
|
||||
override var isUserVerified: Boolean = false
|
||||
|
||||
override var authenticationAttempts: Int = 0
|
||||
|
||||
override suspend fun registerFido2Credential(
|
||||
userId: String,
|
||||
fido2CredentialRequest: Fido2CredentialRequest,
|
||||
selectedCipherView: CipherView,
|
||||
): Fido2RegisterCredentialResult {
|
||||
val clientData = if (fido2CredentialRequest.callingAppInfo.isOriginPopulated()) {
|
||||
fido2CredentialRequest
|
||||
.callingAppInfo
|
||||
.getAppSigningSignatureFingerprint()
|
||||
?.let { ClientData.DefaultWithCustomHash(hash = it) }
|
||||
?: return Fido2RegisterCredentialResult.Error
|
||||
} else {
|
||||
ClientData.DefaultWithExtraData(
|
||||
androidPackageName = fido2CredentialRequest
|
||||
.callingAppInfo
|
||||
.packageName,
|
||||
)
|
||||
}
|
||||
val assetLinkUrl = fido2CredentialRequest
|
||||
.origin
|
||||
?: getOriginUrlFromAttestationOptionsOrNull(fido2CredentialRequest.requestJson)
|
||||
?: return Fido2RegisterCredentialResult.Error
|
||||
|
||||
val origin = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
packageName = fido2CredentialRequest.packageName,
|
||||
sha256CertFingerprint = fido2CredentialRequest
|
||||
.callingAppInfo
|
||||
.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2RegisterCredentialResult.Error,
|
||||
host = assetLinkUrl.toHostOrPathOrNull()
|
||||
?: return Fido2RegisterCredentialResult.Error,
|
||||
assetLinkUrl = assetLinkUrl,
|
||||
),
|
||||
)
|
||||
return vaultSdkSource
|
||||
.registerFido2Credential(
|
||||
request = RegisterFido2CredentialRequest(
|
||||
userId = userId,
|
||||
origin = origin,
|
||||
requestJson = """{"publicKey": ${fido2CredentialRequest.requestJson}}""",
|
||||
clientData = clientData,
|
||||
selectedCipherView = selectedCipherView,
|
||||
// User verification is handled prior to engaging the SDK. We always respond
|
||||
// `true` so that the SDK does not fail if the relying party requests UV.
|
||||
isUserVerificationSupported = true,
|
||||
),
|
||||
fido2CredentialStore = this,
|
||||
)
|
||||
.map { it.toAndroidAttestationResponse() }
|
||||
.mapCatching { json.encodeToString(it) }
|
||||
.fold(
|
||||
onSuccess = { Fido2RegisterCredentialResult.Success(it) },
|
||||
onFailure = { Fido2RegisterCredentialResult.Error },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun validateOrigin(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
relyingPartyId: String,
|
||||
): Fido2ValidateOriginResult {
|
||||
return if (callingAppInfo.isOriginPopulated()) {
|
||||
validatePrivilegedAppOrigin(callingAppInfo)
|
||||
} else {
|
||||
validateCallingApplicationAssetLinks(callingAppInfo, relyingPartyId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPasskeyAttestationOptionsOrNull(
|
||||
requestJson: String,
|
||||
): PasskeyAttestationOptions? =
|
||||
try {
|
||||
json.decodeFromString<PasskeyAttestationOptions>(requestJson)
|
||||
} catch (e: SerializationException) {
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
|
||||
override fun getPasskeyAssertionOptionsOrNull(
|
||||
requestJson: String,
|
||||
): PasskeyAssertionOptions? =
|
||||
try {
|
||||
json.decodeFromString<PasskeyAssertionOptions>(requestJson)
|
||||
} catch (e: SerializationException) {
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
|
||||
override suspend fun authenticateFido2Credential(
|
||||
userId: String,
|
||||
request: Fido2CredentialAssertionRequest,
|
||||
selectedCipherView: CipherView,
|
||||
): Fido2CredentialAssertionResult {
|
||||
val callingAppInfo = request.callingAppInfo
|
||||
val clientData = request.clientDataHash
|
||||
?.let { ClientData.DefaultWithCustomHash(hash = it) }
|
||||
?: ClientData.DefaultWithExtraData(androidPackageName = callingAppInfo.getAppOrigin())
|
||||
val origin = callingAppInfo.origin
|
||||
?: getOriginUrlFromAssertionOptionsOrNull(request.requestJson)
|
||||
?: return Fido2CredentialAssertionResult.Error
|
||||
val relyingPartyId = json
|
||||
.decodeFromStringOrNull<PasskeyAssertionOptions>(request.requestJson)
|
||||
?.relyingPartyId
|
||||
?: return Fido2CredentialAssertionResult.Error
|
||||
|
||||
val validateOriginResult = validateOrigin(
|
||||
callingAppInfo = callingAppInfo,
|
||||
relyingPartyId = relyingPartyId,
|
||||
)
|
||||
|
||||
return when (validateOriginResult) {
|
||||
is Fido2ValidateOriginResult.Error -> {
|
||||
Fido2CredentialAssertionResult.Error
|
||||
}
|
||||
|
||||
Fido2ValidateOriginResult.Success -> {
|
||||
vaultSdkSource
|
||||
.authenticateFido2Credential(
|
||||
request = AuthenticateFido2CredentialRequest(
|
||||
userId = userId,
|
||||
origin = Origin.Android(
|
||||
UnverifiedAssetLink(
|
||||
callingAppInfo.packageName,
|
||||
callingAppInfo.getSignatureFingerprintAsHexString()
|
||||
?: return Fido2CredentialAssertionResult.Error,
|
||||
origin.toHostOrPathOrNull()
|
||||
?: return Fido2CredentialAssertionResult.Error,
|
||||
origin,
|
||||
),
|
||||
),
|
||||
requestJson = """{"publicKey": ${request.requestJson}}""",
|
||||
clientData = clientData,
|
||||
selectedCipherView = selectedCipherView,
|
||||
isUserVerificationSupported = true,
|
||||
),
|
||||
fido2CredentialStore = this,
|
||||
)
|
||||
.map { it.toAndroidFido2PublicKeyCredential() }
|
||||
.mapCatching { json.encodeToString(it) }
|
||||
.fold(
|
||||
onSuccess = { Fido2CredentialAssertionResult.Success(it) },
|
||||
onFailure = { Fido2CredentialAssertionResult.Error },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun validateCallingApplicationAssetLinks(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
relyingPartyId: String,
|
||||
): Fido2ValidateOriginResult {
|
||||
return digitalAssetLinkService
|
||||
.getDigitalAssetLinkForRp(relyingParty = relyingPartyId)
|
||||
.onFailure {
|
||||
return Fido2ValidateOriginResult.Error.AssetLinkNotFound
|
||||
}
|
||||
.map { statements ->
|
||||
statements
|
||||
.filterMatchingAppStatementsOrNull(
|
||||
rpPackageName = callingAppInfo.packageName,
|
||||
)
|
||||
?: return Fido2ValidateOriginResult.Error.ApplicationNotFound
|
||||
}
|
||||
.map { matchingStatements ->
|
||||
callingAppInfo
|
||||
.getSignatureFingerprintAsHexString()
|
||||
?.let { certificateFingerprint ->
|
||||
matchingStatements
|
||||
.filterMatchingAppSignaturesOrNull(
|
||||
signature = certificateFingerprint,
|
||||
)
|
||||
}
|
||||
?: return Fido2ValidateOriginResult.Error.ApplicationNotVerified
|
||||
}
|
||||
.fold(
|
||||
onSuccess = {
|
||||
Fido2ValidateOriginResult.Success
|
||||
},
|
||||
onFailure = {
|
||||
Fido2ValidateOriginResult.Error.Unknown
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun validatePrivilegedAppOrigin(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
): Fido2ValidateOriginResult {
|
||||
val googleAllowListResult =
|
||||
validatePrivilegedAppSignatureWithGoogleList(callingAppInfo)
|
||||
return when (googleAllowListResult) {
|
||||
is Fido2ValidateOriginResult.Success -> {
|
||||
// Application was found and successfully validated against the Google allow list so
|
||||
// we can return the result as the final validation result.
|
||||
googleAllowListResult
|
||||
}
|
||||
|
||||
is Fido2ValidateOriginResult.Error -> {
|
||||
// Check the community allow list if the Google allow list failed, and return the
|
||||
// result as the final validation result.
|
||||
validatePrivilegedAppSignatureWithCommunityList(callingAppInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun validatePrivilegedAppSignatureWithGoogleList(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
): Fido2ValidateOriginResult =
|
||||
validatePrivilegedAppSignatureWithAllowList(
|
||||
callingAppInfo = callingAppInfo,
|
||||
fileName = GOOGLE_ALLOW_LIST_FILE_NAME,
|
||||
)
|
||||
|
||||
private suspend fun validatePrivilegedAppSignatureWithCommunityList(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
): Fido2ValidateOriginResult =
|
||||
validatePrivilegedAppSignatureWithAllowList(
|
||||
callingAppInfo = callingAppInfo,
|
||||
fileName = COMMUNITY_ALLOW_LIST_FILE_NAME,
|
||||
)
|
||||
|
||||
private suspend fun validatePrivilegedAppSignatureWithAllowList(
|
||||
callingAppInfo: CallingAppInfo,
|
||||
fileName: String,
|
||||
): Fido2ValidateOriginResult =
|
||||
assetManager
|
||||
.readAsset(fileName)
|
||||
.map { allowList ->
|
||||
callingAppInfo.validatePrivilegedApp(
|
||||
allowList = allowList,
|
||||
)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { it },
|
||||
onFailure = { Fido2ValidateOriginResult.Error.Unknown },
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns statements targeting the calling Android application, or null.
|
||||
*/
|
||||
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppStatementsOrNull(
|
||||
rpPackageName: String,
|
||||
): List<DigitalAssetLinkResponseJson>? =
|
||||
filter { statement ->
|
||||
val target = statement.target
|
||||
target.namespace == "android_app" &&
|
||||
target.packageName == rpPackageName &&
|
||||
statement.relation.containsAll(
|
||||
listOf(
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls",
|
||||
),
|
||||
)
|
||||
}
|
||||
.takeUnless { it.isEmpty() }
|
||||
|
||||
/**
|
||||
* Returns statements that match the given [signature], or null.
|
||||
*/
|
||||
private fun List<DigitalAssetLinkResponseJson>.filterMatchingAppSignaturesOrNull(
|
||||
signature: String,
|
||||
): List<DigitalAssetLinkResponseJson>? =
|
||||
filter { statement ->
|
||||
statement.target.sha256CertFingerprints
|
||||
?.contains(signature)
|
||||
?: false
|
||||
}
|
||||
.takeUnless { it.isEmpty() }
|
||||
|
||||
override fun hasAuthenticationAttemptsRemaining(): Boolean =
|
||||
authenticationAttempts < MAX_AUTHENTICATION_ATTEMPTS
|
||||
|
||||
private fun getOriginUrlFromAssertionOptionsOrNull(requestJson: String) =
|
||||
getPasskeyAssertionOptionsOrNull(requestJson)
|
||||
?.relyingPartyId
|
||||
?.let { "$HTTPS$it" }
|
||||
|
||||
private fun getOriginUrlFromAttestationOptionsOrNull(requestJson: String) =
|
||||
getPasskeyAttestationOptionsOrNull(requestJson)
|
||||
?.relyingParty
|
||||
?.id
|
||||
?.let { "$HTTPS$it" }
|
||||
}
|
||||
|
||||
private const val MAX_AUTHENTICATION_ATTEMPTS = 5
|
||||
private const val HTTPS = "https://"
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import android.content.pm.SigningInfo
|
||||
import android.os.Parcelable
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Models a FIDO 2 credential authentication request parsed from the launching intent.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Fido2CredentialAssertionRequest(
|
||||
val userId: String,
|
||||
val cipherId: String?,
|
||||
val credentialId: String?,
|
||||
val requestJson: String,
|
||||
val clientDataHash: ByteArray?,
|
||||
val packageName: String,
|
||||
val signingInfo: SigningInfo,
|
||||
val origin: String?,
|
||||
) : Parcelable {
|
||||
val callingAppInfo: CallingAppInfo
|
||||
get() = CallingAppInfo(packageName, signingInfo, origin)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
/**
|
||||
* Represents possible outcomes of a FIDO 2 credential assertion request.
|
||||
*/
|
||||
sealed class Fido2CredentialAssertionResult {
|
||||
|
||||
/**
|
||||
* Indicates the assertion request completed and [responseJson] was successfully generated.
|
||||
*/
|
||||
data class Success(val responseJson: String) : Fido2CredentialAssertionResult()
|
||||
|
||||
/**
|
||||
* Indicates there was an error and the assertion was not successful.
|
||||
*/
|
||||
data object Error : Fido2CredentialAssertionResult()
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import android.content.pm.SigningInfo
|
||||
import android.os.Parcelable
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Represents raw data from the a user deciding to create a passkey in their vault via the
|
||||
* credential manager framework.
|
||||
*
|
||||
* @property userId The user under which the passkey should be saved.
|
||||
* @property requestJson JSON payload containing the RP request.
|
||||
* @property callingAppInfo Information about the application that initiated the request.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Fido2CredentialRequest(
|
||||
val userId: String,
|
||||
val requestJson: String,
|
||||
val packageName: String,
|
||||
val signingInfo: SigningInfo,
|
||||
val origin: String?,
|
||||
) : Parcelable {
|
||||
val callingAppInfo: CallingAppInfo
|
||||
get() = CallingAppInfo(
|
||||
packageName = packageName,
|
||||
signingInfo = signingInfo,
|
||||
origin = origin,
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
import android.content.pm.SigningInfo
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Models a FIDO 2 request to retrieve FIDO credentials parsed from the launching intent.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Fido2GetCredentialsRequest(
|
||||
val candidateQueryData: Bundle,
|
||||
val id: String,
|
||||
val userId: String,
|
||||
val requestJson: String,
|
||||
val clientDataHash: ByteArray? = null,
|
||||
val packageName: String,
|
||||
val signingInfo: SigningInfo,
|
||||
val origin: String?,
|
||||
) : Parcelable {
|
||||
val callingAppInfo: CallingAppInfo
|
||||
get() = CallingAppInfo(packageName, signingInfo, origin)
|
||||
|
||||
val option: BeginGetPublicKeyCredentialOption
|
||||
get() = BeginGetPublicKeyCredentialOption(
|
||||
candidateQueryData,
|
||||
id,
|
||||
requestJson,
|
||||
clientDataHash,
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.model
|
||||
|
||||
/**
|
||||
* Models the data returned from creating a FIDO 2 credential.
|
||||
*/
|
||||
sealed class Fido2RegisterCredentialResult {
|
||||
|
||||
/**
|
||||
* Indicates the credential has been successfully registered.
|
||||
*/
|
||||
data class Success(
|
||||
val registrationResponse: String,
|
||||
) : Fido2RegisterCredentialResult()
|
||||
|
||||
/**
|
||||
* Indicates there was an error and the credential was not registered.
|
||||
*/
|
||||
data object Error : Fido2RegisterCredentialResult()
|
||||
|
||||
/**
|
||||
* Indicates the user cancelled the request.
|
||||
*/
|
||||
data object Cancelled : Fido2RegisterCredentialResult()
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.fido2.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||
import androidx.credentials.GetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CIPHER_ID
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_CREDENTIAL_ID
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.EXTRA_KEY_USER_ID
|
||||
|
||||
/**
|
||||
* Checks if this [Intent] contains a [Fido2CredentialRequest] related to an ongoing FIDO 2
|
||||
* credential creation process.
|
||||
*/
|
||||
fun Intent.getFido2CredentialRequestOrNull(): Fido2CredentialRequest? {
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
|
||||
|
||||
val systemRequest = PendingIntentHandler
|
||||
.retrieveProviderCreateCredentialRequest(this)
|
||||
?: return null
|
||||
|
||||
val createPublicKeyRequest = systemRequest
|
||||
.callingRequest
|
||||
as? CreatePublicKeyCredentialRequest
|
||||
?: return null
|
||||
|
||||
val userId = getStringExtra(EXTRA_KEY_USER_ID)
|
||||
?: return null
|
||||
|
||||
return Fido2CredentialRequest(
|
||||
userId = userId,
|
||||
requestJson = createPublicKeyRequest.requestJson,
|
||||
packageName = systemRequest.callingAppInfo.packageName,
|
||||
signingInfo = systemRequest.callingAppInfo.signingInfo,
|
||||
origin = systemRequest.callingAppInfo.origin,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this [Intent] contains a [Fido2CredentialAssertionRequest] related to an ongoing FIDO 2
|
||||
* credential authentication process.
|
||||
*/
|
||||
fun Intent.getFido2AssertionRequestOrNull(): Fido2CredentialAssertionRequest? {
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
|
||||
|
||||
val systemRequest = PendingIntentHandler
|
||||
.retrieveProviderGetCredentialRequest(this)
|
||||
?: return null
|
||||
|
||||
val option: GetPublicKeyCredentialOption = systemRequest
|
||||
.credentialOptions
|
||||
.firstNotNullOfOrNull { it as? GetPublicKeyCredentialOption }
|
||||
?: return null
|
||||
|
||||
val credentialId = getStringExtra(EXTRA_KEY_CREDENTIAL_ID)
|
||||
?: return null
|
||||
|
||||
val cipherId = getStringExtra(EXTRA_KEY_CIPHER_ID)
|
||||
?: return null
|
||||
|
||||
val userId: String = getStringExtra(EXTRA_KEY_USER_ID)
|
||||
?: return null
|
||||
|
||||
return Fido2CredentialAssertionRequest(
|
||||
userId = userId,
|
||||
cipherId = cipherId,
|
||||
credentialId = credentialId,
|
||||
requestJson = option.requestJson,
|
||||
clientDataHash = option.clientDataHash,
|
||||
packageName = systemRequest.callingAppInfo.packageName,
|
||||
signingInfo = systemRequest.callingAppInfo.signingInfo,
|
||||
origin = systemRequest.callingAppInfo.origin,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this [Intent] contains a [Fido2GetCredentialsRequest] related to an ongoing FIDO 2
|
||||
* credential lookup process.
|
||||
*/
|
||||
fun Intent.getFido2GetCredentialsRequestOrNull(): Fido2GetCredentialsRequest? {
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) return null
|
||||
|
||||
val systemRequest = PendingIntentHandler
|
||||
.retrieveBeginGetCredentialRequest(this)
|
||||
?: return null
|
||||
|
||||
val option: BeginGetPublicKeyCredentialOption = systemRequest
|
||||
.beginGetCredentialOptions
|
||||
.firstNotNullOfOrNull { it as? BeginGetPublicKeyCredentialOption }
|
||||
?: return null
|
||||
|
||||
val callingAppInfo = systemRequest
|
||||
.callingAppInfo
|
||||
?: return null
|
||||
|
||||
val userId: String = getStringExtra(EXTRA_KEY_USER_ID)
|
||||
?: return null
|
||||
|
||||
return Fido2GetCredentialsRequest(
|
||||
candidateQueryData = option.candidateQueryData,
|
||||
id = option.id,
|
||||
userId = userId,
|
||||
requestJson = option.requestJson,
|
||||
clientDataHash = option.clientDataHash,
|
||||
packageName = callingAppInfo.packageName,
|
||||
signingInfo = callingAppInfo.signingInfo,
|
||||
origin = callingAppInfo.origin,
|
||||
)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import android.view.autofill.AutofillManager
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
/**
|
||||
* Primary implementation of [AutofillActivityManager].
|
||||
*/
|
||||
class AutofillActivityManagerImpl(
|
||||
private val autofillManager: AutofillManager,
|
||||
private val autofillEnabledManager: AutofillEnabledManager,
|
||||
appForegroundManager: AppForegroundManager,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
) : AutofillActivityManager {
|
||||
private val isAutofillEnabledAndSupported: Boolean
|
||||
get() = autofillManager.isEnabled &&
|
||||
autofillManager.hasEnabledAutofillServices() &&
|
||||
autofillManager.isAutofillSupported
|
||||
|
||||
init {
|
||||
appForegroundManager
|
||||
.appForegroundStateFlow
|
||||
.onEach { autofillEnabledManager.isAutofillEnabled = isAutofillEnabledAndSupported }
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.autofill.model
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* The app information required for the autofill service.
|
||||
*/
|
||||
data class AutofillAppInfo(
|
||||
val context: Context,
|
||||
val packageName: String,
|
||||
val sdkInt: Int,
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/**
|
||||
* Used to denote an instance of [SharedPreferences] that encrypts its data.
|
||||
*/
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class EncryptedPreferences
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.GET
|
||||
|
||||
/**
|
||||
* This interface defines the API service for fetching configuration data.
|
||||
*/
|
||||
interface ConfigApi {
|
||||
|
||||
@GET("config")
|
||||
suspend fun getConfig(): NetworkResult<ConfigResponseJson>
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.authenticator
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
|
||||
/**
|
||||
* A provider for all the functionality needed to properly refresh the users access token.
|
||||
*/
|
||||
interface AuthenticatorProvider {
|
||||
|
||||
/**
|
||||
* The currently active user's ID.
|
||||
*/
|
||||
val activeUserId: String?
|
||||
|
||||
/**
|
||||
* Attempts to logout the user based on the [userId].
|
||||
*/
|
||||
fun logout(userId: String)
|
||||
|
||||
/**
|
||||
* Attempt to refresh the user's access token based on the [userId].
|
||||
*
|
||||
* This call is both synchronous and performs a network request. Make sure that you are calling
|
||||
* from an appropriate thread.
|
||||
*/
|
||||
fun refreshAccessTokenSynchronously(userId: String): Result<RefreshTokenResponseJson>
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.authenticator
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.parseJwtTokenDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* An authenticator used to refresh the access token when a 401 is returned from an API. Upon
|
||||
* successfully getting a new access token, the original request is retried.
|
||||
*/
|
||||
@Singleton
|
||||
class RefreshAuthenticator : Authenticator {
|
||||
|
||||
/**
|
||||
* A provider required to update tokens.
|
||||
*/
|
||||
var authenticatorProvider: AuthenticatorProvider? = null
|
||||
|
||||
override fun authenticate(
|
||||
route: Route?,
|
||||
response: Response,
|
||||
): Request? {
|
||||
val accessToken = requireNotNull(
|
||||
response
|
||||
.request
|
||||
.header(name = HEADER_KEY_AUTHORIZATION)
|
||||
?.substringAfter(delimiter = HEADER_VALUE_BEARER_PREFIX),
|
||||
)
|
||||
return when (val userId = parseJwtTokenDataOrNull(accessToken)?.userId) {
|
||||
null -> {
|
||||
// We unable to get the user ID, let's just let the 401 pass through.
|
||||
null
|
||||
}
|
||||
|
||||
authenticatorProvider?.activeUserId -> {
|
||||
// In order to prevent potential deadlocks or thread starvation we want the call
|
||||
// to refresh the access token to be strictly synchronous with no internal thread
|
||||
// hopping.
|
||||
authenticatorProvider
|
||||
?.refreshAccessTokenSynchronously(userId)
|
||||
?.fold(
|
||||
onFailure = {
|
||||
authenticatorProvider?.logout(userId)
|
||||
null
|
||||
},
|
||||
onSuccess = {
|
||||
response.request
|
||||
.newBuilder()
|
||||
.header(
|
||||
name = HEADER_KEY_AUTHORIZATION,
|
||||
value = "$HEADER_VALUE_BEARER_PREFIX${it.accessToken}",
|
||||
)
|
||||
.build()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// We are no longer the active user, let's just cancel.
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.RetrofitsImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.ZonedDateTimeSerializer
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushServiceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class provides network-related functionality for the application.
|
||||
* It initializes and configures the networking components.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object PlatformNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesConfigService(
|
||||
retrofits: Retrofits,
|
||||
): ConfigService = ConfigServiceImpl(retrofits.unauthenticatedApiRetrofit.create())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesEventService(
|
||||
retrofits: Retrofits,
|
||||
): EventService = EventServiceImpl(
|
||||
eventApi = retrofits.authenticatedEventsRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushService(
|
||||
retrofits: Retrofits,
|
||||
authDiskSource: AuthDiskSource,
|
||||
): PushService = PushServiceImpl(
|
||||
pushApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
appId = authDiskSource.uniqueAppId,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAuthTokenInterceptor(
|
||||
authDiskSource: AuthDiskSource,
|
||||
): AuthTokenInterceptor = AuthTokenInterceptor(authDiskSource = authDiskSource)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesHeadersInterceptor(): HeadersInterceptor = HeadersInterceptor()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesRefreshAuthenticator(): RefreshAuthenticator = RefreshAuthenticator()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofits(
|
||||
authTokenInterceptor: AuthTokenInterceptor,
|
||||
baseUrlInterceptors: BaseUrlInterceptors,
|
||||
headersInterceptor: HeadersInterceptor,
|
||||
refreshAuthenticator: RefreshAuthenticator,
|
||||
json: Json,
|
||||
): Retrofits =
|
||||
RetrofitsImpl(
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
baseUrlInterceptors = baseUrlInterceptors,
|
||||
headersInterceptor = headersInterceptor,
|
||||
refreshAuthenticator = refreshAuthenticator,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesJson(): Json = Json {
|
||||
|
||||
// If there are keys returned by the server not modeled by a serializable class,
|
||||
// ignore them.
|
||||
// This makes additive server changes non-breaking.
|
||||
ignoreUnknownKeys = true
|
||||
|
||||
// We allow for nullable values to have keys missing in the JSON response.
|
||||
explicitNulls = false
|
||||
serializersModule = SerializersModule {
|
||||
contextual(ZonedDateTimeSerializer())
|
||||
}
|
||||
|
||||
// Respect model default property values.
|
||||
coerceInputValues = true
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Interceptor responsible for adding the auth token(Bearer) to API requests.
|
||||
*/
|
||||
@Singleton
|
||||
class AuthTokenInterceptor(
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
) : Interceptor {
|
||||
/**
|
||||
* The auth token to be added to API requests.
|
||||
*
|
||||
* Note: This is done on demand to ensure that no race conditions can exist when retrieving the
|
||||
* token.
|
||||
*/
|
||||
private val authToken: String?
|
||||
get() = authDiskSource
|
||||
.userState
|
||||
?.activeUserId
|
||||
?.let { userId -> authDiskSource.getAccountTokens(userId = userId)?.accessToken }
|
||||
|
||||
private val missingTokenMessage = "Auth token is missing!"
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val token = authToken ?: throw IOException(IllegalStateException(missingTokenMessage))
|
||||
val request = chain
|
||||
.request()
|
||||
.newBuilder()
|
||||
.addHeader(
|
||||
name = HEADER_KEY_AUTHORIZATION,
|
||||
value = "$HEADER_VALUE_BEARER_PREFIX$token",
|
||||
)
|
||||
.build()
|
||||
return chain
|
||||
.proceed(request)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* A [Interceptor] that optionally takes the current base URL of a request and replaces it with
|
||||
* the currently set [baseUrl]
|
||||
*/
|
||||
class BaseUrlInterceptor : Interceptor {
|
||||
|
||||
/**
|
||||
* The base URL to use as an override, or `null` if no override should be performed.
|
||||
*/
|
||||
var baseUrl: String? = null
|
||||
set(value) {
|
||||
field = value
|
||||
baseHttpUrl = baseUrl?.let { requireNotNull(it.toHttpUrlOrNull()) }
|
||||
}
|
||||
|
||||
private var baseHttpUrl: HttpUrl? = null
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
// If no base URL is set, we can simply skip
|
||||
val base = baseHttpUrl ?: return chain.proceed(request)
|
||||
|
||||
// Update the base URL used.
|
||||
return chain.proceed(
|
||||
request
|
||||
.newBuilder()
|
||||
.url(
|
||||
request
|
||||
.url
|
||||
.replaceBaseUrlWith(base),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a [HttpUrl], replaces the existing base URL with the given [baseUrl].
|
||||
*/
|
||||
private fun HttpUrl.replaceBaseUrlWith(
|
||||
baseUrl: HttpUrl,
|
||||
) = baseUrl
|
||||
.newBuilder()
|
||||
.addEncodedPathSegments(
|
||||
this
|
||||
.encodedPathSegments
|
||||
.joinToString(separator = "/"),
|
||||
)
|
||||
.encodedQuery(this.encodedQuery)
|
||||
.build()
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseApiUrl
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseEventsUrl
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.baseIdentityUrl
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* An overall container for various [BaseUrlInterceptor] implementations for different API groups.
|
||||
*/
|
||||
@Singleton
|
||||
class BaseUrlInterceptors @Inject constructor() {
|
||||
var environment: Environment = Environment.Us
|
||||
set(value) {
|
||||
field = value
|
||||
updateBaseUrls(environment = value)
|
||||
}
|
||||
|
||||
/**
|
||||
* An interceptor for "/api" calls.
|
||||
*/
|
||||
val apiInterceptor: BaseUrlInterceptor = BaseUrlInterceptor()
|
||||
|
||||
/**
|
||||
* An interceptor for "/identity" calls.
|
||||
*/
|
||||
val identityInterceptor: BaseUrlInterceptor = BaseUrlInterceptor()
|
||||
|
||||
/**
|
||||
* An interceptor for "/events" calls.
|
||||
*/
|
||||
val eventsInterceptor: BaseUrlInterceptor = BaseUrlInterceptor()
|
||||
|
||||
init {
|
||||
// Ensure all interceptors begin with a default value
|
||||
environment = Environment.Us
|
||||
}
|
||||
|
||||
private fun updateBaseUrls(environment: Environment) {
|
||||
val environmentUrlData = environment.environmentUrlData
|
||||
apiInterceptor.baseUrl = environmentUrlData.baseApiUrl
|
||||
identityInterceptor.baseUrl = environmentUrlData.baseIdentityUrl
|
||||
eventsInterceptor.baseUrl = environmentUrlData.baseEventsUrl
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.interceptor
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_CLIENT_NAME
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_CLIENT_VERSION
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_DEVICE_TYPE
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_USER_AGENT
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_NAME
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_DEVICE_TYPE
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Interceptor responsible for adding various headers to all API requests.
|
||||
*/
|
||||
class HeadersInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response = chain.proceed(
|
||||
chain.request()
|
||||
.newBuilder()
|
||||
.header(HEADER_KEY_USER_AGENT, HEADER_VALUE_USER_AGENT)
|
||||
.header(HEADER_KEY_CLIENT_NAME, HEADER_VALUE_CLIENT_NAME)
|
||||
.header(HEADER_KEY_CLIENT_VERSION, HEADER_VALUE_CLIENT_VERSION)
|
||||
.header(HEADER_KEY_DEVICE_TYPE, HEADER_VALUE_DEVICE_TYPE)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.ConfigApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||
|
||||
class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService {
|
||||
override suspend fun getConfig(): Result<ConfigResponseJson> = configApi.getConfig().toResult()
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||
|
||||
/**
|
||||
* The default implementation of the [EventService].
|
||||
*/
|
||||
class EventServiceImpl(
|
||||
private val eventApi: EventApi,
|
||||
) : EventService {
|
||||
override suspend fun sendOrganizationEvents(
|
||||
events: List<OrganizationEventJson>,
|
||||
): Result<Unit> = eventApi.collectOrganizationEvents(events = events).toResult()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user