Compare commits
308 Commits
feature/ma
...
vault/pm-7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f312e8c4d2 | ||
|
|
1b3d5e5eb2 | ||
|
|
81fbb91c76 | ||
|
|
45641aadfe | ||
|
|
27380abd89 | ||
|
|
9db32ca019 | ||
|
|
1fd7dd462e | ||
|
|
f04ff7777a | ||
|
|
64775694e0 | ||
|
|
3c0007a21a | ||
|
|
ff49d041be | ||
|
|
b931263662 | ||
|
|
3a10e09469 | ||
|
|
ebc068d820 | ||
|
|
6bec0ede05 | ||
|
|
35ff235010 | ||
|
|
01bd5a7b8d | ||
|
|
3fce8c76bc | ||
|
|
3b64d7b979 | ||
|
|
f343a2cdbb | ||
|
|
39da2a82c6 | ||
|
|
970d3c2621 | ||
|
|
faa515b415 | ||
|
|
74085689d3 | ||
|
|
9a9fb85ad8 | ||
|
|
e7f9d64edb | ||
|
|
144fc7c727 | ||
|
|
53aedea93a | ||
|
|
459d20c019 | ||
|
|
dd997aaa47 | ||
|
|
a8529fa4b7 | ||
|
|
d1e82c9f1d | ||
|
|
46c1d72b3c | ||
|
|
9bc2901255 | ||
|
|
01fe329f3b | ||
|
|
67f7b3156e | ||
|
|
e3441845cd | ||
|
|
3f463647a0 | ||
|
|
4f169a6fe3 | ||
|
|
82c2e91446 | ||
|
|
39187732c0 | ||
|
|
7482808857 | ||
|
|
4292542155 | ||
|
|
e41abf5003 | ||
|
|
fd233fa27f | ||
|
|
4c2932f4d0 | ||
|
|
a10481603d | ||
|
|
19f238d9bb | ||
|
|
6f6487ccc9 | ||
|
|
b8ff0e0244 | ||
|
|
dd3dc82595 | ||
|
|
40c80f082d | ||
|
|
85755902e1 | ||
|
|
bca5b95446 | ||
|
|
602627b5fa | ||
|
|
6f32afb919 | ||
|
|
2ca47a4da4 | ||
|
|
38d3a7ed41 | ||
|
|
4ff56ba11e | ||
|
|
22d0cc681c | ||
|
|
4e0a18cce5 | ||
|
|
c9fdfa7a15 | ||
|
|
850a7e754a | ||
|
|
18fae7ddd8 | ||
|
|
67c5f79625 | ||
|
|
04e7cfe06d | ||
|
|
d6c2ebe4c2 | ||
|
|
2a28294f91 | ||
|
|
b83473ce3a | ||
|
|
8584bbaecc | ||
|
|
2f3cded9c5 | ||
|
|
eff0ea7ce7 | ||
|
|
6c3a53dd76 | ||
|
|
e34a58e875 | ||
|
|
9f92fdeb29 | ||
|
|
cf8d801c55 | ||
|
|
eaa6844742 | ||
|
|
29e2f728e0 | ||
|
|
c31444dc8b | ||
|
|
16e1b60a4d | ||
|
|
fe160a570f | ||
|
|
71de3bedf4 | ||
|
|
a508bea4b0 | ||
|
|
a73923c4f7 | ||
|
|
11465e8975 | ||
|
|
4c88524f0e | ||
|
|
f1c20e03bc | ||
|
|
920a2273c5 | ||
|
|
d339514d9a | ||
|
|
75ec96f282 | ||
|
|
96a9978ef8 | ||
|
|
1ae388cb03 | ||
|
|
c6aaf5002f | ||
|
|
75be6504e1 | ||
|
|
cae1825e3f | ||
|
|
c23100d281 | ||
|
|
c9c0d0b4d6 | ||
|
|
fec0743e4d | ||
|
|
118dcf164c | ||
|
|
bd03b6b5aa | ||
|
|
333917c00d | ||
|
|
450101d9e4 | ||
|
|
4e50f1697d | ||
|
|
3c96ae2220 | ||
|
|
fdbd16a6fd | ||
|
|
39a34bd8c4 | ||
|
|
8f8a5795d3 | ||
|
|
4631a9e62c | ||
|
|
51ee6a84b5 | ||
|
|
f30158adf5 | ||
|
|
8d5006c0bd | ||
|
|
37208571fe | ||
|
|
759627b3c7 | ||
|
|
08fac4752f | ||
|
|
9307e7e0d8 | ||
|
|
b1a0801f9b | ||
|
|
04cc53b934 | ||
|
|
c138658a31 | ||
|
|
c6a086fe62 | ||
|
|
b217451ea9 | ||
|
|
8cb7d5e1a3 | ||
|
|
82b837ef33 | ||
|
|
f1854f2c04 | ||
|
|
e4056d9ee6 | ||
|
|
eb95a54db2 | ||
|
|
7ddea4c70b | ||
|
|
3804e86995 | ||
|
|
b23bed182f | ||
|
|
f8e421871b | ||
|
|
ebb2a288a1 | ||
|
|
de7ae27a77 | ||
|
|
d0103496b9 | ||
|
|
cd8952221e | ||
|
|
155c7539bd | ||
|
|
d3dd2e9342 | ||
|
|
5f43681fb1 | ||
|
|
d2965e6e10 | ||
|
|
ec1ade7761 | ||
|
|
f35bef0d7b | ||
|
|
a5878d3341 | ||
|
|
1dc55f78e7 | ||
|
|
37b62b317f | ||
|
|
528e412458 | ||
|
|
0f22f2750e | ||
|
|
7bbb711175 | ||
|
|
138d37cf5e | ||
|
|
fc2fed079f | ||
|
|
9c441a98f4 | ||
|
|
fd80a9ce7c | ||
|
|
1491872b62 | ||
|
|
c74636ffa5 | ||
|
|
05677f93c5 | ||
|
|
0aef241df6 | ||
|
|
3f10a6be24 | ||
|
|
f3537b1a74 | ||
|
|
849a0c24b0 | ||
|
|
e0b58461b5 | ||
|
|
cd33c7f608 | ||
|
|
f6a58e469f | ||
|
|
9d29af36e5 | ||
|
|
4472d7f9a8 | ||
|
|
999579915c | ||
|
|
63904fd303 | ||
|
|
983937c9eb | ||
|
|
2cb6872e4e | ||
|
|
f539bf051d | ||
|
|
14f845d623 | ||
|
|
b2f93d3d4b | ||
|
|
133a80acef | ||
|
|
b43790de9a | ||
|
|
64c694e593 | ||
|
|
56b9e3f615 | ||
|
|
7558f60a44 | ||
|
|
0bdd63df06 | ||
|
|
c6544b49e9 | ||
|
|
8e1a8b5f0e | ||
|
|
4717f5e230 | ||
|
|
e66ac9dd44 | ||
|
|
01ee1ff845 | ||
|
|
75b4655f38 | ||
|
|
9b2f596d15 | ||
|
|
55fb71744d | ||
|
|
ee252be634 | ||
|
|
66f0471f2e | ||
|
|
6b9eeba88d | ||
|
|
0a1fbfafb5 | ||
|
|
d6c139cb8a | ||
|
|
0a5d772886 | ||
|
|
6b7c6eac71 | ||
|
|
70c8a264d2 | ||
|
|
b5fbb2cade | ||
|
|
9027755b71 | ||
|
|
6d625f285b | ||
|
|
822ad7564e | ||
|
|
1949a450fd | ||
|
|
27fa79e0bd | ||
|
|
9e1d6c7b03 | ||
|
|
1e29eacc61 | ||
|
|
b81d26d589 | ||
|
|
cd107b6161 | ||
|
|
7ac3646fb0 | ||
|
|
d1e4e8645a | ||
|
|
e107b893ea | ||
|
|
5de02c863f | ||
|
|
0e95d4d4ca | ||
|
|
36a648e53e | ||
|
|
6c04ac67b1 | ||
|
|
a42b88b666 | ||
|
|
dfb7a0621f | ||
|
|
1eb9e5f8ea | ||
|
|
b149e7549c | ||
|
|
e3877cc589 | ||
|
|
275ae76761 | ||
|
|
a1e4f0aaa2 | ||
|
|
adaef0d15b | ||
|
|
af6866cee1 | ||
|
|
0cec49f121 | ||
|
|
d091922017 | ||
|
|
f14be2a3a2 | ||
|
|
8ee744b746 | ||
|
|
fa4a2247e3 | ||
|
|
5d2fc4530f | ||
|
|
9b64af3423 | ||
|
|
b6ff6e34f6 | ||
|
|
6d4c706026 | ||
|
|
14fd026ea0 | ||
|
|
a4392a8730 | ||
|
|
1b885ea438 | ||
|
|
15a03ba573 | ||
|
|
fa022a1a4f | ||
|
|
6011b63958 | ||
|
|
7d79b98bf2 | ||
|
|
d4e75e9de8 | ||
|
|
82711a0235 | ||
|
|
c3370b58ec | ||
|
|
3de13325c9 | ||
|
|
c253c110c1 | ||
|
|
bf35d1f2dc | ||
|
|
05b6aa90b6 | ||
|
|
e39898bba6 | ||
|
|
da0866cc85 | ||
|
|
b3140381ab | ||
|
|
c01a8f8d93 | ||
|
|
8484b4af30 | ||
|
|
6b9faed45f | ||
|
|
770a1c5dfe | ||
|
|
e6635564aa | ||
|
|
3c87d4db1c | ||
|
|
6c078fe343 | ||
|
|
8b3c6ab35f | ||
|
|
90912977c4 | ||
|
|
740b368b8c | ||
|
|
3a40a4cda8 | ||
|
|
9bcd2e51f7 | ||
|
|
741214a1cc | ||
|
|
aad87dfdce | ||
|
|
8fc1e9a3b9 | ||
|
|
2a8e15146e | ||
|
|
8559d5908e | ||
|
|
f60c4d94fe | ||
|
|
05858bea48 | ||
|
|
5cbef47fd4 | ||
|
|
4bf695d18c | ||
|
|
c24e0dfa28 | ||
|
|
9ccd0834ff | ||
|
|
743e71ff92 | ||
|
|
a806f17d3b | ||
|
|
436a162df2 | ||
|
|
7b579b7aa5 | ||
|
|
fe10fd7766 | ||
|
|
3c0de8aacc | ||
|
|
18d9a77f25 | ||
|
|
9eca82a62b | ||
|
|
f2c298607e | ||
|
|
b90e030b8f | ||
|
|
b5dbb9ae5e | ||
|
|
7a5f7c0274 | ||
|
|
17acb57732 | ||
|
|
9a28419a4e | ||
|
|
5803635f44 | ||
|
|
19c393842f | ||
|
|
15a306490d | ||
|
|
a4a3d31c19 | ||
|
|
f4c468e6a1 | ||
|
|
922dc683af | ||
|
|
2c346eb710 | ||
|
|
9c0908f7b7 | ||
|
|
827fbbc9ce | ||
|
|
bae1b3e891 | ||
|
|
a5888827c9 | ||
|
|
0348940a12 | ||
|
|
5b249bed67 | ||
|
|
4c2998337d | ||
|
|
7ea86380f4 | ||
|
|
406f4425c8 | ||
|
|
95ca911444 | ||
|
|
fa62510e09 | ||
|
|
65dc73495d | ||
|
|
02a2e41118 | ||
|
|
bd6f8295e7 | ||
|
|
0a0cb7093b | ||
|
|
465e5eff76 | ||
|
|
5b756aaf7a | ||
|
|
afbcb212f6 | ||
|
|
d168a7b750 | ||
|
|
a71c28536d | ||
|
|
ba5fa8a518 | ||
|
|
65ea5574de |
28
.github/CODEOWNERS
vendored
@@ -1,12 +1,21 @@
|
|||||||
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
|
# Please sort into logical groups with comment headers. Sort groups in order of specificity.
|
||||||
|
# For example, default owners should always be the first group.
|
||||||
|
# Sort lines alphabetically within these groups to avoid accidentally adding duplicates.
|
||||||
#
|
#
|
||||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||||
|
|
||||||
# The following owners will be the default owners for everything in the repo.
|
# Default file owners
|
||||||
# Unless a later match takes precedence
|
* @bitwarden/dept-development-mobile
|
||||||
# @bitwarden/tech-leads
|
|
||||||
|
|
||||||
@bitwarden/dept-development-mobile
|
# DevOps for Actions and other workflow changes
|
||||||
|
.github/workflows @bitwarden/dept-devops
|
||||||
|
|
||||||
|
# DevOps for Version Bumping
|
||||||
|
src/App/Platforms/Android/AndroidManifest.xml
|
||||||
|
src/iOS.Autofill/Info.plist
|
||||||
|
src/iOS.Extension/Info.plist
|
||||||
|
src/iOS.ShareExtension/Info.plist
|
||||||
|
src/App/Platforms/iOS/Info.plist
|
||||||
|
|
||||||
## Auth team files ##
|
## Auth team files ##
|
||||||
|
|
||||||
@@ -21,14 +30,17 @@ src/watchOS @bitwarden/team-vault-dev
|
|||||||
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
|
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
|
||||||
|
|
||||||
## Crowdin Sync files ##
|
## Crowdin Sync files ##
|
||||||
src/App/Resources @bitwarden/team-tools-dev
|
src/Core/Resources/Localization @bitwarden/team-tools-dev
|
||||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
|
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
|
||||||
store/apple @bitwarden/team-tools-dev
|
store/apple @bitwarden/team-tools-dev
|
||||||
store/google @bitwarden/team-tools-dev
|
store/google @bitwarden/team-tools-dev
|
||||||
|
|
||||||
## Locales ##
|
## Locales ##
|
||||||
src/App/Resources/AppResources.Designer.cs
|
src/Core/Resources/Localization/AppResources.Designer.cs
|
||||||
src/App/Resources/AppResources.resx
|
src/Core/Resources/Localization/AppResources.resx
|
||||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
|
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
|
||||||
store/apple/en
|
store/apple/en
|
||||||
store/google/en
|
store/google/en
|
||||||
|
|
||||||
|
## Utils ##
|
||||||
|
store/google/Publisher
|
||||||
|
|||||||
17
.github/renovate.json
vendored
@@ -2,22 +2,21 @@
|
|||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:base",
|
"config:base",
|
||||||
|
"github>bitwarden/renovate-config:pin-actions",
|
||||||
":combinePatchMinorReleases",
|
":combinePatchMinorReleases",
|
||||||
":dependencyDashboard",
|
":dependencyDashboard",
|
||||||
":maintainLockFilesWeekly",
|
":maintainLockFilesWeekly",
|
||||||
":pinAllExceptPeerDependencies",
|
":pinAllExceptPeerDependencies",
|
||||||
":prConcurrentLimit10",
|
":prConcurrentLimit10",
|
||||||
":rebaseStalePrs",
|
":rebaseStalePrs",
|
||||||
"schedule:weekends",
|
":separateMajorReleases",
|
||||||
":separateMajorReleases"
|
"group:monorepos",
|
||||||
|
"schedule:weekends"
|
||||||
],
|
],
|
||||||
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
|
"enabledManagers": ["github-actions", "npm", "nuget"],
|
||||||
|
"commitMessagePrefix": "[deps]:",
|
||||||
|
"commitMessageTopic": "{{depName}}",
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
|
||||||
"groupName": "cargo minor",
|
|
||||||
"matchManagers": ["cargo"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"groupName": "gh minor",
|
"groupName": "gh minor",
|
||||||
"matchManagers": ["github-actions"],
|
"matchManagers": ["github-actions"],
|
||||||
@@ -32,6 +31,6 @@
|
|||||||
"groupName": "nuget minor",
|
"groupName": "nuget minor",
|
||||||
"matchManagers": ["nuget"],
|
"matchManagers": ["nuget"],
|
||||||
"matchUpdateTypes": ["minor", "patch"]
|
"matchUpdateTypes": ["minor", "patch"]
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
.github/secrets/GoogleService-Info.plist.gpg
vendored
BIN
.github/secrets/app_fdroid-keystore.jks.gpg
vendored
BIN
.github/secrets/app_play-keystore.jks.gpg
vendored
BIN
.github/secrets/app_upload-keystore.jks.gpg
vendored
BIN
.github/secrets/bitwarden-mobile-key.p12.gpg
vendored
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_watch_app.mobileprovision.gpg
vendored
3
.github/secrets/google-services.json.gpg
vendored
@@ -1,3 +0,0 @@
|
|||||||
<EFBFBD>
|
|
||||||
K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>'F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74>튖<EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>
|
|
||||||
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E><EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>
|
|
||||||
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
BIN
.github/secrets/play_creds.json.gpg
vendored
BIN
.github/secrets/store_fdroid-keystore.jks.gpg
vendored
5
.github/workflows/build-beta.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: Build Beta
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
718
.github/workflows/build.yml
vendored
53
.github/workflows/cleanup-rc-branch.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
name: Cleanup RC Branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
delete-rc:
|
||||||
|
name: Delete RC Branch
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Login to Azure - CI Subscription
|
||||||
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
|
- name: Retrieve bot secrets
|
||||||
|
id: retrieve-bot-secrets
|
||||||
|
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||||
|
with:
|
||||||
|
keyvault: bitwarden-ci
|
||||||
|
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
|
|
||||||
|
- name: Checkout main
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
|
|
||||||
|
- name: Check if a RC branch exists
|
||||||
|
id: branch-check
|
||||||
|
run: |
|
||||||
|
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
|
||||||
|
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||||
|
|
||||||
|
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
|
||||||
|
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${rc_branch_check}" -gt 0 ]]; then
|
||||||
|
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
echo "name=rc" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Delete RC branch
|
||||||
|
env:
|
||||||
|
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
|
||||||
|
run: |
|
||||||
|
if ! [[ -z "$BRANCH_NAME" ]]; then
|
||||||
|
git push --quiet origin --delete $BRANCH_NAME
|
||||||
|
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
8
.github/workflows/crowdin-pull.yml
vendored
@@ -15,10 +15,10 @@ jobs:
|
|||||||
_CROWDIN_PROJECT_ID: "269690"
|
_CROWDIN_PROJECT_ID: "269690"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
@@ -30,13 +30,13 @@ jobs:
|
|||||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
crowdin_branch_name: master
|
crowdin_branch_name: main
|
||||||
upload_sources: false
|
upload_sources: false
|
||||||
upload_translations: false
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
|||||||
66
.github/workflows/release.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
branch-name: ${{ steps.branch.outputs.branch-name }}
|
branch-name: ${{ steps.branch.outputs.branch-name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Branch check
|
- name: Branch check
|
||||||
if: github.event.inputs.release_type != 'Dry Run'
|
if: inputs.release_type != 'Dry Run'
|
||||||
run: |
|
run: |
|
||||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
@@ -38,15 +38,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
- name: Check Release Version
|
- name: Check Release Version
|
||||||
id: version
|
id: version
|
||||||
uses: bitwarden/gh-actions/release-version-check@main
|
uses: bitwarden/gh-actions/release-version-check@main
|
||||||
with:
|
with:
|
||||||
release-type: ${{ github.event.inputs.release_type }}
|
release-type: ${{ inputs.release_type }}
|
||||||
project-type: xamarin
|
project-type: xamarin
|
||||||
file: src/Android/Properties/AndroidManifest.xml
|
file: src/App/Platforms/Android/AndroidManifest.xml
|
||||||
|
|
||||||
- name: Get branch name
|
- name: Get branch name
|
||||||
id: branch
|
id: branch
|
||||||
@@ -55,8 +55,8 @@ jobs:
|
|||||||
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create GitHub deployment
|
- name: Create GitHub deployment
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||||
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
|
uses: chrnorm/deployment-action@55729fcebec3d284f60f5bcabbd8376437d696b1 # v2.0.7
|
||||||
id: deployment
|
id: deployment
|
||||||
with:
|
with:
|
||||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
@@ -67,27 +67,27 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: ${{ steps.branch.outputs.branch-name }}
|
branch: ${{ steps.branch.outputs.branch-name }}
|
||||||
|
|
||||||
- name: Dry Run - Download all artifacts
|
- name: Dry Run - Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: master
|
branch: main
|
||||||
|
|
||||||
- name: Prep Bitwarden iOS release asset
|
- name: Prep Bitwarden iOS release asset
|
||||||
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||||
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
|
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
|
||||||
with:
|
with:
|
||||||
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
||||||
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
||||||
@@ -103,16 +103,16 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
- name: Update deployment status to Success
|
- name: Update deployment status to Success
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
|
if: ${{ inputs.release_type != 'Dry Run' && success() }}
|
||||||
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
|
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
|
||||||
with:
|
with:
|
||||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
state: 'success'
|
state: 'success'
|
||||||
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
||||||
|
|
||||||
- name: Update deployment status to Failure
|
- name: Update deployment status to Failure
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
|
if: ${{ inputs.release_type != 'Dry Run' && failure() }}
|
||||||
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
|
uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3
|
||||||
with:
|
with:
|
||||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
state: 'failure'
|
state: 'failure'
|
||||||
@@ -126,11 +126,11 @@ jobs:
|
|||||||
if: inputs.fdroid_publish
|
if: inputs.fdroid_publish
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
- name: Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -138,16 +138,16 @@ jobs:
|
|||||||
name: com.x8bit.bitwarden-fdroid.apk
|
name: com.x8bit.bitwarden-fdroid.apk
|
||||||
|
|
||||||
- name: Dry Run - Download F-Droid .apk artifact
|
- name: Dry Run - Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
branch: master
|
branch: main
|
||||||
name: com.x8bit.bitwarden-fdroid.apk
|
name: com.x8bit.bitwarden-fdroid.apk
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '16.x'
|
||||||
|
|
||||||
@@ -176,13 +176,19 @@ jobs:
|
|||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
- name: Decrypt secrets
|
- name: Login to Azure - CI Subscription
|
||||||
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
|
- name: Download secrets
|
||||||
env:
|
env:
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
ACCOUNT_NAME: bitwardenci
|
||||||
|
CONTAINER_NAME: mobile
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/secrets
|
mkdir -p $HOME/secrets
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
|
||||||
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
|
--name store_fdroid-keystore.jks --file ./store/fdroid/keystore.jks --output none
|
||||||
|
|
||||||
- name: Compile for F-Droid Store
|
- name: Compile for F-Droid Store
|
||||||
env:
|
env:
|
||||||
@@ -211,5 +217,5 @@ jobs:
|
|||||||
cd $GITHUB_WORKSPACE
|
cd $GITHUB_WORKSPACE
|
||||||
|
|
||||||
- name: Deploy to gh-pages
|
- name: Deploy to gh-pages
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||||
run: npm run deploy
|
run: npm run deploy
|
||||||
|
|||||||
4
.github/workflows/stale-bot.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: 'Run stale action'
|
- name: 'Run stale action'
|
||||||
uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
|
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||||
with:
|
with:
|
||||||
stale-issue-label: 'needs-reply'
|
stale-issue-label: 'needs-reply'
|
||||||
stale-pr-label: 'needs-changes'
|
stale-pr-label: 'needs-changes'
|
||||||
@@ -27,4 +27,4 @@ jobs:
|
|||||||
|
|
||||||
If you’re still working on this, please respond here after you’ve made the changes we’ve requested and our team will re-open it for further review.
|
If you’re still working on this, please respond here after you’ve made the changes we’ve requested and our team will re-open it for further review.
|
||||||
|
|
||||||
Please make sure to resolve any conflicts with the master branch before requesting another review.
|
Please make sure to resolve any conflicts with the main branch before requesting another review.
|
||||||
|
|||||||
44
.github/workflows/version-auto-bump.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: Version Auto Bump
|
name: Auto Bump Mobile Version
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,33 +7,25 @@ on:
|
|||||||
- v**
|
- v**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
bump-version:
|
||||||
name: "Setup"
|
name: Bump Mobile Version
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
outputs:
|
|
||||||
version_number: ${{ steps.version.outputs.new-version }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
- name: Login to Azure - CI Subscription
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
|
with:
|
||||||
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
- name: Calculate bumped version
|
- name: Retrieve bot secrets
|
||||||
id: version
|
id: retrieve-bot-secrets
|
||||||
|
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||||
|
with:
|
||||||
|
keyvault: bitwarden-ci
|
||||||
|
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
|
|
||||||
|
- name: Trigger Version Bump workflow
|
||||||
env:
|
env:
|
||||||
RELEASE_TAG: ${{ github.ref }}
|
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
run: |
|
run: |
|
||||||
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
|
echo '{"cut_rc_branch": "false"}' | \
|
||||||
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
|
gh workflow run version-bump.yml --json --repo bitwarden/mobile
|
||||||
echo "Current Major: $CURR_MAJOR"
|
|
||||||
echo "Current Patch: $CURR_PATCH"
|
|
||||||
|
|
||||||
NEW_PATCH=$((CURR_PATCH+1))
|
|
||||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
|
||||||
echo "New Version: $NEW_VER"
|
|
||||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
trigger_version_bump:
|
|
||||||
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
|
||||||
needs: setup
|
|
||||||
uses: ./.github/workflows/version-bump.yml
|
|
||||||
with:
|
|
||||||
version_number: ${{ needs.setup.outputs.version_number }}
|
|
||||||
|
|||||||
287
.github/workflows/version-bump.yml
vendored
@@ -4,81 +4,200 @@ name: Version Bump
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version_number:
|
version_number_override:
|
||||||
description: "New Version"
|
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
|
||||||
required: true
|
required: false
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
version_number:
|
|
||||||
required: true
|
|
||||||
type: string
|
type: string
|
||||||
|
cut_rc_branch:
|
||||||
|
description: "Cut RC branch?"
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump_version:
|
bump_version:
|
||||||
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
name: Bump Version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.set-final-version-output.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Validate version input
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-check@main
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Check if RC branch exists
|
||||||
|
if: ${{ inputs.cut_rc_branch == true }}
|
||||||
|
run: |
|
||||||
|
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||||
|
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
|
||||||
|
echo "Remote RC branch exists."
|
||||||
|
echo "Please delete current RC branch before running again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
- name: Retrieve secrets
|
- name: Retrieve secrets
|
||||||
id: retrieve-secrets
|
id: retrieve-secrets
|
||||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "github-gpg-private-key,
|
||||||
|
github-gpg-private-key-passphrase,
|
||||||
|
github-pat-bitwarden-devops-bot-repo-scope"
|
||||||
|
|
||||||
- name: Import GPG key
|
- name: Import GPG key
|
||||||
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
|
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
|
||||||
with:
|
with:
|
||||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||||
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||||
git_user_signingkey: true
|
git_user_signingkey: true
|
||||||
git_commit_gpgsign: true
|
git_commit_gpgsign: true
|
||||||
|
|
||||||
- name: Create Version Branch
|
|
||||||
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Bump Version - Android XML
|
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/Android/Properties/AndroidManifest.xml"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS.Autofill
|
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS.Autofill/Info.plist"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS.Extension
|
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS.Extension/Info.plist"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS.ShareExtension
|
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS.ShareExtension/Info.plist"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS
|
|
||||||
uses: bitwarden/gh-actions/version-bump@main
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS/Info.plist"
|
|
||||||
|
|
||||||
- name: Setup git
|
- name: Setup git
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||||
git config --local user.name "bitwarden-devops-bot"
|
git config --local user.name "bitwarden-devops-bot"
|
||||||
|
|
||||||
|
- name: Create Version Branch
|
||||||
|
id: create-branch
|
||||||
|
run: |
|
||||||
|
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
|
||||||
|
git switch -c $NAME
|
||||||
|
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Install xmllint
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
|
- name: Get current version
|
||||||
|
id: current-version
|
||||||
|
run: |
|
||||||
|
CURRENT_VERSION=$(xmllint --xpath '
|
||||||
|
string(/manifest/@*[local-name()="versionName"
|
||||||
|
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||||
|
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Verify input version
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
env:
|
||||||
|
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
|
||||||
|
NEW_VERSION: ${{ inputs.version_number_override }}
|
||||||
|
run: |
|
||||||
|
# Error if version has not changed.
|
||||||
|
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||||
|
echo "Version has not changed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if version is newer.
|
||||||
|
printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Version check successful."
|
||||||
|
else
|
||||||
|
echo "Version check failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Calculate next release version
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
id: calculate-next-version
|
||||||
|
uses: bitwarden/gh-actions/version-next@main
|
||||||
|
with:
|
||||||
|
version: ${{ steps.current-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Version - Android XML - Version Override
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
id: bump-version-override
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Version - Android XML - Automatic Calculation
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
id: bump-version-automatic
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
|
||||||
|
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.Autofill - Version Override
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.Autofill/Info.plist"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.Autofill - Automatic Calculation
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.Autofill/Info.plist"
|
||||||
|
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.Extension - Version Override
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.Extension/Info.plist"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.Extension - Automatic Calculation
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.Extension/Info.plist"
|
||||||
|
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.ShareExtension - Version Override
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.ShareExtension/Info.plist"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS.ShareExtension - Automatic Calculation
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/iOS.ShareExtension/Info.plist"
|
||||||
|
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS - Version Override
|
||||||
|
if: ${{ inputs.version_number_override != '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||||
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
|
- name: Bump Version - iOS - Automatic Calculation
|
||||||
|
if: ${{ inputs.version_number_override == '' }}
|
||||||
|
uses: bitwarden/gh-actions/version-bump@main
|
||||||
|
with:
|
||||||
|
file_path: "src/App/Platforms/iOS/Info.plist"
|
||||||
|
version: ${{ steps.calculate-next-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Set Job output
|
||||||
|
id: set-final-version-output
|
||||||
|
run: |
|
||||||
|
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then
|
||||||
|
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
|
||||||
|
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Check if version changed
|
- name: Check if version changed
|
||||||
id: version-changed
|
id: version-changed
|
||||||
run: |
|
run: |
|
||||||
@@ -91,22 +210,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Commit files
|
- name: Commit files
|
||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
run: git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
env:
|
||||||
|
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||||
|
run: git push -u origin $PR_BRANCH
|
||||||
|
|
||||||
- name: Create Version PR
|
- name: Create Version PR
|
||||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
|
id: create-pr
|
||||||
env:
|
env:
|
||||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||||
BASE_BRANCH: master
|
TITLE: "Bump version to ${{ steps.set-final-version-output.outputs.version }}"
|
||||||
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
|
||||||
run: |
|
run: |
|
||||||
gh pr create --title "$TITLE" \
|
PR_URL=$(gh pr create --title "$TITLE" \
|
||||||
--base "$BASE" \
|
--base "main" \
|
||||||
--head "$PR_BRANCH" \
|
--head "$PR_BRANCH" \
|
||||||
--label "version update" \
|
--label "version update" \
|
||||||
--label "automated pr" \
|
--label "automated pr" \
|
||||||
@@ -119,4 +240,58 @@ jobs:
|
|||||||
- [X] Other
|
- [X] Other
|
||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
Automated version bump to ${{ github.event.inputs.version_number }}"
|
Automated version bump to ${{ steps.set-final-version-output.outputs.version }}")
|
||||||
|
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Approve PR
|
||||||
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||||
|
run: gh pr review $PR_NUMBER --approve
|
||||||
|
|
||||||
|
- name: Merge PR
|
||||||
|
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||||
|
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||||
|
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
|
||||||
|
|
||||||
|
cut_rc:
|
||||||
|
name: Cut RC branch
|
||||||
|
if: ${{ inputs.cut_rc_branch == true }}
|
||||||
|
needs: bump_version
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout Branch
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Install xmllint
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libxml2-utils
|
||||||
|
|
||||||
|
- name: Verify version has been updated
|
||||||
|
env:
|
||||||
|
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
|
||||||
|
run: |
|
||||||
|
# Wait for version to change.
|
||||||
|
while : ; do
|
||||||
|
echo "Waiting for version to be updated..."
|
||||||
|
git pull --force
|
||||||
|
CURRENT_VERSION=$(xmllint --xpath '
|
||||||
|
string(/manifest/@*[local-name()="versionName"
|
||||||
|
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||||
|
' src/App/Platforms/Android/AndroidManifest.xml)
|
||||||
|
|
||||||
|
# If the versions don't match we continue the loop, otherwise we break out of the loop.
|
||||||
|
[[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Cut RC branch
|
||||||
|
run: |
|
||||||
|
git switch --quiet --create rc
|
||||||
|
git push --quiet --set-upstream origin rc
|
||||||
|
|||||||
11
.github/workflows/workflow-linter.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: Workflow Linter
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- .github/workflows/**
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
call-workflow:
|
|
||||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main
|
|
||||||
1
.gitignore
vendored
@@ -148,6 +148,7 @@ publish/
|
|||||||
|
|
||||||
# NuGet Packages
|
# NuGet Packages
|
||||||
*.nupkg
|
*.nupkg
|
||||||
|
!**/Xamarin.AndroidX.Credentials.1.0.0.nupkg
|
||||||
# The packages folder can be ignored because of Package Restore
|
# The packages folder can be ignored because of Package Restore
|
||||||
**/packages/*
|
**/packages/*
|
||||||
# except build/, which is used as an MSBuild target.
|
# except build/, which is used as an MSBuild target.
|
||||||
|
|||||||
16
Directory.Build.props
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<MauiVersion>8.0.7</MauiVersion>
|
||||||
|
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
|
||||||
|
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
|
||||||
|
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
|
||||||
|
<IncludeBitwardenWatchOSApp>True</IncludeBitwardenWatchOSApp>
|
||||||
|
<Argon2IdLoadMtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</Argon2IdLoadMtouchExtraArgs>
|
||||||
|
|
||||||
|
<!-- Uncomment this when Unit Testing-->
|
||||||
|
<!-- <CustomConstants>UT</CustomConstants> -->
|
||||||
|
|
||||||
|
<!-- Uncomment this when building FDROID-->
|
||||||
|
<!-- <CustomConstants>FDROID</CustomConstants> -->
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
|
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:main)
|
||||||
[](https://crowdin.com/project/bitwarden-mobile)
|
[](https://crowdin.com/project/bitwarden-mobile)
|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
|
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
|
||||||
|
|
||||||
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
The Bitwarden mobile application is written in C# using .NET MAUI.
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
|
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
|
|||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Please commit any pull requests against the `master` 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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ VisualStudioVersion = 17.8.34112.27
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{971FDF07-E288-4239-B47A-E9E7E912193B}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "src\Core\Core.csproj", "{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -15,6 +15,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.ShareExtension", "src\i
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{83449CC4-1F76-4CFE-92B1-D2E13A62506F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -27,6 +35,7 @@ Global
|
|||||||
AppStore|iPhone = AppStore|iPhone
|
AppStore|iPhone = AppStore|iPhone
|
||||||
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
||||||
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
||||||
|
FDroid|Any CPU = FDroid|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
@@ -51,6 +60,8 @@ Global
|
|||||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{971FDF07-E288-4239-B47A-E9E7E912193B}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -71,6 +82,8 @@ Global
|
|||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -91,6 +104,8 @@ Global
|
|||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -111,6 +126,8 @@ Global
|
|||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -131,6 +148,8 @@ Global
|
|||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8C3F648-EA5A-4719-8005-85D1690B1655}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -143,6 +162,52 @@ Global
|
|||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -155,4 +220,14 @@ Global
|
|||||||
$0.DotNetNamingPolicy = $1
|
$0.DotNetNamingPolicy = $1
|
||||||
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
|
$1.DirectoryNamespaceAssociation = PrefixedHierarchical
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{971FDF07-E288-4239-B47A-E9E7E912193B} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{11DBC05E-F8B4-49ED-AAC9-96D92336D21C} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{83449CC4-1F76-4CFE-92B1-D2E13A62506F} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{E71F3053-056C-4381-9638-048ED73BDFF6} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{F8C3F648-EA5A-4719-8005-85D1690B1655} = {B972BBFA-917F-4A10-B07E-B89CFEC6BBDC}
|
||||||
|
{137959BD-073B-4EC7-8ED5-31D73FA7DBC6} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
|
||||||
|
{1AC5ED7F-301E-4B3C-ACDE-C0EADFA5AE44} = {BB702EBD-3B79-4ECA-A2A6-1237B07F0AF0}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ project_id_env: _CROWDIN_PROJECT_ID
|
|||||||
api_token_env: CROWDIN_API_TOKEN
|
api_token_env: CROWDIN_API_TOKEN
|
||||||
preserve_hierarchy: true
|
preserve_hierarchy: true
|
||||||
files:
|
files:
|
||||||
- source: /src/App/Resources/AppResources.resx
|
- source: /src/Core/Resources/Localization/AppResources.resx
|
||||||
dest: /src/App/Resources/%original_file_name%
|
dest: /src/Core/Resources/Localization/%original_file_name%
|
||||||
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
|
translation: /src/Core/Resources/Localization/AppResources.%two_letters_code%.resx
|
||||||
update_option: update_as_unapproved
|
update_option: update_as_unapproved
|
||||||
languages_mapping:
|
languages_mapping:
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<doc>
|
||||||
|
<assembly>
|
||||||
|
<name>Xamarin.AndroidX.Credentials</name>
|
||||||
|
</assembly>
|
||||||
|
<members>
|
||||||
|
</members>
|
||||||
|
</doc>
|
||||||
7
nuget.config
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
|
||||||
|
<add key="Local AndroidX Credentials" value="lib/android/Xamarin.AndroidX.Credentials" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
2
package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "bitwarden-mobile",
|
"name": "bitwarden-mobile",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gh-pages": "^3.2.3"
|
"gh-pages": "3.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/array-union": {
|
"node_modules/array-union": {
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
"clean:l10n": "git push origin --delete l10n_master"
|
"clean:l10n": "git push origin --delete l10n_master"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gh-pages": "^3.2.3"
|
"gh-pages": "3.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,24 +53,28 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
|
||||||
<CreatePackage>false</CreatePackage>
|
<CreatePackage>false</CreatePackage>
|
||||||
<CodesignProvision>Automatic</CodesignProvision>
|
<CodesignProvision>Automatic</CodesignProvision>
|
||||||
<CodesignKey>iPhone Developer</CodesignKey>
|
<CodesignKey>iPhone Developer</CodesignKey>
|
||||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||||
<MtouchInterpreter>all</MtouchInterpreter>
|
<UseInterpreter>true</UseInterpreter>
|
||||||
<MtouchLink>None</MtouchLink>
|
</PropertyGroup>
|
||||||
<!--TODO: add argon2id load when library is built with the corresponding architecture for iOS Simulator-->
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|iossimulator-x64'">
|
||||||
|
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(RuntimeIdentifier)'=='Debug|net8.0-ios|ios-arm64'">
|
||||||
<MtouchExtraArgs>-gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
|
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
|
||||||
<CreatePackage>false</CreatePackage>
|
<CreatePackage>false</CreatePackage>
|
||||||
<CodesignProvision>Automatic:AppStore</CodesignProvision>
|
<CodesignProvision>$(ReleaseCodesignProvision)</CodesignProvision>
|
||||||
<CodesignKey>iPhone Distribution</CodesignKey>
|
<CodesignKey>$(ReleaseCodesignKey)</CodesignKey>
|
||||||
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
<CodesignEntitlements>Platforms\iOS\Entitlements.plist</CodesignEntitlements>
|
||||||
<MtouchInterpreter>all</MtouchInterpreter>
|
<UseInterpreter>true</UseInterpreter>
|
||||||
<MtouchLink>None</MtouchLink>
|
<MtouchExtraArgs>$(Argon2IdLoadMtouchExtraArgs)</MtouchExtraArgs>
|
||||||
<MtouchExtraArgs>--weak-framework=NewsstandKit.framework/NewsstandKit --linkskip=LiteDB --linkskip=CsvHelper --linkskip=Core --linkskip=iOS.Core --linkskip=iOS.Autofill --linkskip=iOS.Extension --linkskip=iOS.ShareExtension --linkskip=App -gcc_flags "-L$(ProjectDir)../../lib/ios -largon2 -force_load $(ProjectDir)../../lib/ios/libargon2.a"</MtouchExtraArgs>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
||||||
|
<!--This is needed for PCLCrypto to work correctly-->
|
||||||
|
<TrimmerRootAssembly Include="System.Security.Cryptography" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidNativeLibrary Include="Platforms\Android\lib\arm64-v8a\libargon2.so" />
|
<AndroidNativeLibrary Include="Platforms\Android\lib\arm64-v8a\libargon2.so" />
|
||||||
<AndroidNativeLibrary Include="Platforms\Android\lib\armeabi-v7a\libargon2.so" />
|
<AndroidNativeLibrary Include="Platforms\Android\lib\armeabi-v7a\libargon2.so" />
|
||||||
@@ -92,7 +96,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="zxcvbn-core" Version="7.0.92" />
|
<PackageReference Include="zxcvbn-core" Version="7.0.92" />
|
||||||
<PackageReference Include="CommunityToolkit.Maui" Version="5.2.0" />
|
<PackageReference Include="CommunityToolkit.Maui" Version="5.2.0" />
|
||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
<PackageReference Include="Plugin.Fingerprint" Version="3.0.0-beta.1" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.4-preview.84" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
<PackageReference Include="SkiaSharp.Views.Maui.Controls.Compatibility" Version="2.88.4-preview.84" />
|
||||||
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
<PackageReference Include="FFImageLoadingCompat.Maui" Version="0.1.1" />
|
||||||
@@ -115,9 +119,9 @@
|
|||||||
<Folder Include="Platforms\Android\Utilities\" />
|
<Folder Include="Platforms\Android\Utilities\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||||
<PackageReference Include="Plugin.CurrentActivity" Version="2.1.0.4" />
|
|
||||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
|
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
|
||||||
|
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
|
||||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
|
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
|
||||||
@@ -154,6 +158,15 @@
|
|||||||
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
|
<BundleResource Include="Platforms\Android\Resources\drawable-hdpi\logo_white_legacy.png" />
|
||||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
|
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher.png" />
|
||||||
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
<BundleResource Include="Platforms\Android\Resources\mipmap-xhdpi\ic_launcher_round.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo%403x.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\more_vert.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo_white.png" />
|
||||||
|
<BundleResource Include="Platforms\iOS\Resources\logo%402x.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
|
<ProjectReference Include="..\iOS.Core\iOS.Core.csproj" Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'" />
|
||||||
@@ -193,15 +206,12 @@
|
|||||||
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
|
<MauiImage Include="Resources\plus.svg" TintColor="#FFFFFFFF">
|
||||||
<BaseSize>24,24</BaseSize>
|
<BaseSize>24,24</BaseSize>
|
||||||
</MauiImage>
|
</MauiImage>
|
||||||
<MauiImage Include="Resources\search.svg" TintColor="#FFFFFFFF">
|
|
||||||
<BaseSize>24,24</BaseSize>
|
|
||||||
</MauiImage>
|
|
||||||
<MauiImage Include="Resources\send.svg">
|
<MauiImage Include="Resources\send.svg">
|
||||||
<BaseSize>24,24</BaseSize>
|
<BaseSize>24,24</BaseSize>
|
||||||
</MauiImage>
|
</MauiImage>
|
||||||
<MauiImage Include="Resources\yubikey.png" />
|
<MauiImage Include="Resources\yubikey.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
|
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' AND '$(IncludeBitwardeniOSExtensions)' == 'True'">
|
||||||
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
|
<ProjectReference Include="..\iOS.Autofill\iOS.Autofill.csproj">
|
||||||
<IsAppExtension>true</IsAppExtension>
|
<IsAppExtension>true</IsAppExtension>
|
||||||
<IsWatchApp>false</IsWatchApp>
|
<IsWatchApp>false</IsWatchApp>
|
||||||
@@ -215,15 +225,15 @@
|
|||||||
<IsWatchApp>false</IsWatchApp>
|
<IsWatchApp>false</IsWatchApp>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
|
<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND '$(IncludeBitwardenWatchOSApp)' == 'True'">
|
||||||
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
|
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-acgkbpwvmebfiofokotvoerzkqcl/Build/Products</WatchAppBuildPath>
|
||||||
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
|
||||||
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
|
||||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'"> >watchsimulator</WatchAppConfiguration>
|
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'!='ios-arm64'">watchsimulator</WatchAppConfiguration>
|
||||||
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'"> >watchos</WatchAppConfiguration>
|
<WatchAppConfiguration Condition="'$(RuntimeIdentifier)'=='ios-arm64'">watchos</WatchAppConfiguration>
|
||||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||||
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
|
<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios' AND Exists('$(WatchAppBundleFullPath)') ">
|
||||||
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -237,4 +247,15 @@
|
|||||||
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
|
<GoogleServicesJson Include="Platforms\Android\google-services.json" />
|
||||||
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
|
<GoogleServicesJson Include="Platforms\Android\google-services.json.enc" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo_white%402x.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\more_vert%402x.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo_white%403x.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo%403x.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\more_vert%403x.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\more_vert.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo_white.png" />
|
||||||
|
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#if IOS || MACCATALYST
|
|
||||||
using PlatformView = WebKit.WKWebView;
|
|
||||||
#elif ANDROID
|
|
||||||
using PlatformView = Android.Webkit.WebView;
|
|
||||||
#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
|
|
||||||
using PlatformView = System.Object;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Microsoft.Maui.Handlers;
|
|
||||||
|
|
||||||
namespace Bit.App.Handlers
|
|
||||||
{
|
|
||||||
public partial class HybridWebViewHandler
|
|
||||||
{
|
|
||||||
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
|
||||||
{
|
|
||||||
[nameof(HybridWebView.Uri)] = MapUri
|
|
||||||
};
|
|
||||||
|
|
||||||
public HybridWebViewHandler() : base(PropertyMapper)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
},
|
},
|
||||||
handlers =>
|
handlers =>
|
||||||
{
|
{
|
||||||
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
|
||||||
#if ANDROID
|
#if ANDROID
|
||||||
Bit.App.Handlers.EntryHandlerMappings.Setup();
|
Bit.App.Handlers.EntryHandlerMappings.Setup();
|
||||||
Bit.App.Handlers.EditorHandlerMappings.Setup();
|
Bit.App.Handlers.EditorHandlerMappings.Setup();
|
||||||
@@ -28,6 +27,7 @@
|
|||||||
Bit.App.Handlers.ButtonHandlerMappings.Setup();
|
Bit.App.Handlers.ButtonHandlerMappings.Setup();
|
||||||
Bit.App.Handlers.ToolbarHandlerMappings.Setup();
|
Bit.App.Handlers.ToolbarHandlerMappings.Setup();
|
||||||
|
|
||||||
|
handlers.AddHandler(typeof(Bit.App.Controls.HybridWebView), typeof(Bit.App.Handlers.HybridWebViewHandler));
|
||||||
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
handlers.AddHandler(typeof(Bit.App.Pages.TabsPage), typeof(Bit.App.Handlers.CustomTabbedPageHandler));
|
||||||
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
|
handlers.AddHandler(typeof(Bit.App.Controls.ExtendedDatePicker), typeof(Bit.App.Handlers.ExtendedDatePickerHandler));
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("org.bromite.chromium", "url_bar"),
|
new Browser("org.bromite.chromium", "url_bar"),
|
||||||
new Browser("org.chromium.chrome", "url_bar"),
|
new Browser("org.chromium.chrome", "url_bar"),
|
||||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||||
|
new Browser("org.cromite.cromite", "url_bar"),
|
||||||
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||||
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.10.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.3.3" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
@@ -43,6 +43,9 @@
|
|||||||
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
|
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
|
||||||
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
|
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
|
||||||
<queries>
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||||
|
</intent>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ namespace Bit.Droid.Autofill
|
|||||||
"org.bromite.chromium",
|
"org.bromite.chromium",
|
||||||
"org.chromium.chrome",
|
"org.chromium.chrome",
|
||||||
"org.codeaurora.swe.browser",
|
"org.codeaurora.swe.browser",
|
||||||
|
"org.cromite.cromite",
|
||||||
"org.gnu.icecat",
|
"org.gnu.icecat",
|
||||||
"org.mozilla.fenix",
|
"org.mozilla.fenix",
|
||||||
"org.mozilla.fenix.nightly",
|
"org.mozilla.fenix.nightly",
|
||||||
@@ -346,7 +347,7 @@ namespace Bit.Droid.Autofill
|
|||||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
||||||
// "my vault" presentation) so we're including an empty one here
|
// "my vault" presentation) so we're including an empty one here
|
||||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
||||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
|
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, false));
|
||||||
}
|
}
|
||||||
var slice = CreateInlinePresentationSlice(
|
var slice = CreateInlinePresentationSlice(
|
||||||
inlinePresentationSpec,
|
inlinePresentationSpec,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Bit.Droid.Autofill
|
||||||
|
{
|
||||||
|
public class CredentialProviderConstants
|
||||||
|
{
|
||||||
|
public const string CredentialProviderCipherId = "credentialProviderCipherId";
|
||||||
|
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
|
||||||
|
public const string CredentialIdIntentExtra = "credId";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content.PM;
|
||||||
|
using Android.OS;
|
||||||
|
using AndroidX.Credentials.Provider;
|
||||||
|
using AndroidX.Credentials.WebAuthn;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.App.Droid.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Autofill
|
||||||
|
{
|
||||||
|
[Activity(
|
||||||
|
NoHistory = true,
|
||||||
|
LaunchMode = LaunchMode.SingleTop)]
|
||||||
|
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
||||||
|
{
|
||||||
|
protected override void OnCreate(Bundle bundle)
|
||||||
|
{
|
||||||
|
Intent?.Validate();
|
||||||
|
base.OnCreate(bundle);
|
||||||
|
|
||||||
|
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
|
||||||
|
if (string.IsNullOrEmpty(cipherId))
|
||||||
|
{
|
||||||
|
SetResult(Result.Canceled);
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetCipherAndPerformPasskeyAuthAsync(cipherId).FireAndForget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetCipherAndPerformPasskeyAuthAsync(string cipherId)
|
||||||
|
{
|
||||||
|
// TODO this is a work in progress
|
||||||
|
// https://developer.android.com/training/sign-in/credential-provider#passkeys-implement
|
||||||
|
|
||||||
|
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
|
||||||
|
// var publicKeyRequest = getRequest?.CredentialOptions as PublicKeyCredentialRequestOptions;
|
||||||
|
|
||||||
|
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
|
||||||
|
var credIdEnc = requestInfo?.GetString(CredentialProviderConstants.CredentialIdIntentExtra);
|
||||||
|
|
||||||
|
var cipherService = ServiceContainer.Resolve<ICipherService>();
|
||||||
|
var cipher = await cipherService.GetAsync(cipherId);
|
||||||
|
var decCipher = await cipher.DecryptAsync();
|
||||||
|
|
||||||
|
var passkey = decCipher.Login.Fido2Credentials.Find(f => f.CredentialId == credIdEnc);
|
||||||
|
|
||||||
|
var credId = Convert.FromBase64String(credIdEnc);
|
||||||
|
// var privateKey = Convert.FromBase64String(passkey.PrivateKey);
|
||||||
|
// var uid = Convert.FromBase64String(passkey.uid);
|
||||||
|
|
||||||
|
var origin = getRequest?.CallingAppInfo.Origin;
|
||||||
|
var packageName = getRequest?.CallingAppInfo.PackageName;
|
||||||
|
|
||||||
|
// --- continue WIP here (save TOTP copy as last step) ---
|
||||||
|
|
||||||
|
// Copy TOTP if needed
|
||||||
|
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||||
|
autofillHandler.Autofill(decCipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
147
src/App/Platforms/Android/Autofill/CredentialProviderService.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using Android;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using AndroidX.Credentials.Provider;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using AndroidX.Credentials.Exceptions;
|
||||||
|
using AndroidX.Credentials.WebAuthn;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Resource = Microsoft.Maui.Resource;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Autofill
|
||||||
|
{
|
||||||
|
[Service(Permission = Manifest.Permission.BindCredentialProviderService, Label = "Bitwarden", Exported = true)]
|
||||||
|
[IntentFilter(new string[] { "android.service.credentials.CredentialProviderService" })]
|
||||||
|
[MetaData("android.credentials.provider", Resource = "@xml/provider")]
|
||||||
|
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
|
||||||
|
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
|
||||||
|
{
|
||||||
|
private const string GetPasskeyIntentAction = "PACKAGE_NAME.GET_PASSKEY";
|
||||||
|
private const int UniqueRequestCode = 94556023;
|
||||||
|
|
||||||
|
private ICipherService _cipherService;
|
||||||
|
private IUserVerificationService _userVerificationService;
|
||||||
|
private IVaultTimeoutService _vaultTimeoutService;
|
||||||
|
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
|
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
|
||||||
|
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
|
||||||
|
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_vaultTimeoutService ??= ServiceContainer.Resolve<IVaultTimeoutService>();
|
||||||
|
|
||||||
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
|
var locked = await _vaultTimeoutService.IsLockedAsync();
|
||||||
|
if (!locked)
|
||||||
|
{
|
||||||
|
var response = await ProcessGetCredentialsRequestAsync(request);
|
||||||
|
callback.OnResult(response);
|
||||||
|
}
|
||||||
|
// TODO handle auth/unlock account flow
|
||||||
|
}
|
||||||
|
catch (GetCredentialException e)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(e);
|
||||||
|
callback.OnError(e.ErrorMessage ?? "Error getting credentials");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Value.Exception(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
|
||||||
|
BeginGetCredentialRequest request)
|
||||||
|
{
|
||||||
|
IList<CredentialEntry> credentialEntries = null;
|
||||||
|
|
||||||
|
foreach (var option in request.BeginGetCredentialOptions)
|
||||||
|
{
|
||||||
|
var credentialOption = option as BeginGetPublicKeyCredentialOption;
|
||||||
|
if (credentialOption != null)
|
||||||
|
{
|
||||||
|
credentialEntries ??= new List<CredentialEntry>();
|
||||||
|
((List<CredentialEntry>)credentialEntries).AddRange(
|
||||||
|
await PopulatePasskeyDataAsync(request.CallingAppInfo, credentialOption));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credentialEntries == null)
|
||||||
|
{
|
||||||
|
return new BeginGetCredentialResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BeginGetCredentialResponse.Builder()
|
||||||
|
.SetCredentialEntries(credentialEntries)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
|
||||||
|
BeginGetPublicKeyCredentialOption option)
|
||||||
|
{
|
||||||
|
var packageName = callingAppInfo.PackageName;
|
||||||
|
var origin = callingAppInfo.Origin;
|
||||||
|
var signingInfo = callingAppInfo.SigningInfo;
|
||||||
|
|
||||||
|
var request = new PublicKeyCredentialRequestOptions(option.RequestJson);
|
||||||
|
|
||||||
|
var passkeyEntries = new List<CredentialEntry>();
|
||||||
|
|
||||||
|
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
|
||||||
|
var ciphers = await _cipherService.GetAllDecryptedForUrlAsync(origin);
|
||||||
|
if (ciphers == null)
|
||||||
|
{
|
||||||
|
return passkeyEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
var passkeyCiphers = ciphers.Where(cipher => cipher.HasFido2Credential).ToList();
|
||||||
|
if (!passkeyCiphers.Any())
|
||||||
|
{
|
||||||
|
return passkeyEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cipher in passkeyCiphers)
|
||||||
|
{
|
||||||
|
var passkeyEntry = GetPasskey(cipher, option);
|
||||||
|
passkeyEntries.Add(passkeyEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return passkeyEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKeyCredentialEntry GetPasskey(CipherView cipher, BeginGetPublicKeyCredentialOption option)
|
||||||
|
{
|
||||||
|
var credDataBundle = new Bundle();
|
||||||
|
credDataBundle.PutString(CredentialProviderConstants.CredentialIdIntentExtra,
|
||||||
|
cipher.Login.MainFido2Credential.CredentialId);
|
||||||
|
|
||||||
|
var intent = new Intent(ApplicationContext, typeof(CredentialProviderSelectionActivity))
|
||||||
|
.SetAction(GetPasskeyIntentAction).SetPackage(Constants.PACKAGE_NAME);
|
||||||
|
intent.PutExtra(CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
|
||||||
|
intent.PutExtra(CredentialProviderConstants.CredentialProviderCipherId, cipher.Id);
|
||||||
|
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueRequestCode, intent,
|
||||||
|
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
|
||||||
|
|
||||||
|
return new PublicKeyCredentialEntry.Builder(
|
||||||
|
ApplicationContext,
|
||||||
|
cipher.Login.Username ?? "No username",
|
||||||
|
pendingIntent,
|
||||||
|
option)
|
||||||
|
.SetDisplayName(cipher.Name)
|
||||||
|
.SetIcon(Icon.CreateWithResource(ApplicationContext, Resource.Drawable.icon))
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
|
||||||
|
CancellationSignal cancellationSignal, IOutcomeReceiver callback) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using AndroidX.AppCompat.View.Menu;
|
using AndroidX.AppCompat.View.Menu;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Google.Android.Material.BottomNavigation;
|
using Google.Android.Material.BottomNavigation;
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
@@ -90,7 +91,17 @@ namespace Bit.App.Handlers
|
|||||||
if(e.Item is MenuItemImpl item)
|
if(e.Item is MenuItemImpl item)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
|
System.Diagnostics.Debug.WriteLine($"Tab '{item.Title}' was reselected so we'll PopToRoot.");
|
||||||
MainThread.BeginInvokeOnMainThread(async () => await _tabbedPage.CurrentPage.Navigation.PopToRootAsync());
|
MainThread.BeginInvokeOnMainThread(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _tabbedPage.CurrentPage.Navigation.PopToRootAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,19 @@ using AWebkit = Android.Webkit;
|
|||||||
|
|
||||||
namespace Bit.App.Handlers
|
namespace Bit.App.Handlers
|
||||||
{
|
{
|
||||||
public partial class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
|
public class HybridWebViewHandler : ViewHandler<HybridWebView, AWebkit.WebView>
|
||||||
{
|
{
|
||||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||||
|
|
||||||
|
public static PropertyMapper<HybridWebView, HybridWebViewHandler> PropertyMapper = new PropertyMapper<HybridWebView, HybridWebViewHandler>(ViewHandler.ViewMapper)
|
||||||
|
{
|
||||||
|
[nameof(HybridWebView.Uri)] = MapUri
|
||||||
|
};
|
||||||
|
|
||||||
|
public HybridWebViewHandler() : base(PropertyMapper)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
|
public HybridWebViewHandler([NotNull] IPropertyMapper mapper, CommandMapper commandMapper = null) : base(mapper, commandMapper)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
using Android.Graphics.Drawables;
|
using Android.Graphics.Drawables;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using AndroidX.Core.Content.Resources;
|
using AndroidX.Core.Content.Resources;
|
||||||
|
using AndroidX.Core.Graphics;
|
||||||
using Bit.App.Droid.Utilities;
|
using Bit.App.Droid.Utilities;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Microsoft.Maui.Platform;
|
||||||
|
|
||||||
namespace Bit.App.Handlers
|
namespace Bit.App.Handlers
|
||||||
{
|
{
|
||||||
@@ -37,6 +40,31 @@ namespace Bit.App.Handlers
|
|||||||
};
|
};
|
||||||
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
|
handler.PlatformView.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Microsoft.Maui.Handlers.SwitchHandler.Mapper.AppendToMapping(nameof(ISwitch.TrackColor), (handler, mauiSwitch) =>
|
||||||
|
{
|
||||||
|
var trackStates = new[]
|
||||||
|
{
|
||||||
|
new[] { Android.Resource.Attribute.StateChecked }, // checked
|
||||||
|
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.5f);
|
||||||
|
var unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.7f);
|
||||||
|
if (ThemeManager.UsingLightTheme)
|
||||||
|
{
|
||||||
|
selectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchOnColor.ToArgb(), Colors.White.ToPlatform().ToArgb(), 0.7f);
|
||||||
|
unselectedColor = ColorUtils.BlendARGB(ThemeHelpers.SwitchThumbColor.ToArgb(), Colors.Black.ToPlatform().ToArgb(), 0.3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackColors = new int[]
|
||||||
|
{
|
||||||
|
selectedColor,
|
||||||
|
unselectedColor
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.PlatformView.TrackTintList = new ColorStateList(trackStates, trackColors);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ namespace Bit.Droid
|
|||||||
// this needs to be called here before base.OnCreate(...)
|
// this needs to be called here before base.OnCreate(...)
|
||||||
Intent?.Validate();
|
Intent?.Validate();
|
||||||
|
|
||||||
|
//We need to get and set the Options before calling OnCreate as that will "trigger" CreateWindow on App.xaml.cs
|
||||||
|
_appOptions = GetOptions();
|
||||||
|
//This does not replace existing Options in App.xaml.cs if it exists already. It only updates properties in Options related with Autofill/CreateSend/etc..
|
||||||
|
((Bit.App.App)Microsoft.Maui.Controls.Application.Current).SetAndroidOptions(_appOptions);
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
||||||
@@ -89,7 +94,6 @@ namespace Bit.Droid
|
|||||||
toplayout.FilterTouchesWhenObscured = true;
|
toplayout.FilterTouchesWhenObscured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_appOptions = GetOptions();
|
|
||||||
CreateNotificationChannel();
|
CreateNotificationChannel();
|
||||||
DisableAndroidFontScale();
|
DisableAndroidFontScale();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Services;
|
using Bit.Droid.Services;
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using Plugin.Fingerprint;
|
using Plugin.Fingerprint;
|
||||||
using Xamarin.Android.Net;
|
using Xamarin.Android.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -86,6 +85,12 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Resolve<IWatchDeviceService>(),
|
ServiceContainer.Resolve<IWatchDeviceService>(),
|
||||||
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
||||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||||
|
|
||||||
|
var userPinService = new UserPinService(
|
||||||
|
ServiceContainer.Resolve<IStateService>(),
|
||||||
|
ServiceContainer.Resolve<ICryptoService>(),
|
||||||
|
ServiceContainer.Resolve<IVaultTimeoutService>());
|
||||||
|
ServiceContainer.Register<IUserPinService>(userPinService);
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
@@ -101,7 +106,6 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
base.OnCreate();
|
base.OnCreate();
|
||||||
Bootstrap();
|
Bootstrap();
|
||||||
CrossCurrentActivity.Current.Init(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnProviderInstallFailed(int errorCode, Intent recoveryIntent)
|
public void OnProviderInstallFailed(int errorCode, Intent recoveryIntent)
|
||||||
@@ -136,7 +140,7 @@ namespace Bit.Droid
|
|||||||
// });
|
// });
|
||||||
// ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
// ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||||
//});
|
//});
|
||||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
CrossFingerprint.SetCurrentActivityResolver(() => Microsoft.Maui.ApplicationModel.Platform.CurrentActivity);
|
||||||
|
|
||||||
var preferencesStorage = new PreferencesStorageService(null);
|
var preferencesStorage = new PreferencesStorageService(null);
|
||||||
var localAppDataFolderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
|
var localAppDataFolderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
|
||||||
@@ -160,9 +164,8 @@ namespace Bit.Droid
|
|||||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||||
platformUtilsService, new LazyResolve<IEventService>());
|
platformUtilsService, new LazyResolve<IEventService>());
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
|
||||||
var biometricService = new BiometricService(stateService, cryptoService);
|
var biometricService = new BiometricService(stateService, cryptoService);
|
||||||
var userPinService = new UserPinService(stateService, cryptoService);
|
|
||||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
||||||
|
|
||||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||||
@@ -186,7 +189,6 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||||
ServiceContainer.Register<IUserPinService>(userPinService);
|
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
#if FDROID
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BaseTheme" parent="Theme.AppCompat">
|
<style name="BaseTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
|
<item name="colorPrimaryDark">@color/dark_notificationBar</item>
|
||||||
@@ -18,10 +18,6 @@
|
|||||||
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
|
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
|
||||||
<item name="android:textCursorDrawable">@null</item>
|
<item name="android:textCursorDrawable">@null</item>
|
||||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
|
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
|
||||||
<item name="buttonStyle">@style/ButtonStyle</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
|
|
||||||
<item name="android:textAllCaps">false</item>
|
<item name="android:textAllCaps">false</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -20,11 +20,6 @@
|
|||||||
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
|
<item name="android:colorActivatedHighlight">@android:color/transparent</item>
|
||||||
<item name="android:textCursorDrawable">@null</item>
|
<item name="android:textCursorDrawable">@null</item>
|
||||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||||
<item name="buttonStyle">@style/ButtonStyle</item>
|
|
||||||
<item name="android:textAllCaps">false</item>
|
<item name="android:textAllCaps">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
|
|
||||||
<item name="android:textAllCaps">false</item>
|
|
||||||
</style>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -236,6 +236,9 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="org.codeaurora.swe.browser"
|
android:name="org.codeaurora.swe.browser"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="org.cromite.cromite"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="org.gnu.icecat"
|
android:name="org.gnu.icecat"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
|||||||
6
src/App/Platforms/Android/Resources/xml/provider.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<capabilities>
|
||||||
|
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
|
||||||
|
</capabilities>
|
||||||
|
</credential-provider>
|
||||||
@@ -79,24 +79,29 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var context = Android.App.Application.Context;
|
var context = Android.App.Application.Context;
|
||||||
var intent = new Intent(context, typeof(MainActivity));
|
var intent = context.PackageManager?.GetLaunchIntentForPackage(context.PackageName ?? string.Empty);
|
||||||
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
|
||||||
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
|
||||||
|
|
||||||
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
|
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId);
|
||||||
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
if(intent != null && context.PackageManager != null && !string.IsNullOrEmpty(context.PackageName))
|
||||||
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
|
{
|
||||||
|
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
||||||
|
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
||||||
|
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
||||||
|
|
||||||
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
|
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
|
||||||
.SetContentIntent(pendingIntent)
|
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
||||||
.SetContentTitle(title)
|
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
|
||||||
|
|
||||||
|
builder.SetContentIntent(pendingIntent)
|
||||||
|
.SetDeleteIntent(deletePendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.SetContentTitle(title)
|
||||||
.SetContentText(message)
|
.SetContentText(message)
|
||||||
.SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification)
|
.SetSmallIcon(Bit.Core.Resource.Drawable.ic_notification)
|
||||||
.SetColor((int)Android.Graphics.Color.White)
|
.SetColor((int)Android.Graphics.Color.White)
|
||||||
.SetDeleteIntent(deletePendingIntent)
|
|
||||||
.SetAutoCancel(true);
|
.SetAutoCancel(true);
|
||||||
|
|
||||||
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
|
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
|
||||||
{
|
{
|
||||||
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
|
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Autofill;
|
using Bit.Droid.Autofill;
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using Application = Android.App.Application;
|
using Application = Android.App.Application;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
@@ -38,6 +37,23 @@ namespace Bit.Droid.Services
|
|||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CredentialProviderServiceEnabled()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO - find a way to programmatically check if the credential provider service is enabled
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool AutofillServiceEnabled()
|
public bool AutofillServiceEnabled()
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||||
@@ -46,7 +62,7 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var afm = (AutofillManager)activity.GetSystemService(
|
var afm = (AutofillManager)activity.GetSystemService(
|
||||||
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
||||||
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
||||||
@@ -65,7 +81,7 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||||
return manager.IsAutofillSupported;
|
return manager.IsAutofillSupported;
|
||||||
@@ -78,7 +94,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void Autofill(CipherView cipher)
|
public void Autofill(CipherView cipher)
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current.Activity as MauiAppCompatActivity;
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity as MauiAppCompatActivity;
|
||||||
if (activity == null)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -164,13 +180,20 @@ namespace Bit.Droid.Services
|
|||||||
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisableCredentialProviderService()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// TODO - find a way to programmatically disable the provider service, or take the user to the settings page where they can do it
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
public void DisableAutofillService()
|
public void DisableAutofillService()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||||
manager.DisableAutofillServices();
|
manager.DisableAutofillServices();
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ namespace Bit.Droid.Services
|
|||||||
JavaSystem.LoadLibrary("argon2");
|
JavaSystem.LoadLibrary("argon2");
|
||||||
int keySize = 32;
|
int keySize = 32;
|
||||||
var key = new byte[keySize];
|
var key = new byte[keySize];
|
||||||
//argon2id_hash_raw(iterations, memory, parallelism,
|
argon2id_hash_raw(iterations, memory, parallelism,
|
||||||
// password, password.Length, salt, salt.Length, key, key.Length);
|
password, password.Length, salt, salt.Length, key, key.Length);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
//[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
|
[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
|
||||||
//private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
|
private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
|
||||||
// byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
|
byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Android.Text.Method;
|
|||||||
using Android.Views;
|
using Android.Views;
|
||||||
using Android.Views.InputMethods;
|
using Android.Views.InputMethods;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
|
using AndroidX.Credentials;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Resources.Localization;
|
using Bit.Core.Resources.Localization;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
@@ -18,7 +19,6 @@ using Bit.App.Utilities.Prompts;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.App.Droid.Utilities;
|
using Bit.App.Droid.Utilities;
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
|
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
|
||||||
using Resource = Bit.Core.Resource;
|
using Resource = Bit.Core.Resource;
|
||||||
using Application = Android.App.Application;
|
using Application = Android.App.Application;
|
||||||
@@ -66,24 +66,35 @@ namespace Bit.Droid.Services
|
|||||||
_toast.Dispose();
|
_toast.Dispose();
|
||||||
_toast = null;
|
_toast = null;
|
||||||
}
|
}
|
||||||
_toast = Android.Widget.Toast.MakeText(CrossCurrentActivity.Current.Activity, text,
|
_toast = Android.Widget.Toast.MakeText(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity, text,
|
||||||
longDuration ? ToastLength.Long : ToastLength.Short);
|
longDuration ? ToastLength.Long : ToastLength.Short);
|
||||||
_toast.Show();
|
_toast.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LaunchApp(string appName)
|
public bool LaunchApp(string appName)
|
||||||
{
|
{
|
||||||
if ((int)Build.VERSION.SdkInt < 33)
|
try
|
||||||
|
{
|
||||||
|
if ((int)Build.VERSION.SdkInt < 33)
|
||||||
|
{
|
||||||
|
// API 33 required to avoid using wildcard app visibility or dangerous permissions
|
||||||
|
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
|
appName = appName.Replace("androidapp://", string.Empty);
|
||||||
|
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
||||||
|
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
||||||
|
return launchIntentSender != null;
|
||||||
|
}
|
||||||
|
catch (IntentSender.SendIntentException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Android.Util.AndroidException)
|
||||||
{
|
{
|
||||||
// API 33 required to avoid using wildcard app visibility or dangerous permissions
|
|
||||||
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var activity = CrossCurrentActivity.Current.Activity;
|
|
||||||
appName = appName.Replace("androidapp://", string.Empty);
|
|
||||||
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
|
||||||
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
|
||||||
return launchIntentSender != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ShowLoadingAsync(string text)
|
public async Task ShowLoadingAsync(string text)
|
||||||
@@ -93,7 +104,7 @@ namespace Bit.Droid.Services
|
|||||||
await HideLoadingAsync();
|
await HideLoadingAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var activity = CrossCurrentActivity.Current.Activity;
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var inflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService);
|
var inflater = (LayoutInflater)activity.GetSystemService(Context.LayoutInflaterService);
|
||||||
var dialogView = inflater.Inflate(Resource.Layout.progress_dialog_layout, null);
|
var dialogView = inflater.Inflate(Resource.Layout.progress_dialog_layout, null);
|
||||||
|
|
||||||
@@ -159,7 +170,7 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally if all else fails, let's see if current activity is MainActivity
|
// Finally if all else fails, let's see if current activity is MainActivity
|
||||||
if (CrossCurrentActivity.Current.Activity is MainActivity activity && IsAlive(activity))
|
if (Microsoft.Maui.ApplicationModel.Platform.CurrentActivity is MainActivity activity && IsAlive(activity))
|
||||||
{
|
{
|
||||||
activity.RunOnUiThread(actionDismiss);
|
activity.RunOnUiThread(actionDismiss);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -193,7 +204,7 @@ namespace Bit.Droid.Services
|
|||||||
string text = null, string okButtonText = null, string cancelButtonText = null,
|
string text = null, string okButtonText = null, string cancelButtonText = null,
|
||||||
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
if (activity == null)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult<string>(null);
|
return Task.FromResult<string>(null);
|
||||||
@@ -250,7 +261,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
|
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
if (activity == null)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult<ValidatablePromptResponse?>(null);
|
return Task.FromResult<ValidatablePromptResponse?>(null);
|
||||||
@@ -327,7 +338,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void RateApp()
|
public void RateApp()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rateIntent = RateIntentForUrl("market://details", activity);
|
var rateIntent = RateIntentForUrl("market://details", activity);
|
||||||
@@ -360,14 +371,14 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public bool SupportsNfc()
|
public bool SupportsNfc()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var manager = activity.GetSystemService(Context.NfcService) as NfcManager;
|
var manager = activity.GetSystemService(Context.NfcService) as NfcManager;
|
||||||
return manager.DefaultAdapter?.IsEnabled ?? false;
|
return manager.DefaultAdapter?.IsEnabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportsCamera()
|
public bool SupportsCamera()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
|
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +394,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
|
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
if (activity == null)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult<string>(null);
|
return Task.FromResult<string>(null);
|
||||||
@@ -464,7 +475,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void OpenAccessibilityOverlayPermissionSettings()
|
public void OpenAccessibilityOverlayPermissionSettings()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var intent = new Intent(Settings.ActionManageOverlayPermission);
|
var intent = new Intent(Settings.ActionManageOverlayPermission);
|
||||||
@@ -491,11 +502,32 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenCredentialProviderSettings()
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pendingIntent = CredentialManager.Create(activity).CreateSettingsPendingIntent();
|
||||||
|
pendingIntent.Send();
|
||||||
|
}
|
||||||
|
catch (ActivityNotFoundException)
|
||||||
|
{
|
||||||
|
var alertBuilder = new AlertDialog.Builder(activity);
|
||||||
|
alertBuilder.SetMessage(AppResources.BitwardenCredentialProviderGoToSettings);
|
||||||
|
alertBuilder.SetCancelable(true);
|
||||||
|
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
|
||||||
|
{
|
||||||
|
(sender as AlertDialog)?.Cancel();
|
||||||
|
});
|
||||||
|
alertBuilder.Create().Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenAccessibilitySettings()
|
public void OpenAccessibilitySettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var intent = new Intent(Settings.ActionAccessibilitySettings);
|
var intent = new Intent(Settings.ActionAccessibilitySettings);
|
||||||
activity.StartActivity(intent);
|
activity.StartActivity(intent);
|
||||||
}
|
}
|
||||||
@@ -504,7 +536,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void OpenAutofillSettings()
|
public void OpenAutofillSettings()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var intent = new Intent(Settings.ActionRequestSetAutofillService);
|
var intent = new Intent(Settings.ActionRequestSetAutofillService);
|
||||||
@@ -535,7 +567,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void CloseMainApp()
|
public void CloseMainApp()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
if (activity == null)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -549,6 +581,8 @@ namespace Bit.Droid.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsCredentialProviderService() => Build.VERSION.SdkInt >= BuildVersionCodes.UpsideDownCake;
|
||||||
|
|
||||||
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
|
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
|
||||||
|
|
||||||
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
|
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
|
||||||
@@ -574,7 +608,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public float GetSystemFontSizeScale()
|
public float GetSystemFontSizeScale()
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity as MainActivity;
|
||||||
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
return activity?.Resources?.Configuration?.FontScale ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +619,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync()
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
{
|
{
|
||||||
var activity = CrossCurrentActivity.Current?.Activity;
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
if (await _stateService.GetScreenCaptureAllowedAsync())
|
if (await _stateService.GetScreenCaptureAllowedAsync())
|
||||||
{
|
{
|
||||||
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
|
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using AndroidX.Core.Content;
|
|||||||
using Bit.Core.Resources.Localization;
|
using Bit.Core.Resources.Localization;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using FileProvider = AndroidX.Core.Content.FileProvider;
|
using FileProvider = AndroidX.Core.Content.FileProvider;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
@@ -43,7 +42,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var intent = BuildOpenFileIntent(fileData, fileName);
|
var intent = BuildOpenFileIntent(fileData, fileName);
|
||||||
if (intent == null)
|
if (intent == null)
|
||||||
{
|
{
|
||||||
@@ -60,7 +59,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
||||||
if (intent == null)
|
if (intent == null)
|
||||||
{
|
{
|
||||||
@@ -87,7 +86,7 @@ namespace Bit.Droid.Services
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var cachePath = activity.CacheDir;
|
var cachePath = activity.CacheDir;
|
||||||
var filePath = Path.Combine(cachePath.Path, fileName);
|
var filePath = Path.Combine(cachePath.Path, fileName);
|
||||||
File.WriteAllBytes(filePath, fileData);
|
File.WriteAllBytes(filePath, fileData);
|
||||||
@@ -114,7 +113,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
|
|
||||||
if (contentUri != null)
|
if (contentUri != null)
|
||||||
{
|
{
|
||||||
@@ -162,7 +161,7 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
DeleteDir(Microsoft.Maui.ApplicationModel.Platform.CurrentActivity?.CacheDir);
|
||||||
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
@@ -170,7 +169,7 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public Task SelectFileAsync()
|
public Task SelectFileAsync()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
||||||
HasPermission(Manifest.Permission.WriteExternalStorage);
|
HasPermission(Manifest.Permission.WriteExternalStorage);
|
||||||
var additionalIntents = new List<IParcelable>();
|
var additionalIntents = new List<IParcelable>();
|
||||||
@@ -249,20 +248,30 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
private bool HasPermission(string permission)
|
private bool HasPermission(string permission)
|
||||||
{
|
{
|
||||||
return ContextCompat.CheckSelfPermission(
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
if (activity != null)
|
||||||
|
{
|
||||||
|
return ContextCompat.CheckSelfPermission(activity, permission) == Permission.Granted;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AskPermission(string permission)
|
private void AskPermission(string permission)
|
||||||
{
|
{
|
||||||
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
|
||||||
Core.Constants.SelectFilePermissionRequestCode);
|
if (activity != null)
|
||||||
|
{
|
||||||
|
ActivityCompat.RequestPermissions(activity, new string[] { permission }, Core.Constants.SelectFilePermissionRequestCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
||||||
{
|
{
|
||||||
var intents = new List<IParcelable>();
|
var intents = new List<IParcelable>();
|
||||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
var pm = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity?.PackageManager;
|
||||||
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
||||||
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
||||||
foreach (var res in listCam)
|
foreach (var res in listCam)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Accessibility;
|
using Bit.Droid.Accessibility;
|
||||||
using Java.Lang;
|
using Java.Lang;
|
||||||
|
using Bit.App.Droid.Utilities;
|
||||||
|
|
||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
{
|
{
|
||||||
@@ -76,7 +77,7 @@ namespace Bit.Droid.Tile
|
|||||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
var intent = new Intent(this, typeof(AccessibilityActivity));
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
intent.PutExtra("autofillTileClicked", true);
|
intent.PutExtra("autofillTileClicked", true);
|
||||||
StartActivityAndCollapse(intent);
|
this.StartActivityAndCollapseWithIntent(intent, isMutable: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowConfigErrorDialog()
|
private void ShowConfigErrorDialog()
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
using System;
|
using Android.App;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Service.QuickSettings;
|
using Android.Service.QuickSettings;
|
||||||
using Android.Views;
|
using Bit.App.Droid.Utilities;
|
||||||
using Android.Widget;
|
|
||||||
using Java.Lang;
|
using Java.Lang;
|
||||||
|
|
||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
@@ -62,7 +55,7 @@ namespace Bit.Droid.Tile
|
|||||||
var intent = new Intent(this, typeof(MainActivity));
|
var intent = new Intent(this, typeof(MainActivity));
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
intent.PutExtra("generatorTile", true);
|
intent.PutExtra("generatorTile", true);
|
||||||
StartActivityAndCollapse(intent);
|
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
using System;
|
using Android.App;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Service.QuickSettings;
|
using Android.Service.QuickSettings;
|
||||||
using Android.Views;
|
using Bit.App.Droid.Utilities;
|
||||||
using Android.Widget;
|
|
||||||
using Java.Lang;
|
using Java.Lang;
|
||||||
|
|
||||||
namespace Bit.Droid.Tile
|
namespace Bit.Droid.Tile
|
||||||
@@ -63,7 +56,7 @@ namespace Bit.Droid.Tile
|
|||||||
var intent = new Intent(this, typeof(MainActivity));
|
var intent = new Intent(this, typeof(MainActivity));
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
intent.PutExtra("myVaultTile", true);
|
intent.PutExtra("myVaultTile", true);
|
||||||
StartActivityAndCollapse(intent);
|
this.StartActivityAndCollapseWithIntent(intent, isMutable: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Provider;
|
using Android.Provider;
|
||||||
|
using Android.Service.QuickSettings;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
|
||||||
namespace Bit.App.Droid.Utilities
|
namespace Bit.App.Droid.Utilities
|
||||||
@@ -64,5 +65,26 @@ namespace Bit.App.Droid.Utilities
|
|||||||
|
|
||||||
return pendingIntentFlags;
|
return pendingIntentFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void StartActivityAndCollapseWithIntent(this TileService service, Intent intent, bool isMutable)
|
||||||
|
{
|
||||||
|
//For Android 14+ We need to use PendingIntent instead of Intent directly. Older versions still need to use Intent.
|
||||||
|
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
|
||||||
|
{
|
||||||
|
service.StartActivityAndCollapse(intent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var pendingIntent = PendingIntent.GetActivity(
|
||||||
|
service.ApplicationContext,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, isMutable)
|
||||||
|
);
|
||||||
|
if (pendingIntent == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
service.StartActivityAndCollapse(pendingIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using CoreNFC;
|
|||||||
using Foundation;
|
using Foundation;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using UserNotifications;
|
||||||
using WatchConnectivity;
|
using WatchConnectivity;
|
||||||
|
|
||||||
namespace Bit.iOS
|
namespace Bit.iOS
|
||||||
@@ -41,204 +42,253 @@ namespace Bit.iOS
|
|||||||
private IStateService _stateService;
|
private IStateService _stateService;
|
||||||
private IEventService _eventService;
|
private IEventService _eventService;
|
||||||
|
|
||||||
private LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
|
private readonly LazyResolve<IDeepLinkContext> _deepLinkContext = new LazyResolve<IDeepLinkContext>();
|
||||||
|
|
||||||
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
|
||||||
{
|
{
|
||||||
InitApp();
|
try
|
||||||
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
|
||||||
|
|
||||||
//LoadApplication(new App.App(null));
|
|
||||||
//iOSCoreHelpers.AppearanceAdjustments();
|
|
||||||
//ZXing.Net.Mobile.Forms.iOS.Platform.Init();
|
|
||||||
|
|
||||||
ConnectToWatchIfNeededAsync().FireAndForget();
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
|
|
||||||
{
|
{
|
||||||
try
|
InitApp();
|
||||||
|
|
||||||
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
|
|
||||||
|
ConnectToWatchIfNeededAsync().FireAndForget();
|
||||||
|
|
||||||
|
_broadcasterService.Subscribe(nameof(AppDelegate), async (message) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "startEventTimer")
|
try
|
||||||
{
|
{
|
||||||
StartEventTimer();
|
if (message.Command == "startEventTimer")
|
||||||
}
|
|
||||||
else if (message.Command == "stopEventTimer")
|
|
||||||
{
|
|
||||||
var task = StopEventTimerAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(() =>
|
|
||||||
{
|
{
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
StartEventTimer();
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == "listenYubiKeyOTP")
|
|
||||||
{
|
|
||||||
iOSCoreHelpers.ListenYubiKey((bool)message.Data, _deviceActionService, _nfcSession, _nfcDelegate);
|
|
||||||
}
|
|
||||||
else if (message.Command == "unlocked")
|
|
||||||
{
|
|
||||||
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
|
|
||||||
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
|
||||||
if (needsAutofillReplacement.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
}
|
||||||
}
|
else if (message.Command == "stopEventTimer")
|
||||||
else if (message.Command == "showAppExtension")
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
|
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
if (message.Data is Dictionary<string, object> data && data.ContainsKey("successfully"))
|
|
||||||
{
|
{
|
||||||
var success = data["successfully"] as bool?;
|
var task = StopEventTimerAsync();
|
||||||
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
}
|
||||||
|
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||||
|
{
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
iOSCoreHelpers.AppearanceAdjustments();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (message.Command == "listenYubiKeyOTP" && message.Data is bool listen)
|
||||||
|
{
|
||||||
|
iOSCoreHelpers.ListenYubiKey(listen, _deviceActionService, _nfcSession, _nfcDelegate);
|
||||||
|
}
|
||||||
|
else if (message.Command == "unlocked")
|
||||||
|
{
|
||||||
|
var needsAutofillReplacement = await _storageService.GetAsync<bool?>(
|
||||||
|
Core.Constants.AutofillNeedsIdentityReplacementKey);
|
||||||
|
if (needsAutofillReplacement.GetValueOrDefault())
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (message.Command == "showAppExtension")
|
||||||
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
|
||||||
message.Command == "restoredCipher")
|
|
||||||
{
|
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
{
|
||||||
if (await ASHelpers.IdentitiesCanIncremental())
|
await MainThread.InvokeOnMainThreadAsync(() => ShowAppExtension((ExtensionPageViewModel)message.Data));
|
||||||
|
}
|
||||||
|
else if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
if (message.Data is Dictionary<string, object> data && data.TryGetValue("successfully", out var value))
|
||||||
|
{
|
||||||
|
var success = value as bool?;
|
||||||
|
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "addedCipher" || message.Command == "editedCipher" ||
|
||||||
|
message.Command == "restoredCipher")
|
||||||
|
{
|
||||||
|
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
|
||||||
{
|
{
|
||||||
var cipherId = message.Data as string;
|
var cipherId = message.Data as string;
|
||||||
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
|
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
|
||||||
{
|
{
|
||||||
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
|
var identity = await ASHelpers.GetCipherPasswordIdentityAsync(cipherId);
|
||||||
if (identity == null)
|
if (identity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
|
await ASCredentialIdentityStoreExtensions.SaveCredentialIdentitiesAsync(identity);
|
||||||
new ASPasswordCredentialIdentity[] { identity });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
}
|
}
|
||||||
}
|
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
||||||
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
|
|
||||||
{
|
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
{
|
||||||
if (await ASHelpers.IdentitiesCanIncremental())
|
if (!UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
{
|
{
|
||||||
var identity = ASHelpers.ToCredentialIdentity(
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
|
||||||
|
{
|
||||||
|
var identity = ASHelpers.ToPasswordCredentialIdentity(
|
||||||
message.Data as Bit.Core.Models.View.CipherView);
|
message.Data as Bit.Core.Models.View.CipherView);
|
||||||
if (identity == null)
|
if (identity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
|
await ASCredentialIdentityStoreExtensions.RemoveCredentialIdentitiesAsync(identity);
|
||||||
new ASPasswordCredentialIdentity[] { identity });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
}
|
}
|
||||||
}
|
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
else if (message.Command == "logout")
|
|
||||||
{
|
|
||||||
if (_deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
|
||||||
}
|
}
|
||||||
}
|
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
||||||
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
|
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
&& _deviceActionService.SystemMajorVersion() >= 12)
|
|
||||||
{
|
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
|
||||||
}
|
|
||||||
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
|
|
||||||
{
|
|
||||||
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
|
||||||
if (timeoutAction == VaultTimeoutAction.Logout)
|
|
||||||
{
|
{
|
||||||
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
}
|
}
|
||||||
else
|
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
|
||||||
{
|
{
|
||||||
await ASHelpers.ReplaceAllIdentities();
|
var timeoutAction = await _stateService.GetVaultTimeoutActionAsync();
|
||||||
|
if (timeoutAction == VaultTimeoutAction.Logout)
|
||||||
|
{
|
||||||
|
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
|
||||||
|
{
|
||||||
|
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
var finishedLaunching = base.FinishedLaunching(app, options);
|
var finishedLaunching = base.FinishedLaunching(app, options);
|
||||||
|
|
||||||
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
|
ThemeManager.SetTheme(Microsoft.Maui.Controls.Application.Current.Resources);
|
||||||
iOSCoreHelpers.AppearanceAdjustments();
|
iOSCoreHelpers.AppearanceAdjustments();
|
||||||
|
|
||||||
return finishedLaunching;
|
return finishedLaunching;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResignActivation(UIApplication uiApplication)
|
public override void OnResignActivation(UIApplication uiApplication)
|
||||||
{
|
{
|
||||||
if (UIApplication.SharedApplication.KeyWindow != null)
|
try
|
||||||
{
|
{
|
||||||
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
if (UIApplication.SharedApplication.KeyWindow != null)
|
||||||
{
|
{
|
||||||
Tag = SPLASH_VIEW_TAG
|
var view = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||||
};
|
{
|
||||||
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
Tag = SPLASH_VIEW_TAG
|
||||||
{
|
};
|
||||||
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
|
var backgroundView = new UIView(UIApplication.SharedApplication.KeyWindow.Frame)
|
||||||
};
|
{
|
||||||
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
|
BackgroundColor = ThemeManager.GetResourceColor("SplashBackgroundColor").ToPlatform()
|
||||||
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
|
};
|
||||||
var imageView = new UIImageView(frame)
|
var logo = new UIImage(!ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png");
|
||||||
{
|
var frame = new CGRect(0, 0, 280, 100); //Setting image width to avoid it being larger and getting cropped on smaller devices. This harcoded size should be good even for very small devices.
|
||||||
Image = logo,
|
var imageView = new UIImageView(frame)
|
||||||
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
|
{
|
||||||
ContentMode = UIViewContentMode.ScaleAspectFit
|
Image = logo,
|
||||||
};
|
Center = new CGPoint(view.Center.X, view.Center.Y - 30),
|
||||||
view.AddSubview(backgroundView);
|
ContentMode = UIViewContentMode.ScaleAspectFit
|
||||||
view.AddSubview(imageView);
|
};
|
||||||
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
|
view.AddSubview(backgroundView);
|
||||||
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
|
view.AddSubview(imageView);
|
||||||
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
|
UIApplication.SharedApplication.KeyWindow.AddSubview(view);
|
||||||
|
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(view);
|
||||||
|
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
|
||||||
|
}
|
||||||
|
base.OnResignActivation(uiApplication);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
base.OnResignActivation(uiApplication);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DidEnterBackground(UIApplication uiApplication)
|
public override void DidEnterBackground(UIApplication uiApplication)
|
||||||
{
|
{
|
||||||
_stateService?.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
try
|
||||||
_messagingService?.Send("slept");
|
{
|
||||||
base.DidEnterBackground(uiApplication);
|
if (_stateService != null && _deviceActionService != null)
|
||||||
|
{
|
||||||
|
_stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
_messagingService?.Send("slept");
|
||||||
|
base.DidEnterBackground(uiApplication);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnActivated(UIApplication uiApplication)
|
public override async void OnActivated(UIApplication uiApplication)
|
||||||
{
|
{
|
||||||
base.OnActivated(uiApplication);
|
try
|
||||||
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
{
|
||||||
UIApplication.SharedApplication.KeyWindow?
|
base.OnActivated(uiApplication);
|
||||||
.ViewWithTag(SPLASH_VIEW_TAG)?
|
|
||||||
.RemoveFromSuperview();
|
|
||||||
|
|
||||||
ThemeManager.UpdateThemeOnPagesAsync();
|
if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||||
|
{
|
||||||
|
await UNUserNotificationCenter.Current.SetBadgeCountAsync(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UIApplication.SharedApplication.ApplicationIconBadgeNumber = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIApplication.SharedApplication.KeyWindow?
|
||||||
|
.ViewWithTag(SPLASH_VIEW_TAG)?
|
||||||
|
.RemoveFromSuperview();
|
||||||
|
|
||||||
|
ThemeManager.UpdateThemeOnPagesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WillEnterForeground(UIApplication uiApplication)
|
public override void WillEnterForeground(UIApplication uiApplication)
|
||||||
{
|
{
|
||||||
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
|
try
|
||||||
base.WillEnterForeground(uiApplication);
|
{
|
||||||
|
_messagingService?.Send(AppHelpers.RESUMED_MESSAGE_COMMAND);
|
||||||
|
base.WillEnterForeground(uiApplication);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export("application:openURL:sourceApplication:annotation:")]
|
[Export("application:openURL:sourceApplication:annotation:")]
|
||||||
@@ -249,15 +299,30 @@ namespace Bit.iOS
|
|||||||
|
|
||||||
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||||
{
|
{
|
||||||
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
|
try
|
||||||
|
{
|
||||||
|
return _deepLinkContext.Value.OnNewUri(url) || base.OpenUrl(app, url, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity,
|
||||||
UIApplicationRestorationHandler completionHandler)
|
UIApplicationRestorationHandler completionHandler)
|
||||||
{
|
{
|
||||||
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
|
try
|
||||||
{
|
{
|
||||||
return true;
|
if (Microsoft.Maui.ApplicationModel.Platform.ContinueUserActivity(application, userActivity, completionHandler))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
return base.ContinueUserActivity(application, userActivity, completionHandler);
|
return base.ContinueUserActivity(application, userActivity, completionHandler);
|
||||||
}
|
}
|
||||||
@@ -265,33 +330,68 @@ namespace Bit.iOS
|
|||||||
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
|
[Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
|
||||||
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnErrorReceived(error);
|
try
|
||||||
|
{
|
||||||
|
_pushHandler?.OnErrorReceived(error);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
|
[Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
|
||||||
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
|
public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnRegisteredSuccess(deviceToken);
|
try
|
||||||
|
{
|
||||||
|
_pushHandler?.OnRegisteredSuccess(deviceToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export("application:didRegisterUserNotificationSettings:")]
|
[Export("application:didRegisterUserNotificationSettings:")]
|
||||||
public void DidRegisterUserNotificationSettings(UIApplication application,
|
public void DidRegisterUserNotificationSettings(UIApplication application,
|
||||||
UIUserNotificationSettings notificationSettings)
|
UIUserNotificationSettings notificationSettings)
|
||||||
{
|
{
|
||||||
application.RegisterForRemoteNotifications();
|
try
|
||||||
|
{
|
||||||
|
application.RegisterForRemoteNotifications();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
|
[Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
|
||||||
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
|
public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo,
|
||||||
Action<UIBackgroundFetchResult> completionHandler)
|
Action<UIBackgroundFetchResult> completionHandler)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnMessageReceived(userInfo);
|
try
|
||||||
|
{
|
||||||
|
_pushHandler?.OnMessageReceived(userInfo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Export("application:didReceiveRemoteNotification:")]
|
[Export("application:didReceiveRemoteNotification:")]
|
||||||
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
|
public void ReceivedRemoteNotification(UIApplication application, NSDictionary userInfo)
|
||||||
{
|
{
|
||||||
_pushHandler?.OnMessageReceived(userInfo);
|
try
|
||||||
|
{
|
||||||
|
_pushHandler?.OnMessageReceived(userInfo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitApp()
|
public void InitApp()
|
||||||
@@ -304,17 +404,6 @@ namespace Bit.iOS
|
|||||||
// Migration services
|
// Migration services
|
||||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
|
ServiceContainer.Register<INativeLogService>("nativeLogService", new ConsoleLogService());
|
||||||
|
|
||||||
// Note: This might cause a race condition. Investigate more.
|
|
||||||
//Task.Run(() =>
|
|
||||||
//{
|
|
||||||
// FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
|
|
||||||
// FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
|
|
||||||
// {
|
|
||||||
// FadeAnimationEnabled = false,
|
|
||||||
// FadeAnimationForCachedImages = false
|
|
||||||
// });
|
|
||||||
//});
|
|
||||||
|
|
||||||
iOSCoreHelpers.RegisterLocalServices();
|
iOSCoreHelpers.RegisterLocalServices();
|
||||||
RegisterPush();
|
RegisterPush();
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
@@ -328,7 +417,7 @@ namespace Bit.iOS
|
|||||||
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
_nfcDelegate = new Core.NFCReaderDelegate((success, message) =>
|
||||||
_messagingService.Send("gotYubiKeyOTP", message));
|
_messagingService.Send("gotYubiKeyOTP", message));
|
||||||
|
|
||||||
iOSCoreHelpers.Bootstrap(async () => await ApplyManagedSettingsAsync());
|
iOSCoreHelpers.Bootstrap(ApplyManagedSettingsAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterPush()
|
private void RegisterPush()
|
||||||
@@ -373,31 +462,45 @@ namespace Bit.iOS
|
|||||||
_eventTimer = null;
|
_eventTimer = null;
|
||||||
MainThread.BeginInvokeOnMainThread(() =>
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
|
try
|
||||||
{
|
{
|
||||||
var task = Task.Run(() => _eventService.UploadEventsAsync());
|
_eventTimer = NSTimer.CreateScheduledTimer(60, true, timer =>
|
||||||
});
|
{
|
||||||
|
_eventService?.UploadEventsAsync().FireAndForget();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StopEventTimerAsync()
|
private async Task StopEventTimerAsync()
|
||||||
{
|
{
|
||||||
_eventTimer?.Invalidate();
|
try
|
||||||
_eventTimer?.Dispose();
|
|
||||||
_eventTimer = null;
|
|
||||||
if (_eventBackgroundTaskId > 0)
|
|
||||||
{
|
{
|
||||||
|
_eventTimer?.Invalidate();
|
||||||
|
_eventTimer?.Dispose();
|
||||||
|
_eventTimer = null;
|
||||||
|
if (_eventBackgroundTaskId > 0)
|
||||||
|
{
|
||||||
|
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||||
|
_eventBackgroundTaskId = 0;
|
||||||
|
}
|
||||||
|
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
|
||||||
|
{
|
||||||
|
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||||
|
_eventBackgroundTaskId = 0;
|
||||||
|
});
|
||||||
|
await _eventService.UploadEventsAsync();
|
||||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
||||||
_eventBackgroundTaskId = 0;
|
_eventBackgroundTaskId = 0;
|
||||||
}
|
}
|
||||||
_eventBackgroundTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
_eventBackgroundTaskId = 0;
|
}
|
||||||
});
|
|
||||||
await _eventService.UploadEventsAsync();
|
|
||||||
UIApplication.SharedApplication.EndBackgroundTask(_eventBackgroundTaskId);
|
|
||||||
_eventBackgroundTaskId = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ApplyManagedSettingsAsync()
|
private async Task ApplyManagedSettingsAsync()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.9.2</string>
|
<string>2024.3.3</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
27
src/App/Platforms/iOS/Resources/Assets.xcassets/search.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"appearances": [],
|
||||||
|
"scale": "1x",
|
||||||
|
"idiom": "universal",
|
||||||
|
"filename": "search.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances": [],
|
||||||
|
"scale": "2x",
|
||||||
|
"idiom": "universal",
|
||||||
|
"filename": "search@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances": [],
|
||||||
|
"scale": "3x",
|
||||||
|
"idiom": "universal",
|
||||||
|
"filename": "search@3x.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {},
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 164 B After Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 338 B |
@@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IAutofillHandler
|
public interface IAutofillHandler
|
||||||
{
|
{
|
||||||
|
bool CredentialProviderServiceEnabled();
|
||||||
bool AutofillServicesEnabled();
|
bool AutofillServicesEnabled();
|
||||||
bool SupportsAutofillService();
|
bool SupportsAutofillService();
|
||||||
void Autofill(CipherView cipher);
|
void Autofill(CipherView cipher);
|
||||||
@@ -11,6 +12,7 @@ namespace Bit.Core.Abstractions
|
|||||||
bool AutofillAccessibilityServiceRunning();
|
bool AutofillAccessibilityServiceRunning();
|
||||||
bool AutofillAccessibilityOverlayPermitted();
|
bool AutofillAccessibilityOverlayPermitted();
|
||||||
bool AutofillServiceEnabled();
|
bool AutofillServiceEnabled();
|
||||||
|
void DisableCredentialProviderService();
|
||||||
void DisableAutofillService();
|
void DisableAutofillService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
using Bit.Core.Enums;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
@@ -37,5 +34,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
|
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
|
||||||
Task SoftDeleteWithServerAsync(string id);
|
Task SoftDeleteWithServerAsync(string id);
|
||||||
Task RestoreWithServerAsync(string id);
|
Task RestoreWithServerAsync(string id);
|
||||||
|
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
|
||||||
|
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System;
|
namespace Bit.Core.Abstractions
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
|
||||||
{
|
{
|
||||||
public enum AwaiterPrecondition
|
public enum AwaiterPrecondition
|
||||||
{
|
{
|
||||||
EnvironmentUrlsInited
|
EnvironmentUrlsInited,
|
||||||
|
AndroidWindowCreated,
|
||||||
|
AutofillIOSExtensionViewDidAppear
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IConditionedAwaiterManager
|
public interface IConditionedAwaiterManager
|
||||||
@@ -13,5 +12,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
|
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
|
||||||
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
|
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
|
||||||
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
|
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
|
||||||
|
void Recreate(AwaiterPrecondition awaiterPrecondition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,5 +63,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
|
Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
|
||||||
Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null);
|
Task<MasterKey> GetOrDeriveMasterKeyAsync(string password, string userId = null);
|
||||||
Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey);
|
Task UpdateMasterKeyAndUserKeyAsync(MasterKey masterKey);
|
||||||
|
Task<string> HashAsync(string value, CryptoHashAlgorithm hashAlgorithm);
|
||||||
|
Task<bool> ValidateUriChecksumAsync(EncString remoteUriChecksum, string rawUri, string orgId, SymmetricCryptoKey key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ namespace Bit.App.Abstractions
|
|||||||
bool SupportsNfc();
|
bool SupportsNfc();
|
||||||
bool SupportsCamera();
|
bool SupportsCamera();
|
||||||
bool SupportsFido2();
|
bool SupportsFido2();
|
||||||
|
bool SupportsCredentialProviderService();
|
||||||
bool SupportsAutofillServices();
|
bool SupportsAutofillServices();
|
||||||
bool SupportsInlineAutofill();
|
bool SupportsInlineAutofill();
|
||||||
bool SupportsDrawOver();
|
bool SupportsDrawOver();
|
||||||
@@ -36,6 +37,7 @@ namespace Bit.App.Abstractions
|
|||||||
void RateApp();
|
void RateApp();
|
||||||
void OpenAccessibilitySettings();
|
void OpenAccessibilitySettings();
|
||||||
void OpenAccessibilityOverlayPermissionSettings();
|
void OpenAccessibilityOverlayPermissionSettings();
|
||||||
|
void OpenCredentialProviderSettings();
|
||||||
void OpenAutofillSettings();
|
void OpenAutofillSettings();
|
||||||
long GetActiveTime();
|
long GetActiveTime();
|
||||||
void CloseMainApp();
|
void CloseMainApp();
|
||||||
|
|||||||
12
src/Core/Abstractions/IFido2AuthenticatorService.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IFido2AuthenticatorService
|
||||||
|
{
|
||||||
|
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
|
||||||
|
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
|
||||||
|
// TODO: Should this return a List? Or maybe IEnumerable?
|
||||||
|
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/Core/Abstractions/IFido2ClientService.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class represents an abstraction of the WebAuthn Client as described by W3C:
|
||||||
|
/// https://www.w3.org/TR/webauthn-3/#webauthn-client
|
||||||
|
///
|
||||||
|
/// The WebAuthn Client is an intermediary entity typically implemented in the user agent
|
||||||
|
/// (in whole, or in part). Conceptually, it underlies the Web Authentication API and embodies
|
||||||
|
/// the implementation of the Web Authentication API's operations.
|
||||||
|
///
|
||||||
|
/// It is responsible for both marshalling the inputs for the underlying authenticator operations,
|
||||||
|
/// and for returning the results of the latter operations to the Web Authentication API's callers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFido2ClientService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allows WebAuthn Relying Party scripts to request the creation of a new public key credential source.
|
||||||
|
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="createCredentialParams">The parameters for the credential creation operation</param>
|
||||||
|
/// <returns>The new credential</returns>
|
||||||
|
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows WebAuthn Relying Party scripts to discover and use an existing public key credential, with the user’s consent.
|
||||||
|
/// Relying Party script can optionally specify some criteria to indicate what credential sources are acceptable to it.
|
||||||
|
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-getAssertion
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="assertCredentialParams">The parameters for the credential assertion operation</param>
|
||||||
|
/// <returns>The asserted credential</returns>
|
||||||
|
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Core/Abstractions/IFido2GetAssertionUserInterface.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public struct Fido2GetAssertionUserInterfaceCredential
|
||||||
|
{
|
||||||
|
public string CipherId { get; set; }
|
||||||
|
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IFido2GetAssertionUserInterface : IFido2UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ask the user to pick a credential from a list of existing credentials.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentials">The credentials that the user can pick from, and if the user must be verified before completing the operation</param>
|
||||||
|
/// <returns>The ID of the cipher that contains the credentials the user picked, and if the user was verified before completing the operation</returns>
|
||||||
|
Task<(string CipherId, bool UserVerified)> PickCredentialAsync(Fido2GetAssertionUserInterfaceCredential[] credentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/Core/Abstractions/IFido2MakeCredentialUserInterface.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public struct Fido2ConfirmNewCredentialParams
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
/// The name of the credential.
|
||||||
|
///</summary>
|
||||||
|
public string CredentialName { get; set; }
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// The name of the user.
|
||||||
|
///</summary>
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The preference to whether or not the user must be verified before completing the operation.
|
||||||
|
/// </summary>
|
||||||
|
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relying party identifier
|
||||||
|
/// </summary>
|
||||||
|
public string RpId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IFido2MakeCredentialUserInterface : IFido2UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inform the user that the operation was cancelled because their vault contains excluded credentials.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="existingCipherIds">The IDs of the excluded credentials.</param>
|
||||||
|
/// <returns>When user has confirmed the message</returns>
|
||||||
|
Task InformExcludedCredentialAsync(string[] existingCipherIds);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ask the user to confirm the creation of a new credential.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="confirmNewCredentialParams">The parameters to use when asking the user to confirm the creation of a new credential.</param>
|
||||||
|
/// <returns>The ID of the cipher where the new credential should be saved, and if the user was verified before completing the operation</returns>
|
||||||
|
Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Core/Abstractions/IFido2MediatorService.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IFido2MediatorService
|
||||||
|
{
|
||||||
|
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
|
||||||
|
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
|
||||||
|
|
||||||
|
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
|
||||||
|
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
|
||||||
|
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Core/Abstractions/IFido2UserInterface.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IFido2UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the vault has been unlocked during this transaction
|
||||||
|
/// </summary>
|
||||||
|
bool HasVaultBeenUnlockedInThisTransaction { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make sure that the vault is unlocked.
|
||||||
|
/// This should open a window and ask the user to login or unlock the vault if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>When vault has been unlocked.</returns>
|
||||||
|
Task EnsureUnlockedVaultAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Threading.Tasks;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
@@ -10,5 +9,7 @@ namespace Bit.App.Abstractions
|
|||||||
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
||||||
|
|
||||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||||
|
|
||||||
|
Task<bool> ShouldByPassMasterPasswordRepromptAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System;
|
using Bit.Core.Enums;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
@@ -29,7 +26,7 @@ namespace Bit.Core.Abstractions
|
|||||||
bool SupportsDuo();
|
bool SupportsDuo();
|
||||||
Task<bool> SupportsBiometricAsync();
|
Task<bool> SupportsBiometricAsync();
|
||||||
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
|
||||||
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false);
|
Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false);
|
||||||
long GetActiveTime();
|
long GetActiveTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<BwRegion?> GetActiveUserRegionAsync();
|
Task<BwRegion?> GetActiveUserRegionAsync();
|
||||||
Task<BwRegion?> GetPreAuthRegionAsync();
|
Task<BwRegion?> GetPreAuthRegionAsync();
|
||||||
Task SetPreAuthRegionAsync(BwRegion value);
|
Task SetPreAuthRegionAsync(BwRegion value);
|
||||||
|
Task ReloadStateAsync();
|
||||||
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||||
Task<string> GetPinProtectedAsync(string userId = null);
|
Task<string> GetPinProtectedAsync(string userId = null);
|
||||||
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System.Threading.Tasks;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
public interface IUserPinService
|
public interface IUserPinService
|
||||||
{
|
{
|
||||||
|
Task<bool> IsPinLockEnabledAsync();
|
||||||
Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart);
|
Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart);
|
||||||
|
Task<bool> VerifyPinAsync(string inputPin);
|
||||||
|
Task<bool> VerifyPinAsync(string inputPin, string email, KdfConfig kdfConfig, PinLockType pinLockType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/Core/Abstractions/IUserVerificationMediatorService.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IUserVerificationMediatorService
|
||||||
|
{
|
||||||
|
Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
|
||||||
|
Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options);
|
||||||
|
Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options);
|
||||||
|
Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options);
|
||||||
|
Task<CancellableResult<UVResult>> PerformOSUnlockAsync();
|
||||||
|
Task<CancellableResult<UVResult>> VerifyPinCodeAsync();
|
||||||
|
Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt);
|
||||||
|
|
||||||
|
public struct UVResult
|
||||||
|
{
|
||||||
|
public UVResult(bool canPerform, bool isVerified)
|
||||||
|
{
|
||||||
|
CanPerform = canPerform;
|
||||||
|
IsVerified = isVerified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanPerform { get; set; }
|
||||||
|
public bool IsVerified { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using System.Threading.Tasks;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.Core.Abstractions
|
namespace Bit.Core.Abstractions
|
||||||
{
|
{
|
||||||
public interface IUserVerificationService
|
public interface IUserVerificationService
|
||||||
{
|
{
|
||||||
Task<bool> VerifyUser(string secret, VerificationType verificationType);
|
Task<bool> VerifyUser(string secret, VerificationType verificationType);
|
||||||
|
Task<bool> VerifyMasterPasswordAsync(string masterPassword);
|
||||||
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
|
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ using Bit.Core;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Pages;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
@@ -34,6 +36,8 @@ namespace Bit.App
|
|||||||
private readonly IAccountsManager _accountsManager;
|
private readonly IAccountsManager _accountsManager;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private static bool _isResumed;
|
private static bool _isResumed;
|
||||||
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
|
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
|
||||||
private static bool _pendingCheckPasswordlessLoginRequests;
|
private static bool _pendingCheckPasswordlessLoginRequests;
|
||||||
@@ -44,6 +48,99 @@ namespace Bit.App
|
|||||||
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
|
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
|
||||||
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
|
private readonly Queue<Action> _onResumeActions = new Queue<Action>();
|
||||||
|
|
||||||
|
#if ANDROID
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ** Workaround for our Android crashes when trying to use Autofill **
|
||||||
|
*
|
||||||
|
* This workaround works by managing the "Window Creation" ourselves.
|
||||||
|
* - If we get an AutofillExternalActivity we just create a "dummy" window/navigation page so that the activity can run without crashing. (no visible UI is needed)
|
||||||
|
* - If we get an FromAutofillFramework/Uri/Otp/CreateSend special Option request we create an Autofill Window
|
||||||
|
* - For everything else we use the default "mainWindow"
|
||||||
|
*/
|
||||||
|
|
||||||
|
public new static Page MainPage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return CurrentWindow?.Page;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (CurrentWindow != null)
|
||||||
|
{
|
||||||
|
CurrentWindow.Page = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find the Current Active Window. There should only be one at any point in Android
|
||||||
|
/// </summary>
|
||||||
|
public static ResumeWindow CurrentWindow
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Application.Current?.Windows.OfType<ResumeWindow>().FirstOrDefault(w => w.IsActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows setting Options from MainActivity before base.OnCreate
|
||||||
|
/// Note 1: This is only be used by Android due to way it's Lifecycle works
|
||||||
|
/// Note 2: This method does not replace existing Options in App.xaml.cs if it exists already.
|
||||||
|
/// It only updates properties in Options related with Autofill/CreateSend/etc..
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appOptions">Options created in Android MainActivity.cs</param>
|
||||||
|
public void SetAndroidOptions(AppOptions appOptions)
|
||||||
|
{
|
||||||
|
if (Options == null)
|
||||||
|
{
|
||||||
|
Options = appOptions ?? new AppOptions();
|
||||||
|
}
|
||||||
|
else if(appOptions != null)
|
||||||
|
{
|
||||||
|
Options.Uri = appOptions.Uri;
|
||||||
|
Options.MyVaultTile = appOptions.MyVaultTile;
|
||||||
|
Options.GeneratorTile = appOptions.GeneratorTile;
|
||||||
|
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
|
||||||
|
Options.CreateSend = appOptions.CreateSend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Window CreateWindow(IActivationState activationState)
|
||||||
|
{
|
||||||
|
//When executing from AutofillExternalActivity we don't have "Options" so we need to filter "manually"
|
||||||
|
//In the AutofillExternalActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
|
||||||
|
if (activationState != null
|
||||||
|
&& activationState.State.TryGetValue("autofillFramework", out string autofillFramework)
|
||||||
|
&& autofillFramework == "true"
|
||||||
|
&& activationState.State.ContainsKey("autofillFrameworkCipherId"))
|
||||||
|
{
|
||||||
|
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
|
||||||
|
}
|
||||||
|
|
||||||
|
_isResumed = true;
|
||||||
|
return new ResumeWindow(new NavigationPage(new AndroidNavigationRedirectPage(Options)));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
//iOS doesn't use the CreateWindow override used in Android so we just set the Application.Current.MainPage directly
|
||||||
|
public new static Page MainPage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Application.Current?.MainPage;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Application.Current != null)
|
||||||
|
{
|
||||||
|
Application.Current.MainPage = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public App() : this(null)
|
public App() : this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -67,139 +164,157 @@ namespace Bit.App
|
|||||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
|
||||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||||
_configService = ServiceContainer.Resolve<IConfigService>();
|
_configService = ServiceContainer.Resolve<IConfigService>();
|
||||||
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
|
||||||
_accountsManager.Init(() => Options, this);
|
_accountsManager.Init(() => Options, this);
|
||||||
|
|
||||||
Bootstrap();
|
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
|
||||||
_broadcasterService.Subscribe(nameof(App), async (message) =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (message.Command == "showDialog")
|
|
||||||
{
|
|
||||||
var details = message.Data as DialogDetails;
|
|
||||||
var confirmed = true;
|
|
||||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
|
||||||
AppResources.Ok : details.ConfirmText;
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
|
||||||
{
|
|
||||||
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
|
||||||
details.CancelText);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
|
||||||
}
|
|
||||||
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
|
||||||
{
|
|
||||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
|
||||||
{
|
|
||||||
ResumedAsync().FireAndForget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.Command == "slept")
|
|
||||||
{
|
|
||||||
if (DeviceInfo.Platform == DevicePlatform.iOS)
|
|
||||||
{
|
|
||||||
await SleptAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (message.Command == "migrated")
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
await _accountsManager.NavigateOnAccountChangeAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
|
|
||||||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
|
|
||||||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
|
|
||||||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
|
|
||||||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
|
||||||
{
|
|
||||||
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
|
||||||
{
|
|
||||||
Options.OtpData = new OtpData((string)message.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
MainThread.InvokeOnMainThreadAsync(async () =>
|
Bootstrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BroadcastServiceMessageCallbackAsync(Message message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(message);
|
||||||
|
if (message.Command == "showDialog")
|
||||||
|
{
|
||||||
|
var details = message.Data as DialogDetails;
|
||||||
|
ArgumentNullException.ThrowIfNull(details);
|
||||||
|
ArgumentNullException.ThrowIfNull(MainPage);
|
||||||
|
|
||||||
|
var confirmed = true;
|
||||||
|
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||||
|
AppResources.Ok : details.ConfirmText;
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction);
|
||||||
|
async Task ShowDialogAction()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(details.CancelText))
|
||||||
{
|
{
|
||||||
if (MainPage is TabsPage tabsPage)
|
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||||
|
details.CancelText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
|
||||||
|
}
|
||||||
|
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if IOS
|
||||||
|
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
|
||||||
|
{
|
||||||
|
ResumedAsync().FireAndForget();
|
||||||
|
}
|
||||||
|
else if (message.Command == "slept")
|
||||||
|
{
|
||||||
|
await SleptAsync();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (message.Command == "migrated")
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await _accountsManager.NavigateOnAccountChangeAsync();
|
||||||
|
}
|
||||||
|
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
|
||||||
|
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
|
||||||
|
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
|
||||||
|
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
|
||||||
|
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||||
|
{
|
||||||
|
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||||
|
{
|
||||||
|
Options.OtpData = new OtpData((string)message.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction);
|
||||||
|
async Task ExecuteNavigationAction()
|
||||||
|
{
|
||||||
|
if (MainPage is TabsPage tabsPage)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
|
||||||
|
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
|
||||||
|
while (tabsPage.Navigation.ModalStack.Count > 0)
|
||||||
{
|
{
|
||||||
while (tabsPage.Navigation.ModalStack.Count > 0)
|
await tabsPage.Navigation.PopModalAsync(false);
|
||||||
{
|
}
|
||||||
await tabsPage.Navigation.PopModalAsync(false);
|
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
|
||||||
}
|
{
|
||||||
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
|
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||||
{
|
}
|
||||||
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
|
||||||
}
|
{
|
||||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
|
Options.MyVaultTile = false;
|
||||||
{
|
tabsPage.ResetToVaultPage();
|
||||||
Options.MyVaultTile = false;
|
}
|
||||||
tabsPage.ResetToVaultPage();
|
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
|
||||||
}
|
{
|
||||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
|
Options.GeneratorTile = false;
|
||||||
{
|
tabsPage.ResetToGeneratorPage();
|
||||||
Options.GeneratorTile = false;
|
}
|
||||||
tabsPage.ResetToGeneratorPage();
|
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
|
||||||
}
|
{
|
||||||
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
|
tabsPage.ResetToSendPage();
|
||||||
{
|
}
|
||||||
tabsPage.ResetToSendPage();
|
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
||||||
}
|
{
|
||||||
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
|
tabsPage.ResetToVaultPage();
|
||||||
{
|
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
|
||||||
tabsPage.ResetToVaultPage();
|
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
|
||||||
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == "convertAccountToKeyConnector")
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
await MainPage.Navigation.PushModalAsync(
|
|
||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == Constants.ForceUpdatePassword)
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(async () =>
|
|
||||||
{
|
|
||||||
await MainPage.Navigation.PushModalAsync(
|
|
||||||
new NavigationPage(new UpdateTempPasswordPage()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (message.Command == Constants.ForceSetPassword)
|
|
||||||
{
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
|
|
||||||
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
|
|
||||||
}
|
|
||||||
else if (message.Command == "syncCompleted")
|
|
||||||
{
|
|
||||||
await _configService.GetAsync(true);
|
|
||||||
}
|
|
||||||
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
|
||||||
|| message.Command == "unlocked"
|
|
||||||
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
|
||||||
{
|
|
||||||
lock (_processingLoginRequestLock)
|
|
||||||
{
|
|
||||||
// lock doesn't allow for async execution
|
|
||||||
CheckPasswordlessLoginRequestsAsync().Wait();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else if (message.Command == "convertAccountToKeyConnector")
|
||||||
{
|
{
|
||||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
ArgumentNullException.ThrowIfNull(MainPage);
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
|
||||||
|
async Task NavigateToRemoveMasterPasswordPageAction()
|
||||||
|
{
|
||||||
|
await MainPage.Navigation.PushModalAsync(
|
||||||
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
else if (message.Command == Constants.ForceUpdatePassword)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(MainPage);
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
|
||||||
|
async Task NavigateToUpdateTempPasswordPageAction()
|
||||||
|
{
|
||||||
|
await MainPage.Navigation.PushModalAsync(
|
||||||
|
new NavigationPage(new UpdateTempPasswordPage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == Constants.ForceSetPassword)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(MainPage);
|
||||||
|
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction);
|
||||||
|
void NavigateToSetPasswordPageAction()
|
||||||
|
{
|
||||||
|
MainPage.Navigation.PushModalAsync(
|
||||||
|
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message.Command == "syncCompleted")
|
||||||
|
{
|
||||||
|
await _configService.GetAsync(true);
|
||||||
|
}
|
||||||
|
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||||
|
|| message.Command == "unlocked"
|
||||||
|
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||||
|
{
|
||||||
|
lock (_processingLoginRequestLock)
|
||||||
|
{
|
||||||
|
// lock doesn't allow for async execution
|
||||||
|
CheckPasswordlessLoginRequestsAsync().Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckPasswordlessLoginRequestsAsync()
|
private async Task CheckPasswordlessLoginRequestsAsync()
|
||||||
@@ -214,7 +329,6 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
|
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
@@ -285,40 +399,52 @@ namespace Bit.App
|
|||||||
|
|
||||||
protected override async void OnStart()
|
protected override async void OnStart()
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
try
|
||||||
_isResumed = true;
|
|
||||||
await ClearCacheIfNeededAsync();
|
|
||||||
Prime();
|
|
||||||
if (string.IsNullOrWhiteSpace(Options.Uri))
|
|
||||||
{
|
{
|
||||||
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
System.Diagnostics.Debug.WriteLine("XF App: OnStart");
|
||||||
_stateService);
|
_isResumed = true;
|
||||||
if (!updated)
|
await ClearCacheIfNeededAsync();
|
||||||
|
Prime();
|
||||||
|
if (string.IsNullOrWhiteSpace(Options.Uri))
|
||||||
{
|
{
|
||||||
SyncIfNeeded();
|
var updated = await AppHelpers.PerformUpdateTasksAsync(_syncService, _deviceActionService,
|
||||||
|
_stateService);
|
||||||
|
if (!updated)
|
||||||
|
{
|
||||||
|
SyncIfNeeded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (_pendingCheckPasswordlessLoginRequests)
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
{
|
||||||
{
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
}
|
||||||
}
|
#if ANDROID
|
||||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
||||||
{
|
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
// Reset delay on every start
|
// Reset delay on every start
|
||||||
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
await _configService.GetAsync();
|
await _configService.GetAsync();
|
||||||
_messagingService.Send("startEventTimer");
|
_messagingService.Send("startEventTimer");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Exception(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ANDROID
|
||||||
protected override async void OnSleep()
|
protected override async void OnSleep()
|
||||||
|
#else
|
||||||
|
protected override void OnSleep()
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
|
try
|
||||||
_isResumed = false;
|
|
||||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("XF App: OnSleep");
|
||||||
|
_isResumed = false;
|
||||||
|
#if ANDROID
|
||||||
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
var isLocked = await _vaultTimeoutService.IsLockedAsync();
|
||||||
if (!isLocked)
|
if (!isLocked)
|
||||||
{
|
{
|
||||||
@@ -329,20 +455,34 @@ namespace Bit.App
|
|||||||
ClearAutofillUri();
|
ClearAutofillUri();
|
||||||
}
|
}
|
||||||
await SleptAsync();
|
await SleptAsync();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Exception(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
try
|
||||||
_isResumed = true;
|
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
|
||||||
{
|
|
||||||
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
|
||||||
}
|
|
||||||
if (DeviceInfo.Platform == DevicePlatform.Android)
|
|
||||||
{
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("XF App: OnResume");
|
||||||
|
_isResumed = true;
|
||||||
|
if (_pendingCheckPasswordlessLoginRequests)
|
||||||
|
{
|
||||||
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
|
}
|
||||||
|
#if ANDROID
|
||||||
ResumedAsync().FireAndForget();
|
ResumedAsync().FireAndForget();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger?.Exception(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,14 +569,22 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
MainThread.BeginInvokeOnMainThread(() =>
|
MainThread.BeginInvokeOnMainThread(() =>
|
||||||
{
|
{
|
||||||
Options.Uri = null;
|
try
|
||||||
if (isLocked)
|
|
||||||
{
|
{
|
||||||
MainPage = new NavigationPage(new LockPage());
|
Options.Uri = null;
|
||||||
|
if (isLocked)
|
||||||
|
{
|
||||||
|
App.MainPage = new NavigationPage(new LockPage());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
App.MainPage = new TabsPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MainPage = new TabsPage();
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -461,9 +609,13 @@ namespace Bit.App
|
|||||||
ThemeManager.SetTheme(Resources);
|
ThemeManager.SetTheme(Resources);
|
||||||
RequestedThemeChanged += (s, a) =>
|
RequestedThemeChanged += (s, a) =>
|
||||||
{
|
{
|
||||||
UpdateThemeAsync();
|
UpdateThemeAsync().FireAndForget();
|
||||||
};
|
};
|
||||||
MainPage = new NavigationPage(new HomePage(Options));
|
_isResumed = true;
|
||||||
|
#if IOS
|
||||||
|
//We only set the MainPage here for iOS. Android is using the CreateWindow override for the initial page.
|
||||||
|
App.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
|
#endif
|
||||||
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
|
||||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||||
}
|
}
|
||||||
@@ -476,11 +628,18 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var lastSync = await _syncService.GetLastSyncAsync();
|
try
|
||||||
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
|
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
var lastSync = await _syncService.GetLastSyncAsync();
|
||||||
await _syncService.FullSyncAsync(false);
|
if (lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await _syncService.FullSyncAsync(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -535,36 +694,36 @@ namespace Bit.App
|
|||||||
switch (navTarget)
|
switch (navTarget)
|
||||||
{
|
{
|
||||||
case NavigationTarget.HomeLogin:
|
case NavigationTarget.HomeLogin:
|
||||||
MainPage = new NavigationPage(new HomePage(Options));
|
App.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Login:
|
case NavigationTarget.Login:
|
||||||
if (navParams is LoginNavigationParams loginParams)
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
{
|
{
|
||||||
MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
App.MainPage = new NavigationPage(new LoginPage(loginParams.Email, Options));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Lock:
|
case NavigationTarget.Lock:
|
||||||
if (navParams is LockNavigationParams lockParams)
|
if (navParams is LockNavigationParams lockParams)
|
||||||
{
|
{
|
||||||
MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
App.MainPage = new NavigationPage(new LockPage(Options, lockParams.AutoPromptBiometric));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MainPage = new NavigationPage(new LockPage(Options));
|
App.MainPage = new NavigationPage(new LockPage(Options));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Home:
|
case NavigationTarget.Home:
|
||||||
MainPage = new TabsPage(Options);
|
App.MainPage = new TabsPage(Options);
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.AddEditCipher:
|
case NavigationTarget.AddEditCipher:
|
||||||
MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
|
App.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.AutofillCiphers:
|
case NavigationTarget.AutofillCiphers:
|
||||||
case NavigationTarget.OtpCipherSelection:
|
case NavigationTarget.OtpCipherSelection:
|
||||||
MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
App.MainPage = new NavigationPage(new CipherSelectionPage(Options));
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.SendAddEdit:
|
case NavigationTarget.SendAddEdit:
|
||||||
MainPage = new NavigationPage(new SendAddEditPage(Options));
|
App.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace Bit.Core
|
|||||||
public const string ConfigsKey = "configsKey";
|
public const string ConfigsKey = "configsKey";
|
||||||
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
|
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
|
||||||
public const string RegionEnvironment = "regionEnvironment";
|
public const string RegionEnvironment = "regionEnvironment";
|
||||||
|
public const string DuoCallback = "bitwarden://duo-callback";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
|
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
|
||||||
@@ -70,10 +71,11 @@ namespace Bit.Core
|
|||||||
public const int Argon2Parallelism = 4;
|
public const int Argon2Parallelism = 4;
|
||||||
public const int MasterPasswordMinimumChars = 12;
|
public const int MasterPasswordMinimumChars = 12;
|
||||||
public const int CipherKeyRandomBytesLength = 64;
|
public const int CipherKeyRandomBytesLength = 64;
|
||||||
public const string CipherKeyEncryptionMinServerVersion = "2023.9.1";
|
public const string CipherKeyEncryptionMinServerVersion = "2024.2.0";
|
||||||
public const string DefaultFido2CredentialType = "public-key";
|
public const string DefaultFido2CredentialType = "public-key";
|
||||||
public const string DefaultFido2CredentialAlgorithm = "ECDSA";
|
public const string DefaultFido2CredentialAlgorithm = "ECDSA";
|
||||||
public const string DefaultFido2CredentialCurve = "P-256";
|
public const string DefaultFido2CredentialCurve = "P-256";
|
||||||
|
public const int LatestStateVersion = 7;
|
||||||
|
|
||||||
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
||||||
{
|
{
|
||||||
|
|||||||