mirror of
https://github.com/bitwarden/android.git
synced 2026-05-09 13:29:18 -05:00
Compare commits
2081 Commits
v2024.6.1
...
debug-rele
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e37aa54a95 | ||
|
|
44ad1d34b0 | ||
|
|
6f107f6610 | ||
|
|
a1c6276092 | ||
|
|
bc67bf3dff | ||
|
|
bc5788556c | ||
|
|
45e20d8c9e | ||
|
|
a972a40a49 | ||
|
|
717d5665e0 | ||
|
|
bc0a18f250 | ||
|
|
2f72553454 | ||
|
|
e5a1546291 | ||
|
|
d8e319948c | ||
|
|
b3528249e9 | ||
|
|
5f42c9bb39 | ||
|
|
b010c9a29d | ||
|
|
3e55f561c9 | ||
|
|
277e4d8d6f | ||
|
|
32e8fb7d8e | ||
|
|
4a18e57cca | ||
|
|
070ef45087 | ||
|
|
a658cf890a | ||
|
|
d3dea3c9cb | ||
|
|
5ab0517bf3 | ||
|
|
e8b01c2d44 | ||
|
|
b34d873471 | ||
|
|
3c3d8710c9 | ||
|
|
20dea9b5ff | ||
|
|
44410efe56 | ||
|
|
a999592fb6 | ||
|
|
25a78f60ab | ||
|
|
a8546bb4eb | ||
|
|
6c6d4f2d91 | ||
|
|
7347d91fdd | ||
|
|
0a99359978 | ||
|
|
aca4b05b59 | ||
|
|
b0c7995cb7 | ||
|
|
af322b5d1f | ||
|
|
9fcfcc9e41 | ||
|
|
ff6b7b675d | ||
|
|
3164c29184 | ||
|
|
c5663431af | ||
|
|
4fb96cb782 | ||
|
|
36e06cdac7 | ||
|
|
3cf325becf | ||
|
|
584bdb6277 | ||
|
|
b2a9f4b455 | ||
|
|
b0b4379307 | ||
|
|
b9cc664efa | ||
|
|
e30e0ffbb4 | ||
|
|
2ffd71c69a | ||
|
|
3488ad6217 | ||
|
|
58005d908a | ||
|
|
a320e6ea61 | ||
|
|
5a23ceabc1 | ||
|
|
f4102bcd30 | ||
|
|
6d25c12271 | ||
|
|
ef03cdb2db | ||
|
|
474ec4907f | ||
|
|
a68fd8b44f | ||
|
|
3282992221 | ||
|
|
26252ebcdb | ||
|
|
a688693f43 | ||
|
|
3ed63ef5eb | ||
|
|
1e2bc4aa70 | ||
|
|
694865c213 | ||
|
|
29243c8f44 | ||
|
|
4e1dfcaeec | ||
|
|
75f3065085 | ||
|
|
402e399fd4 | ||
|
|
810cbc8da5 | ||
|
|
9bfbe0c087 | ||
|
|
d06c87beb3 | ||
|
|
9b120701eb | ||
|
|
e8f1242744 | ||
|
|
1c525b9dfc | ||
|
|
c613c2df86 | ||
|
|
9a9125321e | ||
|
|
2902b89402 | ||
|
|
93edbb61bf | ||
|
|
85bc76d0a6 | ||
|
|
db18e8012a | ||
|
|
db03c7d703 | ||
|
|
6ee7f9b80f | ||
|
|
fc88ca1ba8 | ||
|
|
3c033d4aa2 | ||
|
|
59c2261e7c | ||
|
|
b6aa0952b1 | ||
|
|
905e3248f2 | ||
|
|
72250dce90 | ||
|
|
60ee129e0b | ||
|
|
911bb40be8 | ||
|
|
308a8a564c | ||
|
|
337e751c05 | ||
|
|
f4c4e06dcc | ||
|
|
38b92133ff | ||
|
|
e381d72d5c | ||
|
|
87a61bbbbd | ||
|
|
7cc3c1c755 | ||
|
|
f614d6039f | ||
|
|
a6d622c3b9 | ||
|
|
b781acb1fa | ||
|
|
45f0ddc60f | ||
|
|
8876418177 | ||
|
|
67b64034ff | ||
|
|
79a232919a | ||
|
|
2fa9ea18b5 | ||
|
|
9b297286e5 | ||
|
|
376a62edaf | ||
|
|
01570c2555 | ||
|
|
4a3db4fea7 | ||
|
|
3c0818232f | ||
|
|
1799d0b716 | ||
|
|
cf896d6bf1 | ||
|
|
40dff74d3f | ||
|
|
ddd2d7fad5 | ||
|
|
b4efc0e59d | ||
|
|
4ffd41c33f | ||
|
|
a70f441064 | ||
|
|
867e2287dc | ||
|
|
912f734cae | ||
|
|
02b5cbb199 | ||
|
|
f589546e6a | ||
|
|
517198b265 | ||
|
|
91f1180be7 | ||
|
|
8589a37e5a | ||
|
|
e4678cc7df | ||
|
|
e665c386ff | ||
|
|
2f2ec71fc4 | ||
|
|
7b115df83a | ||
|
|
edd1763198 | ||
|
|
37d3ff30e4 | ||
|
|
258a58aa25 | ||
|
|
da5dcef41e | ||
|
|
7a578ff2c5 | ||
|
|
355facc36b | ||
|
|
c60f3131b6 | ||
|
|
bb950c8c59 | ||
|
|
c7df80ff00 | ||
|
|
d308b84943 | ||
|
|
79ad18877d | ||
|
|
4f51507e4b | ||
|
|
88fcd35d1a | ||
|
|
987639b2a3 | ||
|
|
d32b4c7c7e | ||
|
|
9ed59e61a3 | ||
|
|
3342ebf139 | ||
|
|
4050215145 | ||
|
|
3e0ee5fcd8 | ||
|
|
fcd7326f2c | ||
|
|
c94fe56b47 | ||
|
|
17287680d9 | ||
|
|
e4935318de | ||
|
|
f22643fec1 | ||
|
|
6454dc1a58 | ||
|
|
411e359600 | ||
|
|
e75d7844de | ||
|
|
25680f9255 | ||
|
|
628cb12081 | ||
|
|
710e35680b | ||
|
|
b5cd0c9d9d | ||
|
|
9995fa92f1 | ||
|
|
44aae70fe4 | ||
|
|
fca4ebe023 | ||
|
|
2d2a5e74da | ||
|
|
b53ca30974 | ||
|
|
8178a61dba | ||
|
|
f0bdc8ede3 | ||
|
|
145c19da22 | ||
|
|
39b1409cbd | ||
|
|
f26d54a2e2 | ||
|
|
33cfaa5e95 | ||
|
|
9274e0f349 | ||
|
|
46656d659e | ||
|
|
811f0f2757 | ||
|
|
8f783a43e4 | ||
|
|
b8f74cdefa | ||
|
|
5e6dcb5b58 | ||
|
|
c5a40a89d9 | ||
|
|
929233081c | ||
|
|
37af6a1773 | ||
|
|
557c5b46a5 | ||
|
|
390ef34398 | ||
|
|
d2f7d52132 | ||
|
|
0feac46711 | ||
|
|
bc50c0d873 | ||
|
|
fb3b9c9ea7 | ||
|
|
9a81e18cb4 | ||
|
|
f9914e5b46 | ||
|
|
e193661f5f | ||
|
|
532fcbb40e | ||
|
|
187d50faa2 | ||
|
|
8f5376c2de | ||
|
|
56192a7e8b | ||
|
|
70350746ce | ||
|
|
febfc82a53 | ||
|
|
5f5c71979f | ||
|
|
ba49a3e91f | ||
|
|
965ab67e58 | ||
|
|
2932ed831b | ||
|
|
2ff3f3e23d | ||
|
|
eb5893dde4 | ||
|
|
1165e7002b | ||
|
|
5fa7239130 | ||
|
|
fd9bdfa228 | ||
|
|
7db8f040e4 | ||
|
|
790331e058 | ||
|
|
d0640b7e20 | ||
|
|
5429e27228 | ||
|
|
917aaac3a6 | ||
|
|
0b7209b3c9 | ||
|
|
a7b3201015 | ||
|
|
348e14e52d | ||
|
|
ef9dda5159 | ||
|
|
b0309e876e | ||
|
|
59a49355fd | ||
|
|
901184db45 | ||
|
|
a2507c317d | ||
|
|
f608852dc7 | ||
|
|
e44d63229c | ||
|
|
f7b876f204 | ||
|
|
1268afaef8 | ||
|
|
3f1c1dec17 | ||
|
|
5eea55f173 | ||
|
|
1a8cf4055a | ||
|
|
defdf8eb58 | ||
|
|
9940c8cf9e | ||
|
|
e1058f5021 | ||
|
|
986cd2ee30 | ||
|
|
eae870cb3a | ||
|
|
79493a55bd | ||
|
|
18bafaba8a | ||
|
|
896be911a4 | ||
|
|
85a86106f6 | ||
|
|
edb7996c28 | ||
|
|
a806109380 | ||
|
|
4f5c28e248 | ||
|
|
b22f06cbf9 | ||
|
|
1070c9d46e | ||
|
|
b1dc894fe8 | ||
|
|
c76945161a | ||
|
|
789cd80eba | ||
|
|
9482890102 | ||
|
|
ed2d6ca585 | ||
|
|
d279f6acae | ||
|
|
6ebcab7b86 | ||
|
|
3ee74d3ec5 | ||
|
|
288efb3611 | ||
|
|
bbdf8552c9 | ||
|
|
44ef598df3 | ||
|
|
73a8e241d4 | ||
|
|
4d6260ea02 | ||
|
|
569bb4f110 | ||
|
|
ffc71371a9 | ||
|
|
8d0b23d166 | ||
|
|
5f525d9d95 | ||
|
|
b94d59ba6b | ||
|
|
4ff1a9ba94 | ||
|
|
9c1673f603 | ||
|
|
ddc099f727 | ||
|
|
fbfcfcd683 | ||
|
|
1234898786 | ||
|
|
182e6475c0 | ||
|
|
f27590a4d6 | ||
|
|
807c76f8ec | ||
|
|
3877c4bd64 | ||
|
|
8c88fd9d53 | ||
|
|
b92493611e | ||
|
|
9235f92206 | ||
|
|
a3610c22dd | ||
|
|
1e4fc31ed4 | ||
|
|
ac1a9a2dc0 | ||
|
|
fe0e6bc67b | ||
|
|
419e5ca918 | ||
|
|
be1a6e2097 | ||
|
|
4fe989ce68 | ||
|
|
8be7410302 | ||
|
|
16225f0d68 | ||
|
|
08679a8973 | ||
|
|
4d3e782b69 | ||
|
|
4d8fe722d1 | ||
|
|
c5600c1d84 | ||
|
|
9816321d93 | ||
|
|
56e8acf81f | ||
|
|
08b07a0050 | ||
|
|
25d7c1e72c | ||
|
|
e311a4f618 | ||
|
|
0eea6b07a3 | ||
|
|
c52e769327 | ||
|
|
292a28d155 | ||
|
|
6c41c358ac | ||
|
|
e7cf5a7efa | ||
|
|
f64364c1b8 | ||
|
|
d42b8ecd2d | ||
|
|
a6f7b1e176 | ||
|
|
d56b9fc0ff | ||
|
|
f290ae411b | ||
|
|
508566f06f | ||
|
|
95f146fb3e | ||
|
|
469df4495a | ||
|
|
053dfc1647 | ||
|
|
7de770ca03 | ||
|
|
861a4281fa | ||
|
|
265014fd64 | ||
|
|
5d32fe9caf | ||
|
|
44ba0f548a | ||
|
|
694443f2e1 | ||
|
|
0ade60025c | ||
|
|
5adccca823 | ||
|
|
3c86bb425b | ||
|
|
3474e0b608 | ||
|
|
0f2476bebf | ||
|
|
edffb8dd6f | ||
|
|
2dc6c170f5 | ||
|
|
2a8a16ab3f | ||
|
|
76995a28ad | ||
|
|
7e146800a8 | ||
|
|
7a2f1c294f | ||
|
|
44d4926300 | ||
|
|
e4c160d1e0 | ||
|
|
0f9f9d9dce | ||
|
|
a0c2600517 | ||
|
|
c60df56648 | ||
|
|
9cdfe0c5d6 | ||
|
|
d822be62e1 | ||
|
|
7adbfdcc84 | ||
|
|
beb4c533c8 | ||
|
|
e1cd813445 | ||
|
|
f769900976 | ||
|
|
a0ff94195f | ||
|
|
9853f137d2 | ||
|
|
b591534bd9 | ||
|
|
d2c329264c | ||
|
|
a9791c3f9f | ||
|
|
a59eaf5d40 | ||
|
|
d2129cf507 | ||
|
|
903c260ad1 | ||
|
|
09a8c01824 | ||
|
|
a1a4c217de | ||
|
|
7fbe3510b5 | ||
|
|
0892e0ff1f | ||
|
|
0934d47159 | ||
|
|
caf1c2eed5 | ||
|
|
803d519c24 | ||
|
|
a3d2e51c8e | ||
|
|
891def5e32 | ||
|
|
00ded69a84 | ||
|
|
c5f597aedb | ||
|
|
f43367ebfa | ||
|
|
997769bb1c | ||
|
|
65d1a4f12a | ||
|
|
f7c1278805 | ||
|
|
aa3602a5ce | ||
|
|
af18848159 | ||
|
|
f3b7d0f732 | ||
|
|
e250a8dc1e | ||
|
|
ab2ac60957 | ||
|
|
b877487ce1 | ||
|
|
ef68879778 | ||
|
|
a4e4d1488b | ||
|
|
cf8578f3ef | ||
|
|
bdd0660e2b | ||
|
|
294ef674bc | ||
|
|
b9a897a9fc | ||
|
|
6b12b9757f | ||
|
|
61411ca73c | ||
|
|
3908827a14 | ||
|
|
e553d7a015 | ||
|
|
3015b768c6 | ||
|
|
21b8ef92ba | ||
|
|
0e3e6069fa | ||
|
|
8d8dee5171 | ||
|
|
97b6bccd72 | ||
|
|
2a6813e4a2 | ||
|
|
29e7899525 | ||
|
|
4cd603006f | ||
|
|
1a091e198c | ||
|
|
3551e75596 | ||
|
|
6d976bea4c | ||
|
|
d5c04123d9 | ||
|
|
000a7d141e | ||
|
|
c9d4d35f07 | ||
|
|
4216f3f5a0 | ||
|
|
c14545107d | ||
|
|
d1a8cbf59f | ||
|
|
1acc1a87a6 | ||
|
|
fd73360539 | ||
|
|
178625222a | ||
|
|
3ea17eb71c | ||
|
|
6ccb035ffd | ||
|
|
5c3008d080 | ||
|
|
54efc74907 | ||
|
|
34aed2ac65 | ||
|
|
3d152f5c36 | ||
|
|
4c8e5602dd | ||
|
|
6e44ee2eb0 | ||
|
|
4895f2a18a | ||
|
|
fc4f02c4d5 | ||
|
|
6719534494 | ||
|
|
183584f678 | ||
|
|
046bb0fa39 | ||
|
|
9508b4ba90 | ||
|
|
07e4e6a806 | ||
|
|
4d142a6a5c | ||
|
|
f02a3a249b | ||
|
|
28149532a0 | ||
|
|
7f5426dea0 | ||
|
|
d7d703c977 | ||
|
|
7fda5d799f | ||
|
|
3c1a0a352a | ||
|
|
cb0b135429 | ||
|
|
7422efd07a | ||
|
|
27a9fc52b7 | ||
|
|
c105c102a3 | ||
|
|
c83bd8f4a8 | ||
|
|
d820b3345a | ||
|
|
b71b01d48d | ||
|
|
8a0f67c0e9 | ||
|
|
b4d85e07ba | ||
|
|
dfd58822b7 | ||
|
|
f14a1404e3 | ||
|
|
f1950600a1 | ||
|
|
7d6b6a5959 | ||
|
|
ea70191429 | ||
|
|
db956b9b91 | ||
|
|
119812507a | ||
|
|
a97c962428 | ||
|
|
456adf3158 | ||
|
|
62cb962298 | ||
|
|
7f4e65d7e4 | ||
|
|
860a2e265f | ||
|
|
6d68c3ae24 | ||
|
|
97b8c51ab3 | ||
|
|
a2449a2f19 | ||
|
|
1d73bbd440 | ||
|
|
da62244000 | ||
|
|
11b767c98f | ||
|
|
cd4db467e3 | ||
|
|
7fdf165273 | ||
|
|
e49bab637c | ||
|
|
14ac194cb7 | ||
|
|
578f96a944 | ||
|
|
2c71ab7d27 | ||
|
|
c5ee389231 | ||
|
|
5037af07c7 | ||
|
|
d6d1e8e97f | ||
|
|
652168f946 | ||
|
|
d4d5d2c2a8 | ||
|
|
ed148c2089 | ||
|
|
564304616d | ||
|
|
0d0b8d6780 | ||
|
|
472e41f6bc | ||
|
|
fb9c68755a | ||
|
|
d7671f47ea | ||
|
|
9c7270df69 | ||
|
|
733290569c | ||
|
|
cbaa8a329e | ||
|
|
f968d7698a | ||
|
|
68cd08b069 | ||
|
|
84683894a6 | ||
|
|
ed2d9ecb80 | ||
|
|
8f4d46954e | ||
|
|
a9fc6ff589 | ||
|
|
5c8f5670e4 | ||
|
|
eec88d4924 | ||
|
|
82da193e55 | ||
|
|
76fb85ac1f | ||
|
|
625ac0ea5f | ||
|
|
4e88833737 | ||
|
|
ecea2ef7c1 | ||
|
|
0eccc7197e | ||
|
|
5dd34afe81 | ||
|
|
5abcc5b1f7 | ||
|
|
6fec95cb84 | ||
|
|
1d68c1fdf6 | ||
|
|
0c2de427dc | ||
|
|
f932682949 | ||
|
|
e1f432ea5d | ||
|
|
31de7fc331 | ||
|
|
07469672ba | ||
|
|
1a2beea770 | ||
|
|
639ca02739 | ||
|
|
186bea2d1d | ||
|
|
3dc187da87 | ||
|
|
69708c1285 | ||
|
|
ad1566f4b0 | ||
|
|
32d0ca7bcd | ||
|
|
0353f0c153 | ||
|
|
7436122953 | ||
|
|
23ef5b38fe | ||
|
|
fe1fe770c7 | ||
|
|
240bca3c2f | ||
|
|
8c7cc27c5d | ||
|
|
a4aa9837a6 | ||
|
|
b901de9ddf | ||
|
|
96df23f0af | ||
|
|
0eb149941d | ||
|
|
6f44e64375 | ||
|
|
cda86b842e | ||
|
|
e1608b426d | ||
|
|
b6017baf54 | ||
|
|
0f6d15d6a6 | ||
|
|
cd11164544 | ||
|
|
1b9d2bfab4 | ||
|
|
373b789fbb | ||
|
|
985e576a82 | ||
|
|
b11e4481f9 | ||
|
|
5ac0f2b111 | ||
|
|
37a0d19efc | ||
|
|
54983bc92e | ||
|
|
36989875a6 | ||
|
|
88b0fe59bb | ||
|
|
e4d0c48eed | ||
|
|
bd364a1108 | ||
|
|
39b88d6064 | ||
|
|
da709e039b | ||
|
|
31311964d0 | ||
|
|
8cbd7369c5 | ||
|
|
2a1669cf87 | ||
|
|
4c4007a734 | ||
|
|
70dc82d1b6 | ||
|
|
021ece138b | ||
|
|
bee09de972 | ||
|
|
33da0d8138 | ||
|
|
d5d8da2410 | ||
|
|
3722a45359 | ||
|
|
f23079b5ac | ||
|
|
524ddb6d0c | ||
|
|
0d40d1e569 | ||
|
|
4f65044179 | ||
|
|
d67e74e48b | ||
|
|
b760b58669 | ||
|
|
36e6fbc14c | ||
|
|
0be26c1eda | ||
|
|
3311086dfc | ||
|
|
c912a3f12a | ||
|
|
e67790438e | ||
|
|
ff72efe0ed | ||
|
|
9dd71eaea2 | ||
|
|
2d416eade5 | ||
|
|
83de8b888d | ||
|
|
e35be360d7 | ||
|
|
899689ba7b | ||
|
|
71237cb3a7 | ||
|
|
18c7333cf3 | ||
|
|
b6d9bee266 | ||
|
|
868f8091d2 | ||
|
|
dcd2d26d6c | ||
|
|
548c9ee092 | ||
|
|
186741e052 | ||
|
|
d989c61bc2 | ||
|
|
8f311861e3 | ||
|
|
942b95927f | ||
|
|
f087b6aac7 | ||
|
|
87ef0e2961 | ||
|
|
b60f30d8a1 | ||
|
|
c553d2caec | ||
|
|
d1ba730012 | ||
|
|
944eb64562 | ||
|
|
12ce1e5229 | ||
|
|
b72da1ba69 | ||
|
|
ed97695228 | ||
|
|
05b1266b1b | ||
|
|
8153b25d89 | ||
|
|
2fd5ab6f8d | ||
|
|
392695cb54 | ||
|
|
d7f771990e | ||
|
|
dd0f08d759 | ||
|
|
dd9ca853a7 | ||
|
|
2ef0ca3620 | ||
|
|
97ef0ec004 | ||
|
|
9d7df2bc10 | ||
|
|
cb05787256 | ||
|
|
8767f2dd24 | ||
|
|
bf1ed690a4 | ||
|
|
a12f325815 | ||
|
|
c2872b6b9b | ||
|
|
622d68e40c | ||
|
|
6ebece1b1e | ||
|
|
c540d3ef47 | ||
|
|
385f5efac5 | ||
|
|
6668af58d2 | ||
|
|
c5e216783e | ||
|
|
acf222d151 | ||
|
|
1df96fdb62 | ||
|
|
942f6e2475 | ||
|
|
2176b61cd3 | ||
|
|
4a63a709b8 | ||
|
|
62cfcbbd72 | ||
|
|
538f1feb2e | ||
|
|
70e42a27db | ||
|
|
4d9a19f43c | ||
|
|
dda8237ce5 | ||
|
|
c4f54ee93c | ||
|
|
cac22346dd | ||
|
|
3f35ace6e9 | ||
|
|
d1e4078c5a | ||
|
|
241a89fe13 | ||
|
|
4df1b245e8 | ||
|
|
853069ee1c | ||
|
|
1149e91dd5 | ||
|
|
d5de173431 | ||
|
|
f6d5302a73 | ||
|
|
0ea7d00e93 | ||
|
|
ebdf5f816a | ||
|
|
85109a2e4b | ||
|
|
5017e935d7 | ||
|
|
ce0aa7adda | ||
|
|
111d141fac | ||
|
|
4676f4bf8c | ||
|
|
321a764f20 | ||
|
|
5d4df86bc9 | ||
|
|
291c568583 | ||
|
|
7938c8c2bb | ||
|
|
1fecd4af5f | ||
|
|
1e6f896328 | ||
|
|
e67a143474 | ||
|
|
a6862bb791 | ||
|
|
d70e658c8b | ||
|
|
f43702cb83 | ||
|
|
20bda929b3 | ||
|
|
ce139623d6 | ||
|
|
8e7de92609 | ||
|
|
b82b1ad570 | ||
|
|
56cce8ffdd | ||
|
|
7abae5e86a | ||
|
|
862384db3a | ||
|
|
01c4a3db03 | ||
|
|
e5ddeb44fd | ||
|
|
a476436bff | ||
|
|
d3e14e8f52 | ||
|
|
36fa907d87 | ||
|
|
a4ee8017ae | ||
|
|
aa35e2f93c | ||
|
|
40179429bf | ||
|
|
bdf50fd1a6 | ||
|
|
445d6ec3b8 | ||
|
|
c304a4306f | ||
|
|
f82bda5bce | ||
|
|
a2f4e4f3b5 | ||
|
|
d4c079140d | ||
|
|
f1c82eb027 | ||
|
|
932eced64e | ||
|
|
1eced037a4 | ||
|
|
a4fb50d3d8 | ||
|
|
9a50399116 | ||
|
|
5d5bc25a45 | ||
|
|
af528fdd82 | ||
|
|
7d119fb552 | ||
|
|
81b43d13b0 | ||
|
|
cd86413ff6 | ||
|
|
05094cf6e7 | ||
|
|
75af4868e2 | ||
|
|
b7948948f0 | ||
|
|
efec5cb4ca | ||
|
|
6369b20f18 | ||
|
|
2e11c81f45 | ||
|
|
3926fbca7f | ||
|
|
29838b19e0 | ||
|
|
66dae199c8 | ||
|
|
f698d09b43 | ||
|
|
9486f4c4e2 | ||
|
|
6664ccc53f | ||
|
|
d62e3164dc | ||
|
|
b0729e8cd2 | ||
|
|
c51f61c585 | ||
|
|
6340c2dd04 | ||
|
|
42df9733c8 | ||
|
|
cfa753cb12 | ||
|
|
71250a28fa | ||
|
|
5279e6d18c | ||
|
|
8dd5a9df9f | ||
|
|
346961856f | ||
|
|
4cae0823f2 | ||
|
|
98b6f68821 | ||
|
|
a6d1f210c8 | ||
|
|
c0707ae08c | ||
|
|
3e15b60178 | ||
|
|
6081b7e932 | ||
|
|
bc76e46185 | ||
|
|
b4b4f753ca | ||
|
|
22376bfe4b | ||
|
|
ab270cd243 | ||
|
|
8ed9b97805 | ||
|
|
27e4c6a2b4 | ||
|
|
a6ed702a95 | ||
|
|
0792f44b6b | ||
|
|
94a91702cc | ||
|
|
21af60f4de | ||
|
|
1add57d56c | ||
|
|
b0421c774b | ||
|
|
7d846ae383 | ||
|
|
29371bdcb5 | ||
|
|
3eed1c1abe | ||
|
|
ad6bc883b8 | ||
|
|
f4f669683e | ||
|
|
475a82e0fb | ||
|
|
f22156389b | ||
|
|
4f09f5dae4 | ||
|
|
3934bc9ae2 | ||
|
|
72c9149d27 | ||
|
|
a040a38ce8 | ||
|
|
ef3b7730d0 | ||
|
|
ad8d8d271a | ||
|
|
4954e57007 | ||
|
|
6f50fffd17 | ||
|
|
44c5755301 | ||
|
|
b20eece3aa | ||
|
|
869a3b00a5 | ||
|
|
7f8e848c46 | ||
|
|
6c784d28eb | ||
|
|
dfcdc72499 | ||
|
|
db287ddce5 | ||
|
|
9ea85917b1 | ||
|
|
6db4165c4c | ||
|
|
18ce45e7e5 | ||
|
|
6fe9eba620 | ||
|
|
b084987758 | ||
|
|
5d0593026f | ||
|
|
47abeb7843 | ||
|
|
e2779e4edb | ||
|
|
90cc9f77c5 | ||
|
|
40760f270a | ||
|
|
8a773141a4 | ||
|
|
0c149abdd9 | ||
|
|
f540f86b19 | ||
|
|
ca64ce2176 | ||
|
|
da63c9e36b | ||
|
|
e16ad44d5e | ||
|
|
d26a2ee52a | ||
|
|
1eb741ab58 | ||
|
|
e10ca9a6ec | ||
|
|
3fca61ad3e | ||
|
|
409529b9ca | ||
|
|
4568dd53d4 | ||
|
|
b9b90165bf | ||
|
|
778a630012 | ||
|
|
4809066ad7 | ||
|
|
d03c6c243d | ||
|
|
d19ab498ff | ||
|
|
efc3d21fde | ||
|
|
35e585a60e | ||
|
|
39787f9bf0 | ||
|
|
3940997ef9 | ||
|
|
ce482e744d | ||
|
|
b0157d10e2 | ||
|
|
cf3c2fb56d | ||
|
|
a88a173e00 | ||
|
|
ac6ff98041 | ||
|
|
ec030f2c2e | ||
|
|
be08c1a536 | ||
|
|
84edf4ead0 | ||
|
|
915aac561c | ||
|
|
2027a66f02 | ||
|
|
ef6d9bc68c | ||
|
|
3ceda9e40a | ||
|
|
3f1a6e97fd | ||
|
|
4448ab05ce | ||
|
|
d584391843 | ||
|
|
e0d91d7682 | ||
|
|
e8a98dd3ed | ||
|
|
b6163cf53c | ||
|
|
a24c6f2719 | ||
|
|
b3219d4040 | ||
|
|
27043e28a8 | ||
|
|
7c36b7cb82 | ||
|
|
fcfcf48cee | ||
|
|
757994ec18 | ||
|
|
548de56d60 | ||
|
|
71cd917328 | ||
|
|
01188c21b2 | ||
|
|
5c076871ab | ||
|
|
dca7284230 | ||
|
|
07d3849c4b | ||
|
|
33c3fd28e9 | ||
|
|
88609c2f5b | ||
|
|
af8cfcd2f0 | ||
|
|
7cc8108498 | ||
|
|
a31c499b15 | ||
|
|
d7d099477f | ||
|
|
0ba240852f | ||
|
|
727d943fae | ||
|
|
234f49a92c | ||
|
|
4f49d3d504 | ||
|
|
aba8344df1 | ||
|
|
6da8e2c47b | ||
|
|
78d5965271 | ||
|
|
6953d5e132 | ||
|
|
7073124495 | ||
|
|
0cc7067808 | ||
|
|
9e920f1cf5 | ||
|
|
537e743891 | ||
|
|
60da236f3e | ||
|
|
7804d8430f | ||
|
|
2893c3871f | ||
|
|
d04ac5e672 | ||
|
|
55e03565a6 | ||
|
|
768f7a3fd9 | ||
|
|
1d02737093 | ||
|
|
dab06b0ed4 | ||
|
|
fb792a668b | ||
|
|
64da29ffaa | ||
|
|
675cbb7c4f | ||
|
|
6b63218839 | ||
|
|
30a1bba796 | ||
|
|
c2d9e4858b | ||
|
|
d8e42083b7 | ||
|
|
ac7fbfd129 | ||
|
|
25dfa74bdf | ||
|
|
85a98e86c4 | ||
|
|
078d80d3f6 | ||
|
|
00eb78f02e | ||
|
|
eadfac5ea8 | ||
|
|
a651d9b1fc | ||
|
|
6308aed34a | ||
|
|
011d637f7c | ||
|
|
0b03d2c0d5 | ||
|
|
bb7e4061cc | ||
|
|
d1308cb936 | ||
|
|
892f817b2a | ||
|
|
86e5789f30 | ||
|
|
80bd1bfde2 | ||
|
|
4943df24b3 | ||
|
|
b7464b87d9 | ||
|
|
3fb7904a36 | ||
|
|
9d9f9e3e72 | ||
|
|
a061cbb1d3 | ||
|
|
ad03f8c996 | ||
|
|
aac2345a64 | ||
|
|
61c48bf673 | ||
|
|
77b631b021 | ||
|
|
c59a28a9df | ||
|
|
1349165156 | ||
|
|
65cc0a0dd8 | ||
|
|
0ba67f5887 | ||
|
|
87f64d7aba | ||
|
|
063003b4aa | ||
|
|
f2eb524da4 | ||
|
|
e929ca8a7d | ||
|
|
acc5e30b7a | ||
|
|
26d4a397a6 | ||
|
|
ba9a0d8884 | ||
|
|
bb2e98eac6 | ||
|
|
133d8548e8 | ||
|
|
9f19a99eb9 | ||
|
|
22931bbd38 | ||
|
|
359dedc9d7 | ||
|
|
be416f87de | ||
|
|
6a3a534304 | ||
|
|
09254a2285 | ||
|
|
f0d6599eb3 | ||
|
|
d7a50b092b | ||
|
|
9f616b67c9 | ||
|
|
1198de2a74 | ||
|
|
3db7b17e3a | ||
|
|
631be3fca5 | ||
|
|
e8e6040318 | ||
|
|
5790397a27 | ||
|
|
8b98e8f461 | ||
|
|
f3482bd3e2 | ||
|
|
14ef7351bd | ||
|
|
0820061466 | ||
|
|
c5616b9e38 | ||
|
|
0538becf7f | ||
|
|
2ae3d5e100 | ||
|
|
2aa371a972 | ||
|
|
72ac8d3699 | ||
|
|
00f30c922d | ||
|
|
a68b370cba | ||
|
|
60a6b2a545 | ||
|
|
2c63c1ab94 | ||
|
|
fdc92712c4 | ||
|
|
4eb4d5f256 | ||
|
|
6fdb39026f | ||
|
|
ac6c90eb7a | ||
|
|
eb3bc838d6 | ||
|
|
30e882d7d1 | ||
|
|
3adc80fdf8 | ||
|
|
e70b21c951 | ||
|
|
571b8368e1 | ||
|
|
fd26472f71 | ||
|
|
6ffe9df353 | ||
|
|
7e0fc07a7c | ||
|
|
d27ab1d31a | ||
|
|
7b9cbb7bef | ||
|
|
a1a3d55656 | ||
|
|
c672bff18c | ||
|
|
7ab5972893 | ||
|
|
13aa3251d5 | ||
|
|
a5f6864512 | ||
|
|
c3506c1c25 | ||
|
|
1710b563c0 | ||
|
|
01b646afa7 | ||
|
|
445bd90f67 | ||
|
|
de7416d96e | ||
|
|
5e29df5f30 | ||
|
|
5f48d7bebd | ||
|
|
b342f21cbb | ||
|
|
4b3ec1d918 | ||
|
|
5f52c49a68 | ||
|
|
efe1475fbf | ||
|
|
ffe5ddb4a6 | ||
|
|
6f82251332 | ||
|
|
76e780f813 | ||
|
|
161d8517a4 | ||
|
|
0786ab98a7 | ||
|
|
3d12c98969 | ||
|
|
07fe6e53ea | ||
|
|
2bbadf8726 | ||
|
|
f004e10d41 | ||
|
|
38c9d6cfbc | ||
|
|
62d26491d5 | ||
|
|
925db01b44 | ||
|
|
2b79cc9a17 | ||
|
|
f141465b41 | ||
|
|
ecc5d4ce30 | ||
|
|
de558bf94d | ||
|
|
9b3bb32c87 | ||
|
|
6f5cb0df96 | ||
|
|
2195d07f78 | ||
|
|
c09b02f1d0 | ||
|
|
76c7f8c41d | ||
|
|
6a1f37b243 | ||
|
|
590fc21820 | ||
|
|
4575fa96c1 | ||
|
|
1a7ddbef7a | ||
|
|
555d30645f | ||
|
|
80d04fdf9c | ||
|
|
4ea92223c0 | ||
|
|
f8e41caaf2 | ||
|
|
3963623407 | ||
|
|
bbb2d8dc05 | ||
|
|
db035bbd7d | ||
|
|
69f33ddca9 | ||
|
|
2eca28d571 | ||
|
|
d9ac445149 | ||
|
|
fe06bf48e7 | ||
|
|
3f1f9983e3 | ||
|
|
a681402956 | ||
|
|
3c7262d2b3 | ||
|
|
7a25aafc23 | ||
|
|
b2c4fbb593 | ||
|
|
e2dec1f2fe | ||
|
|
5f1ef71b3b | ||
|
|
b3550bc933 | ||
|
|
09b898d655 | ||
|
|
92b92403c6 | ||
|
|
b84d393208 | ||
|
|
464f8de5f5 | ||
|
|
3a6db38172 | ||
|
|
dbefcfe342 | ||
|
|
e2d2d7fd7d | ||
|
|
31ccf490ae | ||
|
|
50ae0902f0 | ||
|
|
873f416fec | ||
|
|
70e72da404 | ||
|
|
d573ce6e10 | ||
|
|
e9159cc8c1 | ||
|
|
bf60f8f9e3 | ||
|
|
2787edbf45 | ||
|
|
bb5aeaaf15 | ||
|
|
f68f44615c | ||
|
|
66ff5942e4 | ||
|
|
a11d8eff4a | ||
|
|
b233505c24 | ||
|
|
ad564f98ef | ||
|
|
d488e7d5dd | ||
|
|
2d66fb5702 | ||
|
|
2b94e01c56 | ||
|
|
08e51fde98 | ||
|
|
e25743e3f0 | ||
|
|
055c598491 | ||
|
|
a185a94d56 | ||
|
|
f1ee9c89c0 | ||
|
|
5b81c4dc7c | ||
|
|
e54d9ab5a9 | ||
|
|
f9fc61ecf5 | ||
|
|
a0eadc282b | ||
|
|
412649ed9e | ||
|
|
be4014962c | ||
|
|
5840bcdf85 | ||
|
|
2672f426dc | ||
|
|
20f9421ea4 | ||
|
|
c1bb58cf17 | ||
|
|
f92d748de3 | ||
|
|
21597ba746 | ||
|
|
efbb959ecc | ||
|
|
b128d5de0a | ||
|
|
41d9e96406 | ||
|
|
ac6bb35d76 | ||
|
|
1316882ef4 | ||
|
|
7fdb669786 | ||
|
|
f595cee7f6 | ||
|
|
43844739a2 | ||
|
|
a3096c04f1 | ||
|
|
0684011bda | ||
|
|
eef0b1cbbb | ||
|
|
542d2ad1e9 | ||
|
|
e63a549485 | ||
|
|
3e552564dc | ||
|
|
0a8d1fa0f5 | ||
|
|
f2c87d1f66 | ||
|
|
2f2db1a03f | ||
|
|
0493710cb4 | ||
|
|
b5d73c98fe | ||
|
|
6b6e95aa3f | ||
|
|
f35ee76c95 | ||
|
|
bb66150b5c | ||
|
|
9c2a902b51 | ||
|
|
69da467b7c | ||
|
|
c8d3f341a8 | ||
|
|
3717e33d00 | ||
|
|
7ab72543a3 | ||
|
|
80f31cdff9 | ||
|
|
40056f3181 | ||
|
|
b0e9703d9f | ||
|
|
0ca97de1b2 | ||
|
|
b958734946 | ||
|
|
2acede98f3 | ||
|
|
fb2edf43be | ||
|
|
484faedcc5 | ||
|
|
8bde8741dd | ||
|
|
d5a02e6285 | ||
|
|
a35ec8cf3c | ||
|
|
ae8db9256c | ||
|
|
688dd3a39b | ||
|
|
6223f362c3 | ||
|
|
1148e4821c | ||
|
|
f32eecc0d7 | ||
|
|
2ba516f50f | ||
|
|
c4c7af54ff | ||
|
|
ae0bf6318d | ||
|
|
3be2242431 | ||
|
|
d9c0911238 | ||
|
|
5aa8369ac5 | ||
|
|
35e8cecdcf | ||
|
|
a7939414ae | ||
|
|
6c355ae5b7 | ||
|
|
843247b02d | ||
|
|
efbb8446e3 | ||
|
|
a279a2b1a3 | ||
|
|
3329dfaf20 | ||
|
|
b615bfa664 | ||
|
|
f6bd467ec8 | ||
|
|
8548c74d58 | ||
|
|
e2b93ec08c | ||
|
|
e565a5a118 | ||
|
|
3a41138f39 | ||
|
|
889457ae96 | ||
|
|
be88cdf42e | ||
|
|
e37cefeb1d | ||
|
|
ae20e55b1a | ||
|
|
bd29e1738c | ||
|
|
f28f5ee688 | ||
|
|
c92c334e03 | ||
|
|
fb8f260a94 | ||
|
|
9635bd9b43 | ||
|
|
9f5e97b8c9 | ||
|
|
8dd9ba65d0 | ||
|
|
1aec94ee7d | ||
|
|
d4b153107a | ||
|
|
d405a0d04b | ||
|
|
86587258c9 | ||
|
|
7571d35fb3 | ||
|
|
c7bef56639 | ||
|
|
d5a75c2d53 | ||
|
|
ad90785c39 | ||
|
|
e11b0c7839 | ||
|
|
a6929f2d8d | ||
|
|
9b064eea90 | ||
|
|
d9ef87e21f | ||
|
|
7b3ad98698 | ||
|
|
ea1a4e4710 | ||
|
|
2f678ba32e | ||
|
|
c00cdc7407 | ||
|
|
4bab5a59fc | ||
|
|
4932353003 | ||
|
|
5997579330 | ||
|
|
7abb52b42d | ||
|
|
5b0b3b6c70 | ||
|
|
65d361625e | ||
|
|
8d09d9d604 | ||
|
|
41cf116e6d | ||
|
|
ddfd9bd0d8 | ||
|
|
5abdf1e4b0 | ||
|
|
4234008337 | ||
|
|
7b7c2da67c | ||
|
|
12dd865cec | ||
|
|
ff09bdd110 | ||
|
|
ec788f2472 | ||
|
|
0c53fa6c0b | ||
|
|
b2eae2c33f | ||
|
|
7da1e48aa5 | ||
|
|
10b6f533b2 | ||
|
|
6a77dbe8b5 | ||
|
|
6f7aedbcd2 | ||
|
|
97285f463e | ||
|
|
6e643cb43b | ||
|
|
6cb734fb94 | ||
|
|
df846374f5 | ||
|
|
65ff843ada | ||
|
|
26a7876525 | ||
|
|
718cece22e | ||
|
|
00ce3c038b | ||
|
|
d4e7211156 | ||
|
|
1b3e254a3f | ||
|
|
52748ba66f | ||
|
|
86057b5923 | ||
|
|
ef8223bd8b | ||
|
|
7d1e2fec90 | ||
|
|
382597f356 | ||
|
|
45200d0480 | ||
|
|
1534fb598b | ||
|
|
819cc625a1 | ||
|
|
7e82b6e400 | ||
|
|
02c44f514a | ||
|
|
6ef5d4b6aa | ||
|
|
cb8c1341e2 | ||
|
|
b2391dd66a | ||
|
|
bca9f5e859 | ||
|
|
b654ef1b43 | ||
|
|
348abcefe9 | ||
|
|
a96fcd944e | ||
|
|
05aa52b032 | ||
|
|
cce9befe8c | ||
|
|
8e7ec7af4c | ||
|
|
2e1845887c | ||
|
|
c1be5be188 | ||
|
|
76b6853f90 | ||
|
|
89935ac42b | ||
|
|
050b3b3007 | ||
|
|
249dbdaaf8 | ||
|
|
dbb006d745 | ||
|
|
5d4197076c | ||
|
|
1e223b1a2a | ||
|
|
b19e7e1495 | ||
|
|
1ef7e2173b | ||
|
|
bef6ffc094 | ||
|
|
06b08d595b | ||
|
|
4fb031d76c | ||
|
|
c4f13fc8bd | ||
|
|
8a534d11d2 | ||
|
|
57ea58fc3c | ||
|
|
245fcd7502 | ||
|
|
dbeb00ba1c | ||
|
|
cbfd7ad1b1 | ||
|
|
d4033a7705 | ||
|
|
96bd25eae5 | ||
|
|
ec8e934bf4 | ||
|
|
3092ba1fc6 | ||
|
|
5ea17700b3 | ||
|
|
d418444dc0 | ||
|
|
531b003347 | ||
|
|
dca88a58e7 | ||
|
|
da878a9fab | ||
|
|
95552a7a55 | ||
|
|
90b638eff0 | ||
|
|
ccd4fd9aba | ||
|
|
5e94b1b689 | ||
|
|
f3f51cf244 | ||
|
|
5f109b5085 | ||
|
|
2d15c4864f | ||
|
|
b183f7af42 | ||
|
|
2ece0856d4 | ||
|
|
429c76ce03 | ||
|
|
a0cc8a8a3d | ||
|
|
5b53b50b01 | ||
|
|
506d0f13c7 | ||
|
|
6a0a7d70bc | ||
|
|
3742940290 | ||
|
|
1cb647b7ae | ||
|
|
ffeae93728 | ||
|
|
e90bd136f6 | ||
|
|
30eb11b85e | ||
|
|
a04598c77a | ||
|
|
cad2df79b6 | ||
|
|
089136552b | ||
|
|
1b0bc13903 | ||
|
|
d125fab0b7 | ||
|
|
13210343db | ||
|
|
3c4ac8b01a | ||
|
|
0a888a72c8 | ||
|
|
40f33dff89 | ||
|
|
5938e38070 | ||
|
|
31bc171d6b | ||
|
|
0967234ad8 | ||
|
|
911c9e4704 | ||
|
|
072c3a992c | ||
|
|
1e0e4831b8 | ||
|
|
e804dbd48e | ||
|
|
9a5aa217e6 | ||
|
|
c6beaec102 | ||
|
|
5a9944f79d | ||
|
|
89c267aa5d | ||
|
|
f33296b44f | ||
|
|
a8416b073e | ||
|
|
fd4a7c5716 | ||
|
|
771e719963 | ||
|
|
abbf6565d4 | ||
|
|
5263329ab5 | ||
|
|
c3d2c17830 | ||
|
|
29213421ec | ||
|
|
c5293715e1 | ||
|
|
2c40a7f105 | ||
|
|
a3ed2bc068 | ||
|
|
16cc70f344 | ||
|
|
6dd783051f | ||
|
|
1bb85d0fa0 | ||
|
|
dfeb87be10 | ||
|
|
dae50a7b88 | ||
|
|
63324fcec1 | ||
|
|
49642f5a1d | ||
|
|
016d0f889c | ||
|
|
5184be0e98 | ||
|
|
0643ecc00b | ||
|
|
7e5dcd3814 | ||
|
|
fe84feb184 | ||
|
|
54d3b34876 | ||
|
|
b6dfc3d17b | ||
|
|
96c6b9c214 | ||
|
|
27666c193e | ||
|
|
b76f7202a4 | ||
|
|
7ccba88780 | ||
|
|
87d324b063 | ||
|
|
e397c036e4 | ||
|
|
29384596d4 | ||
|
|
88a741c93a | ||
|
|
db3490f61a | ||
|
|
4930c1032e | ||
|
|
202b4de5ca | ||
|
|
6763bfd997 | ||
|
|
aeba03a769 | ||
|
|
8f9585e4bc | ||
|
|
e5e0464929 | ||
|
|
7f25fa07a4 | ||
|
|
c2537f329d | ||
|
|
b7ffa3966d | ||
|
|
9240fb82e4 | ||
|
|
2eb41e932b | ||
|
|
51e299998f | ||
|
|
8b00773c84 | ||
|
|
b42ec0ae13 | ||
|
|
78d7865207 | ||
|
|
2f6578fd5a | ||
|
|
0844939eca | ||
|
|
8f2d55c146 | ||
|
|
3e5e6ce3ab | ||
|
|
4831750ffd | ||
|
|
7d7380d622 | ||
|
|
a0b9e92ae9 | ||
|
|
ce180f1bbb | ||
|
|
c99e5ce2de | ||
|
|
5ac5e31dd2 | ||
|
|
eaa7923d1f | ||
|
|
bcc7e6756e | ||
|
|
56367cc14e | ||
|
|
6e0ce3b742 | ||
|
|
aa42e0ad18 | ||
|
|
c0abc1d647 | ||
|
|
5de6dc3473 | ||
|
|
fab018782c | ||
|
|
0211729525 | ||
|
|
540ece5a40 | ||
|
|
bb6255b6a4 | ||
|
|
78e7adfbc1 | ||
|
|
6f26ae50ea | ||
|
|
a5e57f1836 | ||
|
|
9e5fefa3ee | ||
|
|
8b16135955 | ||
|
|
a1108889cb | ||
|
|
150c8e0312 | ||
|
|
f3916b4ef6 | ||
|
|
8df4292e08 | ||
|
|
05c768610e | ||
|
|
21a5242abe | ||
|
|
deb9eb8d9b | ||
|
|
4a91d87d9d | ||
|
|
408d01d546 | ||
|
|
3b9e42a256 | ||
|
|
6d500e52e7 | ||
|
|
3f72b49286 | ||
|
|
8b9f13e9e7 | ||
|
|
064db9fb6a | ||
|
|
c47f8606cd | ||
|
|
3e2f10a5b9 | ||
|
|
b060b70a6b | ||
|
|
7ea7d78e66 | ||
|
|
b64175ff6e | ||
|
|
164cc09f19 | ||
|
|
0960f61c37 | ||
|
|
f8bf864fc9 | ||
|
|
93aece75cf | ||
|
|
5159258de5 | ||
|
|
68a834ac14 | ||
|
|
33a430419c | ||
|
|
eb4ffebba0 | ||
|
|
53d4c4c03e | ||
|
|
e80585f77e | ||
|
|
0ff2fe6d6a | ||
|
|
b0885ff60a | ||
|
|
a55fbca16a | ||
|
|
fcd69e3e6f | ||
|
|
28e87fe216 | ||
|
|
1d2095b0b3 | ||
|
|
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 | ||
|
|
291af8d017 | ||
|
|
b0ff0b9185 | ||
|
|
7653d71b3d | ||
|
|
5ea2f1c736 | ||
|
|
721c69619e | ||
|
|
58937d4e20 | ||
|
|
ed53abb29f | ||
|
|
53c5d11076 | ||
|
|
4d65230476 | ||
|
|
20d37e2f90 | ||
|
|
101b807b5c | ||
|
|
2f526a7725 | ||
|
|
3931af096d | ||
|
|
c6d05b4631 | ||
|
|
27747b6cb9 | ||
|
|
dbf1d423e8 | ||
|
|
eb771e9dfa | ||
|
|
a84694b100 | ||
|
|
9b240ddf5f | ||
|
|
c409132825 | ||
|
|
f6f28f6a58 | ||
|
|
b2300328e1 | ||
|
|
9205dbef59 | ||
|
|
0651494393 | ||
|
|
4f3d42264d | ||
|
|
0cdae9bccf | ||
|
|
5677373421 | ||
|
|
0dc606b470 | ||
|
|
65237c18d7 | ||
|
|
adf7916a4c | ||
|
|
01b786fcf9 | ||
|
|
99c50a3fc8 | ||
|
|
e9057cb866 | ||
|
|
4b0c6ad911 | ||
|
|
263b401dc6 | ||
|
|
43855ab555 | ||
|
|
9fdeb8e639 | ||
|
|
a19523a3f8 | ||
|
|
023e5dd2fc | ||
|
|
0af9bb887a | ||
|
|
cae000ef5f | ||
|
|
5856403c66 | ||
|
|
02b5542388 | ||
|
|
1e1beec6e4 | ||
|
|
870de652be | ||
|
|
f13679cd2c | ||
|
|
9e0e07967f | ||
|
|
d182b4edf1 | ||
|
|
a2572d996b | ||
|
|
c3d2389829 | ||
|
|
074979095b | ||
|
|
ba95a53ebf | ||
|
|
bb6a7af423 | ||
|
|
b181d0d026 | ||
|
|
f5039d72b9 | ||
|
|
9a72c0c8e5 | ||
|
|
44e2596a30 | ||
|
|
32e3f1e9ba | ||
|
|
d7032b8475 | ||
|
|
7d03b62dd7 | ||
|
|
fd7ceef2ee | ||
|
|
581539b0d0 | ||
|
|
fae92be38a | ||
|
|
9f354bf248 | ||
|
|
fc84a89dd2 | ||
|
|
1d6707ef82 | ||
|
|
82e2c82666 | ||
|
|
0f838965fb | ||
|
|
4f5454b4b7 | ||
|
|
1bc348fa1a | ||
|
|
54218f86d8 | ||
|
|
d9d1e9a12c | ||
|
|
2720a80e2a | ||
|
|
1e1d41579a | ||
|
|
96b4326d7d | ||
|
|
f1d05f2e6a | ||
|
|
280d34d85c | ||
|
|
732ebb61fb | ||
|
|
67874655fa | ||
|
|
fc78037fb0 | ||
|
|
2226c8d59e | ||
|
|
a3c72e572c | ||
|
|
159cb02899 | ||
|
|
d8bd3ee6b6 | ||
|
|
d9b1809e58 | ||
|
|
63e7465433 | ||
|
|
ad7a223c22 | ||
|
|
c2b41859c5 | ||
|
|
ed645cb421 | ||
|
|
d1e8ed63a4 | ||
|
|
949768ac95 | ||
|
|
94b56f624f | ||
|
|
cff5c7b9a2 | ||
|
|
9f5a27c06c | ||
|
|
6bd628c346 | ||
|
|
448ef1597f | ||
|
|
be96f1b9d4 | ||
|
|
b8511ec4f8 | ||
|
|
e5f7488a7c | ||
|
|
5183832e35 | ||
|
|
ae15372d8d | ||
|
|
16fce43739 | ||
|
|
170db5077d | ||
|
|
a2f2216df2 | ||
|
|
cd25e2b280 | ||
|
|
efbb9b3a19 | ||
|
|
0faa1be4e4 | ||
|
|
f03f9e63d4 | ||
|
|
5e52b5a53d | ||
|
|
f4bdf5178a | ||
|
|
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
|
||||
|
||||
# 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.
|
||||
20
.github/ISSUE_TEMPLATE/bug.yml
vendored
20
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Android 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:
|
||||
@@ -51,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:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -12,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
|
||||
|
||||
20
.github/actions/log-inputs/action.yml
vendored
Normal file
20
.github/actions/log-inputs/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Log Inputs to Job Summary'
|
||||
description: 'Log workflow inputs to the GitHub Actions job summary'
|
||||
|
||||
inputs:
|
||||
inputs:
|
||||
description: 'Workflow inputs as JSON'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
shell: bash
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ inputs.inputs }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
49
.github/actions/setup-android-build/action.yml
vendored
Normal file
49
.github/actions/setup-android-build/action.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: 'Setup Android Build'
|
||||
description: 'Setup Android build environment with Gradle, Ruby, and Fastlane'
|
||||
inputs:
|
||||
java-version:
|
||||
description: 'Java version to use'
|
||||
required: false
|
||||
default: '17'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
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@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
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@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ inputs.java-version }}
|
||||
|
||||
- name: Install Fastlane
|
||||
shell: bash
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
2
.github/codecov.yml
vendored
Normal file
2
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- "src/test/**" # Tests
|
||||
50
.github/renovate.json
vendored
50
.github/renovate.json
vendored
@@ -1,32 +1,56 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>bitwarden/renovate-config"],
|
||||
"enabledManagers": ["github-actions", "gradle", "bundler"],
|
||||
"extends": [
|
||||
"github>bitwarden/renovate-config"
|
||||
],
|
||||
"enabledManagers": [
|
||||
"github-actions",
|
||||
"gradle",
|
||||
"bundler"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "gh minor",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "gradle minor",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchManagers": ["gradle"]
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "kotlin",
|
||||
"description": "Kotlin and Compose dependencies that must be updated together to maintain compatibility.",
|
||||
"matchPackagePatterns": [
|
||||
"androidx.compose:compose-bom",
|
||||
"org.jetbrains.kotlin.*",
|
||||
"com.google.devtools.ksp"
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
],
|
||||
"matchManagers": ["gradle"]
|
||||
"matchPackageNames": [
|
||||
"/androidx.compose:compose-bom/",
|
||||
"/androidx.lifecycle:*/",
|
||||
"/org.jetbrains.kotlin.*/",
|
||||
"/com.google.devtools.ksp/"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "bundler minor",
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchManagers": ["bundler"]
|
||||
"matchUpdateTypes": [
|
||||
"minor",
|
||||
"patch"
|
||||
],
|
||||
"matchManagers": [
|
||||
"bundler"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
133
.github/scripts/jira-get-release-notes/README.md
vendored
Normal file
133
.github/scripts/jira-get-release-notes/README.md
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# Get Release Notes from Jira script
|
||||
|
||||
Fetches release notes from Jira issues.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python dev environment - use [uv](https://github.com/astral-sh/uv)
|
||||
- Jira API token. Generate one at: https://id.atlassian.com/manage-profile/security/api-tokens
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
uv pip install -r pyproject.toml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./jira_release_notes.py RELEASE-1762 example@example.com T0k3n123
|
||||
```
|
||||
|
||||
# Output Format
|
||||
|
||||
The script retrieves the content from a custom field and handles two types of Jira release notes formats:
|
||||
|
||||
1. Bullet Points:
|
||||
```
|
||||
• Point 1
|
||||
• Point 2
|
||||
• Point 3
|
||||
```
|
||||
|
||||
2. Single Line:
|
||||
```
|
||||
Single line of release notes text
|
||||
```
|
||||
|
||||
## Jira JSON format example
|
||||
|
||||
### Single line
|
||||
|
||||
```json
|
||||
...
|
||||
"customfield_10335": {
|
||||
"type": "doc",
|
||||
"version": 1,
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Single line release notes"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
### Bullet points
|
||||
|
||||
```json
|
||||
...
|
||||
"customfield_10335": {
|
||||
"type": "doc",
|
||||
"version": 1,
|
||||
"content": [
|
||||
{
|
||||
"type": "bulletList",
|
||||
"content": [
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "listItem",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Release notes list item 4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
...
|
||||
```
|
||||
70
.github/scripts/jira-get-release-notes/jira_release_notes.py
vendored
Executable file
70
.github/scripts/jira-get-release-notes/jira_release_notes.py
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import base64
|
||||
import json
|
||||
import requests
|
||||
|
||||
def extract_text_from_content(content):
|
||||
if isinstance(content, list):
|
||||
texts = [extract_text_from_content(item) for item in content]
|
||||
return '\n'.join(text for text in texts if text.strip())
|
||||
|
||||
if isinstance(content, dict):
|
||||
if content.get('type') == 'text':
|
||||
return content.get('text', '')
|
||||
elif content.get('type') == 'paragraph':
|
||||
return extract_text_from_content(content.get('content', []))
|
||||
elif content.get('type') == 'bulletList':
|
||||
return extract_text_from_content(content.get('content', []))
|
||||
elif content.get('type') == 'listItem':
|
||||
item_text = extract_text_from_content(content.get('content', []))
|
||||
return f"* {item_text.strip()}"
|
||||
|
||||
return ''
|
||||
|
||||
def parse_release_notes(response_json):
|
||||
try:
|
||||
fields = response_json.get('fields', {})
|
||||
release_notes_field = fields.get('customfield_10335', {})
|
||||
|
||||
if not release_notes_field or not release_notes_field.get('content'):
|
||||
return ''
|
||||
|
||||
release_notes = extract_text_from_content(release_notes_field.get('content', []))
|
||||
return release_notes
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing release notes: {str(e)}", file=sys.stderr)
|
||||
return ''
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(f"Usage: {sys.argv[0]} <issue_id> <jira_email> <jira_api_token>")
|
||||
sys.exit(1)
|
||||
|
||||
jira_issue_id = sys.argv[1]
|
||||
jira_email = sys.argv[2]
|
||||
jira_api_token = sys.argv[3]
|
||||
jira_base_url = "https://bitwarden.atlassian.net"
|
||||
|
||||
auth = base64.b64encode(f"{jira_email}:{jira_api_token}".encode()).decode()
|
||||
headers = {
|
||||
"Authorization": f"Basic {auth}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{jira_base_url}/rest/api/3/issue/{jira_issue_id}",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error fetching Jira issue: {response.status_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
release_notes = parse_release_notes(response.json())
|
||||
print(release_notes)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
.github/scripts/jira-get-release-notes/pyproject.toml
vendored
Normal file
9
.github/scripts/jira-get-release-notes/pyproject.toml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "jira-get-release-notes"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
91
.github/scripts/jira-get-release-notes/uv.lock
generated
vendored
Normal file
91
.github/scripts/jira-get-release-notes/uv.lock
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.4.26"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jira-get-release-notes"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "requests", specifier = ">=2.32.3" }]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
|
||||
]
|
||||
1
.github/scripts/validate-json/.python-version
vendored
Normal file
1
.github/scripts/validate-json/.python-version
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
39
.github/scripts/validate-json/README.md
vendored
Normal file
39
.github/scripts/validate-json/README.md
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# JSON Validation Scripts
|
||||
|
||||
Utility scripts for validating JSON files and checking for duplicate package names between Google and Community privileged browser lists.
|
||||
|
||||
## Usage
|
||||
|
||||
### Validate a JSON file
|
||||
|
||||
```bash
|
||||
python validate_json.py validate <json_file>
|
||||
```
|
||||
|
||||
### Check for duplicates between two JSON files
|
||||
|
||||
```bash
|
||||
python validate_json.py duplicates <json_file1> <json_file2> [output_file]
|
||||
```
|
||||
|
||||
If `output_file` is not specified, duplicates will be saved to `duplicates.txt`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
python -m unittest test_validate_json.py
|
||||
|
||||
# Run the invalid JSON test individually
|
||||
python -m unittest test_validate_json.TestValidateJson.test_validate_json_invalid
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Validate Google privileged browsers list
|
||||
python validate_json.py validate ../../app/src/main/assets/fido2_privileged_google.json
|
||||
|
||||
# Check for duplicates between Google and Community lists
|
||||
python validate_json.py duplicates ../../app/src/main/assets/fido2_privileged_google.json ../../app/src/main/assets/fido2_privileged_community.json duplicates.txt
|
||||
```
|
||||
20
.github/scripts/validate-json/fixtures/sample-invalid.json
vendored
Normal file
20
.github/scripts/validate-json/fixtures/sample-invalid.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"apps": [
|
||||
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
.github/scripts/validate-json/fixtures/sample-valid1.json
vendored
Normal file
48
.github/scripts/validate-json/fixtures/sample-valid1.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.chrome.dev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.chrome.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
20
.github/scripts/validate-json/fixtures/sample-valid2.json
vendored
Normal file
20
.github/scripts/validate-json/fixtures/sample-valid2.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.chromium.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
48
.github/scripts/validate-json/test_validate_json.py
vendored
Normal file
48
.github/scripts/validate-json/test_validate_json.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
import unittest
|
||||
import os
|
||||
import json
|
||||
from validate_json import validate_json, find_duplicates, get_package_names
|
||||
from unittest.mock import patch
|
||||
import io
|
||||
|
||||
|
||||
class TestValidateJson(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.valid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid1.json")
|
||||
self.valid_file2 = os.path.join(os.path.dirname(__file__), "fixtures/sample-valid2.json")
|
||||
self.invalid_file = os.path.join(os.path.dirname(__file__), "fixtures/sample-invalid.json")
|
||||
|
||||
# Suppress stdout
|
||||
self.stdout_patcher = patch('sys.stdout', new=io.StringIO())
|
||||
self.stdout_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.stdout_patcher.stop()
|
||||
|
||||
def test_validate_json_valid(self):
|
||||
"""Test validation of valid JSON file"""
|
||||
self.assertTrue(validate_json(self.valid_file))
|
||||
|
||||
def test_validate_json_invalid(self):
|
||||
"""Test validation of invalid JSON file"""
|
||||
self.assertFalse(validate_json(self.invalid_file))
|
||||
|
||||
def test_find_duplicates(self):
|
||||
"""Test when using the same file (should find duplicates)"""
|
||||
expected_package_names = get_package_names(self.valid_file)
|
||||
|
||||
duplicates = find_duplicates(self.valid_file, self.valid_file)
|
||||
|
||||
self.assertEqual(len(duplicates), len(expected_package_names))
|
||||
for package_name in expected_package_names:
|
||||
self.assertIn(package_name, duplicates)
|
||||
|
||||
def test_find_duplicates_returns_empty_list_when_no_duplicates(self):
|
||||
"""Test when using different files (should not find duplicates)"""
|
||||
duplicates = find_duplicates(self.valid_file, self.valid_file2)
|
||||
self.assertEqual(len(duplicates), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
145
.github/scripts/validate-json/validate_json.py
vendored
Normal file
145
.github/scripts/validate-json/validate_json.py
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from typing import List, Dict, Any, Set
|
||||
|
||||
|
||||
def get_package_names(file_path: str) -> Set[str]:
|
||||
"""
|
||||
Extracts package names from a JSON file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the JSON file
|
||||
|
||||
Returns:
|
||||
Set of package names
|
||||
"""
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
package_names = set()
|
||||
for app in data["apps"]:
|
||||
package_names.add(app["info"]["package_name"])
|
||||
|
||||
return package_names
|
||||
|
||||
|
||||
def validate_json(file_path: str) -> bool:
|
||||
"""
|
||||
Validates if a JSON file is correctly formatted by attempting to deserialize it.
|
||||
|
||||
Args:
|
||||
file_path: Path to the JSON file to validate
|
||||
|
||||
Returns:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Error: File {file_path} does not exist")
|
||||
return False
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
json.load(f)
|
||||
print(f"✅ JSON file {file_path} is valid")
|
||||
return True
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ Invalid JSON in {file_path}: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error validating {file_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def find_duplicates(file1_path: str, file2_path: str) -> List[str]:
|
||||
"""
|
||||
Checks for duplicate package_name entries between two JSON files.
|
||||
|
||||
Args:
|
||||
file1_path: Path to the first JSON file
|
||||
file2_path: Path to the second JSON file
|
||||
|
||||
Returns:
|
||||
List of duplicate package names, empty list if none found
|
||||
"""
|
||||
try:
|
||||
# Get package names from both files
|
||||
packages1 = get_package_names(file1_path)
|
||||
packages2 = get_package_names(file2_path)
|
||||
|
||||
# Find duplicates
|
||||
duplicates = list(packages1.intersection(packages2))
|
||||
|
||||
if duplicates:
|
||||
print(f"❌ Found {len(duplicates)} duplicate package names between {file1_path} and {file2_path}:")
|
||||
for dup in duplicates:
|
||||
print(f" - {dup}")
|
||||
return duplicates
|
||||
else:
|
||||
print(f"✅ No duplicate package names found between {file1_path} and {file2_path}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking duplicates: {str(e)}")
|
||||
return []
|
||||
|
||||
|
||||
def save_duplicates_to_file(duplicates: List[str], output_file: str) -> None:
|
||||
"""
|
||||
Saves the list of duplicates to a file.
|
||||
|
||||
Args:
|
||||
duplicates: List of duplicate package names
|
||||
output_file: Path to save the list of duplicates
|
||||
"""
|
||||
try:
|
||||
with open(output_file, 'w') as f:
|
||||
for dup in duplicates:
|
||||
f.write(f"{dup}\n")
|
||||
print(f"Duplicates saved to {output_file}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error saving duplicates to file: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print(" Validate JSON: python validate_json.py validate <json_file>")
|
||||
print(" Check duplicates: python validate_json.py duplicates <json_file1> <json_file2> [output_file]")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
match command:
|
||||
case "validate":
|
||||
if len(sys.argv) < 3:
|
||||
print("Error: Missing JSON file path")
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[2]
|
||||
success = validate_json(file_path)
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
case "duplicates":
|
||||
if len(sys.argv) < 4:
|
||||
print("Error: Missing JSON file paths")
|
||||
sys.exit(1)
|
||||
|
||||
file1_path = sys.argv[2]
|
||||
file2_path = sys.argv[3]
|
||||
output_file = sys.argv[4] if len(sys.argv) > 4 else "duplicates.txt"
|
||||
|
||||
duplicates = find_duplicates(file1_path, file2_path)
|
||||
if duplicates:
|
||||
save_duplicates_to_file(duplicates, output_file)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
case _:
|
||||
print(f"Unknown command: {command}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
325
.github/workflows/build-authenticator.yml
vendored
Normal file
325
.github/workflows/build-authenticator.yml
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
name: Build Authenticator
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/**/*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version-name:
|
||||
description: "Optional. Version string to use, in X.Y.Z format. Overrides default in the project."
|
||||
required: false
|
||||
type: string
|
||||
version-code:
|
||||
description: "Optional. Build number to use. Overrides default of GitHub run number."
|
||||
required: false
|
||||
type: number
|
||||
distribute-to-firebase:
|
||||
description: "Optional. Distribute artifacts to Firebase."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
publish-to-play-store:
|
||||
description: "Optional. Deploy bundle artifact to Google Play Store"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Authenticator
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
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@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Check Authenticator
|
||||
run: bundle exec fastlane check
|
||||
|
||||
- name: Build Authenticator
|
||||
run: bundle exec fastlane buildAuthenticatorDebug
|
||||
|
||||
publish_playstore:
|
||||
name: Publish Authenticator Play Store artifacts
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ["aab", "apk"]
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.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: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-android
|
||||
secrets: "BWA-AAB-KEYSTORE-STORE-PASSWORD,BWA-AAB-KEYSTORE-KEY-PASSWORD,BWA-APK-KEYSTORE-STORE-PASSWORD,BWA-APK-KEYSTORE-KEY-PASSWORD"
|
||||
|
||||
- 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: AZ Logout
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- 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@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
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@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: 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=$GITHUB_RUN_NUMBER
|
||||
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
|
||||
bundle exec fastlane setBuildVersionInfo \
|
||||
versionCode:$VERSION_CODE \
|
||||
versionName:${{ inputs.version-name || '' }}
|
||||
|
||||
regex='appVersionName = "([^"]+)"'
|
||||
if [[ "$(cat gradle/libs.versions.toml)" =~ $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:'${{ steps.get-kv-secrets.outputs.BWA-AAB-KEYSTORE-STORE-PASSWORD }}' \
|
||||
keyAlias:authenticatorupload \
|
||||
keyPassword:'${{ steps.get-kv-secrets.outputs.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:'${{ steps.get-kv-secrets.outputs.BWA-APK-KEYSTORE-STORE-PASSWORD }}' \
|
||||
keyAlias:bitwardenauthenticator \
|
||||
keyPassword:'${{ steps.get-kv-secrets.outputs.BWA-APK-KEYSTORE-KEY-PASSWORD }}'
|
||||
|
||||
- name: Upload release Play Store .aab artifact
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.bitwarden.authenticator.aab
|
||||
path: authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload release .apk artifact
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.bitwarden.authenticator.apk
|
||||
path: authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum file for Release AAB
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
run: |
|
||||
sha256sum "authenticator/build/outputs/bundle/release/com.bitwarden.authenticator.aab" \
|
||||
> ./authenticator-android-aab-sha256.txt
|
||||
|
||||
- name: Create checksum for release .apk artifact
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
run: |
|
||||
sha256sum "authenticator/build/outputs/apk/release/com.bitwarden.authenticator.apk" \
|
||||
> ./authenticator-android-apk-sha256.txt
|
||||
|
||||
- name: Upload .apk SHA file for release
|
||||
if: ${{ matrix.variant == 'apk' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: authenticator-android-apk-sha256.txt
|
||||
path: ./authenticator-android-apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .aab SHA file for release
|
||||
if: ${{ matrix.variant == 'aab' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: authenticator-android-aab-sha256.txt
|
||||
path: ./authenticator-android-aab-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install Firebase app distribution plugin
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
run: bundle exec fastlane add_plugin firebase_app_distribution
|
||||
|
||||
- name: Publish release bundle to Firebase
|
||||
if: ${{ matrix.variant == 'aab' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
env:
|
||||
FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/authenticator_play_firebase-creds.json
|
||||
run: |
|
||||
bundle exec fastlane distributeAuthenticatorReleaseBundleToFirebase \
|
||||
serviceCredentialsFile:${{ env.FIREBASE_CREDS_PATH }}
|
||||
|
||||
# Only publish bundles to Play Store when `publish-to-play-store` is true while building
|
||||
# bundles
|
||||
- name: Publish release bundle to Google Play Store
|
||||
if: ${{ inputs.publish-to-play-store && matrix.variant == 'aab' }}
|
||||
env:
|
||||
PLAY_STORE_CREDS_FILE: ${{ github.workspace }}/secrets/authenticator_play_store-creds.json
|
||||
run: |
|
||||
bundle exec fastlane publishAuthenticatorReleaseToGooglePlayStore \
|
||||
serviceCredentialsFile:${{ env.PLAY_STORE_CREDS_FILE }} \
|
||||
310
.github/workflows/build.yml
vendored
310
.github/workflows/build.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release/**/*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version-name:
|
||||
@@ -25,21 +29,36 @@ on:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -49,7 +68,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -58,13 +77,13 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -75,27 +94,34 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Check
|
||||
run: bundle exec fastlane test
|
||||
run: bundle exec fastlane check
|
||||
|
||||
- name: Build
|
||||
run: bundle exec fastlane assembleDebugApks
|
||||
|
||||
- name: Upload test reports on failure
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -106,9 +132,18 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-android
|
||||
secrets: "UPLOAD-KEYSTORE-PASSWORD,UPLOAD-BETA-KEYSTORE-PASSWORD,UPLOAD-BETA-KEY-PASSWORD,PLAY-KEYSTORE-PASSWORD,PLAY-BETA-KEYSTORE-PASSWORD,PLAY-BETA-KEY-PASSWORD"
|
||||
|
||||
- name: Retrieve secrets
|
||||
env:
|
||||
@@ -135,7 +170,7 @@ jobs:
|
||||
--name google-services.json --file ${{ github.workspace }}/app/src/standardBeta/google-services.json --output none
|
||||
|
||||
- name: Download Firebase credentials
|
||||
if: ${{ matrix.variant == 'prod' && inputs.distribute-to-firebase }}
|
||||
if: ${{ matrix.variant == 'prod' && (inputs.distribute-to-firebase || github.event_name == 'push') }}
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
@@ -145,11 +180,14 @@ jobs:
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name app_play_prod_firebase-creds.json --file ${{ github.workspace }}/secrets/app_play_prod_firebase-creds.json --output none
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -159,7 +197,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -168,14 +206,23 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Update app CI Build info
|
||||
run: |
|
||||
./scripts/update_app_ci_build_info.sh \
|
||||
$GITHUB_REPOSITORY \
|
||||
$GITHUB_REF_NAME \
|
||||
$GITHUB_SHA \
|
||||
$GITHUB_RUN_ID \
|
||||
$GITHUB_RUN_ATTEMPT
|
||||
|
||||
- name: Increment version
|
||||
run: |
|
||||
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
||||
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
|
||||
bundle exec fastlane setBuildVersionInfo \
|
||||
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
|
||||
versionName:${{ inputs.version-name }}
|
||||
@@ -183,183 +230,185 @@ jobs:
|
||||
- name: Generate release Play Store bundle
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' }}
|
||||
env:
|
||||
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
UPLOAD-KEYSTORE-PASSWORD: ${{ steps.get-kv-secrets.outputs.UPLOAD-KEYSTORE-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane bundlePlayStoreRelease \
|
||||
storeFile:app_upload-keystore.jks \
|
||||
storePassword:${{ env.UPLOAD_KEYSTORE_PASSWORD }} \
|
||||
storePassword:${{ env.UPLOAD-KEYSTORE-PASSWORD }} \
|
||||
keyAlias:upload \
|
||||
keyPassword:${{ env.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
keyPassword:${{ env.UPLOAD-KEYSTORE-PASSWORD }}
|
||||
|
||||
- name: Generate beta Play Store bundle
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
env:
|
||||
UPLOAD_BETA_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_BETA_KEYSTORE_PASSWORD }}
|
||||
UPLOAD_BETA_KEY_PASSWORD: ${{ secrets.UPLOAD_BETA_KEY_PASSWORD }}
|
||||
UPLOAD-BETA-KEYSTORE-PASSWORD: ${{ steps.get-kv-secrets.outputs.UPLOAD-BETA-KEYSTORE-PASSWORD }}
|
||||
UPLOAD-BETA-KEY-PASSWORD: ${{ steps.get-kv-secrets.outputs.UPLOAD-BETA-KEY-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane bundlePlayStoreBeta \
|
||||
storeFile:app_beta_upload-keystore.jks \
|
||||
storePassword:${{ env.UPLOAD_BETA_KEYSTORE_PASSWORD }} \
|
||||
storePassword:${{ env.UPLOAD-BETA-KEYSTORE-PASSWORD }} \
|
||||
keyAlias:bitwarden-beta-upload \
|
||||
keyPassword:${{ env.UPLOAD_BETA_KEY_PASSWORD }}
|
||||
keyPassword:${{ env.UPLOAD-BETA-KEY-PASSWORD }}
|
||||
|
||||
- name: Generate release Play Store APK
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
||||
PLAY-KEYSTORE-PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-KEYSTORE-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane assemblePlayStoreReleaseApk \
|
||||
storeFile:app_play-keystore.jks \
|
||||
storePassword:${{ env.PLAY_KEYSTORE_PASSWORD }} \
|
||||
storePassword:${{ env.PLAY-KEYSTORE-PASSWORD }} \
|
||||
keyAlias:bitwarden \
|
||||
keyPassword:${{ env.PLAY_KEYSTORE_PASSWORD }}
|
||||
keyPassword:${{ env.PLAY-KEYSTORE-PASSWORD }}
|
||||
|
||||
- name: Generate beta Play Store APK
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
env:
|
||||
PLAY_BETA_KEYSTORE_PASSWORD: ${{ secrets.PLAY_BETA_KEYSTORE_PASSWORD }}
|
||||
PLAY_BETA_KEY_PASSWORD: ${{ secrets.PLAY_BETA_KEY_PASSWORD }}
|
||||
PLAY-BETA-KEYSTORE-PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEYSTORE-PASSWORD }}
|
||||
PLAY-BETA-KEY-PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEY-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane assemblePlayStoreBetaApk \
|
||||
storeFile:app_beta_play-keystore.jks \
|
||||
storePassword:${{ env.PLAY_BETA_KEYSTORE_PASSWORD }} \
|
||||
storePassword:${{ env.PLAY-BETA-KEYSTORE-PASSWORD }} \
|
||||
keyAlias:bitwarden-beta \
|
||||
keyPassword:${{ env.PLAY_BETA_KEY_PASSWORD }}
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab
|
||||
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden-standard-release.aab
|
||||
path: app/build/outputs/bundle/standardRelease/com.x8bit.bitwarden.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload beta Play Store .aab artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.aab
|
||||
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden-standard-beta.aab
|
||||
path: app/build/outputs/bundle/standardBeta/com.x8bit.bitwarden.beta.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload release .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk
|
||||
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden-standard-release.apk
|
||||
path: app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload beta .apk artifact
|
||||
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta.apk
|
||||
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden-standard-beta.apk
|
||||
path: app/build/outputs/apk/standard/beta/com.x8bit.bitwarden.beta.apk
|
||||
if-no-files-found: error
|
||||
|
||||
# When building variants other than 'prod'
|
||||
- name: Upload other .apk artifact
|
||||
- name: Upload debug .apk artifact
|
||||
if: ${{ (matrix.variant != 'prod') && (matrix.artifact == 'apk') }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden-${{ matrix.variant }}.apk
|
||||
path: app/build/outputs/apk/standard/debug/com.x8bit.bitwarden-standard-debug.apk
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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 }}
|
||||
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 }}
|
||||
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 }}
|
||||
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
|
||||
@@ -368,20 +417,22 @@ jobs:
|
||||
bundle exec fastlane run validate_play_store_json_key
|
||||
|
||||
- name: Publish Play Store bundle
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && inputs.publish-to-play-store }}
|
||||
run: bundle exec fastlane publishForInternalTesting
|
||||
if: ${{ matrix.variant == 'prod' && matrix.artifact == 'aab' && (inputs.publish-to-play-store || github.ref_name == 'main') }}
|
||||
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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
@@ -392,9 +443,18 @@ jobs:
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-android
|
||||
secrets: "FDROID-KEYSTORE-PASSWORD,FDROID-BETA-KEYSTORE-PASSWORD,FDROID-BETA-KEY-PASSWORD"
|
||||
|
||||
- name: Retrieve secrets
|
||||
env:
|
||||
@@ -407,7 +467,7 @@ jobs:
|
||||
--name app_beta_fdroid-keystore.jks --file ${{ github.workspace }}/keystores/app_beta_fdroid-keystore.jks --output none
|
||||
|
||||
- name: Download Firebase credentials
|
||||
if: ${{ inputs.distribute-to-firebase }}
|
||||
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
@@ -417,11 +477,14 @@ jobs:
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name app_fdroid_firebase-creds.json --file ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json --output none
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -431,7 +494,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -440,22 +503,38 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
|
||||
- name: Update app CI Build info
|
||||
run: |
|
||||
./scripts/update_app_ci_build_info.sh \
|
||||
$GITHUB_REPOSITORY \
|
||||
$GITHUB_REF_NAME \
|
||||
$GITHUB_SHA \
|
||||
$GITHUB_RUN_ID \
|
||||
$GITHUB_RUN_ATTEMPT
|
||||
|
||||
# Start from 11000 to prevent collisions with mobile build version codes
|
||||
- name: Increment version
|
||||
run: |
|
||||
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
||||
DEFAULT_VERSION_CODE=$((11000+GITHUB_RUN_NUMBER))
|
||||
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
|
||||
bundle exec fastlane setBuildVersionInfo \
|
||||
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
|
||||
versionCode:$VERSION_CODE \
|
||||
versionName:${{ inputs.version-name || '' }}
|
||||
|
||||
regex='appVersionName = "([^"]+)"'
|
||||
if [[ "$(cat gradle/libs.versions.toml)" =~ $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 }}
|
||||
FDROID_STORE_PASSWORD: ${{ steps.get-kv-secrets.outputs.FDROID-KEYSTORE-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane assembleFDroidReleaseApk \
|
||||
storeFile:app_fdroid-keystore.jks \
|
||||
@@ -463,45 +542,64 @@ 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 }}
|
||||
FDROID_BETA_KEY_PASSWORD: ${{ secrets.FDROID_BETA_KEY_PASSWORD }}
|
||||
FDROID-BETA-KEYSTORE-PASSWORD: ${{ steps.get-kv-secrets.outputs.FDROID-BETA-KEYSTORE-PASSWORD }}
|
||||
FDROID-BETA-KEY-PASSWORD: ${{ steps.get-kv-secrets.outputs.FDROID-BETA-KEY-PASSWORD }}
|
||||
run: |
|
||||
bundle exec fastlane assembleFDroidBetaApk \
|
||||
storeFile:app_beta_fdroid-keystore.jks \
|
||||
storePassword:"${{ env.FDROID_BETA_KEYSTORE_PASSWORD }}" \
|
||||
storePassword:"${{ env.FDROID-BETA-KEYSTORE-PASSWORD }}" \
|
||||
keyAlias:bitwarden-beta \
|
||||
keyPassword:"${{ env.FDROID_BETA_KEY_PASSWORD }}"
|
||||
keyPassword:"${{ env.FDROID-BETA-KEY-PASSWORD }}"
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk
|
||||
path: app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for F-Droid artifact
|
||||
run: |
|
||||
sha256sum "app/build/outputs/apk/fdroid/release/com.x8bit.bitwarden-fdroid-release.apk" \
|
||||
> ./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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
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.beta-fdroid.apk" \
|
||||
> ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
|
||||
- name: Upload F-Droid Beta SHA file
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
path: ./com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install Firebase app distribution plugin
|
||||
if: ${{ 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- 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")
|
||||
71
.github/workflows/crowdin-pull.yml
vendored
Normal file
71
.github/workflows/crowdin-pull.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Cron / Crowdin Pull
|
||||
run-name: Crowdin Pull - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'Scheduled' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 5'
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
name: Crowdin Pull - ${{ github.event_name }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-org-bitwarden
|
||||
secrets: "BW-GHAPP-ID,BW-GHAPP-KEY"
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
|
||||
private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }}
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@590c05e09a29f392b203faf4d6aa8e0cd32c7835 # v2.9.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
with:
|
||||
config: crowdin.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: "Crowdin Pull"
|
||||
localization_branch_name: "crowdin-pull"
|
||||
create_pull_request: true
|
||||
pull_request_title: "Crowdin Pull"
|
||||
pull_request_body: ":inbox_tray: New translations received!"
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
47
.github/workflows/crowdin-push.yml
vendored
Normal file
47
.github/workflows/crowdin-push.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: CI / Crowdin Push
|
||||
run-name: Crowdin Push - ${{ github.event_name == 'workflow_dispatch' && 'Manual' || 'CI' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
crowdin-push:
|
||||
name: Crowdin Push - ${{ github.event_name }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Upload sources
|
||||
uses: crowdin/github-action@590c05e09a29f392b203faf4d6aa8e0cd32c7835 # v2.9.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
282
.github/workflows/github-release.yml
vendored
Normal file
282
.github/workflows/github-release.yml
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
name: Create GitHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
artifact-run-id:
|
||||
description: 'GitHub Action Run ID containing artifacts'
|
||||
required: true
|
||||
type: string
|
||||
release-ticket-id:
|
||||
description: 'Release Ticket ID - e.g. RELEASE-1762'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
ARTIFACTS_PATH: artifacts
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log inputs to job summary
|
||||
uses: ./.github/actions/log-inputs
|
||||
with:
|
||||
inputs: ${{ toJson(inputs) }}
|
||||
|
||||
- name: Get branch from workflow run
|
||||
id: get_release_branch
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
run: |
|
||||
workflow_data=$(gh run view $ARTIFACT_RUN_ID --json headBranch,workflowName)
|
||||
release_branch=$(echo "$workflow_data" | jq -r .headBranch)
|
||||
workflow_name=$(echo "$workflow_data" | jq -r .workflowName)
|
||||
|
||||
# branch protection check
|
||||
if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then
|
||||
echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔖 Release branch: $release_branch"
|
||||
echo "🔖 Workflow name: $workflow_name"
|
||||
echo "release_branch=$release_branch" >> $GITHUB_OUTPUT
|
||||
echo "workflow_name=$workflow_name" >> $GITHUB_OUTPUT
|
||||
|
||||
case "$workflow_name" in
|
||||
*"Password Manager"* | "Build")
|
||||
app_name="Password Manager"
|
||||
app_name_suffix="bwpm"
|
||||
;;
|
||||
*"Authenticator"*)
|
||||
app_name="Authenticator"
|
||||
app_name_suffix="bwa"
|
||||
;;
|
||||
*)
|
||||
echo "::error::Unknown workflow name: $workflow_name"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "🔖 App name: $app_name"
|
||||
echo "🔖 App name suffix: $app_name_suffix"
|
||||
echo "app_name=$app_name" >> $GITHUB_OUTPUT
|
||||
echo "app_name_suffix=$app_name_suffix" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get version info from run logs and set release tag name
|
||||
id: get_release_info
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }}
|
||||
run: |
|
||||
workflow_log=$(gh run view $ARTIFACT_RUN_ID --log)
|
||||
|
||||
version_number_with_trailing_dot=$(grep -m 1 "Setting version code to" <<< "$workflow_log" | sed 's/.*Setting version code to //')
|
||||
version_number=${version_number_with_trailing_dot%.} # remove trailing dot
|
||||
|
||||
version_name_with_trailing_dot=$(grep -m 1 "Setting version name to" <<< "$workflow_log" | sed 's/.*Setting version name to //')
|
||||
version_name=${version_name_with_trailing_dot%.} # remove trailing dot
|
||||
|
||||
if [[ -z "$version_name" ]]; then
|
||||
echo "::warning::Version name not found. Using default value - 0.0.0"
|
||||
version_name="0.0.0"
|
||||
else
|
||||
echo "✅ Found version name: $version_name"
|
||||
fi
|
||||
|
||||
if [[ -z "$version_number" ]]; then
|
||||
echo "::warning::Version number not found. Using default value - 0"
|
||||
version_number="0"
|
||||
else
|
||||
echo "✅ Found version number: $version_number"
|
||||
fi
|
||||
|
||||
echo "version_number=$version_number" >> $GITHUB_OUTPUT
|
||||
echo "version_name=$version_name" >> $GITHUB_OUTPUT
|
||||
|
||||
tag_name="v$version_name-$_APP_NAME_SUFFIX" # e.g. v2025.6.0-bwpm
|
||||
echo "🔖 New tag name: $tag_name"
|
||||
echo "tag_name=$tag_name" >> $GITHUB_OUTPUT
|
||||
|
||||
last_release_tag=$(git tag -l --sort=-authordate | grep "$_APP_NAME_SUFFIX" | head -n 1)
|
||||
echo "🔖 Last release tag: $last_release_tag"
|
||||
echo "last_release_tag=$last_release_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
run: |
|
||||
gh run download $ARTIFACT_RUN_ID -D $ARTIFACTS_PATH
|
||||
file_count=$(find $ARTIFACTS_PATH -type f | wc -l)
|
||||
echo "Downloaded $file_count file(s)."
|
||||
if [ "$file_count" -gt 0 ]; then
|
||||
echo "Downloaded files:"
|
||||
find $ARTIFACTS_PATH -type f
|
||||
fi
|
||||
|
||||
# Files that won't be included in any release
|
||||
files_to_remove=(
|
||||
"com.x8bit.bitwarden.aab"
|
||||
"com.x8bit.bitwarden.aab-sha256.txt"
|
||||
|
||||
"com.x8bit.bitwarden.beta.apk"
|
||||
"com.x8bit.bitwarden.beta.apk-sha256.txt"
|
||||
"com.x8bit.bitwarden.beta.aab"
|
||||
"com.x8bit.bitwarden.beta.aab-sha256.txt"
|
||||
|
||||
"com.x8bit.bitwarden.beta-fdroid.apk"
|
||||
"com.x8bit.bitwarden.beta-fdroid.apk-sha256.txt"
|
||||
|
||||
"com.x8bit.bitwarden.dev.apk"
|
||||
"com.x8bit.bitwarden.dev.apk-sha256.txt"
|
||||
|
||||
"com.bitwarden.authenticator.aab"
|
||||
"authenticator-android-aab-sha256.txt"
|
||||
)
|
||||
|
||||
for file in "${files_to_remove[@]}"; do
|
||||
find $ARTIFACTS_PATH -name "$file" -type f -delete
|
||||
done
|
||||
echo "🔖 Removed internal artifacts."
|
||||
echo ""
|
||||
echo "🔖 Files to be included in the release:"
|
||||
find $ARTIFACTS_PATH -type f
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-android
|
||||
secrets: "JIRA-API-EMAIL,JIRA-API-TOKEN"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Get product release notes
|
||||
id: get_release_notes
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_RELEASE_TICKET_ID: ${{ inputs.release-ticket-id }}
|
||||
_JIRA_API_EMAIL: ${{ steps.get-kv-secrets.outputs.JIRA-API-EMAIL }}
|
||||
_JIRA_API_TOKEN: ${{ steps.get-kv-secrets.outputs.JIRA-API-TOKEN }}
|
||||
run: |
|
||||
echo "Getting product release notes"
|
||||
product_release_notes=$(python3 .github/scripts/jira-get-release-notes/jira_release_notes.py $_RELEASE_TICKET_ID $_JIRA_API_EMAIL $_JIRA_API_TOKEN)
|
||||
|
||||
if [[ -z "$product_release_notes" || $product_release_notes == "Error checking"* ]]; then
|
||||
echo "::warning::Failed to fetch release notes from Jira. Output: $product_release_notes"
|
||||
product_release_notes="<insert product release notes here>"
|
||||
else
|
||||
echo "✅ Product release notes:"
|
||||
echo "$product_release_notes"
|
||||
fi
|
||||
|
||||
echo "$product_release_notes" > product_release_notes.txt
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
_APP_NAME: ${{ steps.get_release_branch.outputs.app_name }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
|
||||
_TARGET_COMMIT: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||
_TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }}
|
||||
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
|
||||
run: |
|
||||
is_latest_release=false
|
||||
if [[ "$_APP_NAME" == "Password Manager" ]]; then
|
||||
is_latest_release=true
|
||||
fi
|
||||
|
||||
echo "⌛️ Creating release for $_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER) on $_TARGET_COMMIT"
|
||||
release_url=$(gh release create "$_TAG_NAME" \
|
||||
--title "$_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER)" \
|
||||
--target "$_TARGET_COMMIT" \
|
||||
--generate-notes \
|
||||
--notes-start-tag "$_LAST_RELEASE_TAG" \
|
||||
--latest=$is_latest_release \
|
||||
--draft \
|
||||
$ARTIFACTS_PATH/*/*)
|
||||
|
||||
# Extract release tag from URL
|
||||
release_id_from_url=$(echo "$release_url" | sed 's/.*\/tag\///')
|
||||
echo "release_id_from_url=$release_id_from_url" >> $GITHUB_OUTPUT
|
||||
echo "url=$release_url" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "✅ Release created: $release_url"
|
||||
echo "🔖 Release ID from URL: $release_id_from_url"
|
||||
|
||||
- name: Update Release Description
|
||||
id: update_release_description
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_RELEASE_ID: ${{ steps.create_release.outputs.release_id_from_url }}
|
||||
run: |
|
||||
echo "Getting current release body. Release ID: $_RELEASE_ID"
|
||||
current_body=$(gh release view "$_RELEASE_ID" --json body --jq .body)
|
||||
|
||||
product_release_notes=$(cat product_release_notes.txt)
|
||||
|
||||
# Update release description with product release notes and builds source
|
||||
updated_body="# Overview
|
||||
${product_release_notes}
|
||||
|
||||
${current_body}
|
||||
**Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID"
|
||||
|
||||
new_release_url=$(gh release edit "$_RELEASE_ID" --notes "$updated_body")
|
||||
|
||||
# draft release links change after editing
|
||||
echo "release_url=$new_release_url" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Add Release Summary
|
||||
env:
|
||||
_RELEASE_TAG: ${{ steps.get_release_info.outputs.tag_name }}
|
||||
_LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }}
|
||||
_VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }}
|
||||
_VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }}
|
||||
_RELEASE_BRANCH: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||
_RELEASE_URL: ${{ steps.update_release_description.outputs.release_url }}
|
||||
run: |
|
||||
echo "# :fish_cake: Release ready at:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$_RELEASE_URL" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [[ "$_VERSION_NAME" == "0.0.0" || "$_VERSION_NUMBER" == "0" ]]; then
|
||||
echo "> [!CAUTION]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Version name or number wasn't previously found and a default value was used. You'll need to manually update the release Title, Tag and Description, specifically, the "Full Changelog" link." >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ":clipboard: Confirm that the defined GitHub Release options are correct:" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :bookmark: New tag name: \`$_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :palm_tree: Target branch: \`$_RELEASE_BRANCH\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :ocean: Previous tag set in the description \"Full Changelog\" link: \`$_LAST_RELEASE_TAG\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " * :white_check_mark: Description has automated release notes and they match the commits in the release branch" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> [!NOTE]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Commits directly pushed to branches without a Pull Request won't appear in the automated release notes." >> $GITHUB_STEP_SUMMARY
|
||||
36
.github/workflows/publish-github-release.yml
vendored
Normal file
36
.github/workflows/publish-github-release.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Publish Password Manager and Authenticator GitHub Release as newest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1-5'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
publish-release-password-manager:
|
||||
name: Publish Password Manager Release
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@main
|
||||
with:
|
||||
release_name: "Password Manager"
|
||||
workflow_name: "publish-github-release.yml"
|
||||
credentials_filename: "play_creds.json"
|
||||
project_type: android
|
||||
check_release_command: >
|
||||
bundle exec fastlane getLatestPlayStoreVersion package_name:com.x8bit.bitwarden track:production
|
||||
secrets: inherit
|
||||
|
||||
publish-release-authenticator:
|
||||
name: Publish Authenticator Release
|
||||
uses: bitwarden/gh-actions/.github/workflows/_publish-mobile-github-release.yml@main
|
||||
with:
|
||||
release_name: "Authenticator"
|
||||
workflow_name: "publish-github-release.yml"
|
||||
credentials_filename: "authenticator_play_store-creds.json"
|
||||
project_type: android
|
||||
check_release_command: >
|
||||
bundle exec fastlane getLatestPlayStoreVersion package_name:com.bitwarden.authenticator track:production
|
||||
secrets: inherit
|
||||
160
.github/workflows/publish-store.yml
vendored
Normal file
160
.github/workflows/publish-store.yml
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
name: Publish to Google Play
|
||||
run-name: "Promoting ${{ inputs.product }} ${{ inputs.version-code }} from ${{ inputs.track-from }} to ${{ inputs.track-target }}"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
product:
|
||||
description: "Which app is being released."
|
||||
type: choice
|
||||
options:
|
||||
- Password Manager
|
||||
- Authenticator
|
||||
version-name:
|
||||
description: "Version name to promote to production ex 2025.1.1"
|
||||
type: string
|
||||
version-code:
|
||||
description: "Build number to promote to production."
|
||||
required: true
|
||||
type: string
|
||||
rollout-percentage:
|
||||
description: "Percentage of users who will receive this version update."
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- 10%
|
||||
- 30%
|
||||
- 50%
|
||||
- 100%
|
||||
default: 10%
|
||||
release-notes:
|
||||
description: "Change notes to be included with this release."
|
||||
type: string
|
||||
default: "Bug fixes."
|
||||
required: true
|
||||
track-from:
|
||||
description: "Track to promote from."
|
||||
type: choice
|
||||
options:
|
||||
- internal
|
||||
- Fastlane Automation Source
|
||||
required: true
|
||||
default: "internal"
|
||||
track-target:
|
||||
description: "Track to promote to."
|
||||
type: choice
|
||||
options:
|
||||
- production
|
||||
- Fastlane Automation Target
|
||||
required: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_ACTION_RUN_URL: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
runs-on: ubuntu-24.04
|
||||
name: Promote build to Production in Play Store
|
||||
|
||||
steps:
|
||||
- name: Log inputs to job summary
|
||||
run: |
|
||||
echo "<details><summary>Job Inputs</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
echo '${{ toJson(inputs) }}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.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: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-android
|
||||
secrets: "PLAY-BETA-KEYSTORE-PASSWORD,PLAY-BETA-KEY-PASSWORD"
|
||||
|
||||
- name: Retrieve secrets
|
||||
env:
|
||||
ACCOUNT_NAME: bitwardenci
|
||||
CONTAINER_NAME: mobile
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/secrets
|
||||
mkdir -p ${{ github.workspace }}/app/src/standardRelease
|
||||
|
||||
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||
--name play_creds.json --file ${{ github.workspace }}/secrets/play_creds.json --output none
|
||||
|
||||
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: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Format Release Notes
|
||||
run: |
|
||||
FORMATTED_MESSAGE="$(echo "${{ inputs.release-notes }}" | sed 's/ /\n/g')"
|
||||
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
|
||||
echo "$FORMATTED_MESSAGE" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
- name: Promote Play Store version to production
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEYSTORE-PASSWORD }}
|
||||
PLAY_KEY_PASSWORD: ${{ steps.get-kv-secrets.outputs.PLAY-BETA-KEY-PASSWORD }}
|
||||
VERSION_CODE_INPUT: ${{ inputs.version-code }}
|
||||
VERSION_NAME: ${{inputs.version-name}}
|
||||
ROLLOUT_PERCENTAGE: ${{ inputs.rollout-percentage }}
|
||||
PRODUCT: ${{ inputs.product }}
|
||||
TRACK_FROM: ${{ inputs.track-from }}
|
||||
TRACK_TARGET: ${{ inputs.track-target }}
|
||||
run: |
|
||||
if [ "$PRODUCT" = "Password Manager" ]; then
|
||||
PACKAGE_NAME="com.x8bit.bitwarden"
|
||||
elif [ "$PRODUCT" = "Authenticator" ]; then
|
||||
PACKAGE_NAME="com.bitwarden.authenticator"
|
||||
else
|
||||
echo "Unsupported product: $PRODUCT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_CODE=$(echo "${VERSION_CODE_INPUT}" | tr -d ',')
|
||||
|
||||
decimal=$(echo "scale=2; ${ROLLOUT_PERCENTAGE/\%/} / 100" | bc)
|
||||
|
||||
bundle exec fastlane updateReleaseNotes \
|
||||
releaseNotes:"$RELEASE_NOTES" \
|
||||
versionCode:"$VERSION_CODE" \
|
||||
packageName:"$PACKAGE_NAME"
|
||||
|
||||
bundle exec fastlane promoteToProduction \
|
||||
versionCode:"$VERSION_CODE" \
|
||||
versionName:"$VERSION_NAME" \
|
||||
rolloutPercentage:"$decimal" \
|
||||
packageName:"$PACKAGE_NAME" \
|
||||
releaseNotes:"$RELEASE_NOTES" \
|
||||
track:"$TRACK_FROM" \
|
||||
trackPromoteTo:"$TRACK_TARGET"
|
||||
85
.github/workflows/release-branch.yml
vendored
Normal file
85
.github/workflows/release-branch.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Cut Release Branch
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: 'Release Type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- RC
|
||||
- Hotfix Password Manager
|
||||
- Hotfix Authenticator
|
||||
- Test
|
||||
|
||||
jobs:
|
||||
create-release-branch:
|
||||
name: Create Release Branch
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create RC or Test Branch
|
||||
id: rc_branch
|
||||
if: inputs.release_type == 'RC' || inputs.release_type == 'Test'
|
||||
env:
|
||||
_TEST_MODE: ${{ inputs.release_type == 'Test' }}
|
||||
_RELEASE_TYPE: ${{ inputs.release_type }}
|
||||
run: |
|
||||
current_date=$(date +'%Y.%-m')
|
||||
branch_name="${current_date}-rc${{ github.run_number }}"
|
||||
|
||||
if [ "$_TEST_MODE" = "true" ]; then
|
||||
branch_name="WORKFLOW-TEST-${branch_name}"
|
||||
fi
|
||||
branch_name="release/${branch_name}"
|
||||
|
||||
git switch main
|
||||
git switch -c $branch_name
|
||||
git push origin $branch_name
|
||||
echo "# :cherry_blossom: ${_RELEASE_TYPE} branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Hotfix Branch
|
||||
id: hotfix_branch
|
||||
if: startsWith(inputs.release_type, 'Hotfix')
|
||||
env:
|
||||
_RELEASE_TYPE: ${{ inputs.release_type }}
|
||||
run: |
|
||||
app_codename="bwpm"
|
||||
if [ "$_RELEASE_TYPE" == "Hotfix Authenticator" ]; then
|
||||
app_codename="bwa"
|
||||
fi
|
||||
echo "🌿 app codename: $app_codename"
|
||||
|
||||
latest_tag=$(git tag -l --sort=-creatordate | grep "$app_codename" | 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"
|
||||
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
|
||||
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
|
||||
|
||||
- name: Trigger CI Workflows
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
_BRANCH_NAME: ${{ steps.rc_branch.outputs.branch_name || steps.hotfix_branch.outputs.branch_name }}
|
||||
run: |
|
||||
echo "🌿 branch name: $_BRANCH_NAME"
|
||||
gh workflow run build.yml --ref $_BRANCH_NAME -f distribute-to-firebase=true -f publish-to-play-store=true
|
||||
gh workflow run build-authenticator.yml --ref $_BRANCH_NAME -f distribute-to-firebase=true -f publish-to-play-store=true
|
||||
35
.github/workflows/scan-ci.yml
vendored
Normal file
35
.github/workflows/scan-ci.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Scan Protected Branches On Push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sast:
|
||||
name: Checkmarx
|
||||
uses: bitwarden/gh-actions/.github/workflows/_checkmarx.yml@main
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
|
||||
quality:
|
||||
name: Sonar
|
||||
uses: bitwarden/gh-actions/.github/workflows/_sonar.yml@main
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
56
.github/workflows/scan.yml
vendored
56
.github/workflows/scan.yml
vendored
@@ -1,42 +1,48 @@
|
||||
name: Scan
|
||||
name: Scan Pull Requests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches-ignore:
|
||||
- main
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
sast:
|
||||
name: SAST scan
|
||||
runs-on: ubuntu-22.04
|
||||
name: Checkmarx
|
||||
uses: bitwarden/gh-actions/.github/workflows/_checkmarx.yml@main
|
||||
needs: check-run
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@831a8d51a8a0535c0399f9c12728d8d3cc22d850 # 2.0.28
|
||||
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 }}
|
||||
quality:
|
||||
name: Sonar
|
||||
uses: bitwarden/gh-actions/.github/workflows/_sonar.yml@main
|
||||
needs: check-run
|
||||
secrets:
|
||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
167
.github/workflows/sdlc-sdk-update.yml
vendored
Normal file
167
.github/workflows/sdlc-sdk-update.yml
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
name: SDLC / SDK Update
|
||||
run-name: "SDK ${{inputs.run-mode == 'Update' && format('Update - {0}', inputs.sdk-version) || format('Test #{0} - {1}', inputs.pr-id, inputs.sdk-version)}}"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run-mode:
|
||||
description: "Run Mode"
|
||||
type: choice
|
||||
options:
|
||||
- Test # used for testing sdk-internal repo PRs
|
||||
- Update # opens a PR in this repo updating the SDK
|
||||
default: Test
|
||||
sdk-package:
|
||||
description: "SDK Package ID"
|
||||
required: true
|
||||
default: "com.bitwarden:sdk-android.dev"
|
||||
sdk-version:
|
||||
description: "SDK Version"
|
||||
required: true
|
||||
default: "1.0.0-2686-km-update-kdf-sdk"
|
||||
pr-id:
|
||||
description: "Pull Request ID"
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: Update and PR
|
||||
if: ${{ inputs.run-mode == 'Update' }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Log in to Azure
|
||||
uses: bitwarden/gh-actions/azure-login@main
|
||||
with:
|
||||
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
client_id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
|
||||
- name: Get Azure Key Vault secrets
|
||||
id: get-kv-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: gh-org-bitwarden
|
||||
secrets: "BW-GHAPP-ID,BW-GHAPP-KEY"
|
||||
|
||||
- name: Log out from Azure
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
|
||||
private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }}
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Log inputs to job summary
|
||||
uses: ./.github/actions/log-inputs
|
||||
with:
|
||||
inputs: ${{ toJson(inputs) }}
|
||||
|
||||
- name: Switch to branch
|
||||
id: switch-branch
|
||||
run: |
|
||||
BRANCH_NAME="sdlc/sdk-update"
|
||||
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
git switch -c $BRANCH_NAME
|
||||
|
||||
- name: Get current SDK version
|
||||
id: get-current-sdk
|
||||
run: |
|
||||
SDK_VERSION=$(grep "bitwardenSdk =" gradle/libs.versions.toml | cut -d'"' -f2)
|
||||
GIT_REF=$(echo "$SDK_VERSION" | cut -d'-' -f3-) # handles both commit hashes and branch names
|
||||
echo "Current SDK version: $SDK_VERSION"
|
||||
echo "Current SDK git ref: $GIT_REF"
|
||||
echo "version=$SDK_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "git_ref=$GIT_REF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update SDK Version
|
||||
env:
|
||||
_SDK_PACKAGE: ${{ inputs.sdk-package }}
|
||||
_SDK_VERSION: ${{ inputs.sdk-version }}
|
||||
run: |
|
||||
./scripts/update-sdk-version.sh "$_SDK_PACKAGE" "$_SDK_VERSION"
|
||||
|
||||
- name: Create branch and commit
|
||||
env:
|
||||
_SDK_PACKAGE: ${{ inputs.sdk-package }}
|
||||
_SDK_VERSION: ${{ inputs.sdk-version }}
|
||||
_BRANCH_NAME: ${{ steps.switch-branch.outputs.branch_name }}
|
||||
run: |
|
||||
echo "👀 Committing SDK version update..."
|
||||
|
||||
git config user.name "bw-ghapp[bot]"
|
||||
git config user.email "178206702+bw-ghapp[bot]@users.noreply.github.com"
|
||||
|
||||
git add gradle/libs.versions.toml
|
||||
git commit -m "SDK Update - $_SDK_PACKAGE $_SDK_VERSION"
|
||||
git push origin $_BRANCH_NAME
|
||||
|
||||
- name: Create Pull Request
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
_BRANCH_NAME: ${{ steps.switch-branch.outputs.branch_name }}
|
||||
_SDK_PACKAGE: ${{ inputs.sdk-package }}
|
||||
_SDK_VERSION: ${{ inputs.sdk-version }}
|
||||
_OLD_SDK_VERSION: ${{ steps.get-current-sdk.outputs.version }}
|
||||
_OLD_SDK_GIT_REF: ${{ steps.get-current-sdk.outputs.git_ref }}
|
||||
run: |
|
||||
NEW_SDK_GIT_REF=$(echo "$_SDK_VERSION" | cut -d'-' -f3-)
|
||||
CHANGELOG=$(./scripts/get-repo-changelog.sh "bitwarden/sdk-internal" "$_OLD_SDK_GIT_REF" "$NEW_SDK_GIT_REF")
|
||||
PR_BODY="Updates the SDK version from \`$_OLD_SDK_VERSION\` to \`$_SDK_PACKAGE $_SDK_VERSION\`
|
||||
|
||||
## What's Changed
|
||||
|
||||
$CHANGELOG"
|
||||
|
||||
# Use echo -e to interpret escape sequences and pipe to gh pr create
|
||||
PR_URL=$(echo -e "$PR_BODY" | gh pr create \
|
||||
--title "Update SDK to $_SDK_VERSION" \
|
||||
--body-file - \
|
||||
--base main \
|
||||
--head $_BRANCH_NAME \
|
||||
--label "automated-pr" \
|
||||
--label "t:ci")
|
||||
|
||||
echo "🚀 Created PR: $PR_URL"
|
||||
echo "## 🚀 Created PR: $PR_URL" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
test:
|
||||
name: Test Update
|
||||
if: ${{ inputs.run-mode == 'Test' }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Log inputs to job summary
|
||||
uses: ./.github/actions/log-inputs
|
||||
with:
|
||||
inputs: ${{ toJson(inputs) }}
|
||||
|
||||
- name: Setup Android Build
|
||||
uses: ./.github/actions/setup-android-build
|
||||
|
||||
- name: Update SDK Version
|
||||
env:
|
||||
_SDK_PACKAGE: ${{ inputs.sdk-package }}
|
||||
_SDK_VERSION: ${{ inputs.sdk-version }}
|
||||
run: |
|
||||
./scripts/update-sdk-version.sh "$_SDK_PACKAGE" "$_SDK_VERSION"
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Used in settings.gradle.kts to download the SDK from GitHub Maven Packages
|
||||
run: |
|
||||
./gradlew assembleDebug --warn
|
||||
16
.github/workflows/test-device.yml
vendored
Normal file
16
.github/workflows/test-device.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Test Device
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test Device
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Placeholder step
|
||||
run: echo "Placeholder workflow step"
|
||||
96
.github/workflows/test.yml
vendored
96
.github/workflows/test.yml
vendored
@@ -6,40 +6,34 @@ on:
|
||||
- "main"
|
||||
- "rc"
|
||||
- "hotfix-rc"
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JAVA_VERSION: 17
|
||||
_JAVA_VERSION: 17
|
||||
_GITHUB_ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}
|
||||
|
||||
|
||||
jobs:
|
||||
check-run:
|
||||
name: Check PR run
|
||||
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2
|
||||
uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
|
||||
|
||||
- name: Cache Gradle files
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -49,7 +43,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-v2-
|
||||
|
||||
- name: Cache build output
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/build-cache
|
||||
@@ -58,27 +52,65 @@ jobs:
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- name: Configure Ruby
|
||||
uses: ruby/setup-ruby@3783f195e29b74ae398d7caca108814bbafde90e # v1.180.1
|
||||
uses: ruby/setup-ruby@829114fc20da43a41d27359103ec7a63020954d4 # v1.255.0
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Configure JDK
|
||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
java-version: ${{ env._JAVA_VERSION }}
|
||||
|
||||
- name: Install Fastlane
|
||||
run: |
|
||||
gem install bundler:2.2.27
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Build and test
|
||||
# Run checks on standard variant only because release and beta builds are not configured to
|
||||
# work properly with the Compose testing library. F-Droid related tasks are also skipped as
|
||||
# there is no significant code difference between the builds.
|
||||
run: |
|
||||
./gradlew testStandardDebug \
|
||||
lintStandardDebug \
|
||||
detektStandardDebug \
|
||||
koverXmlReportStandardDebug
|
||||
|
||||
- name: Danger
|
||||
env:
|
||||
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: bundle exec danger
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Used in settings.gradle.kts to download the SDK from GitHub Maven Packages
|
||||
run: |
|
||||
bundle exec fastlane check
|
||||
|
||||
- name: Upload test reports
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: always()
|
||||
with:
|
||||
name: test-reports
|
||||
path: |
|
||||
build/reports/kover/reportMergedCoverage.xml
|
||||
app/build/reports/tests/
|
||||
authenticator/build/reports/tests/
|
||||
authenticatorbridge/build/reports/tests/
|
||||
core/build/reports/tests/
|
||||
data/build/reports/tests/
|
||||
network/build/reports/tests/
|
||||
ui/build/reports/tests/
|
||||
|
||||
- name: Upload to codecov.io
|
||||
id: upload-to-codecov
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
with:
|
||||
os: linux
|
||||
files: build/reports/kover/reportMergedCoverage.xml
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
|
||||
- name: Comment PR if tests failed
|
||||
if: steps.upload-to-codecov.outcome == 'failure' && (github.event_name == 'push' || github.event_name == 'pull_request')
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RUN_ACTOR: ${{ github.triggering_actor }}
|
||||
run: |
|
||||
echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> Uploading code coverage report failed. Please check the \"Upload to codecov.io\" step of \"Process Test Reports\" job for more details." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
message=$'> [!WARNING]\n> @'$RUN_ACTOR' Uploading code coverage report failed. Please check the "Upload to codecov.io" step of [Process Test Reports job]('$_GITHUB_ACTION_RUN_URL') for more details.'
|
||||
gh pr comment --repo $GITHUB_REPOSITORY $PR_NUMBER --body "$message"
|
||||
fi
|
||||
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -3,6 +3,13 @@
|
||||
fastlane/report.xml
|
||||
fastlane/README.md
|
||||
|
||||
# Ruby / Bundler
|
||||
.bundle/
|
||||
vendor/
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -25,5 +32,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 +1 @@
|
||||
3.3.1
|
||||
3.4.2
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
shroud.reportKover 'App', 'app/build/reports/kover/reportStandardDebug.xml', 80, 80, false
|
||||
11
Gemfile
11
Gemfile
@@ -2,10 +2,17 @@ source "https://rubygems.org"
|
||||
|
||||
ruby File.read(".ruby-version").strip
|
||||
|
||||
gem 'danger'
|
||||
gem 'danger-shroud'
|
||||
gem 'fastlane'
|
||||
gem 'time'
|
||||
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||
|
||||
# Since ruby 3.4.0 these are not included in the standard library
|
||||
gem 'abbrev'
|
||||
gem 'logger'
|
||||
gem 'mutex_m'
|
||||
gem 'csv'
|
||||
|
||||
# Starting with Ruby 3.5.0, these are not included in the standard library
|
||||
gem 'ostruct'
|
||||
|
||||
181
Gemfile.lock
181
Gemfile.lock
@@ -5,66 +5,48 @@ GEM
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
abbrev (0.1.2)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.944.0)
|
||||
aws-sdk-core (3.197.0)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1139.0)
|
||||
aws-sdk-core (3.228.0)
|
||||
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
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.85.0)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.152.3)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
logger
|
||||
aws-sdk-kms (1.109.0)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.195.0)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
base64 (0.3.0)
|
||||
bigdecimal (3.2.2)
|
||||
claide (1.1.0)
|
||||
claide-plugins (0.9.2)
|
||||
cork
|
||||
nap
|
||||
open4 (~> 1.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
cork (0.3.0)
|
||||
colored2 (~> 3.1)
|
||||
danger (9.4.3)
|
||||
claide (~> 1.0)
|
||||
claide-plugins (>= 0.9.2)
|
||||
colored2 (~> 3.1)
|
||||
cork (~> 0.1)
|
||||
faraday (>= 0.9.0, < 3.0)
|
||||
faraday-http-cache (~> 2.0)
|
||||
git (~> 1.13)
|
||||
kramdown (~> 2.3)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
no_proxy_fix
|
||||
octokit (>= 4.0)
|
||||
terminal-table (>= 1, < 4)
|
||||
danger-plugin-api (1.0.0)
|
||||
danger (> 2.0)
|
||||
danger-shroud (0.0.7)
|
||||
danger-plugin-api (~> 1.0)
|
||||
nokogiri
|
||||
date (3.3.4)
|
||||
csv (3.3.5)
|
||||
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)
|
||||
@@ -80,22 +62,20 @@ GEM
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-http-cache (2.5.1)
|
||||
faraday (>= 0.8)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
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.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -111,6 +91,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)
|
||||
@@ -134,15 +115,14 @@ GEM
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-plugin-firebase_app_distribution (0.9.1)
|
||||
fastlane-plugin-firebase_app_distribution (0.10.1)
|
||||
google-apis-firebaseappdistribution_v1 (~> 0.3.0)
|
||||
google-apis-firebaseappdistribution_v1alpha (~> 0.2.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
git (1.19.1)
|
||||
addressable (~> 2.8)
|
||||
rchardet (~> 1.8)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
@@ -163,12 +143,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)
|
||||
@@ -184,56 +164,40 @@ 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.1)
|
||||
json (2.13.2)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
mini_magick (4.13.1)
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
multi_json (1.15.0)
|
||||
multi_json (1.17.0)
|
||||
multipart-post (2.4.1)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
no_proxy_fix (0.1.2)
|
||||
nokogiri (1.16.6)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
octokit (9.1.0)
|
||||
faraday (>= 1, < 3)
|
||||
sawyer (~> 0.9)
|
||||
open4 (1.3.4)
|
||||
optparse (0.5.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.1)
|
||||
public_suffix (5.1.1)
|
||||
racc (1.8.0)
|
||||
rake (13.2.1)
|
||||
rchardet (1.8.0)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.9)
|
||||
strscan
|
||||
rouge (2.0.7)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
sawyer (0.9.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (>= 0.17.3, < 3)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
signet (0.20.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -241,11 +205,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)
|
||||
@@ -253,17 +217,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.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
@@ -271,14 +235,17 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
danger
|
||||
danger-shroud
|
||||
abbrev
|
||||
csv
|
||||
fastlane
|
||||
fastlane-plugin-firebase_app_distribution
|
||||
logger
|
||||
mutex_m
|
||||
ostruct
|
||||
time
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.1p55
|
||||
ruby 3.4.2p28
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.6.9
|
||||
|
||||
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.
|
||||
133
README.md
133
README.md
@@ -4,18 +4,18 @@
|
||||
|
||||
- [Compatibility](#compatibility)
|
||||
- [Setup](#setup)
|
||||
- [Theme](#theme)
|
||||
- [Dependencies](#dependencies)
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Minimum SDK**: 28
|
||||
- **Target SDK**: 34
|
||||
- **Minimum SDK**: 29
|
||||
- **Target SDK**: 35
|
||||
- **Device Types Supported**: Phone and Tablet
|
||||
- **Orientations Supported**: Portrait and Landscape
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```sh
|
||||
@@ -25,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:
|
||||
|
||||
@@ -51,12 +52,77 @@
|
||||
|
||||
Please avoid mixing formatting and logical changes in the same commit/PR. When possible, fix any large formatting issues in a separate PR before opening one to make logical changes to the same code. This helps others focus on the meaningful code changes when reviewing the code.
|
||||
|
||||
4. Setup JDK `Version` `17`:
|
||||
|
||||
- Navigate to `Preferences > Build, Execution, Deployment > Build Tools > Gradle`.
|
||||
- Hit the selected Gradle JDK next to `Gradle JDK:`.
|
||||
- Select a `17.x` version or hit `Download JDK...` if not present.
|
||||
- Select `Version` `17`.
|
||||
- Select your preferred `Vendor`.
|
||||
- Hit `Download`.
|
||||
- Hit `Apply`.
|
||||
|
||||
5. Setup `detekt` pre-commit hook (optional):
|
||||
|
||||
Run the following script from the root of the repository to install the hook. This will overwrite any existing pre-commit hook if present.
|
||||
|
||||
```shell
|
||||
echo "Writing detekt pre-commit hook..."
|
||||
cat << 'EOL' > .git/hooks/pre-commit
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running detekt check..."
|
||||
OUTPUT="/tmp/detekt-$(date +%s)"
|
||||
./gradlew -Pprecommit=true detekt > $OUTPUT
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -ne 0 ]; then
|
||||
cat $OUTPUT
|
||||
rm $OUTPUT
|
||||
echo "***********************************************"
|
||||
echo " detekt failed "
|
||||
echo " Please fix the above issues before committing "
|
||||
echo "***********************************************"
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
rm $OUTPUT
|
||||
EOL
|
||||
echo "detekt pre-commit hook written to .git/hooks/pre-commit"
|
||||
echo "Making the hook executable"
|
||||
chmod +x .git/hooks/pre-commit
|
||||
|
||||
echo "detekt pre-commit hook installed successfully to .git/hooks/pre-commit"
|
||||
```
|
||||
|
||||
## Theme
|
||||
|
||||
### Icons & Illustrations
|
||||
|
||||
The app supports light mode, dark mode and dynamic colors. Most icons in the app will display correctly using tinting but multi-tonal icons and illustrations require extra processing in order to be displayed properly with dynamic colors.
|
||||
|
||||
All illustrations and multi-tonal icons require the svg paths to be tagged with the `name` attribute in order for each individual path to be tinted the appropriate color. Any untagged path will not be tinted and the resulting image will be incorrect.
|
||||
|
||||
The supported tags are as follows:
|
||||
|
||||
* outline
|
||||
* primary
|
||||
* secondary
|
||||
* tertiary
|
||||
* accent
|
||||
* logo
|
||||
* navigation
|
||||
* navigationActiveAccent
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Application Dependencies
|
||||
|
||||
The following is a list of all third-party dependencies included as part of the application beyond the standard Android SDK.
|
||||
|
||||
- **AndroidX Activity**
|
||||
- https://developer.android.com/jetpack/androidx/releases/activity
|
||||
- Purpose: Allows access composable APIs built on top of Activity.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Appcompat**
|
||||
- https://developer.android.com/jetpack/androidx/releases/appcompat
|
||||
- Purpose: Allows access to new APIs on older API versions.
|
||||
@@ -77,7 +143,7 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Displays webpages with the user's default browser.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX CameraX Camera2**
|
||||
- **AndroidX Camera**
|
||||
- https://developer.android.com/jetpack/androidx/releases/camera
|
||||
- Purpose: Display and capture images for barcode scanning.
|
||||
- License: Apache 2.0
|
||||
@@ -87,9 +153,9 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: A Kotlin-based declarative UI framework.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Core SplashScreen**
|
||||
- **AndroidX Core**
|
||||
- https://developer.android.com/jetpack/androidx/releases/core
|
||||
- Purpose: Backwards compatible SplashScreen API implementation.
|
||||
- Purpose: Backwards compatible platform features and APIs.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Credentials**
|
||||
@@ -102,6 +168,11 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Lifecycle aware components and tooling.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Navigation**
|
||||
- https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
- Purpose: Provides a consistent API for navigating between Android components.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **AndroidX Room**
|
||||
- https://developer.android.com/jetpack/androidx/releases/room
|
||||
- Purpose: A convenient SQLite-based persistence layer for Android.
|
||||
@@ -122,16 +193,6 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: Dependency injection framework.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Cloud Messaging**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: Allows for push notification support. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Crashlytics**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: SDK for crash and non-fatal error reporting. (**NOTE:** This dependency is not included in builds distributed via F-Droid.)
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Glide**
|
||||
- https://github.com/bumptech/glide
|
||||
- Purpose: Image loading and caching.
|
||||
@@ -152,11 +213,6 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: JSON serialization library for Kotlin.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **kotlinx.serialization converter**
|
||||
- https://github.com/square/retrofit/tree/trunk/retrofit-converters/kotlinx-serialization
|
||||
- Purpose: Converter for Retrofit 2 and kotlinx.serialization.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **OkHttp 3**
|
||||
- https://github.com/square/okhttp
|
||||
- Purpose: An HTTP client used by the library to intercept and log traffic.
|
||||
@@ -167,16 +223,33 @@ The following is a list of all third-party dependencies included as part of the
|
||||
- Purpose: A networking layer interface.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **zxcvbn4j**
|
||||
- https://github.com/nulab/zxcvbn4j
|
||||
- Purpose: Password strength estimation.
|
||||
- License: MIT
|
||||
- **Timber**
|
||||
- https://github.com/JakeWharton/timber
|
||||
- Purpose: Extensible logging library for Android.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **ZXing**
|
||||
- https://github.com/zxing/zxing
|
||||
- Purpose: Barcode scanning and generation.
|
||||
- License: Apache 2.0
|
||||
|
||||
The following is an additional list of third-party dependencies that are only included in the non-F-Droid build variants of the application.
|
||||
|
||||
- **Firebase Cloud Messaging**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: Allows for push notification support.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Firebase Crashlytics**
|
||||
- https://github.com/firebase/firebase-android-sdk
|
||||
- Purpose: SDK for crash and non-fatal error reporting.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Google Play Reviews**
|
||||
- https://developer.android.com/reference/com/google/android/play/core/release-notes
|
||||
- Purpose: On standard builds provide an interface to add a review for the password manager application in Google Play.
|
||||
- License: Apache 2.0
|
||||
|
||||
### Development Environment Dependencies
|
||||
|
||||
The following is a list of additional third-party dependencies used as part of the local development environment. This includes test-related artifacts as well as tools related to code quality and linting. These are not present in the final packaged application.
|
||||
@@ -210,16 +283,6 @@ The following is a list of additional third-party dependencies used as part of t
|
||||
|
||||
The following is a list of additional third-party dependencies used as part of the CI/CD workflows. These are not present in the final packaged application.
|
||||
|
||||
- **Danger**
|
||||
- https://github.com/danger/danger
|
||||
- Purpose: Provides a system for enforcing common Pull Request rules.
|
||||
- License: MIT
|
||||
|
||||
- **Danger Shroud**
|
||||
- https://github.com/livefront/danger-shroud
|
||||
- Purpose: A Danger plugin for enforcing code coverage via Kover / Jacoco.
|
||||
- License: Apache 2.0
|
||||
|
||||
- **Fastlane**
|
||||
- https://fastlane.tools/
|
||||
- Purpose: Automates building, signing, and distributing applications.
|
||||
|
||||
31
SECURITY.md
31
SECURITY.md
@@ -1,21 +1,32 @@
|
||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our users safe. If you believe you've found a security issue in our product or service, we encourage you to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
||||
users safe. If you believe you've found a security issue in our product or service, we encourage you
|
||||
to please submit a report through our [HackerOne Program](https://hackerone.com/bitwarden/). We
|
||||
welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||
|
||||
# Disclosure Policy
|
||||
|
||||
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every effort to quickly resolve the issue.
|
||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
||||
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our service. Only interact with accounts you own or with explicit permission of the account holder.
|
||||
- If you would like to encrypt your report, please use the PGP key with long ID `0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
|
||||
effort to quickly resolve the issue.
|
||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or
|
||||
a third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
||||
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
|
||||
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
||||
account holder.
|
||||
- If you would like to encrypt your report, please use the PGP key with long ID
|
||||
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||
|
||||
While researching, we'd like to ask you to refrain from:
|
||||
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
|
||||
# We want to help you!
|
||||
|
||||
If you have something that you feel is close to exploitation, or if you'd like some information regarding the internal API, or generally have any questions regarding the app that would help in your efforts, please email us at https://bitwarden.com/contact and ask for that information. As stated above, Bitwarden wants to help you find issues, and is more than willing to help.
|
||||
If you have something that you feel is close to exploitation, or if you'd like some information
|
||||
regarding the internal API, or generally have any questions regarding the app that would help in
|
||||
your efforts, please email us at https://bitwarden.com/contact and ask for that information. As
|
||||
stated above, Bitwarden wants to help you find issues, and is more than willing to help.
|
||||
|
||||
Thank you for helping keep Bitwarden and our users safe!
|
||||
|
||||
1
annotation/.gitignore
vendored
Normal file
1
annotation/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
42
annotation/build.gradle.kts
Normal file
42
annotation/build.gradle.kts
Normal file
@@ -0,0 +1,42 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.bitwarden.annotation"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.minSdkBwa.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro",
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility(libs.versions.jvmTarget.get())
|
||||
targetCompatibility(libs.versions.jvmTarget.get())
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
testFixtures {
|
||||
enable = true
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.x8bit.bitwarden.data.platform.annotation
|
||||
package com.bitwarden.annotation
|
||||
|
||||
/**
|
||||
* Used to omit the annotated class from test coverage reporting. This should be used sparingly and
|
||||
@@ -1,43 +1,79 @@
|
||||
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
import com.android.utils.cxx.io.removeExtensionIfPresent
|
||||
import com.google.firebase.crashlytics.buildtools.gradle.tasks.InjectMappingFileIdTask
|
||||
import com.google.firebase.crashlytics.buildtools.gradle.tasks.UploadMappingFileTask
|
||||
import com.google.gms.googleservices.GoogleServicesTask
|
||||
import dagger.hilt.android.plugin.util.capitalize
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.androidx.room)
|
||||
// Crashlytics is enabled for all builds initially but removed for FDroid builds in gradle and
|
||||
// standardDebug builds in the merged manifest.
|
||||
alias(libs.plugins.crashlytics)
|
||||
alias(libs.plugins.detekt)
|
||||
alias(libs.plugins.hilt)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose.compiler)
|
||||
alias(libs.plugins.kotlin.parcelize)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinx.kover)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.google.services)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.x8bit.bitwarden"
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "2024.06.00"
|
||||
|
||||
setProperty("archivesBaseName", "com.x8bit.bitwarden")
|
||||
|
||||
ksp {
|
||||
// The location in which the generated Room Database Schemas will be stored in the repo.
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
}
|
||||
versionCode = libs.versions.appVersionCode.get().toInt()
|
||||
versionName = libs.versions.appVersionName.get()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Set the base archive name for publishing purposes. This is used to derive the APK and AAB
|
||||
// artifact names when uploading to Firebase and Play Store.
|
||||
base.archivesName = "com.x8bit.bitwarden"
|
||||
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "CI_INFO",
|
||||
value = "${ciProperties.getOrDefault("ci.info", "\"\uD83D\uDCBB local\"")}",
|
||||
)
|
||||
buildConfigField(
|
||||
type = "String",
|
||||
name = "SDK_VERSION",
|
||||
value = "\"${libs.versions.bitwardenSdk.get()}\"",
|
||||
)
|
||||
}
|
||||
|
||||
androidResources {
|
||||
@@ -60,20 +96,37 @@ 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
|
||||
isShrinkResources = true
|
||||
matchingFallbacks += listOf("release")
|
||||
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
|
||||
isShrinkResources = 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +141,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())
|
||||
@@ -101,17 +187,31 @@ android {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
@Suppress("UnstableApiUsage")
|
||||
testOptions {
|
||||
// Required for Robolectric
|
||||
unitTests.isIncludeAndroidResources = true
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
lint {
|
||||
disable += listOf(
|
||||
"MissingTranslation",
|
||||
"ExtraTranslation",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
|
||||
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
if ((userProperties["localSdk"] as String?).toBoolean()) {
|
||||
substitute(module("com.bitwarden:sdk-android"))
|
||||
.using(module("com.bitwarden:sdk-android:LOCAL"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +220,14 @@ dependencies {
|
||||
add("standardImplementation", dependencyNotation)
|
||||
}
|
||||
|
||||
implementation(files("libs/authenticatorbridge-1.0.1-release.aar"))
|
||||
|
||||
implementation(project(":annotation"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":network"))
|
||||
implementation(project(":ui"))
|
||||
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.autofill)
|
||||
@@ -131,6 +239,7 @@ dependencies {
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.animation)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.material3.adaptive)
|
||||
implementation(libs.androidx.compose.runtime)
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
@@ -155,12 +264,11 @@ dependencies {
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.kotlinx.serialization)
|
||||
implementation(libs.nulab.zxcvbn4j)
|
||||
implementation(platform(libs.square.okhttp.bom))
|
||||
implementation(libs.square.okhttp)
|
||||
implementation(libs.square.okhttp.logging)
|
||||
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
|
||||
@@ -171,108 +279,56 @@ dependencies {
|
||||
standardImplementation(libs.google.firebase.cloud.messaging)
|
||||
standardImplementation(platform(libs.google.firebase.bom))
|
||||
standardImplementation(libs.google.firebase.crashlytics)
|
||||
standardImplementation(libs.google.play.review)
|
||||
|
||||
// Pull in test fixtures from other modules
|
||||
testImplementation(testFixtures(project(":data")))
|
||||
testImplementation(testFixtures(project(":network")))
|
||||
testImplementation(testFixtures(project(":ui")))
|
||||
|
||||
testImplementation(libs.androidx.compose.ui.test)
|
||||
testImplementation(libs.google.hilt.android.testing)
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testImplementation(libs.junit.junit5)
|
||||
testImplementation(libs.junit.vintage)
|
||||
testImplementation(libs.kotlinx.coroutines.test)
|
||||
testImplementation(libs.mockk.mockk)
|
||||
testImplementation(libs.robolectric.robolectric)
|
||||
testImplementation(libs.square.okhttp.mockwebserver)
|
||||
testImplementation(libs.square.turbine)
|
||||
|
||||
detektPlugins(libs.detekt.detekt.formatting)
|
||||
detektPlugins(libs.detekt.detekt.rules)
|
||||
}
|
||||
|
||||
detekt {
|
||||
autoCorrect = true
|
||||
config.from(files("$rootDir/detekt-config.yml"))
|
||||
}
|
||||
|
||||
kover {
|
||||
currentProject {
|
||||
sources {
|
||||
excludeJava = true
|
||||
}
|
||||
}
|
||||
reports {
|
||||
filters {
|
||||
excludes {
|
||||
androidGeneratedClasses()
|
||||
annotatedBy(
|
||||
// Compose previews
|
||||
"androidx.compose.ui.tooling.preview.Preview",
|
||||
// Manually excluded classes/files/etc.
|
||||
"com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage",
|
||||
)
|
||||
classes(
|
||||
// Navigation helpers
|
||||
"*.*NavigationKt*",
|
||||
// Composable singletons
|
||||
"*.*ComposableSingletons*",
|
||||
// Generated classes related to interfaces with default values
|
||||
"*.*DefaultImpls*",
|
||||
// Databases
|
||||
"*.database.*Database*",
|
||||
"*.dao.*Dao*",
|
||||
// Dagger Hilt
|
||||
"dagger.hilt.*",
|
||||
"hilt_aggregated_deps.*",
|
||||
"*_Factory",
|
||||
"*_Factory\$*",
|
||||
"*_*Factory",
|
||||
"*_*Factory\$*",
|
||||
"*.Hilt_*",
|
||||
"*_HiltModules",
|
||||
"*_HiltModules\$*",
|
||||
"*_Impl",
|
||||
"*_Impl\$*",
|
||||
"*_MembersInjector",
|
||||
)
|
||||
packages(
|
||||
// Dependency injection
|
||||
"*.di",
|
||||
// Models
|
||||
"*.model",
|
||||
// Custom UI components
|
||||
"com.x8bit.bitwarden.ui.platform.components",
|
||||
// Theme-related code
|
||||
"com.x8bit.bitwarden.ui.platform.theme",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
getByName("check") {
|
||||
// Add detekt with type resolution to check
|
||||
dependsOn("detektMain")
|
||||
}
|
||||
|
||||
withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
|
||||
jvmTarget = libs.versions.jvmTarget.get()
|
||||
}
|
||||
withType<io.gitlab.arturbosch.detekt.DetektCreateBaselineTask>().configureEach {
|
||||
jvmTarget = libs.versions.jvmTarget.get()
|
||||
}
|
||||
|
||||
withType<Test> {
|
||||
useJUnitPlatform()
|
||||
maxHeapSize = "2g"
|
||||
maxParallelForks = Runtime.getRuntime().availableProcessors()
|
||||
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC"
|
||||
jvmArgs = jvmArgs.orEmpty() + "-XX:+UseParallelGC" + "-Duser.country=US"
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
// Disable Fdroid-specific tasks that we want to exclude
|
||||
val tasks = tasks.withType<GoogleServicesTask>() +
|
||||
val fdroidTasksToDisable = tasks.withType<GoogleServicesTask>() +
|
||||
tasks.withType<InjectMappingFileIdTask>() +
|
||||
tasks.withType<UploadMappingFileTask>()
|
||||
tasks
|
||||
fdroidTasksToDisable
|
||||
.filter { it.name.contains("Fdroid") }
|
||||
.forEach { it.enabled = false }
|
||||
}
|
||||
|
||||
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 {
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
throw RuntimeException("Failed to rename $originalFile to $newFile")
|
||||
}
|
||||
}
|
||||
|
||||
BIN
app/libs/authenticatorbridge-1.0.1-release.aar
Normal file
BIN
app/libs/authenticatorbridge-1.0.1-release.aar
Normal file
Binary file not shown.
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,38 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "ce40856ec88770d11b7afb587c7deabc",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "privileged_apps",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `signature` TEXT NOT NULL, PRIMARY KEY(`package_name`, `signature`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "signature",
|
||||
"columnName": "signature",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"package_name",
|
||||
"signature"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ce40856ec88770d11b7afb587c7deabc')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "f40d7a933b2f353d8d5b5ca619f28e24",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "organization_events",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `organization_event_type` TEXT NOT NULL, `cipher_id` TEXT, `date` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationEventType",
|
||||
"columnName": "organization_event_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherId",
|
||||
"columnName": "cipher_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_organization_events_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_organization_events_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, 'f40d7a933b2f353d8d5b5ca619f28e24')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "4c6ad1f5268d7e8add7407201788aa2e",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "ciphers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `has_totp` INTEGER NOT NULL DEFAULT 1, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasTotp",
|
||||
"columnName": "has_totp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherType",
|
||||
"columnName": "cipher_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherJson",
|
||||
"columnName": "cipher_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ciphers_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "collections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationId",
|
||||
"columnName": "organization_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "shouldHidePasswords",
|
||||
"columnName": "should_hide_passwords",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalId",
|
||||
"columnName": "external_id",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isReadOnly",
|
||||
"columnName": "read_only",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canManage",
|
||||
"columnName": "manage",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_collections_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_collections_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "domains",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "domainsJson",
|
||||
"columnName": "domains_json",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "folders",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `name` TEXT, `revision_date` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "revisionDate",
|
||||
"columnName": "revision_date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_folders_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_folders_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "sends",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `send_type` TEXT NOT NULL, `send_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendType",
|
||||
"columnName": "send_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendJson",
|
||||
"columnName": "send_json",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_sends_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_sends_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4c6ad1f5268d7e8add7407201788aa2e')"
|
||||
]
|
||||
}
|
||||
}
|
||||
21
app/src/beta/AndroidManifest.xml
Normal file
21
app/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application tools:ignore="MissingApplicationIcon">
|
||||
<activity
|
||||
android:name=".MainActivity">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
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,19 +3,24 @@
|
||||
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"
|
||||
android:value="false" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
tools:ignore="IntentFilterExportedReceiver">
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*.bitwarden.pw" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
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>
|
||||
27
app/src/debug/res/xml/network_security_config.xml
Normal file
27
app/src/debug/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<base-config
|
||||
cleartextTrafficPermitted="true"
|
||||
tools:ignore="InsecureBaseConfiguration">
|
||||
<trust-anchors>
|
||||
<!-- Trust pre-installed CAs -->
|
||||
<certificates src="system" />
|
||||
<!-- Additionally trust user added CAs -->
|
||||
<certificates
|
||||
src="user"
|
||||
tools:ignore="AcceptsUserCertificates" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">bitwarden.com</domain>
|
||||
<domain includeSubdomains="true">bitwarden.eu</domain>
|
||||
<domain includeSubdomains="true">bitwarden.pw</domain>
|
||||
<trust-anchors>
|
||||
<!-- Only trust pre-installed CAs for Bitwarden domains and all subdomains -->
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</domain-config>
|
||||
|
||||
</network-security-config>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<credential-provider>
|
||||
<capabilities>
|
||||
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
|
||||
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
|
||||
</capabilities>
|
||||
</credential-provider>
|
||||
|
||||
@@ -1,14 +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
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.bitwarden.data.repository.model.Environment
|
||||
import com.x8bit.bitwarden.BuildConfig
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import 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"
|
||||
@@ -23,15 +37,18 @@
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:intentMatchingFlags="enforceIntentFilter"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/LaunchTheme"
|
||||
tools:ignore="CredentialDependency"
|
||||
tools:replace="appComponentFactory"
|
||||
tools:targetApi="33">
|
||||
tools:targetApi="36">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="@integer/launchModeAPIlevel"
|
||||
android:theme="@style/LaunchTheme"
|
||||
@@ -55,14 +72,54 @@
|
||||
<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="*.bitwarden.com" />
|
||||
<data android:host="*.bitwarden.eu" />
|
||||
<data android:pathPattern="/redirect-connector.*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_CREATE_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSKEY" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.ACTION_GET_PASSWORD" />
|
||||
<action android:name="com.x8bit.bitwarden.credentials.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=".AutofillTotpCopyActivity"
|
||||
android:name=".AccessibilityActivity"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop"
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<activity
|
||||
android:name=".AutofillCallbackActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/AutofillTotpCopyTheme" />
|
||||
android:theme="@style/AutofillCallbackTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".AuthCallbackActivity"
|
||||
@@ -76,16 +133,6 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="captcha-callback"
|
||||
android:scheme="bitwarden" />
|
||||
</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:host="duo-callback"
|
||||
android:scheme="bitwarden" />
|
||||
@@ -141,6 +188,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"
|
||||
@@ -150,12 +238,101 @@
|
||||
android:name="autoStoreLocales"
|
||||
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" />
|
||||
|
||||
<!-- Firebase SDK initOrder is 100. We use a higher order to initialize first -->
|
||||
<provider
|
||||
android:name=".data.platform.contentprovider.UncaughtErrorLoggingContentProvider"
|
||||
android:authorities="${applicationId}"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="false"
|
||||
android:initOrder="101" />
|
||||
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<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 Privileged Apps -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
<!-- To Query Chrome Beta: -->
|
||||
<package android:name="com.chrome.beta" />
|
||||
|
||||
<!-- To Query Chrome Stable: -->
|
||||
<package android:name="com.android.chrome" />
|
||||
|
||||
<!-- To Query Brave Stable: -->
|
||||
<package android:name="com.brave.browser" />
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
{
|
||||
"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.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DA:63:3D:34:B6:9E:63:AE:21:03:B4:9D:53:CE:05:2F:C5:F7:F3:C5:3A:AB:94:FD:C2:A2:08:BD:FD:14:24:9C"
|
||||
},
|
||||
{
|
||||
"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.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": "20:19: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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.google.android.apps.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"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": "org.mozilla.fennec_webauthndebug",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.firefox",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.firefox_beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fennec_aurora",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "BC:04:88:83:8D:06:F4:CA:6B:F3:23:86:DA:AB:0D:D8:EB:CF:3E:77:30:78:74:59:F6:2F:B3:CD:14:A1:BA:AA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.rocket",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "86:3A:46:F0:97:39:32:B7:D0:19:9B:54:91:12:74:1C:2D:27:31:AC:72:EA:11:B7:52:3A:A9:0A:11:BF:56:91"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.dev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.rolling",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.local",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser_beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser_nightly",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "app.vanadium.browser",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser.snapshot",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser.sopranos",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.citrix.Receiver",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "3D:D1:12:67:10:69:AB:36:4E:F9:BE:73:9A:B7:B5:EE:15:E1:CD:E9:D8:75:7B:1B:F0:64:F5:0C:55:68:9A:49"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "CE:B2:23:D7:77:09:F2:B6:BC:0B:3A:78:36:F5:A5:AF:4C:E1:D3:55:F4:A7:28:86:F7:9D:F8:0D:C9:D6:12:2E"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AA:D0:D4:57:E6:33:C3:78:25:77:30:5B:C1:B2:D9:E3:81:41:C7:21:DF:0D:AA:6E:29:07:2F:C4:1D:34:F0:AB"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C9:00:9D:01:EB:F9:F5:D0:30:2B:C7:1B:2F:E9:AA:9A:47:A4:32:BB:A1:73:08:A3:11:1B:75:D7:B2:14:90:25"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.sec.android.app.sbrowser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.sec.android.app.sbrowser.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.google.android.gms",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "7C:E8:3C:1B:71:F3:D5:72:FE:D0:4C:8D:40:C5:CB:10:FF:75:E6:D8:7D:9D:F6:FB:D5:3F:04:68:C2:90:50:53"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D2:2C:C5:00:29:9F:B2:28:73:A0:1A:01:0D:E1:C8:2F:BE:4D:06:11:19:B9:48:14:DD:30:1D:AB:50:CB:76:78"
|
||||
},
|
||||
{
|
||||
"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": "release",
|
||||
"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.yandex.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.alpha",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.corp",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.broteam",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
64
app/src/main/assets/fido2_privileged_community.json
Normal file
64
app/src/main/assets/fido2_privileged_community.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"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": "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.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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
820
app/src/main/assets/fido2_privileged_google.json
Normal file
820
app/src/main/assets/fido2_privileged_google.json
Normal file
@@ -0,0 +1,820 @@
|
||||
{
|
||||
"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.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DA:63:3D:34:B6:9E:63:AE:21:03:B4:9D:53:CE:05:2F:C5:F7:F3:C5:3A:AB:94:FD:C2:A2:08:BD:FD:14:24:9C"
|
||||
},
|
||||
{
|
||||
"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.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": "20:19: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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.google.android.apps.chrome",
|
||||
"signatures": [
|
||||
{
|
||||
"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": "org.mozilla.fennec_webauthndebug",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.firefox",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.firefox_beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fennec_aurora",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "BC:04:88:83:8D:06:F4:CA:6B:F3:23:86:DA:AB:0D:D8:EB:CF:3E:77:30:78:74:59:F6:2F:B3:CD:14:A1:BA:AA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.rocket",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "86:3A:46:F0:97:39:32:B7:D0:19:9B:54:91:12:74:1C:2D:27:31:AC:72:EA:11:B7:52:3A:A9:0A:11:BF:56:91"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fenix",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "50:04:77:90:88:E7:F9:88:D5:BC:5C:C5:F8:79:8F:EB:F4:F8:CD:08:4A:1B:2A:46:EF:D4:C8:EE:4A:EA:F2:11"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.fenix.debug",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.focus.nightly",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.klar",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "org.mozilla.reference.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "B0:09:90:E3:0F:9D:81:5D:2E:BC:7B:9B:B2:21:CE:47:E5:C9:D5:17:AA:C7:0E:7F:D5:95:B1:E5:3E:9A:4B:14"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.dev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.rolling",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.microsoft.emmx.local",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser_beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.brave.browser_nightly",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "app.vanadium.browser",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser.snapshot",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.vivaldi.browser.sopranos",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.citrix.Receiver",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "3D:D1:12:67:10:69:AB:36:4E:F9:BE:73:9A:B7:B5:EE:15:E1:CD:E9:D8:75:7B:1B:F0:64:F5:0C:55:68:9A:49"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "CE:B2:23:D7:77:09:F2:B6:BC:0B:3A:78:36:F5:A5:AF:4C:E1:D3:55:F4:A7:28:86:F7:9D:F8:0D:C9:D6:12:2E"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AA:D0:D4:57:E6:33:C3:78:25:77:30:5B:C1:B2:D9:E3:81:41:C7:21:DF:0D:AA:6E:29:07:2F:C4:1D:34:F0:AB"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.android.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C9:00:9D:01:EB:F9:F5:D0:30:2B:C7:1B:2F:E9:AA:9A:47:A4:32:BB:A1:73:08:A3:11:1B:75:D7:B2:14:90:25"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.sec.android.app.sbrowser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.sec.android.app.sbrowser.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.google.android.gms",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "7C:E8:3C:1B:71:F3:D5:72:FE:D0:4C:8D:40:C5:CB:10:FF:75:E6:D8:7D:9D:F6:FB:D5:3F:04:68:C2:90:50:53"
|
||||
},
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D2:2C:C5:00:29:9F:B2:28:73:A0:1A:01:0D:E1:C8:2F:BE:4D:06:11:19:B9:48:14:DD:30:1D:AB:50:CB:76:78"
|
||||
},
|
||||
{
|
||||
"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": "release",
|
||||
"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.yandex.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.alpha",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.corp",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.canary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.yandex.browser.broteam",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.Island",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D9:C3:39:AC:9C:3A:EE:E1:75:1D:85:8C:35:D9:BA:C5:CC:87:B3:CE:76:30:93:F0:F5:10:64:F5:A2:F6:9B:04"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandCanary",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "90:17:13:23:45:6E:6F:39:CB:FD:CF:B2:56:BE:1D:CF:F3:BC:1C:59:8A:15:93:30:E4:97:73:D0:4C:B9:C9:05"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandBeta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "35:31:83:1A:9E:2B:21:1D:E6:AA:C3:69:4B:45:83:6E:56:09:B9:D7:D0:04:C3:1B:21:87:40:FB:77:17:38:D1"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.IslandDev",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "C2:38:24:15:41:20:A0:8F:C3:95:42:AC:D8:2A:E9:24:94:78:80:1E:47:FD:6C:66:2B:18:1C:28:CA:7E:59:4E"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.canary.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "1E:16:74:BB:79:EA:09:FB:37:CF:9F:1B:07:1B:1D:51:8D:46:03:0E:D3:EE:F2:C1:4E:AD:93:9E:C6:EE:3A:4C"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.beta.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "D2:5E:AD:F6:1C:E6:36:6C:A4:23:A4:7F:C4:DB:9B:8C:9C:8A:35:B4:B0:19:E8:D9:82:FB:D0:8A:D9:DB:49:5A"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "io.island.island.dev.intune",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "6C:65:BD:B0:33:F5:CE:B1:74:09:EF:F9:99:48:D5:58:9F:55:63:9A:63:78:D5:A5:00:EB:95:FC:01:BC:6D:44"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "net.quetta.browser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "BE:FE:E7:31:12:6A:A5:6E:7E:FD:AE:AF:5E:F3:FA:EA:44:1C:19:CC:E0:CA:EC:42:6B:65:BB:F8:2C:59:46:80"
|
||||
},
|
||||
{
|
||||
"build": "userdebug",
|
||||
"cert_fingerprint_sha256": "F1:38:00:4F:38:04:51:D4:8A:05:2B:B3:A3:EF:17:24:23:D4:B0:D0:C8:A3:AA:DD:FB:DB:66:30:31:48:EC:A4"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "cz.seznam.sbrowser",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "DB:95:40:66:10:78:83:6E:4E:B1:66:F6:9E:F4:07:30:9E:8D:AE:33:34:68:5E:C8:F6:FA:2F:13:81:B9:AC:F6"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.opera.mini.native",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "57:AC:BC:52:5F:1B:2E:BD:19:19:6C:D6:F0:14:39:7C:C9:10:FD:18:84:1E:0A:E8:50:FE:BC:3E:1E:59:3F:F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "android",
|
||||
"info": {
|
||||
"package_name": "com.opera.mini.native.beta",
|
||||
"signatures": [
|
||||
{
|
||||
"build": "release",
|
||||
"cert_fingerprint_sha256": "57:AC:BC:52:5F:1B:2E:BD:19:19:6C:D6:F0:14:39:7C:C9:10:FD:18:84:1E:0A:E8:50:FE:BC:3E:1E:59:3F:F2"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* An activity to receive external authentication-related callbacks so the current state of the
|
||||
* task holding the [MainActivity] can remain undisturbed.
|
||||
*
|
||||
* These callbacks can be from Custom Chrome tabs or other auth related flows, including NFC
|
||||
* related transmissions.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@AndroidEntryPoint
|
||||
class AuthCallbackActivity : AppCompatActivity() {
|
||||
|
||||
private val viewModel: AuthCallbackViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
viewModel.trySendAction(AuthCallbackAction.IntentReceive(intent = intent))
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
.apply {
|
||||
addFlags(
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP,
|
||||
)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* An activity for copying a TOTP code to the clipboard. This is done when an autofill item is
|
||||
* selected and it requires TOTP authentication. Due to the constraints of the autofill framework,
|
||||
* we also have to re-fulfill the autofill for the views that are being filled.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@AndroidEntryPoint
|
||||
class AutofillTotpCopyActivity : AppCompatActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
private val autofillTotpCopyViewModel: AutofillTotpCopyViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
observeViewModelEvents()
|
||||
|
||||
autofillTotpCopyViewModel.trySendAction(
|
||||
AutofillTotpCopyAction.IntentReceived(
|
||||
intent = intent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun observeViewModelEvents() {
|
||||
autofillTotpCopyViewModel
|
||||
.eventFlow
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
is AutofillTotpCopyEvent.CompleteAutofill -> {
|
||||
handleCompleteAutofill(event)
|
||||
}
|
||||
|
||||
is AutofillTotpCopyEvent.FinishActivity -> {
|
||||
finishActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete autofill with the provided data.
|
||||
*/
|
||||
private fun handleCompleteAutofill(event: AutofillTotpCopyEvent.CompleteAutofill) {
|
||||
autofillCompletionManager.completeAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the activity.
|
||||
*/
|
||||
private fun finishActivity() {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.autofill.util.getTotpCopyIntentOrNull
|
||||
import com.x8bit.bitwarden.data.platform.util.launchWithTimeout
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
||||
import com.x8bit.bitwarden.data.vault.repository.util.statusFor
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The amount of time we should wait for ciphers to be loaded before timing out.
|
||||
*/
|
||||
private const val CIPHER_WAIT_TIMEOUT_MILLIS: Long = 500
|
||||
|
||||
/**
|
||||
* A view model that handles logic for the [AutofillTotpCopyActivity].
|
||||
*/
|
||||
@HiltViewModel
|
||||
class AutofillTotpCopyViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
) : BaseViewModel<Unit, AutofillTotpCopyEvent, AutofillTotpCopyAction>(Unit) {
|
||||
private val activeUserId: String? get() = authRepository.activeUserId
|
||||
|
||||
override fun handleAction(action: AutofillTotpCopyAction): Unit = when (action) {
|
||||
is AutofillTotpCopyAction.IntentReceived -> handleIntentReceived(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the received intent and alert the activity of what to do next.
|
||||
*/
|
||||
private fun handleIntentReceived(action: AutofillTotpCopyAction.IntentReceived) {
|
||||
viewModelScope
|
||||
.launchWithTimeout(
|
||||
timeoutBlock = { finishActivity() },
|
||||
timeoutDuration = CIPHER_WAIT_TIMEOUT_MILLIS,
|
||||
) {
|
||||
// Extract TOTP copy data from the intent.
|
||||
val cipherId = action
|
||||
.intent
|
||||
.getTotpCopyIntentOrNull()
|
||||
?.cipherId
|
||||
|
||||
if (cipherId == null || isVaultLocked()) {
|
||||
finishActivity()
|
||||
return@launchWithTimeout
|
||||
}
|
||||
|
||||
// Try and find the matching cipher.
|
||||
vaultRepository
|
||||
.ciphersStateFlow
|
||||
.mapNotNull { it.data }
|
||||
.first()
|
||||
.find { it.id == cipherId }
|
||||
?.let { cipherView ->
|
||||
sendEvent(
|
||||
AutofillTotpCopyEvent.CompleteAutofill(
|
||||
cipherView = cipherView,
|
||||
),
|
||||
)
|
||||
}
|
||||
?: finishActivity()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event to the activity that signals it to finish.
|
||||
*/
|
||||
private fun finishActivity() {
|
||||
sendEvent(AutofillTotpCopyEvent.FinishActivity)
|
||||
}
|
||||
|
||||
private suspend fun isVaultLocked(): Boolean {
|
||||
val userId = activeUserId ?: return true
|
||||
|
||||
// Wait for any unlocking actions to finish. This can be relevant on startup for Never lock
|
||||
// accounts.
|
||||
vaultRepository.vaultUnlockDataStateFlow.first {
|
||||
it.statusFor(userId) != VaultUnlockData.Status.UNLOCKING
|
||||
}
|
||||
|
||||
return !vaultRepository.isVaultUnlocked(userId = userId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents actions that can be sent to the [AutofillTotpCopyViewModel].
|
||||
*/
|
||||
sealed class AutofillTotpCopyAction {
|
||||
/**
|
||||
* An [intent] has been received and is ready to be processed.
|
||||
*/
|
||||
data class IntentReceived(
|
||||
val intent: Intent,
|
||||
) : AutofillTotpCopyAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents events emitted by the [AutofillTotpCopyViewModel].
|
||||
*/
|
||||
sealed class AutofillTotpCopyEvent {
|
||||
/**
|
||||
* Complete autofill with the provided [cipherView].
|
||||
*/
|
||||
data class CompleteAutofill(
|
||||
val cipherView: CipherView,
|
||||
) : AutofillTotpCopyEvent()
|
||||
|
||||
/**
|
||||
* Finish the activity.
|
||||
*/
|
||||
data object FinishActivity : AutofillTotpCopyEvent()
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.AppComponentFactory
|
||||
import com.x8bit.bitwarden.data.autofill.BitwardenAutofillService
|
||||
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"
|
||||
|
||||
/**
|
||||
* A factory class that allows us to intercept when a manifest element is being instantiated
|
||||
* and modify various characteristics before initialization.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@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.
|
||||
*/
|
||||
override fun instantiateServiceCompat(
|
||||
cl: ClassLoader,
|
||||
className: String,
|
||||
intent: Intent?,
|
||||
): Service = when (className) {
|
||||
LEGACY_AUTOFILL_SERVICE_NAME -> {
|
||||
super.instantiateServiceCompat(cl, BitwardenAutofillService::class.java.name, intent)
|
||||
}
|
||||
|
||||
LEGACY_CREDENTIAL_SERVICE_NAME -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
super.instantiateServiceCompat(
|
||||
cl,
|
||||
BitwardenFido2ProviderService::class.java.name,
|
||||
intent,
|
||||
)
|
||||
} else {
|
||||
throw UnsupportedOperationException(
|
||||
"The CredentialProviderService requires API 34 or higher.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> super.instantiateServiceCompat(cl, className, intent)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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 dagger.hilt.android.HiltAndroidApp
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Custom application class.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@HiltAndroidApp
|
||||
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
|
||||
|
||||
@Inject
|
||||
lateinit var crashLogsManager: CrashLogsManager
|
||||
|
||||
@Inject
|
||||
lateinit var authRequestNotificationManager: AuthRequestNotificationManager
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
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.
|
||||
*/
|
||||
@OmitFromCoverage
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val mainViewModel: MainViewModel by viewModels()
|
||||
|
||||
@Inject
|
||||
lateinit var autofillActivityManager: AutofillActivityManager
|
||||
|
||||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
var shouldShowSplashScreen = true
|
||||
installSplashScreen().setKeepOnScreenCondition { shouldShowSplashScreen }
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
observeViewModelEvents()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mainViewModel.trySendAction(
|
||||
MainAction.ReceiveFirstIntent(
|
||||
intent = intent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Within the app the language will change dynamically and will be managed
|
||||
// by the OS, but we need to ensure we properly set the language when
|
||||
// upgrading from older versions that handle this differently.
|
||||
settingsRepository.appLanguage.localeName?.let { localeName ->
|
||||
val localeList = LocaleListCompat.forLanguageTags(localeName)
|
||||
AppCompatDelegate.setApplicationLocales(localeList)
|
||||
}
|
||||
setContent {
|
||||
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
LocalManagerProvider {
|
||||
BitwardenTheme(theme = state.theme) {
|
||||
RootNavScreen(
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
mainViewModel.trySendAction(
|
||||
action = MainAction.ReceiveNewIntent(
|
||||
intent = intent,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
// In some scenarios on an emulator the Activity can leak when recreated
|
||||
// if we don't first clear focus anytime we exit and return to the app.
|
||||
currentFocus?.clearFocus()
|
||||
}
|
||||
|
||||
private fun observeViewModelEvents() {
|
||||
mainViewModel
|
||||
.eventFlow
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAutofill -> {
|
||||
handleCompleteAutofill(event)
|
||||
}
|
||||
|
||||
MainEvent.Recreate -> handleRecreate()
|
||||
|
||||
is MainEvent.ScreenCaptureSettingChange -> {
|
||||
handleScreenCaptureSettingChange(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
||||
autofillCompletionManager.completeAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleScreenCaptureSettingChange(event: MainEvent.ScreenCaptureSettingChange) {
|
||||
if (event.isAllowed) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRecreate() {
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package com.x8bit.bitwarden
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
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.SpecialCircumstanceManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.garbage.GarbageCollectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
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.feature.settings.appearance.model.AppTheme
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||
|
||||
/**
|
||||
* A view model that helps launch actions for the [MainActivity].
|
||||
*/
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
autofillSelectionManager: AutofillSelectionManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val garbageCollectionManager: GarbageCollectionManager,
|
||||
private val intentManager: IntentManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<MainState, MainEvent, MainAction>(
|
||||
MainState(
|
||||
theme = settingsRepository.appTheme,
|
||||
),
|
||||
) {
|
||||
private var specialCircumstance: SpecialCircumstance?
|
||||
get() = savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY]
|
||||
set(value) {
|
||||
savedStateHandle[SPECIAL_CIRCUMSTANCE_KEY] = value
|
||||
}
|
||||
|
||||
init {
|
||||
// Immediately restore the special circumstance if we have one and then listen for changes
|
||||
specialCircumstanceManager.specialCircumstance = specialCircumstance
|
||||
|
||||
specialCircumstanceManager
|
||||
.specialCircumstanceStateFlow
|
||||
.onEach { specialCircumstance = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
autofillSelectionManager
|
||||
.autofillSelectionFlow
|
||||
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.appThemeStateFlow
|
||||
.onEach { trySendAction(MainAction.Internal.ThemeUpdate(it)) }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
settingsRepository
|
||||
.isScreenCaptureAllowedStateFlow
|
||||
.onEach { isAllowed ->
|
||||
sendEvent(MainEvent.ScreenCaptureSettingChange(isAllowed))
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
authRepository
|
||||
.userStateFlow
|
||||
.drop(count = 1)
|
||||
// Trigger an action whenever the current user changes or we go into/out of a pending
|
||||
// account state (which acts like switching to a temporary user).
|
||||
.map { it?.activeUserId to it?.hasPendingAccountAddition }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
// 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)
|
||||
trySendAction(MainAction.Internal.CurrentUserStateChange)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
vaultRepository
|
||||
.vaultStateEventFlow
|
||||
.onEach {
|
||||
when (it) {
|
||||
is VaultStateEvent.Locked -> {
|
||||
trySendAction(MainAction.Internal.VaultUnlockStateChange)
|
||||
}
|
||||
|
||||
is VaultStateEvent.Unlocked -> Unit
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: MainAction) {
|
||||
when (action) {
|
||||
is MainAction.Internal.AutofillSelectionReceive -> {
|
||||
handleAutofillSelectionReceive(action)
|
||||
}
|
||||
|
||||
is MainAction.Internal.CurrentUserStateChange -> handleCurrentUserStateChange()
|
||||
is MainAction.Internal.ThemeUpdate -> handleAppThemeUpdated(action)
|
||||
is MainAction.Internal.VaultUnlockStateChange -> handleVaultUnlockStateChange()
|
||||
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
|
||||
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAutofillSelectionReceive(
|
||||
action: MainAction.Internal.AutofillSelectionReceive,
|
||||
) {
|
||||
sendEvent(MainEvent.CompleteAutofill(cipherView = action.cipherView))
|
||||
}
|
||||
|
||||
private fun handleCurrentUserStateChange() {
|
||||
recreateUiAndGarbageCollect()
|
||||
}
|
||||
|
||||
private fun handleAppThemeUpdated(action: MainAction.Internal.ThemeUpdate) {
|
||||
mutableStateFlow.update { it.copy(theme = action.theme) }
|
||||
}
|
||||
|
||||
private fun handleVaultUnlockStateChange() {
|
||||
recreateUiAndGarbageCollect()
|
||||
}
|
||||
|
||||
private fun handleFirstIntentReceived(action: MainAction.ReceiveFirstIntent) {
|
||||
handleIntent(
|
||||
intent = action.intent,
|
||||
isFirstIntent = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNewIntentReceived(action: MainAction.ReceiveNewIntent) {
|
||||
handleIntent(
|
||||
intent = action.intent,
|
||||
isFirstIntent = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleIntent(
|
||||
intent: Intent,
|
||||
isFirstIntent: Boolean,
|
||||
) {
|
||||
val passwordlessRequestData = intent.getPasswordlessRequestDataIntentOrNull()
|
||||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
||||
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
||||
when {
|
||||
passwordlessRequestData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.PasswordlessRequest(
|
||||
passwordlessRequestData = passwordlessRequestData,
|
||||
// Allow users back into the already-running app when completing the
|
||||
// autofill task when this is not the first intent.
|
||||
shouldFinishWhenComplete = isFirstIntent,
|
||||
)
|
||||
}
|
||||
|
||||
autofillSaveItem != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSave(
|
||||
autofillSaveItem = autofillSaveItem,
|
||||
)
|
||||
}
|
||||
|
||||
autofillSelectionData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
// Allow users back into the already-running app when completing the
|
||||
// autofill task when this is not the first intent.
|
||||
shouldFinishWhenComplete = isFirstIntent,
|
||||
)
|
||||
}
|
||||
|
||||
shareData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.ShareNewSend(
|
||||
data = shareData,
|
||||
// Allow users back into the already-running app when completing the
|
||||
// Send task when this is not the first intent.
|
||||
shouldFinishWhenComplete = isFirstIntent,
|
||||
)
|
||||
}
|
||||
|
||||
fido2CredentialRequestData != null -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.Fido2Save(
|
||||
fido2CredentialRequest = fido2CredentialRequestData,
|
||||
)
|
||||
|
||||
// Switch accounts if the selected user is not the active user.
|
||||
if (authRepository.activeUserId != null &&
|
||||
authRepository.activeUserId != fido2CredentialRequestData.userId
|
||||
) {
|
||||
authRepository.switchAccount(fido2CredentialRequestData.userId)
|
||||
}
|
||||
}
|
||||
|
||||
hasGeneratorShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
SpecialCircumstance.GeneratorShortcut
|
||||
}
|
||||
|
||||
hasVaultShortcut -> {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.VaultShortcut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recreateUiAndGarbageCollect() {
|
||||
sendEvent(MainEvent.Recreate)
|
||||
garbageCollectionManager.tryCollect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models state for the [MainActivity].
|
||||
*/
|
||||
@Parcelize
|
||||
data class MainState(
|
||||
val theme: AppTheme,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Models actions for the [MainActivity].
|
||||
*/
|
||||
sealed class MainAction {
|
||||
/**
|
||||
* Receive first Intent by the application.
|
||||
*/
|
||||
data class ReceiveFirstIntent(val intent: Intent) : MainAction()
|
||||
|
||||
/**
|
||||
* Receive Intent by the application.
|
||||
*/
|
||||
data class ReceiveNewIntent(val intent: Intent) : MainAction()
|
||||
|
||||
/**
|
||||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
sealed class Internal : MainAction() {
|
||||
/**
|
||||
* Indicates the user has manually selected the given [cipherView] for autofill.
|
||||
*/
|
||||
data class AutofillSelectionReceive(
|
||||
val cipherView: CipherView,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a relevant change in the current user state.
|
||||
*/
|
||||
data object CurrentUserStateChange : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that the app theme has changed.
|
||||
*/
|
||||
data class ThemeUpdate(
|
||||
val theme: AppTheme,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a relevant change in the current vault lock state.
|
||||
*/
|
||||
data object VaultUnlockStateChange : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents events that are emitted by the [MainViewModel].
|
||||
*/
|
||||
sealed class MainEvent {
|
||||
/**
|
||||
* Event indicating that the user has chosen the given [cipherView] for autofill and that the
|
||||
* process is ready to complete.
|
||||
*/
|
||||
data class CompleteAutofill(val cipherView: CipherView) : MainEvent()
|
||||
|
||||
/**
|
||||
* Event indicating a change in the screen capture setting.
|
||||
*/
|
||||
data class ScreenCaptureSettingChange(val isAllowed: Boolean) : MainEvent()
|
||||
|
||||
/**
|
||||
* Event indicating that the UI should recreate itself.
|
||||
*/
|
||||
data object Recreate : MainEvent()
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
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.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
|
||||
|
||||
/**
|
||||
* Primary access point for disk information.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface AuthDiskSource {
|
||||
/**
|
||||
* 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
|
||||
* application data.
|
||||
*/
|
||||
val uniqueAppId: String
|
||||
|
||||
/**
|
||||
* The currently persisted saved email address (or `null` if not set).
|
||||
*/
|
||||
var rememberedEmailAddress: String?
|
||||
|
||||
/**
|
||||
* The currently persisted organization identifier (or `null` if not set).
|
||||
*/
|
||||
var rememberedOrgIdentifier: String?
|
||||
|
||||
/**
|
||||
* The currently persisted user state information (or `null` if not set).
|
||||
*/
|
||||
var userState: UserStateJson?
|
||||
|
||||
/**
|
||||
* Emits updates that track [userState]. This will replay the last known value, if any.
|
||||
*/
|
||||
val userStateFlow: Flow<UserStateJson?>
|
||||
|
||||
/**
|
||||
* Clears all the settings data for the given user.
|
||||
*
|
||||
* Note that this does not include any data saved in the [userState].
|
||||
*/
|
||||
fun clearData(userId: String)
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Stores the boolean indicating that the user has chosen to trust this device for the given
|
||||
* [userId].
|
||||
*
|
||||
* Note: This indicates intent to trust the device, the device may not be trusted yet.
|
||||
*/
|
||||
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].
|
||||
*/
|
||||
fun getInvalidUnlockAttempts(userId: String): Int?
|
||||
|
||||
/**
|
||||
* Stores the number of consecutive invalid lock attempts for the given [userId].
|
||||
*/
|
||||
fun storeInvalidUnlockAttempts(
|
||||
userId: String,
|
||||
invalidUnlockAttempts: Int?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves a user key using a [userId].
|
||||
*/
|
||||
fun getUserKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a user key using a [userId].
|
||||
*/
|
||||
fun storeUserKey(userId: String, userKey: String?)
|
||||
|
||||
/**
|
||||
* Retrieves a private key using a [userId].
|
||||
*/
|
||||
fun getPrivateKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a private key using a [userId].
|
||||
*/
|
||||
fun storePrivateKey(userId: String, privateKey: String?)
|
||||
|
||||
/**
|
||||
* Retrieves a user auto-unlock key for the given [userId].
|
||||
*/
|
||||
fun getUserAutoUnlockKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a user auto-unlock key for the given [userId].
|
||||
*/
|
||||
fun storeUserAutoUnlockKey(userId: String, userAutoUnlockKey: String?)
|
||||
|
||||
/**
|
||||
* Gets the device key for the given [userId].
|
||||
*/
|
||||
fun getDeviceKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores the device key for the given [userId].
|
||||
*/
|
||||
fun storeDeviceKey(userId: String, deviceKey: String?)
|
||||
|
||||
/**
|
||||
* Gets the stored [PendingAuthRequestJson] for the given [userId].
|
||||
*/
|
||||
fun getPendingAuthRequest(userId: String): PendingAuthRequestJson?
|
||||
|
||||
/**
|
||||
* Stores the [PendingAuthRequestJson] for the given [userId].
|
||||
*/
|
||||
fun storePendingAuthRequest(
|
||||
userId: String,
|
||||
pendingAuthRequest: PendingAuthRequestJson?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the biometrics key for the given [userId].
|
||||
*/
|
||||
fun getUserBiometricUnlockKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores the biometrics key for the given [userId].
|
||||
*/
|
||||
fun storeUserBiometricUnlockKey(userId: String, biometricsKey: String?)
|
||||
|
||||
/**
|
||||
* Retrieves a pin-protected user key for the given [userId].
|
||||
*/
|
||||
fun getPinProtectedUserKey(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores a pin-protected user key for the given [userId].
|
||||
*
|
||||
* When [inMemoryOnly] is `true`, the value will only be available via a call to
|
||||
* [getPinProtectedUserKey] during the current app session.
|
||||
*/
|
||||
fun storePinProtectedUserKey(
|
||||
userId: String,
|
||||
pinProtectedUserKey: String?,
|
||||
inMemoryOnly: Boolean = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets a two-factor auth token using a user's [email].
|
||||
*/
|
||||
fun getTwoFactorToken(email: String): String?
|
||||
|
||||
/**
|
||||
* Stores a two-factor auth token using a user's [email].
|
||||
*/
|
||||
fun storeTwoFactorToken(email: String, twoFactorToken: String?)
|
||||
|
||||
/**
|
||||
* Retrieves an encrypted PIN for the given [userId].
|
||||
*/
|
||||
fun getEncryptedPin(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores an encrypted PIN for the given [userId].
|
||||
*/
|
||||
fun storeEncryptedPin(userId: String, encryptedPin: String?)
|
||||
|
||||
/**
|
||||
* Gets the organization keys for the given [userId] in the form of a mapping from organization
|
||||
* ID to encrypted organization key.
|
||||
*/
|
||||
fun getOrganizationKeys(userId: String): Map<String, String>?
|
||||
|
||||
/**
|
||||
* Stores the organization keys for the given [userId] in the form of a mapping from
|
||||
* organization ID to encrypted organization key.
|
||||
*/
|
||||
fun storeOrganizationKeys(
|
||||
userId: String,
|
||||
organizationKeys: Map<String, String>?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the organization data for the given [userId].
|
||||
*/
|
||||
fun getOrganizations(userId: String): List<SyncResponseJson.Profile.Organization>?
|
||||
|
||||
/**
|
||||
* Emits updates that track [getOrganizations]. This will replay the last known value, if any.
|
||||
*/
|
||||
fun getOrganizationsFlow(userId: String): Flow<List<SyncResponseJson.Profile.Organization>?>
|
||||
|
||||
/**
|
||||
* Stores the organization data for the given [userId].
|
||||
*/
|
||||
fun storeOrganizations(
|
||||
userId: String,
|
||||
organizations: List<SyncResponseJson.Profile.Organization>?,
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the master password hash for the given [userId].
|
||||
*/
|
||||
fun getMasterPasswordHash(userId: String): String?
|
||||
|
||||
/**
|
||||
* Stores the [passwordHash] for the given [userId].
|
||||
*/
|
||||
fun storeMasterPasswordHash(userId: String, passwordHash: String?)
|
||||
|
||||
/**
|
||||
* Gets the policies for the given [userId].
|
||||
*/
|
||||
fun getPolicies(userId: String): List<SyncResponseJson.Policy>?
|
||||
|
||||
/**
|
||||
* Emits updates that track [getPolicies]. This will replay the last known value, if any.
|
||||
*/
|
||||
fun getPoliciesFlow(userId: String): Flow<List<SyncResponseJson.Policy>?>
|
||||
|
||||
/**
|
||||
* Stores the [policies] for the given [userId].
|
||||
*/
|
||||
fun storePolicies(userId: String, policies: List<SyncResponseJson.Policy>?)
|
||||
|
||||
/**
|
||||
* Gets the account tokens for the given [userId].
|
||||
*/
|
||||
fun getAccountTokens(userId: String): AccountTokensJson?
|
||||
|
||||
/**
|
||||
* Emits updates that track [getAccountTokens]. This will replay the last known value, if any.
|
||||
*/
|
||||
fun getAccountTokensFlow(userId: String): Flow<AccountTokensJson?>
|
||||
|
||||
/**
|
||||
* Stores the [accountTokens] for the given [userId].
|
||||
*/
|
||||
fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?)
|
||||
}
|
||||
@@ -1,424 +0,0 @@
|
||||
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.PendingAuthRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
|
||||
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.util.UUID
|
||||
|
||||
// These keys should be encrypted
|
||||
private const val ACCOUNT_TOKENS_KEY = "accountTokens"
|
||||
private const val BIOMETRICS_UNLOCK_KEY = "userKeyBiometricUnlock"
|
||||
private const val USER_AUTO_UNLOCK_KEY_KEY = "userKeyAutoUnlock"
|
||||
private const val DEVICE_KEY_KEY = "deviceKey"
|
||||
private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "pendingAdminAuthRequest"
|
||||
|
||||
// These keys should not be encrypted
|
||||
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"
|
||||
private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey"
|
||||
private const val ENCRYPTED_PIN_KEY = "protectedPin"
|
||||
private const val ORGANIZATIONS_KEY = "organizations"
|
||||
private const val ORGANIZATION_KEYS_KEY = "encOrgKeys"
|
||||
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"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class AuthDiskSourceImpl(
|
||||
encryptedSharedPreferences: SharedPreferences,
|
||||
sharedPreferences: SharedPreferences,
|
||||
legacySecureStorageMigrator: LegacySecureStorageMigrator,
|
||||
private val json: Json,
|
||||
) : BaseEncryptedDiskSource(
|
||||
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||
sharedPreferences = sharedPreferences,
|
||||
),
|
||||
AuthDiskSource {
|
||||
|
||||
private val inMemoryPinProtectedUserKeys = mutableMapOf<String, String?>()
|
||||
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 mutableUserStateFlow = bufferedMutableSharedFlow<UserStateJson?>(replay = 1)
|
||||
|
||||
override var userState: UserStateJson?
|
||||
get() = getString(key = STATE_KEY)?.let { json.decodeFromStringOrNull(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = STATE_KEY,
|
||||
value = value?.let { json.encodeToString(value) },
|
||||
)
|
||||
mutableUserStateFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
init {
|
||||
// We must migrate if necessary before any of the migrated values would be initialized
|
||||
// and accessed.
|
||||
legacySecureStorageMigrator.migrateIfNecessary()
|
||||
|
||||
// We must migrate the tokens from being stored in the UserState(shared preferences) to
|
||||
// being stored separately in encrypted shared preferences.
|
||||
migrateAccountTokens()
|
||||
}
|
||||
|
||||
override val uniqueAppId: String
|
||||
get() = getString(key = UNIQUE_APP_ID_KEY) ?: generateAndStoreUniqueAppId()
|
||||
|
||||
override var rememberedEmailAddress: String?
|
||||
get() = getString(key = REMEMBERED_EMAIL_ADDRESS_KEY)
|
||||
set(value) {
|
||||
putString(
|
||||
key = REMEMBERED_EMAIL_ADDRESS_KEY,
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
override var rememberedOrgIdentifier: String?
|
||||
get() = getString(key = REMEMBERED_ORG_IDENTIFIER_KEY)
|
||||
set(value) {
|
||||
putString(
|
||||
key = REMEMBERED_ORG_IDENTIFIER_KEY,
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
override val userStateFlow: Flow<UserStateJson?>
|
||||
get() = mutableUserStateFlow
|
||||
.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)
|
||||
storePinProtectedUserKey(userId = userId, pinProtectedUserKey = null)
|
||||
storeEncryptedPin(userId = userId, encryptedPin = null)
|
||||
storePrivateKey(userId = userId, privateKey = null)
|
||||
storeOrganizationKeys(userId = userId, organizationKeys = null)
|
||||
storeOrganizations(userId = userId, organizations = null)
|
||||
storeUserBiometricUnlockKey(userId = userId, biometricsKey = null)
|
||||
storeMasterPasswordHash(userId = userId, passwordHash = null)
|
||||
storePolicies(userId = userId, policies = null)
|
||||
storeAccountTokens(userId = userId, accountTokens = null)
|
||||
|
||||
// Do not remove the DeviceKey or PendingAuthRequest on logout, these are persisted
|
||||
// indefinitely unless the TDE flow explicitly removes them.
|
||||
}
|
||||
|
||||
override fun getShouldTrustDevice(userId: String): Boolean =
|
||||
requireNotNull(
|
||||
getBoolean(key = SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), default = false),
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
override fun storeInvalidUnlockAttempts(
|
||||
userId: String,
|
||||
invalidUnlockAttempts: Int?,
|
||||
) {
|
||||
putInt(
|
||||
key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId),
|
||||
value = invalidUnlockAttempts,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserKey(userId: String): String? =
|
||||
getString(key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeUserKey(userId: String, userKey: String?) {
|
||||
putString(
|
||||
key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId),
|
||||
value = userKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPrivateKey(userId: String): String? =
|
||||
getString(key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storePrivateKey(userId: String, privateKey: String?) {
|
||||
putString(
|
||||
key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId),
|
||||
value = privateKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserAutoUnlockKey(userId: String): String? =
|
||||
getEncryptedString(
|
||||
key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId),
|
||||
default = null,
|
||||
)
|
||||
|
||||
override fun storeUserAutoUnlockKey(
|
||||
userId: String,
|
||||
userAutoUnlockKey: String?,
|
||||
) {
|
||||
putEncryptedString(
|
||||
key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId),
|
||||
value = userAutoUnlockKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getDeviceKey(
|
||||
userId: String,
|
||||
): String? = getEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeDeviceKey(
|
||||
userId: String,
|
||||
deviceKey: String?,
|
||||
) {
|
||||
putEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId), value = deviceKey)
|
||||
}
|
||||
|
||||
override fun getPendingAuthRequest(
|
||||
userId: String,
|
||||
): PendingAuthRequestJson? =
|
||||
getEncryptedString(key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId))
|
||||
?.let { json.decodeFromStringOrNull(it) }
|
||||
|
||||
override fun storePendingAuthRequest(
|
||||
userId: String,
|
||||
pendingAuthRequest: PendingAuthRequestJson?,
|
||||
) {
|
||||
putEncryptedString(
|
||||
key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId),
|
||||
value = pendingAuthRequest?.let { json.encodeToString(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun getUserBiometricUnlockKey(userId: String): String? =
|
||||
getEncryptedString(key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeUserBiometricUnlockKey(
|
||||
userId: String,
|
||||
biometricsKey: String?,
|
||||
) {
|
||||
putEncryptedString(
|
||||
key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId),
|
||||
value = biometricsKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPinProtectedUserKey(userId: String): String? =
|
||||
inMemoryPinProtectedUserKeys[userId]
|
||||
?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storePinProtectedUserKey(
|
||||
userId: String,
|
||||
pinProtectedUserKey: String?,
|
||||
inMemoryOnly: Boolean,
|
||||
) {
|
||||
inMemoryPinProtectedUserKeys[userId] = pinProtectedUserKey
|
||||
if (inMemoryOnly) return
|
||||
putString(
|
||||
key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId),
|
||||
value = pinProtectedUserKey,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getTwoFactorToken(email: String): String? =
|
||||
getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email))
|
||||
|
||||
override fun storeTwoFactorToken(email: String, twoFactorToken: String?) {
|
||||
putString(
|
||||
key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email),
|
||||
value = twoFactorToken,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getEncryptedPin(userId: String): String? =
|
||||
getString(key = ENCRYPTED_PIN_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeEncryptedPin(
|
||||
userId: String,
|
||||
encryptedPin: String?,
|
||||
) {
|
||||
putString(
|
||||
key = ENCRYPTED_PIN_KEY.appendIdentifier(userId),
|
||||
value = encryptedPin,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getOrganizationKeys(userId: String): Map<String, String>? =
|
||||
getString(key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId))
|
||||
?.let { json.decodeFromStringOrNull(it) }
|
||||
|
||||
override fun storeOrganizationKeys(
|
||||
userId: String,
|
||||
organizationKeys: Map<String, String>?,
|
||||
) {
|
||||
putString(
|
||||
key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId),
|
||||
value = organizationKeys?.let { json.encodeToString(it) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun getOrganizations(
|
||||
userId: String,
|
||||
): List<SyncResponseJson.Profile.Organization>? =
|
||||
getString(key = ORGANIZATIONS_KEY.appendIdentifier(userId))
|
||||
?.let {
|
||||
// The organizations are stored as a map
|
||||
val organizationMap: Map<String, SyncResponseJson.Profile.Organization>? =
|
||||
json.decodeFromStringOrNull(it)
|
||||
organizationMap?.values?.toList()
|
||||
}
|
||||
|
||||
override fun getOrganizationsFlow(
|
||||
userId: String,
|
||||
): Flow<List<SyncResponseJson.Profile.Organization>?> =
|
||||
getMutableOrganizationsFlow(userId = userId)
|
||||
.onSubscription { emit(getOrganizations(userId = userId)) }
|
||||
|
||||
override fun storeOrganizations(
|
||||
userId: String,
|
||||
organizations: List<SyncResponseJson.Profile.Organization>?,
|
||||
) {
|
||||
putString(
|
||||
key = ORGANIZATIONS_KEY.appendIdentifier(userId),
|
||||
value = organizations?.let { nonNullOrganizations ->
|
||||
// The organizations are stored as a map
|
||||
val organizationsMap = nonNullOrganizations.associateBy { it.id }
|
||||
json.encodeToString(organizationsMap)
|
||||
},
|
||||
)
|
||||
getMutableOrganizationsFlow(userId = userId).tryEmit(organizations)
|
||||
}
|
||||
|
||||
override fun getMasterPasswordHash(userId: String): String? =
|
||||
getString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId))
|
||||
|
||||
override fun storeMasterPasswordHash(userId: String, passwordHash: String?) {
|
||||
putString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId), value = passwordHash)
|
||||
}
|
||||
|
||||
override fun getPolicies(userId: String): List<SyncResponseJson.Policy>? =
|
||||
getString(key = POLICIES_KEY.appendIdentifier(userId))
|
||||
?.let {
|
||||
// The policies are stored as a map.
|
||||
val policiesMap: Map<String, SyncResponseJson.Policy>? =
|
||||
json.decodeFromStringOrNull(it)
|
||||
policiesMap?.values?.toList()
|
||||
}
|
||||
|
||||
override fun getPoliciesFlow(
|
||||
userId: String,
|
||||
): Flow<List<SyncResponseJson.Policy>?> =
|
||||
getMutablePoliciesFlow(userId = userId)
|
||||
.onSubscription { emit(getPolicies(userId = userId)) }
|
||||
|
||||
override fun storePolicies(userId: String, policies: List<SyncResponseJson.Policy>?) {
|
||||
putString(
|
||||
key = POLICIES_KEY.appendIdentifier(userId),
|
||||
value = policies?.let { nonNullPolicies ->
|
||||
// The policies are stored as a map.
|
||||
val policiesMap = nonNullPolicies.associateBy { it.id }
|
||||
json.encodeToString(policiesMap)
|
||||
},
|
||||
)
|
||||
getMutablePoliciesFlow(userId = userId).tryEmit(policies)
|
||||
}
|
||||
|
||||
override fun getAccountTokens(userId: String): AccountTokensJson? =
|
||||
getEncryptedString(key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId))
|
||||
?.let { json.decodeFromStringOrNull(it) }
|
||||
|
||||
override fun getAccountTokensFlow(userId: String): Flow<AccountTokensJson?> =
|
||||
getMutableAccountTokensFlow(userId = userId)
|
||||
.onSubscription { emit(getAccountTokens(userId = userId)) }
|
||||
|
||||
override fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?) {
|
||||
putEncryptedString(
|
||||
key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId),
|
||||
value = accountTokens?.let { json.encodeToString(it) },
|
||||
)
|
||||
getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens)
|
||||
}
|
||||
|
||||
private fun generateAndStoreUniqueAppId(): String =
|
||||
UUID
|
||||
.randomUUID()
|
||||
.toString()
|
||||
.also {
|
||||
putString(key = UNIQUE_APP_ID_KEY, value = it)
|
||||
}
|
||||
|
||||
private fun getMutableOrganizationsFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<List<SyncResponseJson.Profile.Organization>?> =
|
||||
mutableOrganizationsFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutablePoliciesFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<List<SyncResponseJson.Policy>?> =
|
||||
mutablePoliciesFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun getMutableAccountTokensFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<AccountTokensJson?> =
|
||||
mutableAccountTokensFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
private fun migrateAccountTokens() {
|
||||
userState
|
||||
?.accounts
|
||||
.orEmpty()
|
||||
.values
|
||||
.forEach { accountJson ->
|
||||
@Suppress("DEPRECATION")
|
||||
accountJson.tokens?.let { storeAccountTokens(accountJson.profile.userId, it) }
|
||||
}
|
||||
userState = userState?.copy(
|
||||
accounts = userState
|
||||
?.accounts
|
||||
?.mapValues { (_, accountJson) -> accountJson.copy(tokens = null) }
|
||||
.orEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.di.EncryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.di.UnencryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides persistence-related dependencies in the auth package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AuthDiskModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthDiskSource(
|
||||
@EncryptedPreferences encryptedSharedPreferences: SharedPreferences,
|
||||
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||
legacySecureStorageMigrator: LegacySecureStorageMigrator,
|
||||
json: Json,
|
||||
): AuthDiskSource =
|
||||
AuthDiskSourceImpl(
|
||||
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||
sharedPreferences = sharedPreferences,
|
||||
legacySecureStorageMigrator = legacySecureStorageMigrator,
|
||||
json = json,
|
||||
)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Container for the user's API tokens.
|
||||
*
|
||||
* @property requestId The ID of the pending Auth Request.
|
||||
* @property requestPrivateKey The private of the pending Auth Request.
|
||||
*/
|
||||
@Serializable
|
||||
data class PendingAuthRequestJson(
|
||||
@SerialName("Id")
|
||||
val requestId: String,
|
||||
|
||||
@SerialName("PrivateKey")
|
||||
val requestPrivateKey: String,
|
||||
)
|
||||
@@ -1,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>
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.CreateAccountKeysRequest
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountRequestJson
|
||||
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 retrofit2.http.Body
|
||||
import retrofit2.http.HTTP
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /accounts API with authentication applied.
|
||||
*/
|
||||
interface AuthenticatedAccountsApi {
|
||||
/**
|
||||
* Creates the keys for the current account.
|
||||
*/
|
||||
@POST("/accounts/keys")
|
||||
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): Result<Unit>
|
||||
|
||||
/**
|
||||
* Deletes the current account.
|
||||
*/
|
||||
@HTTP(method = "DELETE", path = "/accounts", hasBody = true)
|
||||
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): Result<Unit>
|
||||
|
||||
@POST("/accounts/request-otp")
|
||||
suspend fun requestOtp(): Result<Unit>
|
||||
|
||||
@POST("/accounts/verify-otp")
|
||||
suspend fun verifyOtp(
|
||||
@Body body: VerifyOtpRequestJson,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Resets the temporary password.
|
||||
*/
|
||||
@HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true)
|
||||
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
||||
|
||||
/**
|
||||
* Resets the password.
|
||||
*/
|
||||
@HTTP(method = "POST", path = "/accounts/password", hasBody = true)
|
||||
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
||||
|
||||
/**
|
||||
* Sets the password.
|
||||
*/
|
||||
@POST("/accounts/set-password")
|
||||
suspend fun setPassword(@Body body: SetPasswordRequestJson): Result<Unit>
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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 retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Defines authenticated raw calls under the /auth-requests API.
|
||||
*/
|
||||
interface AuthenticatedAuthRequestsApi {
|
||||
|
||||
/**
|
||||
* Notifies the server of a new admin authentication request.
|
||||
*/
|
||||
@POST("/auth-requests/admin-request")
|
||||
suspend fun createAdminAuthRequest(
|
||||
@Header("Device-Identifier") deviceIdentifier: String,
|
||||
@Body body: AuthRequestRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Updates an authentication request.
|
||||
*/
|
||||
@PUT("/auth-requests/{id}")
|
||||
suspend fun updateAuthRequest(
|
||||
@Path("id") userId: String,
|
||||
@Body body: AuthRequestUpdateRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Gets a list of auth requests for this device.
|
||||
*/
|
||||
@GET("/auth-requests")
|
||||
suspend fun getAuthRequests(): Result<AuthRequestsResponseJson>
|
||||
|
||||
/**
|
||||
* Retrieves an existing authentication request by ID.
|
||||
*/
|
||||
@GET("/auth-requests/{requestId}")
|
||||
suspend fun getAuthRequest(
|
||||
@Path("requestId") requestId: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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 retrofit2.http.Body
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /devices API that require authentication.
|
||||
*/
|
||||
@Keep
|
||||
interface AuthenticatedDevicesApi {
|
||||
@PUT("/devices/{appId}/keys")
|
||||
suspend fun updateTrustedDeviceKeys(
|
||||
@Path(value = "appId") appId: String,
|
||||
@Body request: TrustedDeviceKeysRequestJson,
|
||||
): Result<TrustedDeviceKeysResponseJson>
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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 retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Defines raw calls under the authenticated /organizations API.
|
||||
*/
|
||||
interface AuthenticatedOrganizationApi {
|
||||
/**
|
||||
* Enrolls this user in the organization's password reset.
|
||||
*/
|
||||
@PUT("/organizations/{orgId}/users/{userId}/reset-password-enrollment")
|
||||
suspend fun organizationResetPasswordEnroll(
|
||||
@Path("orgId") organizationId: String,
|
||||
@Path("userId") userId: String,
|
||||
@Body body: OrganizationResetPasswordEnrollRequestJson,
|
||||
): Result<Unit>
|
||||
|
||||
/**
|
||||
* Checks whether this organization auto enrolls users in password reset.
|
||||
*/
|
||||
@GET("/organizations/{identifier}/auto-enroll-status")
|
||||
suspend fun getOrganizationAutoEnrollResponse(
|
||||
@Path("identifier") organizationIdentifier: String,
|
||||
): Result<OrganizationAutoEnrollStatusResponseJson>
|
||||
|
||||
/**
|
||||
* Gets the public and private keys for this organization.
|
||||
*/
|
||||
@GET("/organizations/{id}/keys")
|
||||
suspend fun getOrganizationKeys(
|
||||
@Path("id") organizationId: String,
|
||||
): Result<OrganizationKeysResponseJson>
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* Defines endpoints for the "have I been pwned" API. For docs see
|
||||
* https://haveibeenpwned.com/API/v2.
|
||||
*/
|
||||
interface HaveIBeenPwnedApi {
|
||||
|
||||
@GET("/range/{hashPrefix}")
|
||||
suspend fun fetchBreachedPasswords(
|
||||
@Path("hashPrefix")
|
||||
hashPrefix: String,
|
||||
): Result<ResponseBody>
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
|
||||
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.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /identity API.
|
||||
*/
|
||||
interface IdentityApi {
|
||||
|
||||
@POST("/connect/token")
|
||||
@Suppress("LongParameterList")
|
||||
@FormUrlEncoded
|
||||
suspend fun getToken(
|
||||
@Field(value = "scope", encoded = true) scope: String,
|
||||
@Field(value = "client_id") clientId: String,
|
||||
@Field(value = "username") email: String,
|
||||
@Header(value = "Auth-Email") authEmail: String,
|
||||
@Field(value = "password") passwordHash: String?,
|
||||
@Field(value = "deviceIdentifier") deviceIdentifier: String,
|
||||
@Field(value = "deviceName") deviceName: String,
|
||||
@Field(value = "deviceType") deviceType: String,
|
||||
@Field(value = "grant_type") grantType: String,
|
||||
@Field(value = "captchaResponse") captchaResponse: String?,
|
||||
@Field(value = "code") ssoCode: String?,
|
||||
@Field(value = "code_verifier") ssoCodeVerifier: String?,
|
||||
@Field(value = "redirect_uri") ssoRedirectUri: String?,
|
||||
@Field(value = "twoFactorToken") twoFactorCode: String?,
|
||||
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
|
||||
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
|
||||
@Field(value = "authRequest") authRequestId: String?,
|
||||
): Result<GetTokenResponseJson.Success>
|
||||
|
||||
@GET("/sso/prevalidate")
|
||||
suspend fun prevalidateSso(
|
||||
@Query("domainHint") organizationIdentifier: String,
|
||||
): Result<PrevalidateSsoResponseJson>
|
||||
|
||||
/**
|
||||
* This call needs to be synchronous so we need it to return a [Call] directly. The identity
|
||||
* service will wrap it up for us.
|
||||
*/
|
||||
@POST("/connect/token")
|
||||
@FormUrlEncoded
|
||||
fun refreshTokenCall(
|
||||
@Field(value = "client_id") clientId: String,
|
||||
@Field(value = "refresh_token") refreshToken: String,
|
||||
@Field(value = "grant_type") grantType: String,
|
||||
): Call<RefreshTokenResponseJson>
|
||||
|
||||
@POST("/accounts/prelogin")
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
||||
|
||||
@POST("/accounts/register")
|
||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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 retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Defines unauthenticated raw calls under the /auth-requests API.
|
||||
*/
|
||||
interface UnauthenticatedAuthRequestsApi {
|
||||
|
||||
/**
|
||||
* Notifies the server of a new authentication request.
|
||||
*/
|
||||
@POST("/auth-requests")
|
||||
suspend fun createAuthRequest(
|
||||
@Header("Device-Identifier") deviceIdentifier: String,
|
||||
@Body body: AuthRequestRequestJson,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
|
||||
/**
|
||||
* Queries for updates to a given auth request.
|
||||
*/
|
||||
@GET("/auth-requests/{requestId}/response")
|
||||
suspend fun getAuthRequestUpdate(
|
||||
@Path("requestId") requestId: String,
|
||||
@Query("code") accessCode: String,
|
||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /devices API that do not require authentication.
|
||||
*/
|
||||
interface UnauthenticatedDevicesApi {
|
||||
@GET("/devices/knowndevice")
|
||||
suspend fun getIsKnownDevice(
|
||||
@Header(value = "X-Request-Email") emailAddress: String,
|
||||
@Header(value = "X-Device-Identifier") deviceId: String,
|
||||
): Result<Boolean>
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AuthRequestsServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.NewAuthRequestServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import retrofit2.create
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides network dependencies in the auth package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AuthNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAccountService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): AccountsService = AccountsServiceImpl(
|
||||
accountsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
authenticatedAccountsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAuthRequestsService(
|
||||
retrofits: Retrofits,
|
||||
): AuthRequestsService = AuthRequestsServiceImpl(
|
||||
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDevicesService(
|
||||
retrofits: Retrofits,
|
||||
): DevicesService = DevicesServiceImpl(
|
||||
authenticatedDevicesApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedDevicesApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIdentityService(
|
||||
retrofits: Retrofits,
|
||||
json: Json,
|
||||
): IdentityService = IdentityServiceImpl(
|
||||
api = retrofits.unauthenticatedIdentityRetrofit.create(),
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesHaveIBeenPwnedService(
|
||||
retrofits: Retrofits,
|
||||
): HaveIBeenPwnedService = HaveIBeenPwnedServiceImpl(
|
||||
retrofits
|
||||
.staticRetrofitBuilder
|
||||
.baseUrl("https://api.pwnedpasswords.com")
|
||||
.build()
|
||||
.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNewAuthRequestService(
|
||||
retrofits: Retrofits,
|
||||
): NewAuthRequestService = NewAuthRequestServiceImpl(
|
||||
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
unauthenticatedAuthRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesOrganizationService(
|
||||
retrofits: Retrofits,
|
||||
): OrganizationService = OrganizationServiceImpl(
|
||||
authenticatedOrganizationApi = retrofits.authenticatedApiRetrofit.create(),
|
||||
organizationApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents the different types of auth requests.
|
||||
*/
|
||||
@Serializable(AuthRequestTypeSerializer::class)
|
||||
enum class AuthRequestTypeJson {
|
||||
@SerialName("0")
|
||||
LOGIN_WITH_DEVICE,
|
||||
|
||||
@SerialName("1")
|
||||
UNLOCK,
|
||||
|
||||
@SerialName("2")
|
||||
ADMIN_APPROVAL,
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class AuthRequestTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<AuthRequestTypeJson>(AuthRequestTypeJson.entries.toTypedArray())
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Models response bodies from the delete account request.
|
||||
*/
|
||||
sealed class DeleteAccountResponseJson {
|
||||
|
||||
/**
|
||||
* Models a successful deletion response.
|
||||
*/
|
||||
data object Success : DeleteAccountResponseJson()
|
||||
|
||||
/**
|
||||
* Models the json body of a deletion error.
|
||||
*
|
||||
* @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("validationErrors")
|
||||
val validationErrors: Map<String, List<String?>>?,
|
||||
) : DeleteAccountResponseJson()
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
/**
|
||||
* Models response bodies from the get token request.
|
||||
*/
|
||||
sealed class GetTokenResponseJson {
|
||||
/**
|
||||
* Models json response of the get token request.
|
||||
*
|
||||
* @property accessToken The user's access token.
|
||||
* @property refreshToken The user's refresh token.
|
||||
* @property tokenType The type of token (ex: "Bearer").
|
||||
* @property expiresInSeconds The amount of time (in seconds) before the [accessToken] expires.
|
||||
* @property key The user's key.
|
||||
* @property privateKey The user's private key.
|
||||
* @property kdfType The KDF type.
|
||||
* @property kdfIterations The number of iterations when calculating a user's password.
|
||||
* @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 shouldForcePasswordReset Whether or not the app must force a password reset.
|
||||
* @property shouldResetMasterPassword Whether or not the user is required to reset their
|
||||
* master password.
|
||||
* @property twoFactorToken If the user has chosen to remember the two-factor authorization,
|
||||
* 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.
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
@SerialName("access_token")
|
||||
val accessToken: String,
|
||||
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String,
|
||||
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
|
||||
@SerialName("expires_in")
|
||||
val expiresInSeconds: Int,
|
||||
|
||||
@SerialName("Key")
|
||||
val key: String?,
|
||||
|
||||
@SerialName("PrivateKey")
|
||||
val privateKey: String?,
|
||||
|
||||
@SerialName("Kdf")
|
||||
val kdfType: KdfTypeJson,
|
||||
|
||||
@SerialName("KdfIterations")
|
||||
val kdfIterations: Int?,
|
||||
|
||||
@SerialName("KdfMemory")
|
||||
val kdfMemory: Int?,
|
||||
|
||||
@SerialName("KdfParallelism")
|
||||
val kdfParallelism: Int?,
|
||||
|
||||
@SerialName("ForcePasswordReset")
|
||||
val shouldForcePasswordReset: Boolean,
|
||||
|
||||
@SerialName("ResetMasterPassword")
|
||||
val shouldResetMasterPassword: Boolean,
|
||||
|
||||
@SerialName("TwoFactorToken")
|
||||
val twoFactorToken: String?,
|
||||
|
||||
@SerialName("MasterPasswordPolicy")
|
||||
val masterPasswordPolicyOptions: MasterPasswordPolicyOptionsJson?,
|
||||
|
||||
@SerialName("UserDecryptionOptions")
|
||||
val userDecryptionOptions: UserDecryptionOptionsJson?,
|
||||
) : GetTokenResponseJson()
|
||||
|
||||
/**
|
||||
* Models json body of a captcha error.
|
||||
*/
|
||||
@Serializable
|
||||
data class CaptchaRequired(
|
||||
@SerialName("HCaptcha_SiteKey")
|
||||
val captchaKey: String,
|
||||
) : GetTokenResponseJson()
|
||||
|
||||
/**
|
||||
* Models json body of an invalid request.
|
||||
*/
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("ErrorModel")
|
||||
val errorModel: ErrorModel,
|
||||
) : GetTokenResponseJson() {
|
||||
|
||||
/**
|
||||
* The error body of an invalid request containing a message.
|
||||
*/
|
||||
@Serializable
|
||||
data class ErrorModel(
|
||||
@SerialName("Message")
|
||||
val errorMessage: String,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Models json body of a two-factor error.
|
||||
*
|
||||
* @property authMethodsData A blob of data formatted as:
|
||||
* `{"1":{"Email":"sh*****@example.com"},"0":{"Email":null}}`
|
||||
* The keys are the raw values of the [TwoFactorAuthMethod],
|
||||
* and the map is any extra information for the method.
|
||||
* @property captchaToken The captcha token used in the second
|
||||
* login attempt if the user has already passed a captcha
|
||||
* authentication in the first attempt.
|
||||
* @property ssoToken If the user is logging on via Single
|
||||
* Sign On, they'll need this value to complete authentication
|
||||
* after entering their two-factor code.
|
||||
*/
|
||||
@Serializable
|
||||
data class TwoFactorRequired(
|
||||
@SerialName("TwoFactorProviders2")
|
||||
val authMethodsData: Map<TwoFactorAuthMethod, JsonObject?>,
|
||||
|
||||
@SerialName("TwoFactorProviders")
|
||||
val twoFactorProviders: List<String>?,
|
||||
|
||||
@SerialName("CaptchaBypassToken")
|
||||
val captchaToken: String?,
|
||||
|
||||
@SerialName("SsoEmail2faSessionToken")
|
||||
val ssoToken: String?,
|
||||
) : GetTokenResponseJson()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents different key derivation functions (KDFs).
|
||||
*/
|
||||
@Serializable(KdfTypeSerializer::class)
|
||||
enum class KdfTypeJson {
|
||||
@SerialName("1")
|
||||
ARGON2_ID,
|
||||
|
||||
@SerialName("0")
|
||||
PBKDF2_SHA256,
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class KdfTypeSerializer :
|
||||
BaseEnumeratedIntSerializer<KdfTypeJson>(KdfTypeJson.entries.toTypedArray())
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Decryption options related to a user's key connector.
|
||||
*
|
||||
* @property keyConnectorUrl URL to the user's key connector.
|
||||
*/
|
||||
@Serializable
|
||||
data class KeyConnectorUserDecryptionOptionsJson(
|
||||
@SerialName("KeyConnectorUrl")
|
||||
val keyConnectorUrl: String,
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body object when retrieving organization domain SSO info.
|
||||
*
|
||||
* @param email The email address to check against.
|
||||
*/
|
||||
@Serializable
|
||||
data class OrganizationDomainSsoDetailsRequestJson(
|
||||
@SerialName("email") val email: String,
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user