mirror of
https://github.com/bitwarden/android.git
synced 2026-05-11 02:15:43 -05:00
Compare commits
1297 Commits
v2024.7.1
...
untagged-4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
693d9f18db | ||
|
|
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 | ||
|
|
1c10a94109 | ||
|
|
6f535c0abe | ||
|
|
5fdfb26950 | ||
|
|
bdb6136d36 | ||
|
|
2d9451cc34 | ||
|
|
6217532237 | ||
|
|
ef1e8403e1 | ||
|
|
24c8406ed8 | ||
|
|
51c87625cb | ||
|
|
fa248243b6 | ||
|
|
f1d7d1a530 | ||
|
|
79ebd2ba33 | ||
|
|
5d7656323b | ||
|
|
a23fc319de | ||
|
|
932c26dfc6 | ||
|
|
5c38522ec6 | ||
|
|
c5a266dfc0 | ||
|
|
fca00d38f5 | ||
|
|
65380095f0 | ||
|
|
002fd06b72 | ||
|
|
5d85060260 | ||
|
|
4dfee643a0 | ||
|
|
7aab846244 | ||
|
|
ab4d9b9984 | ||
|
|
74b1bc8303 | ||
|
|
25e01c86bc | ||
|
|
c704cd2eca | ||
|
|
09c11f4890 | ||
|
|
df6e842201 | ||
|
|
b82614e5fa | ||
|
|
27beb25bf7 | ||
|
|
d1f13e49a4 | ||
|
|
36a718753d | ||
|
|
be0ebb9b3f | ||
|
|
4fc01c77d1 | ||
|
|
258f25cd37 | ||
|
|
98c3ced191 | ||
|
|
c26a7cdf28 | ||
|
|
b127021701 | ||
|
|
123d227c13 | ||
|
|
88b96812de | ||
|
|
083578ec2b | ||
|
|
f73ce842fc | ||
|
|
56ad1ef05b | ||
|
|
8abe62e53e | ||
|
|
5faa30e2f2 | ||
|
|
a9b6f296d8 | ||
|
|
655beb9dd6 | ||
|
|
0d6a8513b2 | ||
|
|
ab9d57b4f2 | ||
|
|
c382227b6a | ||
|
|
cf3624264e | ||
|
|
62cfd5e746 | ||
|
|
1446e43c46 | ||
|
|
43dc2f8116 | ||
|
|
8eb408b140 | ||
|
|
d07a9dcf81 | ||
|
|
1820073b65 | ||
|
|
1669fa4044 | ||
|
|
970a1e14cd | ||
|
|
736912bd6c | ||
|
|
d3c1f8e26a | ||
|
|
a78f81014e | ||
|
|
ec47cb9ee2 | ||
|
|
690de93e63 | ||
|
|
9adb106a12 | ||
|
|
499ab2d2d0 | ||
|
|
12afbea83e | ||
|
|
efbf84238d | ||
|
|
2b87cdac9e | ||
|
|
8eab74d458 | ||
|
|
4c74f720eb | ||
|
|
03251abe88 | ||
|
|
b465cc5078 | ||
|
|
9b5c88e990 | ||
|
|
4756040c4a | ||
|
|
bde47d7919 | ||
|
|
86db9bd3fa | ||
|
|
cd9f4e8723 | ||
|
|
879c2b9107 | ||
|
|
cdb03f5649 | ||
|
|
ba8e3a6c51 | ||
|
|
028242c4be | ||
|
|
3296477932 | ||
|
|
c3af26d83f | ||
|
|
43fb630393 | ||
|
|
3e9e45ba2f | ||
|
|
22c0745993 | ||
|
|
54e59cc61b | ||
|
|
537281f6c3 | ||
|
|
79d2a00bf8 | ||
|
|
57d79cd51c | ||
|
|
57082ff7c1 | ||
|
|
d6ae7da44c | ||
|
|
280aa35b73 | ||
|
|
8a30f14dea | ||
|
|
f3bce23942 | ||
|
|
2af96988ab | ||
|
|
ccb52ae6c5 | ||
|
|
cda4e47414 | ||
|
|
5e7dc26837 | ||
|
|
b5658fda42 | ||
|
|
1015654d27 | ||
|
|
1539c2032e | ||
|
|
94791b4256 | ||
|
|
49d9a46917 | ||
|
|
e7450171cd | ||
|
|
641a48fe44 | ||
|
|
bc057932a0 | ||
|
|
d8c80f7e28 | ||
|
|
3aaa93675d | ||
|
|
af0f894dd3 | ||
|
|
0cb8e369ae | ||
|
|
3d4c901039 | ||
|
|
e62dc5dd21 | ||
|
|
f8592f4e17 | ||
|
|
c4467f0cba | ||
|
|
6e7f5a31f1 | ||
|
|
79b7866e30 | ||
|
|
d8da7a8cc2 | ||
|
|
e36e9e878f | ||
|
|
8d578a9b57 | ||
|
|
73a802a483 | ||
|
|
60fce08c7e | ||
|
|
8ae6433906 | ||
|
|
83652c9699 | ||
|
|
a5cf4f49d7 | ||
|
|
78d14547e4 | ||
|
|
29f00421bb | ||
|
|
8501db0eb2 | ||
|
|
c1e9759dae | ||
|
|
8f24597bad | ||
|
|
954a9acf92 | ||
|
|
3dfe6adc05 | ||
|
|
1d84479cf3 | ||
|
|
c8dcafe737 | ||
|
|
567c2ffb94 | ||
|
|
488ec095bc | ||
|
|
32f2bfb29f | ||
|
|
93423d4b13 | ||
|
|
20383f06a8 | ||
|
|
fd6b276cc8 | ||
|
|
e6eb626d85 | ||
|
|
569ffc3583 | ||
|
|
e2e5042be5 | ||
|
|
ee165491c9 | ||
|
|
8ad9bc5eed | ||
|
|
0c83a1099f | ||
|
|
36a5fee048 | ||
|
|
01ab047d9c | ||
|
|
e2e49a4555 | ||
|
|
4fd81ed3b8 | ||
|
|
a92fd5b35d | ||
|
|
5e5b29677a | ||
|
|
8e092ef860 | ||
|
|
9e4119fe32 | ||
|
|
757baf0290 | ||
|
|
9f4a85d373 | ||
|
|
53b1bec42b | ||
|
|
e63c4806f1 | ||
|
|
2224708fb1 | ||
|
|
b3e885bcb1 | ||
|
|
10bbab971f | ||
|
|
b0edb7cf3b | ||
|
|
0d5bbc177c | ||
|
|
f7c14890f1 | ||
|
|
924ff87979 | ||
|
|
713bb51974 | ||
|
|
6faf720076 | ||
|
|
8ec743736a | ||
|
|
a13a7c363f | ||
|
|
9d6192ba1f | ||
|
|
2f05355487 | ||
|
|
b7c48c2e26 | ||
|
|
1e9583b3be | ||
|
|
75819cce3c | ||
|
|
d60c534e06 | ||
|
|
ad338a8fd6 | ||
|
|
290377af74 | ||
|
|
24195ddb90 | ||
|
|
73c5571d6b | ||
|
|
5beec9d687 | ||
|
|
9d19a73fd6 | ||
|
|
72cb9918ac | ||
|
|
aa6762dc22 | ||
|
|
b696964cb7 | ||
|
|
853f25bf57 | ||
|
|
70e75b910c | ||
|
|
aaa541dd19 | ||
|
|
80c1c26f5c | ||
|
|
f349a72f72 | ||
|
|
21e1d8b5bc | ||
|
|
9213b4843f | ||
|
|
ddeece0a5c | ||
|
|
dad501d37e | ||
|
|
61b09fe8f1 | ||
|
|
415aafb5e9 | ||
|
|
251d13a832 | ||
|
|
40dd0e9776 | ||
|
|
ad154b6c91 | ||
|
|
3445191a05 | ||
|
|
caa0c613a5 | ||
|
|
6908111377 | ||
|
|
0f009943b5 | ||
|
|
2d75679586 | ||
|
|
d676a6c76c | ||
|
|
4f34f6da21 | ||
|
|
3d0dd2996e | ||
|
|
774b828cb0 | ||
|
|
071a5330e6 | ||
|
|
86bbf13175 | ||
|
|
87e223bc59 | ||
|
|
190f92ec67 | ||
|
|
ee7d00247e | ||
|
|
f26374aae7 | ||
|
|
f68b4df9f9 | ||
|
|
05d6c2f61e | ||
|
|
8fe637d207 | ||
|
|
ad8702a34f | ||
|
|
bcdbea13bf | ||
|
|
37b5dc7de3 | ||
|
|
39cc24c13a | ||
|
|
d0c2bb5b7e | ||
|
|
5667a1cfd0 | ||
|
|
3238279290 | ||
|
|
73e158afd0 | ||
|
|
21b41fd4db | ||
|
|
26eeb39f88 | ||
|
|
7fd16c9e10 | ||
|
|
cb1e4f6b39 | ||
|
|
71d3ae9ee8 | ||
|
|
49c2e3030c | ||
|
|
ea267a0298 | ||
|
|
6e0063c977 | ||
|
|
c8b1b71960 | ||
|
|
384be79838 | ||
|
|
cdf8b49d61 | ||
|
|
84f92f1b13 | ||
|
|
2068948035 | ||
|
|
f89b053d2e | ||
|
|
4b53358c67 | ||
|
|
1f89314bbb | ||
|
|
2721f7293c | ||
|
|
afd7097b53 | ||
|
|
1c2ccd5305 | ||
|
|
4f55d622cb | ||
|
|
087cd5a093 | ||
|
|
74ae39a665 | ||
|
|
37f1da1ec2 | ||
|
|
503c966177 | ||
|
|
76cc9c8579 | ||
|
|
ed0494e159 | ||
|
|
fd8eda75b5 | ||
|
|
765a9ebd74 | ||
|
|
39df103f12 | ||
|
|
135a48b3c0 | ||
|
|
368ca49fd6 | ||
|
|
759e926588 | ||
|
|
3ecf1382b2 | ||
|
|
190ba792a1 | ||
|
|
d1f21d3585 | ||
|
|
4c1d55e9fe | ||
|
|
f544ccc3ef | ||
|
|
537c501b9b | ||
|
|
2d3a47f3d1 | ||
|
|
6521848a8d | ||
|
|
fe1f897d64 | ||
|
|
a5726fb72b | ||
|
|
8f30742908 | ||
|
|
98bcff5e06 | ||
|
|
19596ea4c3 | ||
|
|
8dce8cd576 | ||
|
|
b94a1adda9 | ||
|
|
8489693d84 | ||
|
|
647b3e921b | ||
|
|
4e69ed57e8 | ||
|
|
95240a7ce3 | ||
|
|
19facaf8fd | ||
|
|
bef05d5ed9 | ||
|
|
fa93985a2e | ||
|
|
b7544ef7f4 | ||
|
|
fa2d7e0218 | ||
|
|
c817253760 | ||
|
|
eb4e2ab31f | ||
|
|
705153ea21 | ||
|
|
47fe78536f | ||
|
|
3f78ad6d6d | ||
|
|
e468ec695b | ||
|
|
ae349183e8 | ||
|
|
e039d5c3fb | ||
|
|
b1ecf125d1 | ||
|
|
69ca7649e2 | ||
|
|
22958aa5ed | ||
|
|
7ebbe700f0 | ||
|
|
de48bb37d7 | ||
|
|
1dd19a8b2d | ||
|
|
733053b548 | ||
|
|
f10a2b15ba | ||
|
|
c02afb6714 | ||
|
|
b672418c45 | ||
|
|
c017c1b10c | ||
|
|
e3a4a7b153 | ||
|
|
36f13e44a3 | ||
|
|
2597c44117 | ||
|
|
ae685c73d2 | ||
|
|
2b5337c4ff | ||
|
|
06d742bd16 | ||
|
|
05bcaf9dbc | ||
|
|
d37fa652bb | ||
|
|
170e74db55 | ||
|
|
b75133516d | ||
|
|
e90c41a3b7 | ||
|
|
7e5203efa5 | ||
|
|
17c579bfc2 | ||
|
|
3c39d8beac | ||
|
|
5758b34dcf | ||
|
|
b49d8a18a2 | ||
|
|
2a057bb1fb | ||
|
|
0d77e7085b | ||
|
|
d0203eedc4 | ||
|
|
3c74a342d1 | ||
|
|
f778d7ecd1 | ||
|
|
4c983525d3 | ||
|
|
e32a9f303d | ||
|
|
8503610b0b | ||
|
|
ae1225b948 | ||
|
|
522e3bb939 | ||
|
|
0676cf8826 | ||
|
|
5173dfd424 | ||
|
|
5bc31448b4 | ||
|
|
e9a7136a9a | ||
|
|
c36d0851ca | ||
|
|
ace5f19375 | ||
|
|
88b40cfd10 | ||
|
|
38e693f92c | ||
|
|
9dbb40f33b | ||
|
|
76a3265bbb | ||
|
|
666c165b6f | ||
|
|
959361adc2 | ||
|
|
f9ccb766c2 | ||
|
|
e5e5d3c67c | ||
|
|
9db09c18cc | ||
|
|
b7330392cc | ||
|
|
162da64567 | ||
|
|
09f497ca9b | ||
|
|
87d7143cc8 | ||
|
|
23bcfad717 | ||
|
|
f1f16cfee5 | ||
|
|
82d3b44712 | ||
|
|
b56a21b6e5 | ||
|
|
eb2ba8e598 | ||
|
|
91f039ecb6 | ||
|
|
0d6aeee870 | ||
|
|
a0a5070ac7 | ||
|
|
e7bd966e94 | ||
|
|
075956ce17 | ||
|
|
13b256d4e9 | ||
|
|
5761e9510a | ||
|
|
3b3b9ef33b | ||
|
|
17fd3ec0f0 | ||
|
|
43a6495b98 | ||
|
|
ea1813a1b6 | ||
|
|
b1f0a10a55 | ||
|
|
1af16bfbef | ||
|
|
86dabea39f | ||
|
|
8d08b5f7c5 | ||
|
|
13c29c8296 | ||
|
|
eac5516a94 | ||
|
|
88b674f54c | ||
|
|
bcc24a2e25 | ||
|
|
e14f399e2d | ||
|
|
524e0941e4 | ||
|
|
ad2c575b39 | ||
|
|
57c2e7ee4e | ||
|
|
55b57a605e | ||
|
|
397c78b4af | ||
|
|
9e372c29d1 | ||
|
|
82fd7f01f8 | ||
|
|
a15b84a5bf | ||
|
|
5f46423638 | ||
|
|
8aebd36465 | ||
|
|
b4f864d89c | ||
|
|
bcea5a6b25 | ||
|
|
b2a256e2fc | ||
|
|
83c8db6867 | ||
|
|
96e2985aed | ||
|
|
5e544ff1f9 | ||
|
|
8c8db78da6 | ||
|
|
b18d9f53c6 | ||
|
|
7134d89352 | ||
|
|
5a7dc198dd | ||
|
|
7dbfcfdea2 | ||
|
|
b56ccd1bab | ||
|
|
f05828c87d | ||
|
|
48817f0fe4 | ||
|
|
3bed2581af | ||
|
|
acb125b2b9 | ||
|
|
72e5aedccd | ||
|
|
9148a750a5 | ||
|
|
d4600c5c83 | ||
|
|
8094b3fd22 | ||
|
|
bd55b9ce72 | ||
|
|
4726cb743a | ||
|
|
244d259804 | ||
|
|
eab94dde79 | ||
|
|
2bb921b592 | ||
|
|
18b58e75f8 | ||
|
|
e2cd3867dd | ||
|
|
524b9e9a08 | ||
|
|
4b35484abb | ||
|
|
d305dc3081 | ||
|
|
dde90a251a | ||
|
|
516cd72f66 | ||
|
|
63884e8518 | ||
|
|
8a4d436f1f | ||
|
|
ab279e2264 | ||
|
|
2876d75a21 | ||
|
|
aaa0ce4ecd | ||
|
|
499bc20850 | ||
|
|
2bed4986a1 | ||
|
|
151b081161 | ||
|
|
e3371b7620 | ||
|
|
551f948644 | ||
|
|
58c31937a2 | ||
|
|
01ac1f3f93 | ||
|
|
f93d11fe36 | ||
|
|
44a53f18d3 | ||
|
|
72218b643d | ||
|
|
4bd81782c8 | ||
|
|
4dbcec85bb | ||
|
|
d00a77e247 | ||
|
|
6e9f9d62a1 | ||
|
|
5a0b1caecd | ||
|
|
2b13151bd1 | ||
|
|
5e643e11fd | ||
|
|
2789b1cc37 | ||
|
|
b7a47eb91e | ||
|
|
06f6f19255 | ||
|
|
e717183239 | ||
|
|
edb87202d2 | ||
|
|
9b808058f5 | ||
|
|
89589aa907 | ||
|
|
805fea630c | ||
|
|
145f8adf0c | ||
|
|
6bb5ef7417 | ||
|
|
722726882b | ||
|
|
9ed30d7913 | ||
|
|
6c5c0c7c03 | ||
|
|
a57a7e099c | ||
|
|
f17289a104 | ||
|
|
e598fe5714 | ||
|
|
be534f940b | ||
|
|
782b474e54 | ||
|
|
d8471b41ca | ||
|
|
9484eebc70 | ||
|
|
22dae88b42 | ||
|
|
23066769a1 | ||
|
|
59ba585048 | ||
|
|
6c50cbf558 | ||
|
|
0085388446 | ||
|
|
18cd66a34b | ||
|
|
a090000826 | ||
|
|
af82261fba | ||
|
|
b15371bfce | ||
|
|
1e5bee2917 | ||
|
|
3819916241 | ||
|
|
e7c69fc089 | ||
|
|
994a577600 | ||
|
|
02167024b1 | ||
|
|
f110687e76 | ||
|
|
abeb60e237 | ||
|
|
4c8164954d | ||
|
|
7f13822f15 | ||
|
|
31bf696e7e | ||
|
|
f46d12c7b1 | ||
|
|
ad240a9a19 | ||
|
|
93107ec6a3 | ||
|
|
056eb7fdd5 | ||
|
|
bbe50ae0ff | ||
|
|
aae7a6e895 | ||
|
|
32b260ca9f | ||
|
|
0612a5834a | ||
|
|
055fbc1277 | ||
|
|
d0edca67c5 | ||
|
|
d558f94e40 | ||
|
|
1f8d50e788 | ||
|
|
260b3bfb1b | ||
|
|
d5e0ebee12 | ||
|
|
6d22ee9550 | ||
|
|
82096e0625 | ||
|
|
646566edd8 | ||
|
|
b26e1a082e | ||
|
|
deb8f811e5 | ||
|
|
0e90bbb905 | ||
|
|
58a91c15aa | ||
|
|
1daddbc905 | ||
|
|
b6af48fb3b | ||
|
|
3ff70b4598 | ||
|
|
b0079fca5c | ||
|
|
39250e5cb4 | ||
|
|
74132de8ed | ||
|
|
a6bbde2bed | ||
|
|
544eabfaa3 | ||
|
|
680ebc2e47 | ||
|
|
b0f0c0f33b | ||
|
|
c09fe554bc | ||
|
|
5c2ac2e037 | ||
|
|
793971c3a3 | ||
|
|
8ffd14c2fb | ||
|
|
da3d834a91 | ||
|
|
b48837e13c | ||
|
|
b44a320dc8 | ||
|
|
d2432f7cf7 | ||
|
|
7cf7536857 | ||
|
|
779cd1356a | ||
|
|
05dc220303 | ||
|
|
48f30f022d | ||
|
|
21c1fa7131 | ||
|
|
2475bf5a41 | ||
|
|
62154f5261 | ||
|
|
0e44b21361 | ||
|
|
f3d28551b1 | ||
|
|
1927630acb | ||
|
|
975fa91d36 | ||
|
|
7218ca2477 | ||
|
|
ee87d8ada8 | ||
|
|
8a381d8682 | ||
|
|
1fdfbac7b7 | ||
|
|
7fbc6ea4f3 | ||
|
|
7ddbc99add | ||
|
|
4abf907dc5 | ||
|
|
9ffc0360bd | ||
|
|
c4365c0193 | ||
|
|
1ea1e7918b | ||
|
|
815e779475 | ||
|
|
d9f506dd8f | ||
|
|
4377921d20 | ||
|
|
775a73fe54 | ||
|
|
96324f01d7 | ||
|
|
7d18310f30 | ||
|
|
3d584c84f2 | ||
|
|
f1c486bf9a | ||
|
|
a5224c966c | ||
|
|
9b19c71d95 | ||
|
|
36270ec55a | ||
|
|
94781bc1a9 | ||
|
|
93cde9bfdc | ||
|
|
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
|
||||
|
||||
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -5,7 +5,10 @@
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Default file owners.
|
||||
# * @bitwarden/tech-leads
|
||||
* @bitwarden/team-android @brian-livefront @david-livefront @dseverns-livefront @ahaisting-livefront @phil-livefront
|
||||
|
||||
# Actions and workflow changes.
|
||||
.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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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()
|
||||
289
.github/workflows/build-authenticator.yml
vendored
Normal file
289
.github/workflows/build-authenticator.yml
vendored
Normal file
@@ -0,0 +1,289 @@
|
||||
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
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Authenticator
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
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@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.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 checkAuthenticator
|
||||
|
||||
- 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@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.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@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.6.1
|
||||
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@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
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@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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 }} \
|
||||
207
.github/workflows/build.yml
vendored
207
.github/workflows/build.yml
vendored
@@ -28,21 +28,22 @@ on:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -52,7 +53,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -61,13 +62,13 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3a77c29278ae80936b4cb030fefc7d21c96c786f # v1.185.0
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -83,22 +84,29 @@ jobs:
|
||||
- name: Build
|
||||
run: bundle exec fastlane assembleDebugApks
|
||||
|
||||
- name: Upload test reports on failure
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: failure()
|
||||
with:
|
||||
name: test-reports
|
||||
path: app/build/reports/tests/
|
||||
|
||||
publish_playstore:
|
||||
name: Publish Play Store artifacts
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ["prod", "qa"]
|
||||
variant: ["prod", "dev"]
|
||||
artifact: ["apk", "aab"]
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3a77c29278ae80936b4cb030fefc7d21c96c786f # v1.185.0
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -149,10 +157,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@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -162,7 +170,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -171,11 +179,20 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
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))
|
||||
@@ -229,140 +246,142 @@ jobs:
|
||||
keyAlias:bitwarden-beta \
|
||||
keyPassword:${{ env.PLAY_BETA_KEY_PASSWORD }}
|
||||
|
||||
- name: Generate QA Play Store APKs
|
||||
- name: Generate debug Play Store APKs
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
run: |
|
||||
bundle exec fastlane assembleDebugApks
|
||||
|
||||
- name: Upload release Play Store .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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 other .apk artifact
|
||||
- name: Upload debug .apk artifact
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: com.x8bit.bitwarden-${{ matrix.variant }}.apk
|
||||
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.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" \
|
||||
> ./bw-android-apk-sha256.txt
|
||||
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" \
|
||||
> ./bw-android-beta-apk-sha256.txt
|
||||
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" \
|
||||
> ./bw-android-aab-sha256.txt
|
||||
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" \
|
||||
> ./bw-android-beta-aab-sha256.txt
|
||||
sha256sum "app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab" \
|
||||
> ./com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
|
||||
- name: Create checksum for other .apk artifact
|
||||
- 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" \
|
||||
> ./bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-android-apk-sha256.txt
|
||||
path: ./bw-android-apk-sha256.txt
|
||||
name: com.x8bit.bitwarden.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .apk SHA file for beta
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-android-beta-apk-sha256.txt
|
||||
path: ./bw-android-beta-apk-sha256.txt
|
||||
name: com.x8bit.bitwarden.beta.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .aab SHA file for release
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-android-aab-sha256.txt
|
||||
path: ./bw-android-aab-sha256.txt
|
||||
name: com.x8bit.bitwarden.aab-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.aab-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .aab SHA file for beta
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-android-beta-aab-sha256.txt
|
||||
path: ./bw-android-beta-aab-sha256.txt
|
||||
name: com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta.aab-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .apk SHA file for other
|
||||
- name: Upload .apk SHA file for debug
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install Firebase app distribution plugin
|
||||
if: ${{ matrix.variant == 'prod' && github.ref_name == 'main' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
if: ${{ matrix.variant == 'prod' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
run: bundle exec fastlane add_plugin firebase_app_distribution
|
||||
|
||||
- name: Publish release artifacts to Firebase
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'apk' && github.ref_name == 'main' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'apk' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
env:
|
||||
APP_PLAY_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json
|
||||
run: |
|
||||
bundle exec fastlane distributeReleasePlayStoreToFirebase \
|
||||
actionUrl:${{ env.GITHUB_ACTION_RUN_URL }} \
|
||||
service_credentials_file:${{ env.APP_PLAY_FIREBASE_CREDS_PATH }}
|
||||
|
||||
- name: Publish beta artifacts to Firebase
|
||||
if: ${{ (matrix.variant == 'prod' && matrix.artifact == 'apk') && github.ref_name == 'main' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
if: ${{ (matrix.variant == 'prod' && matrix.artifact == 'apk') && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
env:
|
||||
APP_PLAY_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json
|
||||
run: |
|
||||
bundle exec fastlane distributeBetaPlayStoreToFirebase \
|
||||
actionUrl:${{ env.GITHUB_ACTION_RUN_URL }} \
|
||||
service_credentials_file:${{ env.APP_PLAY_FIREBASE_CREDS_PATH }}
|
||||
|
||||
- name: Verify Play Store credentials
|
||||
@@ -372,19 +391,21 @@ jobs:
|
||||
|
||||
- name: Publish Play Store bundle
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && (inputs.publish-to-play-store || github.ref_name == 'main') }}
|
||||
run: bundle exec fastlane publishBetaToPlayStore
|
||||
run: |
|
||||
bundle exec fastlane publishProdToPlayStore
|
||||
bundle exec fastlane publishBetaToPlayStore
|
||||
|
||||
publish_fdroid:
|
||||
name: Publish F-Droid artifacts
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3a77c29278ae80936b4cb030fefc7d21c96c786f # v1.185.0
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -421,10 +442,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@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -434,7 +455,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -443,19 +464,35 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
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))
|
||||
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 }}
|
||||
@@ -466,7 +503,6 @@ jobs:
|
||||
keyAlias:bitwarden \
|
||||
keyPassword:"${{ env.FDROID_STORE_PASSWORD }}"
|
||||
|
||||
# Generate the F-Droid APK for publishing
|
||||
- name: Generate F-Droid Beta Artifacts
|
||||
env:
|
||||
FDROID_BETA_KEYSTORE_PASSWORD: ${{ secrets.FDROID_BETA_KEYSTORE_PASSWORD }}
|
||||
@@ -479,51 +515,52 @@ jobs:
|
||||
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
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" \
|
||||
> ./bw-fdroid-apk-sha256.txt
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-fdroid-apk-sha256.txt
|
||||
path: ./bw-fdroid-apk-sha256.txt
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid-beta.apk
|
||||
path: app/build/outputs/apk/fdroid/beta/com.x8bit.bitwarden-fdroid-beta.apk
|
||||
name: com.x8bit.bitwarden.beta-fdroid.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" \
|
||||
> ./bw-fdroid-beta-apk-sha256.txt
|
||||
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@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: bw-fdroid-beta-apk-sha256.txt
|
||||
path: ./bw-fdroid-beta-apk-sha256.txt
|
||||
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: ${{ github.ref_name == 'main' && inputs.distribute_to_firebase }}
|
||||
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: ${{ github.ref_name == 'main' && inputs.distribute_to_firebase }}
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
env:
|
||||
APP_FDROID_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json
|
||||
run: |
|
||||
bundle exec fastlane distributeReleaseFDroidToFirebase \
|
||||
actionUrl:${{ env.GITHUB_ACTION_RUN_URL }} \
|
||||
service_credentials_file:${{ env.APP_FDROID_FIREBASE_CREDS_PATH }}
|
||||
|
||||
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")
|
||||
56
.github/workflows/crowdin-pull-authenticator.yml
vendored
Normal file
56
.github/workflows/crowdin-pull-authenticator.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Crowdin Sync - Authenticator
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
name: Autosync
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "673718"
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Log in to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
|
||||
with:
|
||||
config: crowdin-bwa.yml
|
||||
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
|
||||
create_pull_request: true
|
||||
pull_request_title: "Autosync Crowdin Translations"
|
||||
pull_request_body: "Autosync the updated translations"
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
18
.github/workflows/crowdin-pull.yml
vendored
18
.github/workflows/crowdin-pull.yml
vendored
@@ -1,21 +1,20 @@
|
||||
---
|
||||
name: Crowdin Sync
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: { }
|
||||
inputs: {}
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
name: Autosync
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
@@ -29,10 +28,17 @@ jobs:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
|
||||
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
|
||||
30
.github/workflows/crowdin-push-authenticator.yml
vendored
Normal file
30
.github/workflows/crowdin-push-authenticator.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Crowdin Push - Authenticator
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
|
||||
jobs:
|
||||
crowdin-push:
|
||||
name: Crowdin Push
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "673718"
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Upload sources
|
||||
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_TOKEN }}
|
||||
with:
|
||||
config: crowdin-bwa.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
8
.github/workflows/crowdin-push.yml
vendored
8
.github/workflows/crowdin-push.yml
vendored
@@ -9,12 +9,12 @@ on:
|
||||
jobs:
|
||||
crowdin-push:
|
||||
name: Crowdin Push
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@cb79c773a3cfa27f31f25eb3f677781210c9ce3d # v1.6.1
|
||||
@@ -23,13 +23,13 @@ jobs:
|
||||
|
||||
- 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@61ac8b980551f674046220c3e104bddae2916ac5 # v2.0.0
|
||||
uses: crowdin/github-action@d1632879d4d4da358f2d040f79fa094571c9a649 # v2.5.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
|
||||
129
.github/workflows/github-release.yml
vendored
Normal file
129
.github/workflows/github-release.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: Create GitHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version-name:
|
||||
description: 'Version Name - E.g. "2024.11.1"'
|
||||
required: true
|
||||
type: string
|
||||
version-number:
|
||||
description: 'Version Number - E.g. "123456"'
|
||||
required: true
|
||||
type: string
|
||||
artifact-run-id:
|
||||
description: 'GitHub Action Run ID containing artifacts'
|
||||
required: true
|
||||
type: string
|
||||
draft:
|
||||
description: 'Create as draft release'
|
||||
type: boolean
|
||||
default: true
|
||||
prerelease:
|
||||
description: 'Mark as pre-release'
|
||||
type: boolean
|
||||
default: true
|
||||
make-latest:
|
||||
description: 'Set as the latest release'
|
||||
type: boolean
|
||||
branch-protection-type:
|
||||
description: 'Branch protection type'
|
||||
type: choice
|
||||
options:
|
||||
- Branch Name
|
||||
- GitHub API
|
||||
default: Branch Name
|
||||
env:
|
||||
ARTIFACTS_PATH: artifacts
|
||||
jobs:
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
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 }}
|
||||
BRANCH_PROTECTION_TYPE: ${{ inputs.branch-protection-type }}
|
||||
run: |
|
||||
release_branch=$(gh run view $ARTIFACT_RUN_ID --json headBranch -q .headBranch)
|
||||
|
||||
case "$BRANCH_PROTECTION_TYPE" in
|
||||
"Branch Name")
|
||||
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
|
||||
;;
|
||||
"GitHub API")
|
||||
#NOTE requires token with "administration:read" scope
|
||||
if ! gh api "repos/${{ github.repository }}/branches/$release_branch/protection" | grep -q "required_status_checks"; then
|
||||
echo "::error::Branch '$release_branch' is not protected. Releases must be created from protected branches. If that's not correct, confirm if the github token user has the 'administration:read' scope."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unsupported branch protection type: $BRANCH_PROTECTION_TYPE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "release_branch=$release_branch" >> $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: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
|
||||
with:
|
||||
tag_name: "v${{ inputs.version-name }}"
|
||||
name: "${{ inputs.version-name }} (${{ inputs.version-number }})"
|
||||
prerelease: ${{ inputs.prerelease }}
|
||||
draft: ${{ inputs.draft }}
|
||||
make_latest: ${{ inputs.make-latest }}
|
||||
target_commitish: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
artifacts/**/*
|
||||
|
||||
- name: Update Release Description
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_ID: ${{ steps.create_release.outputs.id }}
|
||||
RELEASE_URL: ${{ steps.create_release.outputs.url }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
run: |
|
||||
# Get current release body
|
||||
current_body=$(gh api /repos/${{ github.repository }}/releases/$RELEASE_ID --jq .body)
|
||||
|
||||
# Append build source to the end
|
||||
updated_body="${current_body}
|
||||
**Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID"
|
||||
|
||||
# Update release
|
||||
gh api --method PATCH /repos/${{ github.repository }}/releases/$RELEASE_ID \
|
||||
-f body="$updated_body"
|
||||
|
||||
echo "# :rocket: Release ready at:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$RELEASE_URL" >> $GITHUB_STEP_SUMMARY
|
||||
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
|
||||
78
.github/workflows/scan-authenticator.yml
vendored
Normal file
78
.github/workflows/scan-authenticator.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Scan Authenticator
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
|
||||
sast:
|
||||
name: SAST scan
|
||||
runs-on: ubuntu-24.04
|
||||
needs: check-run
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
|
||||
env:
|
||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||
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 . ${{ env.INCREMENTAL }}
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2
|
||||
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
|
||||
runs-on: ubuntu-24.04
|
||||
needs: check-run
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with SonarCloud
|
||||
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_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 }}
|
||||
63
.github/workflows/scan-ci.yml
vendored
Normal file
63
.github/workflows/scan-ci.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
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@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
|
||||
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@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
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
|
||||
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@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_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 }}
|
||||
25
.github/workflows/scan.yml
vendored
25
.github/workflows/scan.yml
vendored
@@ -1,12 +1,7 @@
|
||||
name: Scan
|
||||
name: Scan Pull Requests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
@@ -17,7 +12,7 @@ jobs:
|
||||
|
||||
sast:
|
||||
name: SAST scan
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: check-run
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -26,12 +21,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@92b6d52097badece63efe997ffe75207010bb80c # 2.0.29
|
||||
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
|
||||
env:
|
||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||
with:
|
||||
@@ -46,13 +41,15 @@ jobs:
|
||||
--output-path . ${{ env.INCREMENTAL }}
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3.28.1
|
||||
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
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: check-run
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -60,17 +57,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
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@e44258b109568baa0df60ed515909fc6c72cba92 # v2.3.0
|
||||
uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1
|
||||
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 }}
|
||||
|
||||
82
.github/workflows/test-authenticator.yml
vendored
Normal file
82
.github/workflows/test-authenticator.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Test Authenticator
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
|
||||
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
|
||||
packages: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
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@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Build and test Authenticator
|
||||
run: |
|
||||
bundle exec fastlane checkAuthenticator
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||
with:
|
||||
files: authenticator/build/reports/kover/reportDebug.xml
|
||||
89
.github/workflows/test.yml
vendored
89
.github/workflows/test.yml
vendored
@@ -6,40 +6,33 @@ on:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
type: [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-22.04
|
||||
needs: check-run
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
packages: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@0bdd871935719febd78681f197cd39af5b6e16a6 # v4.2.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -49,7 +42,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -58,15 +51,15 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3a77c29278ae80936b4cb030fefc7d21c96c786f # v1.185.0
|
||||
uses: ruby/setup-ruby@28c4deda893d5a96a6b2d958c5b47fc18d65c9d3 # v1.213.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
java-version: ${{ env._JAVA_VERSION }}
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
@@ -75,12 +68,58 @@ 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 to codecov.io
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: always()
|
||||
with:
|
||||
file: app/build/reports/kover/reportStandardDebug.xml
|
||||
name: test-reports
|
||||
path: |
|
||||
app/build/reports/tests/
|
||||
app/build/reports/kover/reportStandardDebug.xml
|
||||
|
||||
report:
|
||||
name: Process Test Reports
|
||||
needs: test
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
if: success()
|
||||
|
||||
steps:
|
||||
- name: Download test artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
with:
|
||||
name: test-reports
|
||||
|
||||
- name: Upload to codecov.io
|
||||
id: upload-to-codecov
|
||||
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
with:
|
||||
os: linux
|
||||
files: kover/reportStandardDebug.xml
|
||||
fail_ci_if_error: 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 [ ! -z "$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
|
||||
@@ -1 +0,0 @@
|
||||
shroud.reportKover 'App', 'app/build/reports/kover/reportStandardDebug.xml', 80, 80, false
|
||||
99
Gemfile.lock
99
Gemfile.lock
@@ -9,21 +9,22 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.947.0)
|
||||
aws-sdk-core (3.199.0)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1067.0)
|
||||
aws-sdk-core (3.220.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.87.0)
|
||||
aws-sdk-core (~> 3, >= 3.199.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.154.0)
|
||||
aws-sdk-core (~> 3, >= 3.199.0)
|
||||
aws-sdk-kms (1.99.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.182.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
@@ -32,15 +33,15 @@ GEM
|
||||
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)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.110.0)
|
||||
faraday (1.10.3)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -59,17 +60,17 @@ GEM
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.3.1)
|
||||
fastlane (2.221.1)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.227.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -85,6 +86,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 +110,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.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-plugin-firebase_app_distribution (0.9.1)
|
||||
fastlane-plugin-firebase_app_distribution (0.10.0)
|
||||
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 +138,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.0)
|
||||
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,35 +159,36 @@ GEM
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.6)
|
||||
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.8.2)
|
||||
json (2.10.2)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
mini_magick (4.13.1)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
nanaimo (0.3.0)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.2.1)
|
||||
nkf (0.2.0)
|
||||
optparse (0.5.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (6.0.0)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.1)
|
||||
rake (13.2.1)
|
||||
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.2.9)
|
||||
strscan
|
||||
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)
|
||||
addressable (~> 2.8)
|
||||
@@ -193,11 +198,11 @@ GEM
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
strscan (3.1.0)
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
time (0.3.0)
|
||||
time (0.4.1)
|
||||
date
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
@@ -205,17 +210,17 @@ GEM
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.24.0)
|
||||
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)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.0)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
|
||||
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.
|
||||
20
README.md
20
README.md
@@ -1,7 +1,4 @@
|
||||
# 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
|
||||
|
||||
@@ -11,8 +8,8 @@
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Minimum SDK**: 28
|
||||
- **Target SDK**: 34
|
||||
- **Minimum SDK**: 29
|
||||
- **Target SDK**: 35
|
||||
- **Device Types Supported**: Phone and Tablet
|
||||
- **Orientations Supported**: Portrait and Landscape
|
||||
|
||||
@@ -28,6 +25,7 @@
|
||||
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.
|
||||
- `localSdk`: A boolean value to determine if the SDK should be loaded from the local maven artifactory (ex: `localSdk=true`). This is particularly useful when developing new SDK capabilities. Review [Linking SDK to clients](https://contributing.bitwarden.com/getting-started/sdk/#linking-the-sdk-to-clients) for more details.
|
||||
|
||||
3. Setup the code style formatter:
|
||||
|
||||
@@ -134,6 +132,11 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- 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
|
||||
|
||||
- **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
|
||||
|
||||
- **Glide**
|
||||
- https://github.com/bumptech/glide
|
||||
@@ -170,6 +173,11 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: A networking layer interface.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Timber**
|
||||
- https://github.com/JakeWharton/timber
|
||||
- Purpose: Extensible logging library for Android.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **zxcvbn4j**
|
||||
- https://github.com/nulab/zxcvbn4j
|
||||
- Purpose: Password strength estimation.
|
||||
|
||||
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,7 +1,12 @@
|
||||
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)
|
||||
@@ -20,6 +25,26 @@ plugins {
|
||||
alias(libs.plugins.sonarqube)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads local user-specific build properties that are not checked into source control.
|
||||
*/
|
||||
val userProperties = Properties().apply {
|
||||
val buildPropertiesFile = File(rootDir, "user.properties")
|
||||
if (buildPropertiesFile.exists()) {
|
||||
FileInputStream(buildPropertiesFile).use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
@@ -29,7 +54,7 @@ android {
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "2024.06.00"
|
||||
versionName = "2024.9.0"
|
||||
|
||||
setProperty("archivesBaseName", "com.x8bit.bitwarden")
|
||||
|
||||
@@ -39,6 +64,12 @@ android {
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "CI_INFO",
|
||||
value = "${ciProperties.getOrDefault("ci.info", "\"local\"")}",
|
||||
)
|
||||
}
|
||||
|
||||
androidResources {
|
||||
@@ -61,20 +92,34 @@ android {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
|
||||
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "true")
|
||||
buildConfigField(type = "boolean", name = "HAS_LOGS_ENABLED", value = "true")
|
||||
}
|
||||
|
||||
// Beta and Release variants are identical except beta has a different package name
|
||||
create("beta") {
|
||||
initWith(buildTypes.getByName("release"))
|
||||
applicationIdSuffix = ".beta"
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
|
||||
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
|
||||
buildConfigField(type = "boolean", name = "HAS_LOGS_ENABLED", value = "false")
|
||||
}
|
||||
release {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
|
||||
buildConfigField(type = "boolean", name = "HAS_DEBUG_MENU", value = "false")
|
||||
buildConfigField(type = "boolean", name = "HAS_LOGS_ENABLED", value = "false")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,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())
|
||||
@@ -102,14 +180,16 @@ 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +199,22 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
if ((userProperties["localSdk"] as String?).toBoolean()) {
|
||||
substitute(module("com.bitwarden:sdk-android"))
|
||||
.using(module("com.bitwarden:sdk-android:LOCAL"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
fun standardImplementation(dependencyNotation: Any) {
|
||||
add("standardImplementation", dependencyNotation)
|
||||
}
|
||||
|
||||
implementation(files("libs/authenticatorbridge-1.0.0-release.aar"))
|
||||
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.autofill)
|
||||
@@ -165,6 +256,7 @@ dependencies {
|
||||
implementation(platform(libs.square.retrofit.bom))
|
||||
implementation(libs.square.retrofit)
|
||||
implementation(libs.square.retrofit.kotlinx.serialization)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.zxing.zxing.core)
|
||||
|
||||
// For now we are restricted to running Compose tests for debug builds only
|
||||
@@ -175,9 +267,12 @@ dependencies {
|
||||
standardImplementation(libs.google.firebase.cloud.messaging)
|
||||
standardImplementation(platform(libs.google.firebase.bom))
|
||||
standardImplementation(libs.google.firebase.crashlytics)
|
||||
standardImplementation(libs.google.play.review)
|
||||
|
||||
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)
|
||||
@@ -208,6 +303,7 @@ kover {
|
||||
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",
|
||||
)
|
||||
@@ -230,6 +326,7 @@ kover {
|
||||
"*_*Factory\$*",
|
||||
"*.Hilt_*",
|
||||
"*_HiltModules",
|
||||
"*_HiltModules*",
|
||||
"*_HiltModules\$*",
|
||||
"*_Impl",
|
||||
"*_Impl\$*",
|
||||
@@ -256,6 +353,10 @@ tasks {
|
||||
dependsOn("detekt")
|
||||
}
|
||||
|
||||
getByName("sonar") {
|
||||
dependsOn("check")
|
||||
}
|
||||
|
||||
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
|
||||
jvmTarget = libs.versions.jvmTarget.get()
|
||||
}
|
||||
@@ -268,15 +369,16 @@ tasks {
|
||||
maxHeapSize = "2g"
|
||||
maxParallelForks = Runtime.getRuntime().availableProcessors()
|
||||
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC"
|
||||
android.sourceSets["main"].res.srcDirs("src/test/res")
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
@@ -293,8 +395,17 @@ sonar {
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
getByName("sonar") {
|
||||
dependsOn("check")
|
||||
private fun renameFile(path: String, newName: String) {
|
||||
val originalFile = File(path)
|
||||
if (!originalFile.exists()) {
|
||||
println("File $originalFile does not exist!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val newFile = File(originalFile.parentFile, newName)
|
||||
if (originalFile.renameTo(newFile)) {
|
||||
println("Renamed $originalFile to $newFile")
|
||||
} else {
|
||||
throw RuntimeException("Failed to rename $originalFile to $newFile")
|
||||
}
|
||||
}
|
||||
|
||||
BIN
app/libs/authenticatorbridge-1.0.0-release.aar
Normal file
BIN
app/libs/authenticatorbridge-1.0.0-release.aar
Normal file
Binary file not shown.
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -6,6 +6,10 @@
|
||||
# we keep it here.
|
||||
-keep class com.bitwarden.** { *; }
|
||||
|
||||
# The Android Verifier component must be kept because it looks like dead code. Proguard is unable to
|
||||
# see any JNI usage, so our rules must manually opt into keeping it.
|
||||
-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; }
|
||||
|
||||
################################################################################
|
||||
# Bitwarden Models
|
||||
################################################################################
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "f7906c69e0a2c065d4d3be140fc721b6",
|
||||
"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 NOT NULL, 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": true
|
||||
}
|
||||
],
|
||||
"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 NOT NULL, PRIMARY KEY(`user_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domainsJson",
|
||||
"columnName": "domains_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"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, 'f7906c69e0a2c065d4d3be140fc721b6')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "ee697e71290c92fe5b607d0b7665481b",
|
||||
"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 NOT NULL, 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": true
|
||||
}
|
||||
],
|
||||
"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, '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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
9
app/src/beta/res/values/manifest.xml
Normal file
9
app/src/beta/res/values/manifest.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- For beta variant, we don't have a matching variant of the Bitwarden Authenticator app.
|
||||
Therefore, we leave the known app cert null here so that no clients can connect to
|
||||
AuthenticatorBridgeService in the beta variant. If later another variant of the
|
||||
Bitwarden Authenticator app is added, a SHA-256 digest of that variant's APK can be added here.
|
||||
-->
|
||||
<string-array name="known_authenticator_app_certs" />
|
||||
</resources>
|
||||
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>
|
||||
@@ -3,15 +3,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application tools:ignore="MissingApplicationIcon">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.x8bit.bitwarden.fido2.ACTION_CREATE_PASSKEY" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Disable Crashlytics for debug builds -->
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
|
||||
7
app/src/debug/res/values/manifest.xml
Normal file
7
app/src/debug/res/values/manifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="known_authenticator_app_certs">
|
||||
<!-- This is the SHA-256 digest for the Authenticator App debug variant:-->
|
||||
<item>13144ab52af797a88c2fe292674461ef1715e0e1e4f5f538f63f1c174696f476</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
|
||||
/**
|
||||
* CrashLogsManager implementation for F-droid flavor builds.
|
||||
*/
|
||||
class CrashLogsManagerImpl(
|
||||
settingsRepository: SettingsRepository,
|
||||
legacyAppCenterMigrator: LegacyAppCenterMigrator,
|
||||
) : CrashLogsManager {
|
||||
override var isEnabled: Boolean = true
|
||||
|
||||
override fun trackNonFatalException(e: Exception) = Unit
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* [LogsManager] implementation for F-droid flavor builds.
|
||||
*/
|
||||
class LogsManagerImpl(
|
||||
settingsRepository: SettingsRepository,
|
||||
legacyAppCenterMigrator: LegacyAppCenterMigrator,
|
||||
) : LogsManager {
|
||||
init {
|
||||
if (BuildConfig.HAS_LOGS_ENABLED) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
}
|
||||
|
||||
override var isEnabled: Boolean = false
|
||||
|
||||
override fun setUserData(userId: String?, environmentType: Environment.Type) = Unit
|
||||
|
||||
override fun trackNonFatalException(throwable: Throwable) = Unit
|
||||
}
|
||||
@@ -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,6 +15,20 @@
|
||||
<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.
|
||||
|
||||
This in effect means that the only application that can connect to the debug/release/etc
|
||||
variant AuthenticatorBridgeService is the debug/release/etc variant Bitwarden Authenticator
|
||||
app. -->
|
||||
<permission
|
||||
android:name="${applicationId}.permission.AUTHENTICATOR_BRIDGE_SERVICE"
|
||||
android:knownCerts="@array/known_authenticator_app_certs"
|
||||
android:label="Bitwarden Bridge"
|
||||
android:protectionLevel="signature|knownSigner"
|
||||
tools:targetApi="s" />
|
||||
|
||||
<application
|
||||
android:name=".BitwardenApplication"
|
||||
@@ -55,8 +69,49 @@
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
<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="vault.bitwarden.com" />
|
||||
<data android:host="vault.bitwarden.eu" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<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" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="otpauth" />
|
||||
<data android:host="totp" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="bitwarden" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".AccessibilityActivity"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<activity
|
||||
android:name=".AutofillTotpCopyActivity"
|
||||
android:exported="true"
|
||||
@@ -141,6 +196,47 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The AccessibilityService name below refers to the legacy Xamarin app's service name. This
|
||||
must always match in order for the app to properly query if it is providing accessibility
|
||||
services.
|
||||
-->
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.Accessibility.AccessibilityService"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
|
||||
tools:ignore="MissingClass">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service" />
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The CredentialProviderService name below refers to the legacy Xamarin app's service name.
|
||||
This must always match in order for the app to properly query if it is providing credential
|
||||
services.
|
||||
-->
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.Autofill.CredentialProviderService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="@string/bitwarden"
|
||||
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
|
||||
tools:ignore="MissingClass">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.credentials.CredentialProviderService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.credentials.provider"
|
||||
android:resource="@xml/provider" />
|
||||
</service>
|
||||
|
||||
<!-- This is required to support in-app language picker in Android 12 (API 32) and below -->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
@@ -151,16 +247,84 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The AutofillTileService name below refers to the legacy Xamarin app's service name.
|
||||
This must always match in order for the app to properly query if it is providing autofill
|
||||
tile services.
|
||||
-->
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.AutofillTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:label="@string/autofill"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
tools:ignore="MissingClass">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The GeneratorTileService name below refers to the legacy Xamarin app's service name.
|
||||
This must always match in order for the app to properly query if it is providing generator
|
||||
tile services.
|
||||
-->
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.GeneratorTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_generator"
|
||||
android:label="@string/password_generator"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
tools:ignore="MissingClass">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The MyVaultTileService name below refers to the legacy Xamarin app's service name.
|
||||
This must always match in order for the app to properly query if it is providing vault
|
||||
tile services.
|
||||
-->
|
||||
<!--suppress AndroidDomInspection -->
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.MyVaultTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_notification"
|
||||
android:label="@string/my_vault"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
tools:ignore="MissingClass">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.APP_RESTRICTIONS"
|
||||
android:resource="@xml/app_restrictions" />
|
||||
|
||||
<service
|
||||
android:name="com.x8bit.bitwarden.data.platform.service.AuthenticatorBridgeService"
|
||||
android:exported="true"
|
||||
android:permission="${applicationId}.permission.AUTHENTICATOR_BRIDGE_SERVICE" />
|
||||
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
</intent>
|
||||
<!-- To Query Chrome Beta: -->
|
||||
<package android:name="com.chrome.beta" />
|
||||
|
||||
<!-- To Query Chrome Stable: -->
|
||||
<package android:name="com.android.chrome" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
|
||||
90
app/src/main/assets/fido2_privileged_community.json
Normal file
90
app/src/main/assets/fido2_privileged_community.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"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": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.chromium.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A8:56:48:50:79:BC:B3:57:BF:BE:69:BA:19:A9:BA:43:CD:0A:D9:AB:22:67:52:C7:80:B6:88:8A:FD:48:21:6B"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.cromite.cromite",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "63:3F:A4:1D:82:11:D6:D0:91:6A:81:9B:89:66:8C:6D:E9:2E:64:23:2D:A6:7F:9D:16:FD:81:C3:B7:E9:23:FF"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
"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.fennec_fdroid",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "06:66:53:58:EF:D8:BA:05:BE:23:6A:47:A1:2C:B0:95:8D:7D:75:DD:93:9D:77:C2:B3:1F:53:98:53:7E:BD:C5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -475,7 +475,102 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.talonsec.talon",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A3:66:03:44:A6:F6:AF:CA:81:8C:BF:43:96:A2:3C:CF:D5:ED:7A:78:1B:B4:A3:D1:85:03:01:E2:F4:6D:23:83"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E2:A5:64:74:EA:23:7B:06:67:B6:F5:2C:DC:E9:04:5E:24:88:3B:AE:D0:82:59:9A:A2:DF:0B:60:3A:CF:6A:3B"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.talonsec.talon_beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "F5:86:62:7A:32:C8:9F:E6:7E:00:6D:B1:8C:34:31:9E:01:7F:B3:B2:BE:D6:9D:01:01:B7:F9:43:E7:7C:48:AE"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9A:A1:25:D5:E5:5E:3F:B0:DE:96:72:D9:A9:5D:04:65:3F:49:4A:1E:C3:EE:76:1E:94:C4:4E:5D:2F:65:8E:2F"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.duckduckgo.mobile.android.debug",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C4:F0:9E:2B:D7:25:AD:F5:AD:92:0B:A2:80:27:66:AC:16:4A:C1:53:B3:EA:9E:08:48:B0:57:98:37:F7:6A:29"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.duckduckgo.mobile.android",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "BB:7B:B3:1C:57:3C:46:A1:DA:7F:C5:C5:28:A6:AC:F4:32:10:84:56:FE:EC:50:81:0C:7F:33:69:4E:B3:D2:D4"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.naver.whale",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "0B:8B:85:23:BB:4A:EF:FA:34:6E:4B:DD:4F:BF:7D:19:34:50:56:9A:A1:4A:AA:D4:AD:FD:94:A3:F7:B2:27:BB"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.fido.fido2client",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "FC:98:DA:E6:3A:D3:96:26:C8:C6:7F:BE:83:F2:F0:6F:74:93:2A:9C:D1:46:B9:2C:EC:FC:6A:04:7A:90:43:86"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.heytap.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AF:F8:A7:49:CF:0E:7D:75:44:65:D0:FB:FA:7B:8D:0C:64:5E:22:5C:10:C6:E2:32:AD:A0:D9:74:88:36:B8:E5"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A8:FE:A4:CA:FB:93:32:DA:26:B8:E6:81:08:17:C1:DA:90:A5:03:0E:35:A6:0A:79:E0:6C:90:97:AA:C6:A4:42"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* An activity to be launched and then immediately closed so that the OS Shade can be collapsed
|
||||
* after the user clicks on the Autofill Quick Tile.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
class AccessibilityActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,62 @@ package com.x8bit.bitwarden
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.app.AppComponentFactory
|
||||
import com.x8bit.bitwarden.data.autofill.BitwardenAutofillService
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.BitwardenFido2ProviderService
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
||||
private const val LEGACY_AUTOFILL_SERVICE_NAME = "com.x8bit.bitwarden.Autofill.AutofillService"
|
||||
private const val LEGACY_CREDENTIAL_SERVICE_NAME =
|
||||
"com.x8bit.bitwarden.Autofill.CredentialProviderService"
|
||||
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService
|
||||
import com.x8bit.bitwarden.data.tiles.BitwardenGeneratorTileService
|
||||
import com.x8bit.bitwarden.data.tiles.BitwardenVaultTileService
|
||||
|
||||
/**
|
||||
* A factory class that allows us to intercept when a manifest element is being instantiated
|
||||
* and modify various characteristics before initialization.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Keep
|
||||
@OmitFromCoverage
|
||||
class BitwardenAppComponentFactory : AppComponentFactory() {
|
||||
/**
|
||||
* Used to intercept when the [BitwardenAutofillService] or [BitwardenFido2ProviderService] is
|
||||
* being instantiated and modify which service is created. This is required because the
|
||||
* [className] used in the manifest must match the legacy Xamarin app service name but the
|
||||
* service name in this app is different.
|
||||
* Used to intercept when certain legacy services are being instantiated and modify which
|
||||
* service is created. This is required because the [className] used in the manifest must match
|
||||
* the legacy Xamarin app service name but the service name in this app is different.
|
||||
*
|
||||
* Services currently being managed:
|
||||
* * [BitwardenAccessibilityService]
|
||||
* * [BitwardenAutofillService]
|
||||
* * [BitwardenAutofillTileService]
|
||||
* * [BitwardenFido2ProviderService]
|
||||
* * [BitwardenVaultTileService]
|
||||
* * [BitwardenGeneratorTileService]
|
||||
*/
|
||||
override fun instantiateServiceCompat(
|
||||
cl: ClassLoader,
|
||||
className: String,
|
||||
intent: Intent?,
|
||||
): Service = when (className) {
|
||||
LEGACY_ACCESSIBILITY_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(
|
||||
cl,
|
||||
BitwardenAccessibilityService::class.java.name,
|
||||
intent,
|
||||
)
|
||||
}
|
||||
|
||||
LEGACY_AUTOFILL_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(cl, BitwardenAutofillService::class.java.name, intent)
|
||||
}
|
||||
|
||||
LEGACY_AUTOFILL_TILE_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(
|
||||
cl,
|
||||
BitwardenAutofillTileService::class.java.name,
|
||||
intent,
|
||||
)
|
||||
}
|
||||
|
||||
LEGACY_CREDENTIAL_SERVICE_NAME -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
super.instantiateServiceCompat(
|
||||
@@ -48,6 +73,18 @@ class BitwardenAppComponentFactory : AppComponentFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
LEGACY_VAULT_TILE_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(cl, BitwardenVaultTileService::class.java.name, intent)
|
||||
}
|
||||
|
||||
LEGACY_GENERATOR_TILE_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(
|
||||
cl,
|
||||
BitwardenGeneratorTileService::class.java.name,
|
||||
intent,
|
||||
)
|
||||
}
|
||||
|
||||
else -> super.instantiateServiceCompat(cl, className, intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package com.x8bit.bitwarden
|
||||
import android.app.Application
|
||||
import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.LogsManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConfigManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import javax.inject.Inject
|
||||
@@ -19,10 +19,10 @@ class BitwardenApplication : Application() {
|
||||
// Inject classes here that must be triggered on startup but are not otherwise consumed by
|
||||
// other callers.
|
||||
@Inject
|
||||
lateinit var networkConfigManager: NetworkConfigManager
|
||||
lateinit var logsManager: LogsManager
|
||||
|
||||
@Inject
|
||||
lateinit var crashLogsManager: CrashLogsManager
|
||||
lateinit var networkConfigManager: NetworkConfigManager
|
||||
|
||||
@Inject
|
||||
lateinit var authRequestNotificationManager: AuthRequestNotificationManager
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
/**
|
||||
* The legacy name for the accessibility service.
|
||||
*/
|
||||
const val LEGACY_ACCESSIBILITY_SERVICE_NAME: String =
|
||||
"com.x8bit.bitwarden.Accessibility.AccessibilityService"
|
||||
|
||||
/**
|
||||
* The short form legacy name for the accessibility service.
|
||||
*/
|
||||
const val LEGACY_SHORT_ACCESSIBILITY_SERVICE_NAME: String = ".Accessibility.AccessibilityService"
|
||||
|
||||
/**
|
||||
* The legacy name for the autofill service.
|
||||
*/
|
||||
const val LEGACY_AUTOFILL_SERVICE_NAME: String = "com.x8bit.bitwarden.Autofill.AutofillService"
|
||||
|
||||
/**
|
||||
* The legacy name for the accessibility autofill tile service.
|
||||
*/
|
||||
const val LEGACY_AUTOFILL_TILE_SERVICE_NAME: String = "com.x8bit.bitwarden.AutofillTileService"
|
||||
|
||||
/**
|
||||
* The legacy name for the credential service.
|
||||
*/
|
||||
const val LEGACY_CREDENTIAL_SERVICE_NAME: String =
|
||||
"com.x8bit.bitwarden.Autofill.CredentialProviderService"
|
||||
|
||||
/**
|
||||
* The legacy name for the generator tile service.
|
||||
*/
|
||||
const val LEGACY_GENERATOR_TILE_SERVICE_NAME: String = "com.x8bit.bitwarden.GeneratorTileService"
|
||||
|
||||
/**
|
||||
* The legacy name for the vault tile service.
|
||||
*/
|
||||
const val LEGACY_VAULT_TILE_SERVICE_NAME: String = "com.x8bit.bitwarden.MyVaultTileService"
|
||||
@@ -1,32 +1,45 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
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.compose.runtime.remember
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
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.manager.util.ObserveScreenDataEffect
|
||||
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.rootnav.RootNavScreen
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.debugMenuDestination
|
||||
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.ROOT_ROUTE
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.rootNavDestination
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.util.appLanguage
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Primary entry point for the application.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@OmitFromCoverage
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@@ -39,16 +52,21 @@ class MainActivity : AppCompatActivity() {
|
||||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var accessibilityCompletionManager: AccessibilityCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var debugLaunchManager: DebugMenuLaunchManager
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
var shouldShowSplashScreen = true
|
||||
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
observeViewModelEvents()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mainViewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
@@ -57,21 +75,68 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
// Within the app the theme will change dynamically and will be managed by the
|
||||
// OS, but we need to ensure we properly set the values when upgrading from older versions
|
||||
// that handle this differently or when the activity restarts.
|
||||
AppCompatDelegate.setDefaultNightMode(settingsRepository.appTheme.osValue)
|
||||
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()
|
||||
}
|
||||
|
||||
is MainEvent.UpdateAppLocale -> {
|
||||
AppCompatDelegate.setApplicationLocales(
|
||||
LocaleListCompat.forLanguageTags(event.localeName),
|
||||
)
|
||||
}
|
||||
|
||||
is MainEvent.UpdateAppTheme -> {
|
||||
AppCompatDelegate.setDefaultNightMode(event.osTheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
|
||||
LocalManagerProvider {
|
||||
LocalManagerProvider(featureFlagsState = state.featureFlagsState) {
|
||||
ObserveScreenDataEffect(
|
||||
onDataUpdate = remember(mainViewModel) {
|
||||
{
|
||||
mainViewModel.trySendAction(
|
||||
MainAction.ResumeScreenDataReceived(it),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
BitwardenTheme(theme = state.theme) {
|
||||
RootNavScreen(
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = ROOT_ROUTE,
|
||||
) {
|
||||
// Nothing else should end up at this top level, we just want the ability
|
||||
// to have the debug menu appear on top of the rest of the app without
|
||||
// interacting with the state-based navigation used by the RootNavScreen.
|
||||
rootNavDestination { shouldShowSplashScreen = false }
|
||||
debugMenuDestination(
|
||||
onNavigateBack = { navController.popBackStack() },
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +151,31 @@ class MainActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// When the app resumes check for any app specific language which may have been
|
||||
// set via the device settings. Similar to the theme setting in onCreate this
|
||||
// ensures we properly set the values when upgrading from older versions
|
||||
// that handle this differently or when the activity restarts.
|
||||
val appSpecificLanguage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val locales: LocaleListCompat = AppCompatDelegate.getApplicationLocales()
|
||||
if (locales.isEmpty) {
|
||||
// App is using the system language
|
||||
null
|
||||
} else {
|
||||
// App has specific language settings
|
||||
locales.get(0)?.appLanguage
|
||||
}
|
||||
} else {
|
||||
// For older versions, use what ever language is available from the repository.
|
||||
settingsRepository.appLanguage
|
||||
}
|
||||
|
||||
appSpecificLanguage?.let {
|
||||
mainViewModel.trySendAction(MainAction.AppSpecificLanguageUpdate(it))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// In some scenarios on an emulator the Activity can leak when recreated
|
||||
@@ -93,16 +183,27 @@ class MainActivity : AppCompatActivity() {
|
||||
currentFocus?.clearFocus()
|
||||
}
|
||||
|
||||
private fun observeViewModelEvents() {
|
||||
mainViewModel
|
||||
.eventFlow
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
||||
MainEvent.Recreate -> handleRecreate()
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
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) {
|
||||
|
||||
@@ -5,36 +5,60 @@ import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
||||
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CreateCredentialRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2GetCredentialsRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.model.FeatureFlagsState
|
||||
import com.x8bit.bitwarden.ui.platform.util.isAccountSecurityShortcut
|
||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||
private const val ANIMATION_REFRESH_DELAY = 500L
|
||||
|
||||
/**
|
||||
* A view model that helps launch actions for the [MainActivity].
|
||||
@@ -42,19 +66,28 @@ private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
autofillSelectionManager: AutofillSelectionManager,
|
||||
featureFlagManager: FeatureFlagManager,
|
||||
private val addTotpItemFromAuthenticatorManager: AddTotpItemFromAuthenticatorManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val garbageCollectionManager: GarbageCollectionManager,
|
||||
private val fido2CredentialManager: Fido2CredentialManager,
|
||||
private val intentManager: IntentManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val appResumeManager: AppResumeManager,
|
||||
private val clock: Clock,
|
||||
) : BaseViewModel<MainState, MainEvent, MainAction>(
|
||||
initialState = MainState(
|
||||
theme = settingsRepository.appTheme,
|
||||
isScreenCaptureAllowed = settingsRepository.isScreenCaptureAllowed,
|
||||
isErrorReportingDialogEnabled = featureFlagManager.getFeatureFlag(
|
||||
key = FlagKey.MobileErrorReporting,
|
||||
),
|
||||
),
|
||||
) {
|
||||
private var specialCircumstance: SpecialCircumstance?
|
||||
@@ -72,6 +105,18 @@ class MainViewModel @Inject constructor(
|
||||
.onEach { specialCircumstance = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
featureFlagManager
|
||||
.getFeatureFlagFlow(key = FlagKey.MobileErrorReporting)
|
||||
.map { MainAction.Internal.OnMobileErrorReportingReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
accessibilitySelectionManager
|
||||
.accessibilitySelectionFlow
|
||||
.map { MainAction.Internal.AccessibilitySelectionReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
autofillSelectionManager
|
||||
.autofillSelectionFlow
|
||||
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
||||
@@ -81,6 +126,11 @@ class MainViewModel @Inject constructor(
|
||||
.appThemeStateFlow
|
||||
.onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) }
|
||||
.launchIn(viewModelScope)
|
||||
settingsRepository
|
||||
.appLanguageStateFlow
|
||||
.map { MainEvent.UpdateAppLocale(it.localeName) }
|
||||
.onEach(::sendEvent)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.isScreenCaptureAllowedStateFlow
|
||||
@@ -99,8 +149,7 @@ class MainViewModel @Inject constructor(
|
||||
// Switching between account states often involves some kind of animation (ex:
|
||||
// account switcher) that we might want to give time to finish before triggering
|
||||
// a refresh.
|
||||
@Suppress("MagicNumber")
|
||||
delay(500)
|
||||
delay(ANIMATION_REFRESH_DELAY)
|
||||
trySendAction(MainAction.Internal.CurrentUserStateChange)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
@@ -110,6 +159,9 @@ class MainViewModel @Inject constructor(
|
||||
.onEach {
|
||||
when (it) {
|
||||
is VaultStateEvent.Locked -> {
|
||||
// Similar to account switching, triggering this action too soon can
|
||||
// interfere with animations or navigation logic, so we will delay slightly.
|
||||
delay(ANIMATION_REFRESH_DELAY)
|
||||
trySendAction(MainAction.Internal.VaultUnlockStateChange)
|
||||
}
|
||||
|
||||
@@ -117,10 +169,38 @@ class MainViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
// On app launch, mark all active users as having previously logged in.
|
||||
// This covers any users who are active prior to this value being recorded.
|
||||
viewModelScope.launch {
|
||||
val userState = authRepository
|
||||
.userStateFlow
|
||||
.first()
|
||||
userState
|
||||
?.accounts
|
||||
?.forEach {
|
||||
settingsRepository.storeUserHasLoggedInValue(it.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: MainAction) {
|
||||
when (action) {
|
||||
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
|
||||
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
|
||||
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
|
||||
is MainAction.ResumeScreenDataReceived -> handleAppResumeDataUpdated(action)
|
||||
is MainAction.AppSpecificLanguageUpdate -> handleAppSpecificLanguageUpdate(action)
|
||||
is MainAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: MainAction.Internal) {
|
||||
when (action) {
|
||||
is MainAction.Internal.AccessibilitySelectionReceive -> {
|
||||
handleAccessibilitySelectionReceive(action)
|
||||
}
|
||||
|
||||
is MainAction.Internal.AutofillSelectionReceive -> {
|
||||
handleAutofillSelectionReceive(action)
|
||||
}
|
||||
@@ -129,14 +209,46 @@ class MainViewModel @Inject constructor(
|
||||
is MainAction.Internal.ScreenCaptureUpdate -> handleScreenCaptureUpdate(action)
|
||||
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
|
||||
is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange()
|
||||
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
|
||||
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
|
||||
is MainAction.Internal.OnMobileErrorReportingReceive -> {
|
||||
handleOnMobileErrorReportingReceive(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnMobileErrorReportingReceive(
|
||||
action: MainAction.Internal.OnMobileErrorReportingReceive,
|
||||
) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(isErrorReportingDialogEnabled = action.isErrorReportingEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAppSpecificLanguageUpdate(action: MainAction.AppSpecificLanguageUpdate) {
|
||||
settingsRepository.appLanguage = action.appLanguage
|
||||
}
|
||||
|
||||
private fun handleAppResumeDataUpdated(action: MainAction.ResumeScreenDataReceived) {
|
||||
when (val data = action.screenResumeData) {
|
||||
null -> appResumeManager.clearResumeScreen()
|
||||
else -> appResumeManager.setResumeScreen(data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOpenDebugMenu() {
|
||||
sendEvent(MainEvent.NavigateToDebugMenu)
|
||||
}
|
||||
|
||||
private fun handleAccessibilitySelectionReceive(
|
||||
action: MainAction.Internal.AccessibilitySelectionReceive,
|
||||
) {
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
sendEvent(MainEvent.CompleteAccessibilityAutofill(cipherView = action.cipherView))
|
||||
}
|
||||
|
||||
private fun handleAutofillSelectionReceive(
|
||||
action: MainAction.Internal.AutofillSelectionReceive,
|
||||
) {
|
||||
specialCircumstanceManager.specialCircumstance = null
|
||||
sendEvent(MainEvent.CompleteAutofill(cipherView = action.cipherView))
|
||||
}
|
||||
|
||||
@@ -150,6 +262,7 @@ class MainViewModel @Inject constructor(
|
||||
|
||||
private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
|
||||
mutableStateFlow.update { it.copy(theme = action.theme) }
|
||||
sendEvent(MainEvent.UpdateAppTheme(osTheme = action.theme.osValue))
|
||||
}
|
||||
|
||||
private fun handleVaultUnlockStateChange() {
|
||||
@@ -170,6 +283,7 @@ class MainViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun handleIntent(
|
||||
intent: Intent,
|
||||
isFirstIntent: Boolean,
|
||||
@@ -178,11 +292,39 @@ class MainViewModel @Inject constructor(
|
||||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
val totpData: TotpData? =
|
||||
// First grab TOTP URI directly from the intent data:
|
||||
intent.getTotpDataOrNull()
|
||||
?: run {
|
||||
// Then check to see if the intent is coming from the Authenticator app:
|
||||
if (intent.isAddTotpLoginItemFromAuthenticator()) {
|
||||
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData.also {
|
||||
// Clear pending add TOTP data so it is only handled once:
|
||||
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData = null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
||||
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
||||
val hasAccountSecurityShortcut = intent.isAccountSecurityShortcut
|
||||
val fido2CreateCredentialRequestData = intent.getFido2CreateCredentialRequestOrNull()
|
||||
val completeRegistrationData = intent.getCompleteRegistrationDataIntentOrNull()
|
||||
val fido2CredentialAssertionRequest = intent.getFido2AssertionRequestOrNull()
|
||||
val fido2GetCredentialsRequest = intent.getFido2GetCredentialsRequestOrNull()
|
||||
when {
|
||||
passwordlessRequestData != null -> {
|
||||
authRepository.activeUserId?.let {
|
||||
if (it != passwordlessRequestData.userId &&
|
||||
!vaultRepository.isVaultUnlocked(it)
|
||||
) {
|
||||
// We only switch the account here if the current user's vault is not
|
||||
// unlocked, otherwise prompt the user to allow us to change the account
|
||||
// in the LoginApprovalScreen
|
||||
authRepository.switchAccount(passwordlessRequestData.userId)
|
||||
}
|
||||
}
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = passwordlessRequestData,
|
||||
@@ -192,6 +334,10 @@ class MainViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
completeRegistrationData != null -> {
|
||||
handleCompleteRegistrationData(completeRegistrationData)
|
||||
}
|
||||
|
||||
autofillSaveItem != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSave(
|
||||
@@ -209,6 +355,11 @@ class MainViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
totpData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||
}
|
||||
|
||||
shareData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
@@ -219,24 +370,44 @@ class MainViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
fido2CredentialRequestData != null -> {
|
||||
fido2CreateCredentialRequestData != null -> {
|
||||
// Set the user's verification status when a new FIDO 2 request is received to force
|
||||
// explicit verification if the user's vault is unlocked when the request is
|
||||
// received.
|
||||
fido2CredentialManager.isUserVerified = false
|
||||
fido2CreateCredentialRequestData.isUserVerified
|
||||
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequestData,
|
||||
fido2CreateCredentialRequest = fido2CreateCredentialRequestData,
|
||||
)
|
||||
|
||||
// Switch accounts if the selected user is not the active user.
|
||||
if (authRepository.activeUserId != null &&
|
||||
authRepository.activeUserId != fido2CredentialRequestData.userId
|
||||
authRepository.activeUserId != fido2CreateCredentialRequestData.userId
|
||||
) {
|
||||
authRepository.switchAccount(fido2CredentialRequestData.userId)
|
||||
authRepository.switchAccount(fido2CreateCredentialRequestData.userId)
|
||||
}
|
||||
}
|
||||
|
||||
fido2CredentialAssertionRequest != null -> {
|
||||
// If device biometric verification was performed as part of single-tap
|
||||
// authentication, set the user's verification state to the device result.
|
||||
// Otherwise, retain the verification state as-is.
|
||||
fido2CredentialAssertionRequest.isUserVerified
|
||||
?.let { isVerified -> fido2CredentialManager.isUserVerified = isVerified }
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Assertion(
|
||||
fido2AssertionRequest = fido2CredentialAssertionRequest,
|
||||
)
|
||||
}
|
||||
|
||||
fido2GetCredentialsRequest != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2GetCredentials(
|
||||
fido2GetCredentialsRequest = fido2GetCredentialsRequest,
|
||||
)
|
||||
}
|
||||
|
||||
hasGeneratorShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.GeneratorShortcut
|
||||
@@ -245,6 +416,11 @@ class MainViewModel @Inject constructor(
|
||||
hasVaultShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.VaultShortcut
|
||||
}
|
||||
|
||||
hasAccountSecurityShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AccountSecurityShortcut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +428,49 @@ class MainViewModel @Inject constructor(
|
||||
sendEvent(MainEvent.Recreate)
|
||||
garbageCollectionManager.tryCollect()
|
||||
}
|
||||
|
||||
private fun handleCompleteRegistrationData(data: CompleteRegistrationData) {
|
||||
viewModelScope.launch {
|
||||
// Attempt to load the environment for the user if they have a pre-auth environment
|
||||
// saved.
|
||||
environmentRepository.loadEnvironmentForEmail(userEmail = data.email)
|
||||
// Determine if the token is still valid.
|
||||
val emailTokenResult = authRepository.validateEmailToken(
|
||||
email = data.email,
|
||||
token = data.verificationToken,
|
||||
)
|
||||
when (emailTokenResult) {
|
||||
is EmailTokenResult.Error -> {
|
||||
sendEvent(
|
||||
MainEvent.ShowToast(
|
||||
message = emailTokenResult
|
||||
.message
|
||||
?.asText()
|
||||
?: R.string.there_was_an_issue_validating_the_registration_token
|
||||
.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
EmailTokenResult.Expired -> {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance
|
||||
.RegistrationEvent
|
||||
.ExpiredRegistrationLink
|
||||
}
|
||||
|
||||
EmailTokenResult.Success -> {
|
||||
if (authRepository.activeUserId != null) {
|
||||
authRepository.hasPendingAccountAddition = true
|
||||
}
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.RegistrationEvent.CompleteRegistration(
|
||||
completeRegistrationData = data,
|
||||
timestamp = clock.millis(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,7 +480,16 @@ class MainViewModel @Inject constructor(
|
||||
data class MainState(
|
||||
val theme: AppTheme,
|
||||
val isScreenCaptureAllowed: Boolean,
|
||||
) : Parcelable
|
||||
private val isErrorReportingDialogEnabled: Boolean,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Contains all feature flags that are available to the UI.
|
||||
*/
|
||||
val featureFlagsState: FeatureFlagsState
|
||||
get() = FeatureFlagsState(
|
||||
isErrorReportingDialogEnabled = isErrorReportingDialogEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Models actions for the [MainActivity].
|
||||
@@ -277,10 +505,41 @@ sealed class MainAction {
|
||||
*/
|
||||
data class ReceiveNewIntent(val intent: Intent) : MainAction()
|
||||
|
||||
/**
|
||||
* Receive event to open the debug menu.
|
||||
*/
|
||||
data object OpenDebugMenu : MainAction()
|
||||
|
||||
/**
|
||||
* Receive event to save the app resume screen
|
||||
*/
|
||||
data class ResumeScreenDataReceived(val screenResumeData: AppResumeScreenData?) : MainAction()
|
||||
|
||||
/**
|
||||
* Receive if there is an app specific locale selection made by user
|
||||
* in the device's settings.
|
||||
*/
|
||||
data class AppSpecificLanguageUpdate(val appLanguage: AppLanguage) : MainAction()
|
||||
|
||||
/**
|
||||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
sealed class Internal : MainAction() {
|
||||
/**
|
||||
* Indicates the user has manually selected the given [cipherView] for accessibility
|
||||
* autofill.
|
||||
*/
|
||||
data class AccessibilitySelectionReceive(
|
||||
val cipherView: CipherView,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates the Mobile Error Reporting feature flag has been updated.
|
||||
*/
|
||||
data class OnMobileErrorReportingReceive(
|
||||
val isErrorReportingEnabled: Boolean,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates the user has manually selected the given [cipherView] for autofill.
|
||||
*/
|
||||
@@ -318,6 +577,12 @@ sealed class MainAction {
|
||||
* Represents events that are emitted by the [MainViewModel].
|
||||
*/
|
||||
sealed class MainEvent {
|
||||
/**
|
||||
* Event indicating that the user has chosen the given [cipherView] for accessibility autofill
|
||||
* and that the process is ready to complete.
|
||||
*/
|
||||
data class CompleteAccessibilityAutofill(val cipherView: CipherView) : MainEvent()
|
||||
|
||||
/**
|
||||
* Event indicating that the user has chosen the given [cipherView] for autofill and that the
|
||||
* process is ready to complete.
|
||||
@@ -328,4 +593,28 @@ sealed class MainEvent {
|
||||
* Event indicating that the UI should recreate itself.
|
||||
*/
|
||||
data object Recreate : MainEvent()
|
||||
|
||||
/**
|
||||
* Navigate to the debug menu.
|
||||
*/
|
||||
data object NavigateToDebugMenu : MainEvent()
|
||||
|
||||
/**
|
||||
* Show a toast with the given [message].
|
||||
*/
|
||||
data class ShowToast(val message: Text) : MainEvent()
|
||||
|
||||
/**
|
||||
* Indicates that the app language has been updated.
|
||||
*/
|
||||
data class UpdateAppLocale(
|
||||
val localeName: String?,
|
||||
) : MainEvent()
|
||||
|
||||
/**
|
||||
* Indicates that the app theme has been updated.
|
||||
*/
|
||||
data class UpdateAppTheme(
|
||||
val osTheme: Int,
|
||||
) : MainEvent()
|
||||
}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Primary access point for disk information.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface AuthDiskSource {
|
||||
|
||||
/**
|
||||
* The currently persisted authenticator sync symmetric key. This key is used for
|
||||
* encrypting IPC traffic.
|
||||
*/
|
||||
var authenticatorSyncSymmetricKey: ByteArray?
|
||||
|
||||
/**
|
||||
* Retrieves a unique ID for the application that is stored locally. This will generate a new
|
||||
* one if it does not yet exist and it will only be reset for new installs or when clearing
|
||||
@@ -45,12 +55,49 @@ interface AuthDiskSource {
|
||||
*/
|
||||
fun clearData(userId: String)
|
||||
|
||||
/**
|
||||
* Get the authenticator sync unlock key. Null means there is no key, which means the user
|
||||
* has not enabled authenticator syncing
|
||||
*/
|
||||
fun getAuthenticatorSyncUnlockKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Store the authenticator sync unlock key. Storing a null key effectively disables
|
||||
* authenticator syncing.
|
||||
*/
|
||||
fun storeAuthenticatorSyncUnlockKey(userId: String, authenticatorSyncUnlockKey: String?)
|
||||
|
||||
/**
|
||||
* Retrieves the state indicating that the user should use a key connector.
|
||||
*/
|
||||
fun getShouldUseKeyConnector(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Retrieves the state indicating that the user should use a key connector as a flow.
|
||||
*/
|
||||
fun getShouldUseKeyConnectorFlow(userId: String): Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Stores the boolean indicating that the user should use a key connector.
|
||||
*/
|
||||
fun storeShouldUseKeyConnector(userId: String, shouldUseKeyConnector: Boolean?)
|
||||
|
||||
/**
|
||||
* Retrieves the state indicating that the user has completed login with TDE.
|
||||
*/
|
||||
fun getIsTdeLoginComplete(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Stores the boolean indicating that the user has completed login with TDE.
|
||||
*/
|
||||
fun storeIsTdeLoginComplete(userId: String, isTdeLoginComplete: Boolean?)
|
||||
|
||||
/**
|
||||
* Retrieves the state indicating that the user has chosen to trust this device.
|
||||
*
|
||||
* Note: This indicates intent to trust the device, the device may not be trusted yet.
|
||||
*/
|
||||
fun getShouldTrustDevice(userId: String): Boolean
|
||||
fun getShouldTrustDevice(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Stores the boolean indicating that the user has chosen to trust this device for the given
|
||||
@@ -60,25 +107,6 @@ interface AuthDiskSource {
|
||||
*/
|
||||
fun storeShouldTrustDevice(userId: String, shouldTrustDevice: Boolean?)
|
||||
|
||||
/**
|
||||
* Retrieves the "last active time" for the given [userId], in milliseconds.
|
||||
*
|
||||
* This time is intended to be derived from a call to
|
||||
* [SystemClock.elapsedRealtime()](https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime())
|
||||
*/
|
||||
fun getLastActiveTimeMillis(userId: String): Long?
|
||||
|
||||
/**
|
||||
* Stores the [lastActiveTimeMillis] for the given [userId].
|
||||
*
|
||||
* This time is intended to be derived from a call to
|
||||
* [SystemClock.elapsedRealtime()](https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime())
|
||||
*/
|
||||
fun storeLastActiveTimeMillis(
|
||||
userId: String,
|
||||
lastActiveTimeMillis: Long?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves the number of consecutive invalid lock attempts for the given [userId].
|
||||
*/
|
||||
@@ -145,6 +173,16 @@ interface AuthDiskSource {
|
||||
pendingAuthRequest: PendingAuthRequestJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the biometrics initialization vector for the given [userId].
|
||||
*/
|
||||
fun getUserBiometricInitVector(userId: String): ByteArray?
|
||||
|
||||
/**
|
||||
* Stores the biometrics initialization vector for the given [userId].
|
||||
*/
|
||||
fun storeUserBiometricInitVector(userId: String, iv: ByteArray?)
|
||||
|
||||
/**
|
||||
* Gets the biometrics key for the given [userId].
|
||||
*/
|
||||
@@ -155,6 +193,11 @@ interface AuthDiskSource {
|
||||
*/
|
||||
fun storeUserBiometricUnlockKey(userId: String, biometricsKey: String?)
|
||||
|
||||
/**
|
||||
* Gets the flow for the biometrics key for the given [userId].
|
||||
*/
|
||||
fun getUserBiometicUnlockKeyFlow(userId: String): Flow<String?>
|
||||
|
||||
/**
|
||||
* Retrieves a pin-protected user key for the given [userId].
|
||||
*/
|
||||
@@ -172,6 +215,11 @@ interface AuthDiskSource {
|
||||
inMemoryOnly: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves a flow for the pin-protected user key for the given [userId].
|
||||
*/
|
||||
fun getPinProtectedUserKeyFlow(userId: String): Flow<String?>
|
||||
|
||||
/**
|
||||
* Gets a two-factor auth token using a user's [email].
|
||||
*/
|
||||
@@ -264,4 +312,55 @@ interface AuthDiskSource {
|
||||
* Stores the [accountTokens] for the given [userId].
|
||||
*/
|
||||
fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?)
|
||||
|
||||
/**
|
||||
* Gets the onboarding status for the given [userId].
|
||||
*/
|
||||
fun getOnboardingStatus(userId: String): OnboardingStatus?
|
||||
|
||||
/**
|
||||
* Stores the [onboardingStatus] for the given [userId].
|
||||
*/
|
||||
fun storeOnboardingStatus(userId: String, onboardingStatus: OnboardingStatus?)
|
||||
|
||||
/**
|
||||
* Emits updates that track [getOnboardingStatus]. This will replay the last known value,
|
||||
* if any exists.
|
||||
*/
|
||||
fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?>
|
||||
|
||||
/**
|
||||
* Gets the show import logins flag for the given [userId].
|
||||
*/
|
||||
fun getShowImportLogins(userId: String): Boolean?
|
||||
|
||||
/**
|
||||
* Stores the show import logins flag for the given [userId].
|
||||
*/
|
||||
fun storeShowImportLogins(userId: String, showImportLogins: Boolean?)
|
||||
|
||||
/**
|
||||
* Emits updates that track [getShowImportLogins]. This will replay the last known value.
|
||||
*/
|
||||
fun getShowImportLoginsFlow(userId: String): Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* Gets the new device notice state for the given [userId].
|
||||
*/
|
||||
fun getNewDeviceNoticeState(userId: String): NewDeviceNoticeState
|
||||
|
||||
/**
|
||||
* Stores the new device notice state for the given [userId].
|
||||
*/
|
||||
fun storeNewDeviceNoticeState(userId: String, newState: NewDeviceNoticeState?)
|
||||
|
||||
/**
|
||||
* Gets the last lock timestamp for the given [userId].
|
||||
*/
|
||||
fun getLastLockTimestamp(userId: String): Instant?
|
||||
|
||||
/**
|
||||
* Stores the last lock timestamp for the given [userId].
|
||||
*/
|
||||
fun storeLastLockTimestamp(userId: String, lastLockTimestamp: Instant?)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeDisplayStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.NewDeviceNoticeState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
|
||||
@@ -12,12 +15,15 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
// These keys should be encrypted
|
||||
private const val ACCOUNT_TOKENS_KEY = "accountTokens"
|
||||
private const val AUTHENTICATOR_SYNC_SYMMETRIC_KEY = "authenticatorSyncSymmetric"
|
||||
private const val AUTHENTICATOR_SYNC_UNLOCK_KEY = "authenticatorSyncUnlock"
|
||||
private const val BIOMETRICS_INIT_VECTOR_KEY = "biometricInitializationVector"
|
||||
private const val BIOMETRICS_UNLOCK_KEY = "userKeyBiometricUnlock"
|
||||
private const val USER_AUTO_UNLOCK_KEY_KEY = "userKeyAutoUnlock"
|
||||
private const val DEVICE_KEY_KEY = "deviceKey"
|
||||
@@ -28,7 +34,6 @@ private const val UNIQUE_APP_ID_KEY = "appId"
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "rememberedEmail"
|
||||
private const val REMEMBERED_ORG_IDENTIFIER_KEY = "rememberedOrgIdentifier"
|
||||
private const val STATE_KEY = "state"
|
||||
private const val LAST_ACTIVE_TIME_KEY = "lastActiveTime"
|
||||
private const val INVALID_UNLOCK_ATTEMPTS_KEY = "invalidUnlockAttempts"
|
||||
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey"
|
||||
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey"
|
||||
@@ -40,6 +45,12 @@ private const val TWO_FACTOR_TOKEN_KEY = "twoFactorToken"
|
||||
private const val MASTER_PASSWORD_HASH_KEY = "keyHash"
|
||||
private const val POLICIES_KEY = "policies"
|
||||
private const val SHOULD_TRUST_DEVICE_KEY = "shouldTrustDevice"
|
||||
private const val TDE_LOGIN_COMPLETE = "tdeLoginComplete"
|
||||
private const val USES_KEY_CONNECTOR = "usesKeyConnector"
|
||||
private const val ONBOARDING_STATUS_KEY = "onboardingStatus"
|
||||
private const val SHOW_IMPORT_LOGINS_KEY = "showImportLogins"
|
||||
private const val NEW_DEVICE_NOTICE_STATE = "newDeviceNoticeState"
|
||||
private const val LAST_LOCK_TIMESTAMP = "lastLockTimestamp"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
@@ -57,12 +68,21 @@ class AuthDiskSourceImpl(
|
||||
AuthDiskSource {
|
||||
|
||||
private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>()
|
||||
private val mutableShouldUseKeyConnectorFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
private val mutableOrganizationsFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?>>()
|
||||
private val mutablePoliciesFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<List<SyncResponseJson.Policy>?>>()
|
||||
private val mutableAccountTokensFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<AccountTokensJson?>>()
|
||||
private val mutableOnboardingStatusFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<OnboardingStatus?>>()
|
||||
private val mutableShowImportLoginsFlowMap = mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
private val mutableBiometricUnlockKeyFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<String?>>()
|
||||
private val mutablePinProtectedUserKeyFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<String?>>()
|
||||
private val mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
@@ -85,6 +105,14 @@ class AuthDiskSourceImpl(
|
||||
migrateAccountTokens()
|
||||
}
|
||||
|
||||
override var authenticatorSyncSymmetricKey: ByteArray?
|
||||
set(value) {
|
||||
val asString = value?.let { value.toString(Charsets.ISO_8859_1) }
|
||||
putEncryptedString(AUTHENTICATOR_SYNC_SYMMETRIC_KEY, asString)
|
||||
}
|
||||
get() = getEncryptedString(AUTHENTICATOR_SYNC_SYMMETRIC_KEY)
|
||||
?.toByteArray(Charsets.ISO_8859_1)
|
||||
|
||||
override val uniqueAppId: String
|
||||
get() = getString(key = UNIQUE_APP_ID_KEY) ?: generateAndStoreUniqueAppId()
|
||||
|
||||
@@ -111,7 +139,6 @@ class AuthDiskSourceImpl(
|
||||
.onSubscription { emit(userState) }
|
||||
|
||||
override fun clearData(userId: String) {
|
||||
storeLastActiveTimeMillis(userId = userId, lastActiveTimeMillis = null)
|
||||
storeInvalidUnlockAttempts(userId = userId, invalidUnlockAttempts = null)
|
||||
storeUserKey(userId = userId, userKey = null)
|
||||
storeUserAutoUnlockKey(userId = userId, userAutoUnlockKey = null)
|
||||
@@ -120,37 +147,67 @@ class AuthDiskSourceImpl(
|
||||
storePrivateKey(userId = userId, privateKey = null)
|
||||
storeOrganizationKeys(userId = userId, organizationKeys = null)
|
||||
storeOrganizations(userId = userId, organizations = null)
|
||||
storeUserBiometricInitVector(userId = userId, iv = null)
|
||||
storeUserBiometricUnlockKey(userId = userId, biometricsKey = null)
|
||||
storeMasterPasswordHash(userId = userId, passwordHash = null)
|
||||
storePolicies(userId = userId, policies = null)
|
||||
storeAccountTokens(userId = userId, accountTokens = null)
|
||||
storeShouldUseKeyConnector(userId = userId, shouldUseKeyConnector = null)
|
||||
storeIsTdeLoginComplete(userId = userId, isTdeLoginComplete = null)
|
||||
storeAuthenticatorSyncUnlockKey(userId = userId, authenticatorSyncUnlockKey = null)
|
||||
storeShowImportLogins(userId = userId, showImportLogins = null)
|
||||
storeLastLockTimestamp(userId = userId, lastLockTimestamp = null)
|
||||
|
||||
// Do not remove the DeviceKey or PendingAuthRequest on logout, these are persisted
|
||||
// indefinitely unless the TDE flow explicitly removes them.
|
||||
// Do not remove OnboardingStatus we want to keep track of this even after logout.
|
||||
}
|
||||
|
||||
override fun getShouldTrustDevice(userId: String): Boolean =
|
||||
requireNotNull(
|
||||
getBoolean(key = SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), default = false),
|
||||
override fun getAuthenticatorSyncUnlockKey(userId: String): String? =
|
||||
getEncryptedString(AUTHENTICATOR_SYNC_UNLOCK_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeAuthenticatorSyncUnlockKey(
|
||||
userId: String,
|
||||
authenticatorSyncUnlockKey: String?,
|
||||
) {
|
||||
putEncryptedString(
|
||||
key = AUTHENTICATOR_SYNC_UNLOCK_KEY.appendIdentifier(userId),
|
||||
value = authenticatorSyncUnlockKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getShouldUseKeyConnectorFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableShouldUseKeyConnectorFlowMap(userId = userId)
|
||||
.onSubscription { emit(getShouldUseKeyConnector(userId = userId)) }
|
||||
|
||||
override fun getShouldUseKeyConnector(
|
||||
userId: String,
|
||||
): Boolean? = getBoolean(key = USES_KEY_CONNECTOR.appendIdentifier(userId))
|
||||
|
||||
override fun storeShouldUseKeyConnector(userId: String, shouldUseKeyConnector: Boolean?) {
|
||||
putBoolean(
|
||||
key = USES_KEY_CONNECTOR.appendIdentifier(userId),
|
||||
value = shouldUseKeyConnector,
|
||||
)
|
||||
getMutableShouldUseKeyConnectorFlowMap(userId = userId).tryEmit(shouldUseKeyConnector)
|
||||
}
|
||||
|
||||
override fun getIsTdeLoginComplete(
|
||||
userId: String,
|
||||
): Boolean? = getBoolean(key = TDE_LOGIN_COMPLETE.appendIdentifier(userId))
|
||||
|
||||
override fun storeIsTdeLoginComplete(userId: String, isTdeLoginComplete: Boolean?) {
|
||||
putBoolean(TDE_LOGIN_COMPLETE.appendIdentifier(userId), isTdeLoginComplete)
|
||||
}
|
||||
|
||||
override fun getShouldTrustDevice(
|
||||
userId: String,
|
||||
): Boolean? = getBoolean(key = SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeShouldTrustDevice(userId: String, shouldTrustDevice: Boolean?) {
|
||||
putBoolean(SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), shouldTrustDevice)
|
||||
}
|
||||
|
||||
override fun getLastActiveTimeMillis(userId: String): Long? =
|
||||
getLong(key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeLastActiveTimeMillis(
|
||||
userId: String,
|
||||
lastActiveTimeMillis: Long?,
|
||||
) {
|
||||
putLong(
|
||||
key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId),
|
||||
value = lastActiveTimeMillis,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getInvalidUnlockAttempts(userId: String): Int? =
|
||||
getInt(key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId))
|
||||
|
||||
@@ -227,6 +284,17 @@ class AuthDiskSourceImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserBiometricInitVector(userId: String): ByteArray? =
|
||||
getEncryptedString(key = BIOMETRICS_INIT_VECTOR_KEY.appendIdentifier(userId))
|
||||
?.toByteArray(Charsets.ISO_8859_1)
|
||||
|
||||
override fun storeUserBiometricInitVector(userId: String, iv: ByteArray?) {
|
||||
putEncryptedString(
|
||||
key = BIOMETRICS_INIT_VECTOR_KEY.appendIdentifier(userId),
|
||||
value = iv?.toString(Charsets.ISO_8859_1),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserBiometricUnlockKey(userId: String): String? =
|
||||
getEncryptedString(key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId))
|
||||
|
||||
@@ -238,8 +306,13 @@ class AuthDiskSourceImpl(
|
||||
key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId),
|
||||
value = biometricsKey,
|
||||
)
|
||||
getMutableBiometricUnlockKeyFlow(userId).tryEmit(biometricsKey)
|
||||
}
|
||||
|
||||
override fun getUserBiometicUnlockKeyFlow(userId: String): Flow<String?> =
|
||||
getMutableBiometricUnlockKeyFlow(userId)
|
||||
.onSubscription { emit(getUserBiometricUnlockKey(userId = userId)) }
|
||||
|
||||
override fun getPinProtectedUserKey(userId: String): String? =
|
||||
inMemoryPinProtectedUserKeys[userId]
|
||||
?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId))
|
||||
@@ -255,8 +328,13 @@ class AuthDiskSourceImpl(
|
||||
key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId),
|
||||
value = pinProtectedUserKey,
|
||||
)
|
||||
getMutablePinProtectedUserKeyFlow(userId).tryEmit(pinProtectedUserKey)
|
||||
}
|
||||
|
||||
override fun getPinProtectedUserKeyFlow(userId: String): Flow<String?> =
|
||||
getMutablePinProtectedUserKeyFlow(userId)
|
||||
.onSubscription { emit(getPinProtectedUserKey(userId = userId)) }
|
||||
|
||||
override fun getTwoFactorToken(email: String): String? =
|
||||
getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email))
|
||||
|
||||
@@ -376,6 +454,70 @@ class AuthDiskSourceImpl(
|
||||
getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatus(userId: String): OnboardingStatus? {
|
||||
return getString(key = ONBOARDING_STATUS_KEY.appendIdentifier(userId))?.let {
|
||||
json.decodeFromStringOrNull(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeOnboardingStatus(userId: String, onboardingStatus: OnboardingStatus?) {
|
||||
putString(
|
||||
key = ONBOARDING_STATUS_KEY.appendIdentifier(userId),
|
||||
value = onboardingStatus?.let { json.encodeToString(it) },
|
||||
)
|
||||
getMutableOnboardingStatusFlow(userId = userId).tryEmit(onboardingStatus)
|
||||
}
|
||||
|
||||
override fun getOnboardingStatusFlow(userId: String): Flow<OnboardingStatus?> {
|
||||
return getMutableOnboardingStatusFlow(userId = userId)
|
||||
.onSubscription { emit(getOnboardingStatus(userId = userId)) }
|
||||
}
|
||||
|
||||
override fun getShowImportLogins(userId: String): Boolean? {
|
||||
return getBoolean(SHOW_IMPORT_LOGINS_KEY.appendIdentifier(userId))
|
||||
}
|
||||
|
||||
override fun storeShowImportLogins(userId: String, showImportLogins: Boolean?) {
|
||||
putBoolean(
|
||||
key = SHOW_IMPORT_LOGINS_KEY.appendIdentifier(userId),
|
||||
value = showImportLogins,
|
||||
)
|
||||
getMutableShowImportLoginsFlow(userId = userId).tryEmit(showImportLogins)
|
||||
}
|
||||
|
||||
override fun getShowImportLoginsFlow(userId: String): Flow<Boolean?> =
|
||||
getMutableShowImportLoginsFlow(userId)
|
||||
.onSubscription { emit(getShowImportLogins(userId)) }
|
||||
|
||||
override fun getNewDeviceNoticeState(userId: String): NewDeviceNoticeState {
|
||||
return getString(key = NEW_DEVICE_NOTICE_STATE.appendIdentifier(userId))?.let {
|
||||
json.decodeFromStringOrNull(it)
|
||||
} ?: NewDeviceNoticeState(
|
||||
displayStatus = NewDeviceNoticeDisplayStatus.HAS_NOT_SEEN,
|
||||
lastSeenDate = null,
|
||||
)
|
||||
}
|
||||
|
||||
override fun storeNewDeviceNoticeState(userId: String, newState: NewDeviceNoticeState?) {
|
||||
putString(
|
||||
key = NEW_DEVICE_NOTICE_STATE.appendIdentifier(userId),
|
||||
value = newState?.let { json.encodeToString(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun getLastLockTimestamp(userId: String): Instant? {
|
||||
return getLong(key = LAST_LOCK_TIMESTAMP.appendIdentifier(userId))?.let {
|
||||
Instant.ofEpochMilli(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeLastLockTimestamp(userId: String, lastLockTimestamp: Instant?) {
|
||||
putLong(
|
||||
key = LAST_LOCK_TIMESTAMP.appendIdentifier(userId),
|
||||
value = lastLockTimestamp?.toEpochMilli(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateAndStoreUniqueAppId(): String =
|
||||
UUID
|
||||
.randomUUID()
|
||||
@@ -384,6 +526,20 @@ class AuthDiskSourceImpl(
|
||||
putString(key = UNIQUE_APP_ID_KEY, value = it)
|
||||
}
|
||||
|
||||
private fun getMutableOnboardingStatusFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<OnboardingStatus?> =
|
||||
mutableOnboardingStatusFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableShouldUseKeyConnectorFlowMap(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> =
|
||||
mutableShouldUseKeyConnectorFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableOrganizationsFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?> =
|
||||
@@ -405,6 +561,24 @@ class AuthDiskSourceImpl(
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableShowImportLoginsFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Boolean?> = mutableShowImportLoginsFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableBiometricUnlockKeyFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<String?> = mutableBiometricUnlockKeyFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutablePinProtectedUserKeyFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<String?> = mutablePinProtectedUserKeyFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun migrateAccountTokens() {
|
||||
userState
|
||||
?.accounts
|
||||
|
||||
@@ -2,8 +2,12 @@ package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Represents the current account information for a given user.
|
||||
@@ -33,6 +37,7 @@ data class AccountJson(
|
||||
* @property userId The ID of the user.
|
||||
* @property email The user's email address.
|
||||
* @property isEmailVerified Whether or not the user's email is verified.
|
||||
* @property isTwoFactorEnabled If the profile has two factor authentication enabled.
|
||||
* @property name The user's name (if applicable).
|
||||
* @property stamp The account's security stamp (if applicable).
|
||||
* @property organizationId The ID of the associated organization (if applicable).
|
||||
@@ -44,7 +49,9 @@ data class AccountJson(
|
||||
* @property kdfMemory The amount of memory to use when calculating a password hash (MB).
|
||||
* @property kdfParallelism The number of threads to use when calculating a password hash.
|
||||
* @property userDecryptionOptions The options available to a user for decryption.
|
||||
* @property creationDate The creation date of the account.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class Profile(
|
||||
@SerialName("userId")
|
||||
@@ -56,6 +63,9 @@ data class AccountJson(
|
||||
@SerialName("emailVerified")
|
||||
val isEmailVerified: Boolean?,
|
||||
|
||||
@SerialName("isTwoFactorEnabled")
|
||||
val isTwoFactorEnabled: Boolean?,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
@@ -86,8 +96,13 @@ data class AccountJson(
|
||||
@SerialName("kdfParallelism")
|
||||
val kdfParallelism: Int?,
|
||||
|
||||
@SerialName("accountDecryptionOptions")
|
||||
@SerialName("userDecryptionOptions")
|
||||
@JsonNames("accountDecryptionOptions")
|
||||
val userDecryptionOptions: UserDecryptionOptionsJson?,
|
||||
|
||||
@SerialName("creationDate")
|
||||
@Contextual
|
||||
val creationDate: ZonedDateTime?,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import kotlinx.serialization.Serializable
|
||||
* Represents URLs for various Bitwarden domains.
|
||||
*
|
||||
* @property base The overall base URL.
|
||||
* @property keyUri A Uri containing the alias and host of the key used for mutual TLS.
|
||||
* @property api Separate base URL for the "/api" domain (if applicable).
|
||||
* @property identity Separate base URL for the "/identity" domain (if applicable).
|
||||
* @property icon Separate base URL for the icon domain (if applicable).
|
||||
@@ -19,6 +20,9 @@ data class EnvironmentUrlDataJson(
|
||||
@SerialName("base")
|
||||
val base: String,
|
||||
|
||||
@SerialName("keyUri")
|
||||
val keyUri: String? = null,
|
||||
|
||||
@SerialName("api")
|
||||
val api: String? = null,
|
||||
|
||||
@@ -37,6 +41,7 @@ data class EnvironmentUrlDataJson(
|
||||
@SerialName("events")
|
||||
val events: String? = null,
|
||||
) {
|
||||
@Suppress("UndocumentedPublicClass")
|
||||
companion object {
|
||||
/**
|
||||
* Default [EnvironmentUrlDataJson] for the US region.
|
||||
@@ -50,6 +55,7 @@ data class EnvironmentUrlDataJson(
|
||||
*/
|
||||
val DEFAULT_LEGACY_US: EnvironmentUrlDataJson = EnvironmentUrlDataJson(
|
||||
base = "https://vault.bitwarden.com",
|
||||
keyUri = null,
|
||||
api = "https://api.bitwarden.com",
|
||||
identity = "https://identity.bitwarden.com",
|
||||
icon = "https://icons.bitwarden.net",
|
||||
@@ -70,6 +76,7 @@ data class EnvironmentUrlDataJson(
|
||||
*/
|
||||
val DEFAULT_LEGACY_EU: EnvironmentUrlDataJson = EnvironmentUrlDataJson(
|
||||
base = "https://vault.bitwarden.eu",
|
||||
keyUri = null,
|
||||
api = "https://api.bitwarden.eu",
|
||||
identity = "https://identity.bitwarden.eu",
|
||||
icon = "https://icons.bitwarden.eu",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Describes the current display status of the new device notice screen.
|
||||
*/
|
||||
@Serializable
|
||||
enum class NewDeviceNoticeDisplayStatus {
|
||||
/**
|
||||
* The user has seen the screen and indicated they can access their email.
|
||||
*/
|
||||
@SerialName("canAccessEmail")
|
||||
CAN_ACCESS_EMAIL,
|
||||
|
||||
/**
|
||||
* The user has indicated they can access their email
|
||||
* as specified by the Permanent mode of the notice.
|
||||
*/
|
||||
@SerialName("canAccessEmailPermanent")
|
||||
CAN_ACCESS_EMAIL_PERMANENT,
|
||||
|
||||
/**
|
||||
* The user has not seen the screen.
|
||||
*/
|
||||
@SerialName("hasNotSeen")
|
||||
HAS_NOT_SEEN,
|
||||
|
||||
/**
|
||||
* The user has seen the screen and selected "remind me later".
|
||||
*/
|
||||
@SerialName("hasSeen")
|
||||
HAS_SEEN,
|
||||
}
|
||||
|
||||
/**
|
||||
* The state of the new device notice screen.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
@Serializable
|
||||
data class NewDeviceNoticeState(
|
||||
@SerialName("displayStatus")
|
||||
val displayStatus: NewDeviceNoticeDisplayStatus,
|
||||
|
||||
@SerialName("lastSeenDate")
|
||||
@Contextual
|
||||
val lastSeenDate: ZonedDateTime?,
|
||||
) {
|
||||
/**
|
||||
* Whether the [lastSeenDate] is at least 7 days old.
|
||||
*/
|
||||
val shouldDisplayNoticeIfSeen = lastSeenDate
|
||||
?.isBefore(
|
||||
ZonedDateTime.now().minusDays(7),
|
||||
)
|
||||
?: false
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Describes the current status of a user in the account onboarding steps.
|
||||
*/
|
||||
@Serializable
|
||||
enum class OnboardingStatus {
|
||||
|
||||
/**
|
||||
* Onboarding has not yet started.
|
||||
*/
|
||||
@SerialName("notStarted")
|
||||
NOT_STARTED,
|
||||
|
||||
/**
|
||||
* The user is completing the account lock setup.
|
||||
*/
|
||||
@SerialName("accountLockSetup")
|
||||
ACCOUNT_LOCK_SETUP,
|
||||
|
||||
/**
|
||||
* The user is completing the auto fill service setup.
|
||||
*/
|
||||
@SerialName("autofillSetup")
|
||||
AUTOFILL_SETUP,
|
||||
|
||||
/**
|
||||
* The user is completing the final step of the onboarding process.
|
||||
*/
|
||||
@SerialName("finalStep")
|
||||
FINAL_STEP,
|
||||
|
||||
/**
|
||||
* The user has completed all onboarding steps.
|
||||
*/
|
||||
@SerialName("complete")
|
||||
COMPLETE,
|
||||
}
|
||||
@@ -7,13 +7,21 @@ 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.
|
||||
* @property requestPrivateKey The private key of the pending Auth Request.
|
||||
* @property requestAccessCode The access code of the pending Auth Request.
|
||||
* @property requestFingerprint The fingerprint of the pending Auth Request.
|
||||
*/
|
||||
@Serializable
|
||||
data class PendingAuthRequestJson(
|
||||
@SerialName("Id")
|
||||
@SerialName("id")
|
||||
val requestId: String,
|
||||
|
||||
@SerialName("PrivateKey")
|
||||
@SerialName("privateKey")
|
||||
val requestPrivateKey: String,
|
||||
|
||||
@SerialName("accessCode")
|
||||
val requestAccessCode: String,
|
||||
|
||||
@SerialName("fingerprint")
|
||||
val requestFingerprint: String,
|
||||
)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /accounts API.
|
||||
*/
|
||||
interface AccountsApi {
|
||||
@POST("/accounts/password-hint")
|
||||
suspend fun passwordHintRequest(
|
||||
@Body body: PasswordHintRequestJson,
|
||||
): Result<Unit>
|
||||
|
||||
@POST("/two-factor/send-email-login")
|
||||
suspend fun resendVerificationCodeEmail(
|
||||
@Body body: ResendEmailRequestJson,
|
||||
): Result<Unit>
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountReque
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.HTTP
|
||||
import retrofit2.http.POST
|
||||
@@ -13,41 +14,48 @@ import retrofit2.http.POST
|
||||
* Defines raw calls under the /accounts API with authentication applied.
|
||||
*/
|
||||
interface AuthenticatedAccountsApi {
|
||||
|
||||
/**
|
||||
* Converts the currently active account to a key-connector account.
|
||||
*/
|
||||
@POST("/accounts/convert-to-key-connector")
|
||||
suspend fun convertToKeyConnector(): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Creates the keys for the current account.
|
||||
*/
|
||||
@POST("/accounts/keys")
|
||||
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): Result<Unit>
|
||||
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Deletes the current account.
|
||||
*/
|
||||
@HTTP(method = "DELETE", path = "/accounts", hasBody = true)
|
||||
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): Result<Unit>
|
||||
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): NetworkResult<Unit>
|
||||
|
||||
@POST("/accounts/request-otp")
|
||||
suspend fun requestOtp(): Result<Unit>
|
||||
suspend fun requestOtp(): NetworkResult<Unit>
|
||||
|
||||
@POST("/accounts/verify-otp")
|
||||
suspend fun verifyOtp(
|
||||
@Body body: VerifyOtpRequestJson,
|
||||
): Result<Unit>
|
||||
): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Resets the temporary password.
|
||||
*/
|
||||
@HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true)
|
||||
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
||||
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Resets the password.
|
||||
*/
|
||||
@HTTP(method = "POST", path = "/accounts/password", hasBody = true)
|
||||
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
||||
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Sets the password.
|
||||
*/
|
||||
@POST("/accounts/set-password")
|
||||
suspend fun setPassword(@Body body: SetPasswordRequestJson): Result<Unit>
|
||||
suspend fun setPassword(@Body body: SetPasswordRequestJson): NetworkResult<Unit>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
@@ -22,7 +23,7 @@ interface AuthenticatedAuthRequestsApi {
|
||||
suspend fun createAdminAuthRequest(
|
||||
@Header("Device-Identifier") deviceIdentifier: String,
|
||||
@Body body: AuthRequestRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Updates an authentication request.
|
||||
@@ -31,13 +32,13 @@ interface AuthenticatedAuthRequestsApi {
|
||||
suspend fun updateAuthRequest(
|
||||
@Path("id") userId: String,
|
||||
@Body body: AuthRequestUpdateRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Gets a list of auth requests for this device.
|
||||
*/
|
||||
@GET("/auth-requests")
|
||||
suspend fun getAuthRequests(): Result<AuthRequestsResponseJson>
|
||||
suspend fun getAuthRequests(): NetworkResult<AuthRequestsResponseJson>
|
||||
|
||||
/**
|
||||
* Retrieves an existing authentication request by ID.
|
||||
@@ -45,5 +46,5 @@ interface AuthenticatedAuthRequestsApi {
|
||||
@GET("/auth-requests/{requestId}")
|
||||
suspend fun getAuthRequest(
|
||||
@Path("requestId") requestId: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
@@ -16,5 +17,5 @@ interface AuthenticatedDevicesApi {
|
||||
suspend fun updateTrustedDeviceKeys(
|
||||
@Path(value = "appId") appId: String,
|
||||
@Body request: TrustedDeviceKeysRequestJson,
|
||||
): Result<TrustedDeviceKeysResponseJson>
|
||||
): NetworkResult<TrustedDeviceKeysResponseJson>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Url
|
||||
|
||||
/**
|
||||
* Defines raw calls specific for key connectors that use custom urls.
|
||||
*/
|
||||
@Keep
|
||||
interface AuthenticatedKeyConnectorApi {
|
||||
@POST
|
||||
suspend fun storeMasterKeyToKeyConnector(
|
||||
@Url url: String,
|
||||
@Body body: KeyConnectorMasterKeyRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.PUT
|
||||
@@ -20,7 +21,7 @@ interface AuthenticatedOrganizationApi {
|
||||
@Path("orgId") organizationId: String,
|
||||
@Path("userId") userId: String,
|
||||
@Body body: OrganizationResetPasswordEnrollRequestJson,
|
||||
): Result<Unit>
|
||||
): NetworkResult<Unit>
|
||||
|
||||
/**
|
||||
* Checks whether this organization auto enrolls users in password reset.
|
||||
@@ -28,7 +29,7 @@ interface AuthenticatedOrganizationApi {
|
||||
@GET("/organizations/{identifier}/auto-enroll-status")
|
||||
suspend fun getOrganizationAutoEnrollResponse(
|
||||
@Path("identifier") organizationIdentifier: String,
|
||||
): Result<OrganizationAutoEnrollStatusResponseJson>
|
||||
): NetworkResult<OrganizationAutoEnrollStatusResponseJson>
|
||||
|
||||
/**
|
||||
* Gets the public and private keys for this organization.
|
||||
@@ -36,5 +37,5 @@ interface AuthenticatedOrganizationApi {
|
||||
@GET("/organizations/{id}/keys")
|
||||
suspend fun getOrganizationKeys(
|
||||
@Path("id") organizationId: String,
|
||||
): Result<OrganizationKeysResponseJson>
|
||||
): NetworkResult<OrganizationKeysResponseJson>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
@@ -14,5 +15,5 @@ interface HaveIBeenPwnedApi {
|
||||
suspend fun fetchBreachedPasswords(
|
||||
@Path("hashPrefix")
|
||||
hashPrefix: String,
|
||||
): Result<ResponseBody>
|
||||
): NetworkResult<ResponseBody>
|
||||
}
|
||||
|
||||
@@ -1,19 +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 retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /organizations API.
|
||||
*/
|
||||
interface OrganizationApi {
|
||||
/**
|
||||
* Checks for the claimed domain organization of an email for SSO purposes.
|
||||
*/
|
||||
@POST("/organizations/domain/sso/details")
|
||||
suspend fun getClaimedDomainOrganizationDetails(
|
||||
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
||||
): Result<OrganizationDomainSsoDetailsResponseJson>
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendNewDeviceOtpRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /accounts API.
|
||||
*/
|
||||
interface UnauthenticatedAccountsApi {
|
||||
@POST("/accounts/password-hint")
|
||||
suspend fun passwordHintRequest(
|
||||
@Body body: PasswordHintRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
|
||||
@POST("/two-factor/send-email-login")
|
||||
suspend fun resendVerificationCodeEmail(
|
||||
@Body body: ResendEmailRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
|
||||
@POST("/accounts/set-key-connector-key")
|
||||
suspend fun setKeyConnectorKey(
|
||||
@Body body: KeyConnectorKeyRequestJson,
|
||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||
): NetworkResult<Unit>
|
||||
|
||||
@POST("/accounts/resend-new-device-otp")
|
||||
suspend fun resendNewDeviceOtp(
|
||||
@Body body: ResendNewDeviceOtpRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
@@ -21,7 +22,7 @@ interface UnauthenticatedAuthRequestsApi {
|
||||
suspend fun createAuthRequest(
|
||||
@Header("Device-Identifier") deviceIdentifier: String,
|
||||
@Body body: AuthRequestRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Queries for updates to a given auth request.
|
||||
@@ -30,5 +31,5 @@ interface UnauthenticatedAuthRequestsApi {
|
||||
suspend fun getAuthRequestUpdate(
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("code") accessCode: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
|
||||
@@ -11,5 +12,5 @@ interface UnauthenticatedDevicesApi {
|
||||
suspend fun getIsKnownDevice(
|
||||
@Header(value = "X-Request-Email") emailAddress: String,
|
||||
@Header(value = "X-Device-Identifier") deviceId: String,
|
||||
): Result<Boolean>
|
||||
): NetworkResult<Boolean>
|
||||
}
|
||||
|
||||
@@ -5,8 +5,13 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJso
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
@@ -19,7 +24,7 @@ import retrofit2.http.Query
|
||||
/**
|
||||
* Defines raw calls under the /identity API.
|
||||
*/
|
||||
interface IdentityApi {
|
||||
interface UnauthenticatedIdentityApi {
|
||||
|
||||
@POST("/connect/token")
|
||||
@Suppress("LongParameterList")
|
||||
@@ -42,12 +47,13 @@ interface IdentityApi {
|
||||
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
|
||||
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
|
||||
@Field(value = "authRequest") authRequestId: String?,
|
||||
): Result<GetTokenResponseJson.Success>
|
||||
@Field(value = "newDeviceOtp") newDeviceOtp: String?,
|
||||
): NetworkResult<GetTokenResponseJson.Success>
|
||||
|
||||
@GET("/sso/prevalidate")
|
||||
suspend fun prevalidateSso(
|
||||
@Query("domainHint") organizationIdentifier: String,
|
||||
): Result<PrevalidateSsoResponseJson>
|
||||
): NetworkResult<PrevalidateSsoResponseJson.Success>
|
||||
|
||||
/**
|
||||
* This call needs to be synchronous so we need it to return a [Call] directly. The identity
|
||||
@@ -62,8 +68,25 @@ interface IdentityApi {
|
||||
): Call<RefreshTokenResponseJson>
|
||||
|
||||
@POST("/accounts/prelogin")
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): NetworkResult<PreLoginResponseJson>
|
||||
|
||||
@POST("/accounts/register")
|
||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
||||
suspend fun register(
|
||||
@Body body: RegisterRequestJson,
|
||||
): NetworkResult<RegisterResponseJson.Success>
|
||||
|
||||
@POST("/accounts/register/finish")
|
||||
suspend fun registerFinish(
|
||||
@Body body: RegisterFinishRequestJson,
|
||||
): NetworkResult<RegisterResponseJson.Success>
|
||||
|
||||
@POST("/accounts/register/send-verification-email")
|
||||
suspend fun sendVerificationEmail(
|
||||
@Body body: SendVerificationEmailRequestJson,
|
||||
): NetworkResult<JsonPrimitive?>
|
||||
|
||||
@POST("/accounts/register/verification-email-clicked")
|
||||
suspend fun verifyEmailToken(
|
||||
@Body body: VerifyEmailTokenRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Url
|
||||
|
||||
/**
|
||||
* Defines raw calls specific for key connectors that use custom urls.
|
||||
*/
|
||||
@Keep
|
||||
interface UnauthenticatedKeyConnectorApi {
|
||||
@POST
|
||||
suspend fun storeMasterKeyToKeyConnector(
|
||||
@Url url: String,
|
||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||
@Body body: KeyConnectorMasterKeyRequestJson,
|
||||
): NetworkResult<Unit>
|
||||
|
||||
@GET
|
||||
suspend fun getMasterKeyFromKeyConnector(
|
||||
@Url url: String,
|
||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||
): NetworkResult<KeyConnectorMasterKeyResponseJson>
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||
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>
|
||||
|
||||
/**
|
||||
* Checks for the verfied organization domains of an email for SSO purposes.
|
||||
*/
|
||||
@POST("/organizations/domain/sso/verified")
|
||||
suspend fun getVerifiedOrganizationDomainsByEmail(
|
||||
@Body body: VerifiedOrganizationDomainSsoDetailsRequest,
|
||||
): NetworkResult<VerifiedOrganizationDomainSsoDetailsResponse>
|
||||
}
|
||||
@@ -36,8 +36,12 @@ object AuthNetworkModule {
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): AccountsService = AccountsServiceImpl(
|
||||
accountsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
unauthenticatedAccountsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
authenticatedAccountsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedKeyConnectorApi = retrofits.createStaticRetrofit().create(),
|
||||
authenticatedKeyConnectorApi = retrofits
|
||||
.createStaticRetrofit(isAuthenticated = true)
|
||||
.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@@ -64,7 +68,7 @@ object AuthNetworkModule {
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): IdentityService = IdentityServiceImpl(
|
||||
api = retrofits.unauthenticatedIdentityRetrofit.create(),
|
||||
unauthenticatedIdentityApi = retrofits.unauthenticatedIdentityRetrofit.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@@ -73,10 +77,8 @@ object AuthNetworkModule {
|
||||
fun providesHaveIBeenPwnedService(
|
||||
retrofits: Retrofits,
|
||||
): HaveIBeenPwnedService = HaveIBeenPwnedServiceImpl(
|
||||
retrofits
|
||||
.staticRetrofitBuilder
|
||||
.baseUrl("https://api.pwnedpasswords.com")
|
||||
.build()
|
||||
api = retrofits
|
||||
.createStaticRetrofit(baseUrl = "https://api.pwnedpasswords.com")
|
||||
.create(),
|
||||
)
|
||||
|
||||
@@ -95,6 +97,6 @@ object AuthNetworkModule {
|
||||
retrofits: Retrofits,
|
||||
): OrganizationService = OrganizationServiceImpl(
|
||||
authenticatedOrganizationApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
organizationApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
unauthenticatedOrganizationApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,5 +21,7 @@ enum class AuthRequestTypeJson {
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class AuthRequestTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<AuthRequestTypeJson>(AuthRequestTypeJson.entries.toTypedArray())
|
||||
private class AuthRequestTypeSerializer : BaseEnumeratedIntSerializer<AuthRequestTypeJson>(
|
||||
className = "AuthRequestTypeJson",
|
||||
values = AuthRequestTypeJson.entries.toTypedArray(),
|
||||
)
|
||||
|
||||
@@ -23,6 +23,15 @@ sealed class DeleteAccountResponseJson {
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String?>>?,
|
||||
) : DeleteAccountResponseJson()
|
||||
private val validationErrors: Map<String, List<String?>>?,
|
||||
) : DeleteAccountResponseJson() {
|
||||
/**
|
||||
* A human readable error message.
|
||||
*/
|
||||
val message: String?
|
||||
get() = validationErrors
|
||||
?.values
|
||||
?.firstOrNull()
|
||||
?.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
/**
|
||||
@@ -28,6 +30,7 @@ sealed class GetTokenResponseJson {
|
||||
* this token will be cached and used for future auth requests.
|
||||
* @property masterPasswordPolicyOptions The options available for a user's master password.
|
||||
* @property userDecryptionOptions The options available to a user for decryption.
|
||||
* @property keyConnectorUrl URL to the user's key connector.
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
@@ -75,6 +78,9 @@ sealed class GetTokenResponseJson {
|
||||
|
||||
@SerialName("UserDecryptionOptions")
|
||||
val userDecryptionOptions: UserDecryptionOptionsJson?,
|
||||
|
||||
@SerialName("KeyConnectorUrl")
|
||||
val keyConnectorUrl: String?,
|
||||
) : GetTokenResponseJson()
|
||||
|
||||
/**
|
||||
@@ -88,18 +94,53 @@ sealed class GetTokenResponseJson {
|
||||
|
||||
/**
|
||||
* Models json body of an invalid request.
|
||||
*
|
||||
* This model supports older versions of the error response model that used lower-case keys.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@JsonNames("errorModel")
|
||||
@SerialName("ErrorModel")
|
||||
val errorModel: ErrorModel,
|
||||
private val errorModel: ErrorModel?,
|
||||
) : GetTokenResponseJson() {
|
||||
|
||||
/**
|
||||
* The error message returned from the server, or null.
|
||||
*/
|
||||
val errorMessage: String? get() = errorModel?.errorMessage
|
||||
|
||||
/**
|
||||
* The type of invalid responses that can be received.
|
||||
*/
|
||||
sealed class InvalidType {
|
||||
/**
|
||||
* Represents an invalid response indicating that a new device verification is required.
|
||||
*/
|
||||
data object NewDeviceVerification : InvalidType()
|
||||
|
||||
/**
|
||||
* Represents generic invalid response
|
||||
*/
|
||||
data object GenericInvalid : InvalidType()
|
||||
}
|
||||
|
||||
val invalidType: InvalidType
|
||||
get() = if (errorMessage?.lowercase() == "new device verification required") {
|
||||
InvalidType.NewDeviceVerification
|
||||
} else {
|
||||
InvalidType.GenericInvalid
|
||||
}
|
||||
|
||||
/**
|
||||
* The error body of an invalid request containing a message.
|
||||
*
|
||||
* This model supports older versions of the error response model that used lower-case
|
||||
* keys.
|
||||
*/
|
||||
@Serializable
|
||||
data class ErrorModel(
|
||||
@JsonNames("message")
|
||||
@SerialName("Message")
|
||||
val errorMessage: String,
|
||||
)
|
||||
|
||||
@@ -18,5 +18,7 @@ enum class KdfTypeJson {
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class KdfTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<KdfTypeJson>(KdfTypeJson.entries.toTypedArray())
|
||||
private class KdfTypeSerializer : BaseEnumeratedIntSerializer<KdfTypeJson>(
|
||||
className = "KdfTypeJson",
|
||||
values = KdfTypeJson.entries.toTypedArray(),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the request body used to create the key connector keys for an account.
|
||||
*/
|
||||
@Serializable
|
||||
data class KeyConnectorKeyRequestJson(
|
||||
@SerialName("key") val userKey: String,
|
||||
@SerialName("keys") val keys: Keys,
|
||||
@SerialName("kdf") val kdfType: KdfTypeJson,
|
||||
@SerialName("kdfIterations") val kdfIterations: Int?,
|
||||
@SerialName("kdfMemory") val kdfMemory: Int?,
|
||||
@SerialName("kdfParallelism") val kdfParallelism: Int?,
|
||||
@SerialName("orgIdentifier") val organizationIdentifier: String,
|
||||
) {
|
||||
/**
|
||||
* A keys object containing public and private keys.
|
||||
*
|
||||
* @param publicKey the public key (encrypted).
|
||||
* @param encryptedPrivateKey the private key (encrypted).
|
||||
*/
|
||||
@Serializable
|
||||
data class Keys(
|
||||
@SerialName("publicKey")
|
||||
val publicKey: String,
|
||||
|
||||
@SerialName("encryptedPrivateKey")
|
||||
val encryptedPrivateKey: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the request body used to store the master key in the cloud.
|
||||
*/
|
||||
@Serializable
|
||||
data class KeyConnectorMasterKeyRequestJson(
|
||||
@SerialName("Key") val masterKey: String,
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the response body used to retrieve the master key from the cloud.
|
||||
*/
|
||||
@Serializable
|
||||
data class KeyConnectorMasterKeyResponseJson(
|
||||
@SerialName("key") val masterKey: String,
|
||||
)
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* Decryption options related to a user's key connector.
|
||||
*
|
||||
* @property keyConnectorUrl URL to the user's key connector.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class KeyConnectorUserDecryptionOptionsJson(
|
||||
@SerialName("KeyConnectorUrl")
|
||||
@SerialName("keyConnectorUrl")
|
||||
@JsonNames("KeyConnectorUrl")
|
||||
val keyConnectorUrl: String,
|
||||
)
|
||||
|
||||
@@ -9,17 +9,18 @@ import java.time.ZonedDateTime
|
||||
* Response object returned when requesting organization domain SSO details.
|
||||
*
|
||||
* @property isSsoAvailable Whether or not SSO is available for this domain.
|
||||
* @property domainName The organization's domain name.
|
||||
* @property organizationIdentifier The organization's identifier.
|
||||
* @property isSsoRequired Whether or not SSO is required.
|
||||
* @property verifiedDate The date these details were verified.
|
||||
* @property verifiedDate The date the domain was verified.
|
||||
*/
|
||||
@Serializable
|
||||
data class OrganizationDomainSsoDetailsResponseJson(
|
||||
@SerialName("ssoAvailable") val isSsoAvailable: Boolean,
|
||||
@SerialName("domainName") val domainName: String,
|
||||
@SerialName("organizationIdentifier") val organizationIdentifier: String,
|
||||
@SerialName("ssoRequired") val isSsoRequired: Boolean,
|
||||
@SerialName("ssoAvailable")
|
||||
val isSsoAvailable: Boolean,
|
||||
|
||||
@SerialName("organizationIdentifier")
|
||||
val organizationIdentifier: String,
|
||||
|
||||
@SerialName("verifiedDate")
|
||||
@Contextual
|
||||
@SerialName("verifiedDate") val verifiedDate: ZonedDateTime?,
|
||||
val verifiedDate: ZonedDateTime?,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,20 @@ import kotlinx.serialization.Serializable
|
||||
* Response body from the SSO prevalidate request.
|
||||
*/
|
||||
@Serializable
|
||||
data class PrevalidateSsoResponseJson(
|
||||
@SerialName("token") val token: String?,
|
||||
)
|
||||
sealed class PrevalidateSsoResponseJson {
|
||||
/**
|
||||
* Models json body of a successful response.
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
@SerialName("token") val token: String?,
|
||||
) : PrevalidateSsoResponseJson()
|
||||
|
||||
/**
|
||||
* Models json body of an error response.
|
||||
*/
|
||||
@Serializable
|
||||
data class Error(
|
||||
@SerialName("message") val message: String?,
|
||||
) : PrevalidateSsoResponseJson()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson.Keys
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body for register.
|
||||
*
|
||||
* @param email the email to be registered.
|
||||
* @param emailVerificationToken token used to finish the registration process.
|
||||
* @param masterPasswordHash the master password (encrypted).
|
||||
* @param masterPasswordHint the hint for the master password (nullable).
|
||||
* @param captchaResponse the captcha bypass token.
|
||||
* @param userSymmetricKey the user key for the request (encrypted).
|
||||
* @param userAsymmetricKeys a [Keys] object containing public and private keys.
|
||||
* @param kdfType the kdf type represented as an [Int].
|
||||
* @param kdfIterations the number of kdf iterations.
|
||||
*/
|
||||
@Serializable
|
||||
data class RegisterFinishRequestJson(
|
||||
@SerialName("email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("emailVerificationToken")
|
||||
val emailVerificationToken: String,
|
||||
|
||||
@SerialName("masterPasswordHash")
|
||||
val masterPasswordHash: String,
|
||||
|
||||
@SerialName("masterPasswordHint")
|
||||
val masterPasswordHint: String?,
|
||||
|
||||
@SerialName("captchaResponse")
|
||||
val captchaResponse: String?,
|
||||
|
||||
@SerialName("userSymmetricKey")
|
||||
val userSymmetricKey: String,
|
||||
|
||||
@SerialName("userAsymmetricKeys")
|
||||
val userAsymmetricKeys: Keys,
|
||||
|
||||
@SerialName("kdf")
|
||||
val kdfType: KdfTypeJson,
|
||||
|
||||
@SerialName("kdfIterations")
|
||||
val kdfIterations: UInt,
|
||||
) {
|
||||
|
||||
/**
|
||||
* A keys object containing public and private keys.
|
||||
*
|
||||
* @param publicKey the public key (encrypted).
|
||||
* @param encryptedPrivateKey the private key (encrypted).
|
||||
*/
|
||||
@Serializable
|
||||
data class Keys(
|
||||
@SerialName("publicKey")
|
||||
val publicKey: String,
|
||||
|
||||
@SerialName("encryptedPrivateKey")
|
||||
val encryptedPrivateKey: String,
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* Models response bodies for the register request.
|
||||
@@ -46,26 +48,28 @@ sealed class RegisterResponseJson {
|
||||
/**
|
||||
* Represents the json body of an invalid register request.
|
||||
*
|
||||
* @param message
|
||||
* @param validationErrors a map where each value is a list of error messages for each key.
|
||||
* The values in the array should be used for display to the user, since the keys tend to come
|
||||
* back as nonsense. (eg: empty string key)
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("message")
|
||||
val message: String?,
|
||||
@JsonNames("message")
|
||||
@SerialName("Message")
|
||||
private val invalidMessage: String? = null,
|
||||
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String>>?,
|
||||
) : RegisterResponseJson()
|
||||
|
||||
/**
|
||||
* A different register error with a message.
|
||||
*/
|
||||
@Serializable
|
||||
data class Error(
|
||||
@SerialName("Message")
|
||||
val message: String?,
|
||||
) : RegisterResponseJson()
|
||||
private val validationErrors: Map<String, List<String>>?,
|
||||
) : RegisterResponseJson() {
|
||||
/**
|
||||
* A generic error message.
|
||||
*/
|
||||
val message: String?
|
||||
get() = validationErrors
|
||||
?.values
|
||||
?.firstOrNull()
|
||||
?.firstOrNull()
|
||||
?: invalidMessage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Hold the information necessary to resend the email with the
|
||||
* new device verification code.
|
||||
*
|
||||
* @property email The user's email address.
|
||||
* @property passwordHash The master password hash
|
||||
*/
|
||||
@Serializable
|
||||
data class ResendNewDeviceOtpRequestJson(
|
||||
@SerialName("Email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("MasterPasswordHash")
|
||||
val passwordHash: String?,
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body for send verification email.
|
||||
*
|
||||
* @param email the email to be registered.
|
||||
* @param name the name to be registered.
|
||||
* @param receiveMarketingEmails the answer to receive marketing emails.
|
||||
*/
|
||||
@Serializable
|
||||
data class SendVerificationEmailRequestJson(
|
||||
@SerialName("email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
@SerialName("receiveMarketingEmails")
|
||||
val receiveMarketingEmails: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The response body for sending a verification email.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SendVerificationEmailResponseJson {
|
||||
|
||||
/**
|
||||
* Models a successful json response.
|
||||
*
|
||||
* @param emailVerificationToken the token to verify the email.
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
val emailVerificationToken: String?,
|
||||
) : SendVerificationEmailResponseJson()
|
||||
|
||||
/**
|
||||
* Represents the json body of an invalid request.
|
||||
*
|
||||
* @param validationErrors a map where each value is a list of error messages for each key.
|
||||
* The values in the array should be used for display to the user, since the keys tend to come
|
||||
* back as nonsense. (eg: empty string key)
|
||||
*/
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("message")
|
||||
private val invalidMessage: String? = null,
|
||||
|
||||
@SerialName("Message")
|
||||
private val errorMessage: String? = null,
|
||||
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String>>?,
|
||||
) : SendVerificationEmailResponseJson() {
|
||||
/**
|
||||
* A generic error message.
|
||||
*/
|
||||
val message: String? get() = invalidMessage ?: errorMessage
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* Decryption options related to a user's trusted device.
|
||||
@@ -13,20 +15,26 @@ import kotlinx.serialization.Serializable
|
||||
* @property hasManageResetPasswordPermission Whether or not the user has manage reset password
|
||||
* permission.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class TrustedDeviceUserDecryptionOptionsJson(
|
||||
@SerialName("EncryptedPrivateKey")
|
||||
@SerialName("encryptedPrivateKey")
|
||||
@JsonNames("EncryptedPrivateKey")
|
||||
val encryptedPrivateKey: String?,
|
||||
|
||||
@SerialName("EncryptedUserKey")
|
||||
@SerialName("encryptedUserKey")
|
||||
@JsonNames("EncryptedUserKey")
|
||||
val encryptedUserKey: String?,
|
||||
|
||||
@SerialName("HasAdminApproval")
|
||||
@SerialName("hasAdminApproval")
|
||||
@JsonNames("HasAdminApproval")
|
||||
val hasAdminApproval: Boolean,
|
||||
|
||||
@SerialName("HasLoginApprovingDevice")
|
||||
@SerialName("hasLoginApprovingDevice")
|
||||
@JsonNames("HasLoginApprovingDevice")
|
||||
val hasLoginApprovingDevice: Boolean,
|
||||
|
||||
@SerialName("HasManageResetPasswordPermission")
|
||||
@SerialName("hasManageResetPasswordPermission")
|
||||
@JsonNames("HasManageResetPasswordPermission")
|
||||
val hasManageResetPasswordPermission: Boolean,
|
||||
)
|
||||
|
||||
@@ -39,5 +39,7 @@ enum class TwoFactorAuthMethod(val value: UInt) {
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class TwoFactorAuthMethodSerializer :
|
||||
BaseEnumeratedIntSerializer<TwoFactorAuthMethod>(TwoFactorAuthMethod.entries.toTypedArray())
|
||||
private class TwoFactorAuthMethodSerializer : BaseEnumeratedIntSerializer<TwoFactorAuthMethod>(
|
||||
className = "TwoFactorAuthMethod",
|
||||
values = TwoFactorAuthMethod.entries.toTypedArray(),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
|
||||
/**
|
||||
* The options available to a user for decryption.
|
||||
@@ -12,14 +14,18 @@ import kotlinx.serialization.Serializable
|
||||
* device.
|
||||
* @property keyConnectorUserDecryptionOptions Decryption options related to a user's key connector.
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializable
|
||||
data class UserDecryptionOptionsJson(
|
||||
@SerialName("HasMasterPassword")
|
||||
@SerialName("hasMasterPassword")
|
||||
@JsonNames("HasMasterPassword")
|
||||
val hasMasterPassword: Boolean,
|
||||
|
||||
@SerialName("TrustedDeviceOption")
|
||||
@SerialName("trustedDeviceOption")
|
||||
@JsonNames("TrustedDeviceOption")
|
||||
val trustedDeviceUserDecryptionOptions: TrustedDeviceUserDecryptionOptionsJson?,
|
||||
|
||||
@SerialName("KeyConnectorOption")
|
||||
@SerialName("keyConnectorOption")
|
||||
@JsonNames("KeyConnectorOption")
|
||||
val keyConnectorUserDecryptionOptions: KeyConnectorUserDecryptionOptionsJson?,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user