mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
1 Commits
PM-4047/fi
...
beeep/envi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f2f96de9d |
31
.github/CODEOWNERS
vendored
31
.github/CODEOWNERS
vendored
@@ -1,31 +0,0 @@
|
|||||||
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
# Unless a later match takes precedence
|
|
||||||
# @bitwarden/tech-leads
|
|
||||||
|
|
||||||
@bitwarden/dept-development-mobile
|
|
||||||
|
|
||||||
## Auth team files ##
|
|
||||||
|
|
||||||
## Platform team files ##
|
|
||||||
appIcons @bitwarden/team-platform-dev
|
|
||||||
build.cake @bitwarden/team-platform-dev
|
|
||||||
|
|
||||||
## Vault team files ##
|
|
||||||
src/watchOS @bitwarden/team-vault-dev
|
|
||||||
|
|
||||||
## Tools team files ##
|
|
||||||
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
|
|
||||||
|
|
||||||
|
|
||||||
## Crowdin Sync files ##
|
|
||||||
src/App/Resources @bitwarden/tech-leads
|
|
||||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/tech-leads
|
|
||||||
|
|
||||||
## Locales ##
|
|
||||||
src/App/Resources/AppResources.Designer.cs
|
|
||||||
src/App/Resources/AppResources.resx
|
|
||||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
|
|
||||||
45
.github/workflows/build.yml
vendored
45
.github/workflows/build.yml
vendored
@@ -71,11 +71,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
nuget-version: 5.9.0
|
nuget-version: 5.9.0
|
||||||
|
|
||||||
- name: Set up .NET
|
|
||||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: '3.1.x'
|
|
||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||||
|
|
||||||
@@ -525,7 +520,7 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
@@ -667,22 +662,6 @@ jobs:
|
|||||||
$configuration = "AppStore";
|
$configuration = "AppStore";
|
||||||
$platform = "iPhone";
|
$platform = "iPhone";
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
|
||||||
Write-Output "########################################"
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
|
|
||||||
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Done"
|
|
||||||
Write-Output "########################################"
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Archive Build for Mobile Automation
|
|
||||||
run: |
|
|
||||||
$configuration = "Release";
|
|
||||||
$platform = "iPhoneSimulator";
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
@@ -705,15 +684,6 @@ jobs:
|
|||||||
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Export .app for Automation CI
|
|
||||||
run: |
|
|
||||||
ARCHIVE_PATH="./src/iOS/bin/iPhoneSimulator/Release/BitwardeniOS.app"
|
|
||||||
EXPORT_PATH="./bitwarden-export"
|
|
||||||
|
|
||||||
zip -r -q -j BitwardeniOS.app.zip $ARCHIVE_PATH
|
|
||||||
mv BitwardeniOS.app.zip $EXPORT_PATH
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Copy all dSYMs files to upload
|
- name: Copy all dSYMs files to upload
|
||||||
run: |
|
run: |
|
||||||
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
|
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
|
||||||
@@ -736,13 +706,6 @@ jobs:
|
|||||||
./bitwarden-export/dSYMs/*.*
|
./bitwarden-export/dSYMs/*.*
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload .app file for Automation CI
|
|
||||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
|
|
||||||
with:
|
|
||||||
name: BitwardeniOS.app.zip
|
|
||||||
path: ./bitwarden-export/BitwardeniOS.app.zip
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Install AppCenter CLI
|
- name: Install AppCenter CLI
|
||||||
if: |
|
if: |
|
||||||
(github.ref == 'refs/heads/master'
|
(github.ref == 'refs/heads/master'
|
||||||
@@ -811,7 +774,7 @@ jobs:
|
|||||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|
||||||
@@ -830,7 +793,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload Sources
|
- name: Upload Sources
|
||||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
uses: crowdin/github-action@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
|
||||||
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 }}
|
||||||
@@ -877,7 +840,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
|||||||
6
.github/workflows/crowdin-pull.yml
vendored
6
.github/workflows/crowdin-pull.yml
vendored
@@ -18,19 +18,19 @@ jobs:
|
|||||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
|
||||||
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@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
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@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
|
||||||
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 }}
|
||||||
|
|||||||
2
.github/workflows/enforce-labels.yml
vendored
2
.github/workflows/enforce-labels.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Enforce Label
|
- name: Enforce Label
|
||||||
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
|
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # v2.2.2
|
||||||
with:
|
with:
|
||||||
BANNED_LABELS: "hold,needs-qa"
|
BANNED_LABELS: "hold,needs-qa"
|
||||||
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"
|
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"
|
||||||
|
|||||||
2
.github/workflows/pr-labeler.yml
vendored
2
.github/workflows/pr-labeler.yml
vendored
@@ -12,6 +12,6 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
|
- uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b # v4.0.3
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -38,11 +38,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Check Release Version
|
- name: Check Release Version
|
||||||
id: version
|
id: version
|
||||||
uses: bitwarden/gh-actions/release-version-check@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/release-version-check@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
release-type: ${{ github.event.inputs.release_type }}
|
release-type: ${{ github.event.inputs.release_type }}
|
||||||
project-type: xamarin
|
project-type: xamarin
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Dry Run - Download all artifacts
|
- name: Dry Run - Download all artifacts
|
||||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
|
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.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,
|
||||||
@@ -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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
- name: Download F-Droid .apk artifact
|
||||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
|
|
||||||
- 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: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||||
with:
|
with:
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: success
|
workflow_conclusion: success
|
||||||
@@ -147,9 +147,9 @@ jobs:
|
|||||||
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@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '10.x'
|
||||||
|
|
||||||
- name: Set up F-Droid server
|
- name: Set up F-Droid server
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/stale-bot.yml
vendored
2
.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@3cc123766321e9f15a6676375c154ccffb12a358 # v5.0.0
|
||||||
with:
|
with:
|
||||||
stale-issue-label: 'needs-reply'
|
stale-issue-label: 'needs-reply'
|
||||||
stale-pr-label: 'needs-changes'
|
stale-pr-label: 'needs-changes'
|
||||||
|
|||||||
18
.github/workflows/version-auto-bump.yml
vendored
18
.github/workflows/version-auto-bump.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
version_number: ${{ steps.version.outputs.new-version }}
|
version_number: ${{ steps.version.outputs.new-version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Calculate bumped version
|
- name: Calculate bumped version
|
||||||
id: version
|
id: version
|
||||||
@@ -32,8 +32,14 @@ jobs:
|
|||||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
trigger_version_bump:
|
trigger_version_bump:
|
||||||
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
name: "Version bump"
|
||||||
needs: setup
|
runs-on: ubuntu-22.04
|
||||||
uses: ./.github/workflows/version-bump.yml
|
needs:
|
||||||
with:
|
- setup
|
||||||
version_number: ${{ needs.setup.outputs.version_number }}
|
steps:
|
||||||
|
- name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||||
|
uses: ./.github/workflows/version-bump.yml
|
||||||
|
secrets:
|
||||||
|
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
with:
|
||||||
|
version_number: ${{ needs.setup.outputs.version_number }}
|
||||||
|
|||||||
21
.github/workflows/version-bump.yml
vendored
21
.github/workflows/version-bump.yml
vendored
@@ -12,6 +12,9 @@ on:
|
|||||||
version_number:
|
version_number:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
secrets:
|
||||||
|
AZURE_PROD_KV_CREDENTIALS:
|
||||||
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump_version:
|
bump_version:
|
||||||
@@ -19,22 +22,22 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Branch
|
- name: Checkout Branch
|
||||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||||
|
|
||||||
- name: Login to Azure - CI Subscription
|
- name: Login to Azure - CI Subscription
|
||||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
|
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
|
||||||
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@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
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"
|
||||||
|
|
||||||
- name: Import GPG key
|
- name: Import GPG key
|
||||||
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
|
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.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 }}
|
||||||
@@ -45,31 +48,31 @@ jobs:
|
|||||||
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
|
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
- name: Bump Version - Android XML
|
- name: Bump Version - Android XML
|
||||||
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/Android/Properties/AndroidManifest.xml"
|
file_path: "./src/Android/Properties/AndroidManifest.xml"
|
||||||
|
|
||||||
- name: Bump Version - iOS.Autofill
|
- name: Bump Version - iOS.Autofill
|
||||||
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS.Autofill/Info.plist"
|
file_path: "./src/iOS.Autofill/Info.plist"
|
||||||
|
|
||||||
- name: Bump Version - iOS.Extension
|
- name: Bump Version - iOS.Extension
|
||||||
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS.Extension/Info.plist"
|
file_path: "./src/iOS.Extension/Info.plist"
|
||||||
|
|
||||||
- name: Bump Version - iOS.ShareExtension
|
- name: Bump Version - iOS.ShareExtension
|
||||||
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS.ShareExtension/Info.plist"
|
file_path: "./src/iOS.ShareExtension/Info.plist"
|
||||||
|
|
||||||
- name: Bump Version - iOS
|
- name: Bump Version - iOS
|
||||||
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version_number }}
|
version: ${{ github.event.inputs.version_number }}
|
||||||
file_path: "./src/iOS/Info.plist"
|
file_path: "./src/iOS/Info.plist"
|
||||||
|
|||||||
2
.github/workflows/workflow-linter.yml
vendored
2
.github/workflows/workflow-linter.yml
vendored
@@ -8,4 +8,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
call-workflow:
|
call-workflow:
|
||||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
|
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@34ecb67b2a357795dc893549df0795e7383ff50f
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# Bitwarden Mobile Application
|
# Bitwarden Mobile Application
|
||||||
|
|
||||||
<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 Google Play" 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# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||||
|
|
||||||
|
|||||||
12
crowdin.yml
12
crowdin.yml
@@ -38,15 +38,3 @@ files:
|
|||||||
pt-PT: pt-PT
|
pt-PT: pt-PT
|
||||||
en-GB: en-GB
|
en-GB: en-GB
|
||||||
en-IN: en-IN
|
en-IN: en-IN
|
||||||
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
|
|
||||||
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
|
|
||||||
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
|
|
||||||
update_option: update_as_unapproved
|
|
||||||
languages_mapping:
|
|
||||||
two_letters_code:
|
|
||||||
zh-CN: zh-Hans
|
|
||||||
zh-TW: zh-Hant
|
|
||||||
pt-BR: pt-BR
|
|
||||||
pt-PT: pt-PT
|
|
||||||
en-GB: en-GB
|
|
||||||
en-IN: en-IN
|
|
||||||
|
|||||||
@@ -77,21 +77,21 @@
|
|||||||
<PackageReference Include="Portable.BouncyCastle">
|
<PackageReference Include="Portable.BouncyCastle">
|
||||||
<Version>1.9.0</Version>
|
<Version>1.9.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.6.1.3" />
|
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1.1" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.16" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.21" />
|
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.19" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.1.2" />
|
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.0" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.4.0.2" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.8.0</Version>
|
<Version>1.7.5</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>123.1.2.2</Version>
|
<Version>123.1.1.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.9.0.2" />
|
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.8.0" />
|
||||||
<PackageReference Include="Xamarin.Google.Dagger" Version="2.46.1.2" />
|
<PackageReference Include="Xamarin.Google.Dagger" Version="2.44.2.1" />
|
||||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
||||||
<Version>118.0.1.5</Version>
|
<Version>118.0.1.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -159,7 +159,6 @@
|
|||||||
<Compile Include="Constants.cs" />
|
<Compile Include="Constants.cs" />
|
||||||
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
|
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
|
||||||
<Compile Include="Services\WatchDeviceService.cs" />
|
<Compile Include="Services\WatchDeviceService.cs" />
|
||||||
<Compile Include="Renderers\CustomLabelRenderer.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||||
@@ -233,18 +232,6 @@
|
|||||||
<SubType></SubType>
|
<SubType></SubType>
|
||||||
<Generator></Generator>
|
<Generator></Generator>
|
||||||
</AndroidResource>
|
</AndroidResource>
|
||||||
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
|
|
||||||
<SubType></SubType>
|
|
||||||
<Generator></Generator>
|
|
||||||
</AndroidResource>
|
|
||||||
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
|
|
||||||
<SubType></SubType>
|
|
||||||
<Generator></Generator>
|
|
||||||
</AndroidResource>
|
|
||||||
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
|
|
||||||
<SubType></SubType>
|
|
||||||
<Generator></Generator>
|
|
||||||
</AndroidResource>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
||||||
|
|||||||
Binary file not shown.
@@ -12,7 +12,7 @@ namespace Bit.Droid.Autofill
|
|||||||
private List<Field> _passwordFields = null;
|
private List<Field> _passwordFields = null;
|
||||||
private List<Field> _usernameFields = null;
|
private List<Field> _usernameFields = null;
|
||||||
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
||||||
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username" };
|
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username"};
|
||||||
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
||||||
|
|
||||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
||||||
@@ -54,14 +54,15 @@ namespace Bit.Droid.Autofill
|
|||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||||
{
|
{
|
||||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
||||||
return _passwordFields;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
|
||||||
if (!_passwordFields.Any())
|
|
||||||
{
|
{
|
||||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||||
|
if (!_passwordFields.Any())
|
||||||
|
{
|
||||||
|
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _passwordFields;
|
return _passwordFields;
|
||||||
}
|
}
|
||||||
@@ -86,25 +87,23 @@ namespace Bit.Droid.Autofill
|
|||||||
{
|
{
|
||||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
||||||
}
|
}
|
||||||
if (_usernameFields.Any())
|
|
||||||
{
|
|
||||||
return _usernameFields;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
foreach (var passwordField in PasswordFields)
|
|
||||||
{
|
{
|
||||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
foreach (var passwordField in PasswordFields)
|
||||||
.LastOrDefault();
|
|
||||||
if (usernameField != null)
|
|
||||||
{
|
{
|
||||||
_usernameFields.Add(usernameField);
|
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||||
|
.LastOrDefault();
|
||||||
|
if (usernameField != null)
|
||||||
|
{
|
||||||
|
_usernameFields.Add(usernameField);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!_usernameFields.Any())
|
if (!_usernameFields.Any())
|
||||||
{
|
{
|
||||||
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
|
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _usernameFields;
|
return _usernameFields;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ namespace Bit.Droid
|
|||||||
private IAppIdService _appIdService;
|
private IAppIdService _appIdService;
|
||||||
private IEventService _eventService;
|
private IEventService _eventService;
|
||||||
private IPushNotificationListenerService _pushNotificationListenerService;
|
private IPushNotificationListenerService _pushNotificationListenerService;
|
||||||
|
private IVaultTimeoutService _vaultTimeoutService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private PendingIntent _eventUploadPendingIntent;
|
private PendingIntent _eventUploadPendingIntent;
|
||||||
private AppOptions _appOptions;
|
private AppOptions _appOptions;
|
||||||
@@ -68,6 +69,7 @@ namespace Bit.Droid
|
|||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||||
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
||||||
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
TabLayoutResource = Resource.Layout.Tabbar;
|
TabLayoutResource = Resource.Layout.Tabbar;
|
||||||
@@ -232,6 +234,7 @@ namespace Bit.Droid
|
|||||||
|
|
||||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
|
_vaultTimeoutService.ResetTimeoutDelay = true;
|
||||||
if (resultCode == Result.Ok &&
|
if (resultCode == Result.Ok &&
|
||||||
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ namespace Bit.Droid
|
|||||||
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||||
|
|
||||||
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||||
|
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||||
ServiceContainer.Resolve<IUserVerificationService>());
|
|
||||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||||
|
|
||||||
var accountsManager = new AccountsManager(
|
var accountsManager = new AccountsManager(
|
||||||
@@ -156,10 +156,10 @@ namespace Bit.Droid
|
|||||||
messagingService, broadcasterService);
|
messagingService, broadcasterService);
|
||||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||||
platformUtilsService, new LazyResolve<IEventService>());
|
platformUtilsService, new LazyResolve<IEventService>());
|
||||||
|
var biometricService = new BiometricService(stateService);
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||||
var biometricService = new BiometricService(stateService, cryptoService);
|
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
|
||||||
|
|
||||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?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.9.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="2023.5.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<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" />
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomLabelRenderer : LabelRenderer
|
|
||||||
{
|
|
||||||
public CustomLabelRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
if (Control != null && e.NewElement is CustomLabel label)
|
|
||||||
{
|
|
||||||
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
|
|
||||||
{
|
|
||||||
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
var label = sender as CustomLabel;
|
|
||||||
switch (e.PropertyName)
|
|
||||||
{
|
|
||||||
case nameof(CustomLabel.AutomationId):
|
|
||||||
Control.ContentDescription = label.AutomationId;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:viewportWidth="129"
|
|
||||||
android:viewportHeight="124"
|
|
||||||
android:width="129dp"
|
|
||||||
android:height="124dp">
|
|
||||||
<path
|
|
||||||
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
|
||||||
android:fillColor="#F0F0F0"
|
|
||||||
android:strokeColor="#89929F"
|
|
||||||
android:strokeWidth="3" />
|
|
||||||
<path
|
|
||||||
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
|
||||||
android:strokeColor="#89929F"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
|
||||||
android:strokeColor="#89929F"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
|
||||||
android:strokeColor="#89929F"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
|
||||||
android:fillColor="#89929F" />
|
|
||||||
<path
|
|
||||||
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
|
||||||
android:fillColor="#89929F" />
|
|
||||||
<path
|
|
||||||
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
|
||||||
android:fillColor="#89929F" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:viewportWidth="129"
|
|
||||||
android:viewportHeight="124"
|
|
||||||
android:width="129dp"
|
|
||||||
android:height="124dp">
|
|
||||||
<path
|
|
||||||
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
|
||||||
android:fillColor="@android:color/transparent"
|
|
||||||
android:strokeColor="#A3A3A3"
|
|
||||||
android:strokeWidth="3" />
|
|
||||||
<path
|
|
||||||
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
|
||||||
android:strokeColor="#A3A3A3"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
|
||||||
android:strokeColor="#A3A3A3"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
|
||||||
android:strokeColor="#A3A3A3"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeLineCap="round" />
|
|
||||||
<path
|
|
||||||
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
|
||||||
android:fillColor="#A3A3A3" />
|
|
||||||
<path
|
|
||||||
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
|
||||||
android:fillColor="#A3A3A3" />
|
|
||||||
<path
|
|
||||||
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
|
||||||
android:fillColor="#A3A3A3" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="30dp"
|
|
||||||
android:paddingRight="30dp">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/lblHeader"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="@dimen/dialog_header_text_size"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginBottom="-3dp"
|
|
||||||
android:labelFor="@+id/txtValue"/>
|
|
||||||
<EditText
|
|
||||||
android:id="@id/txtValue"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="@dimen/dialog_input_text_size"/>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/lblValueSubinfo"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
@@ -2,7 +2,4 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
|
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
|
||||||
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
|
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
|
||||||
<dimen name="dialog_input_text_size">16sp</dimen>
|
|
||||||
<dimen name="dialog_header_text_size">12sp</dimen>
|
|
||||||
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Security.Keystore;
|
using Android.Security.Keystore;
|
||||||
using Bit.App.Services;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Java.Security;
|
using Java.Security;
|
||||||
@@ -10,8 +9,10 @@ using Javax.Crypto;
|
|||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
public class BiometricService : BaseBiometricService
|
public class BiometricService : IBiometricService
|
||||||
{
|
{
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
||||||
|
|
||||||
private const string KeyStoreName = "AndroidKeyStore";
|
private const string KeyStoreName = "AndroidKeyStore";
|
||||||
@@ -23,14 +24,14 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
private readonly KeyStore _keystore;
|
private readonly KeyStore _keystore;
|
||||||
|
|
||||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
public BiometricService(IStateService stateService)
|
||||||
: base(stateService, cryptoService)
|
|
||||||
{
|
{
|
||||||
|
_stateService = stateService;
|
||||||
_keystore = KeyStore.GetInstance(KeyStoreName);
|
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||||
_keystore.Load(null);
|
_keystore.Load(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||||
{
|
{
|
||||||
@@ -40,7 +41,7 @@ namespace Bit.Droid.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ using Android.Views.InputMethods;
|
|||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.App.Utilities.Prompts;
|
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Xamarin.Forms.Platform.Android;
|
using static Bit.App.Pages.SettingsPageViewModel;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
@@ -210,7 +209,10 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
if (numericKeyboard)
|
if (numericKeyboard)
|
||||||
{
|
{
|
||||||
SetNumericKeyboardTo(input);
|
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
}
|
}
|
||||||
if (password)
|
if (password)
|
||||||
{
|
{
|
||||||
@@ -246,83 +248,6 @@ namespace Bit.Droid.Services
|
|||||||
return result.Task;
|
return result.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
|
|
||||||
{
|
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
|
||||||
if (activity == null)
|
|
||||||
{
|
|
||||||
return Task.FromResult<ValidatablePromptResponse?>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var alertBuilder = new AlertDialog.Builder(activity);
|
|
||||||
alertBuilder.SetTitle(config.Title);
|
|
||||||
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
|
|
||||||
alertBuilder.SetView(view);
|
|
||||||
|
|
||||||
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
|
|
||||||
|
|
||||||
alertBuilder.SetOnCancelListener(new BasicDialogWithResultCancelListener(result));
|
|
||||||
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
|
|
||||||
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
|
|
||||||
if (!string.IsNullOrEmpty(config.ThirdButtonText))
|
|
||||||
{
|
|
||||||
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var alert = alertBuilder.Create();
|
|
||||||
|
|
||||||
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
|
|
||||||
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
|
|
||||||
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
|
|
||||||
|
|
||||||
lblHeader.Text = config.Subtitle;
|
|
||||||
lblValueSubinfo.Text = config.ValueSubInfo;
|
|
||||||
|
|
||||||
var defaultSubInfoColor = lblValueSubinfo.TextColors;
|
|
||||||
|
|
||||||
input.InputType = InputTypes.ClassText;
|
|
||||||
|
|
||||||
if (config.NumericKeyboard)
|
|
||||||
{
|
|
||||||
SetNumericKeyboardTo(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
|
|
||||||
input.Text = config.Text ?? string.Empty;
|
|
||||||
input.SetSelection(config.Text?.Length ?? 0);
|
|
||||||
input.AfterTextChanged += (sender, args) =>
|
|
||||||
{
|
|
||||||
if (lblValueSubinfo.Text != config.ValueSubInfo)
|
|
||||||
{
|
|
||||||
lblValueSubinfo.Text = config.ValueSubInfo;
|
|
||||||
lblHeader.SetTextColor(defaultSubInfoColor);
|
|
||||||
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
|
|
||||||
alert.Show();
|
|
||||||
|
|
||||||
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
|
|
||||||
positiveButton.Click += (sender, args) =>
|
|
||||||
{
|
|
||||||
var error = config.ValidateText(input.Text);
|
|
||||||
if (error != null)
|
|
||||||
{
|
|
||||||
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
|
||||||
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
|
||||||
lblValueSubinfo.Text = error;
|
|
||||||
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
|
|
||||||
alert.Dismiss();
|
|
||||||
};
|
|
||||||
|
|
||||||
return result.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RateApp()
|
public void RateApp()
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
@@ -600,29 +525,5 @@ namespace Bit.Droid.Services
|
|||||||
// only used by iOS
|
// only used by iOS
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetNumericKeyboardTo(EditText editText)
|
|
||||||
{
|
|
||||||
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BasicDialogWithResultCancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
|
|
||||||
{
|
|
||||||
private readonly TaskCompletionSource<ValidatablePromptResponse?> _taskCompletionSource;
|
|
||||||
|
|
||||||
public BasicDialogWithResultCancelListener(TaskCompletionSource<ValidatablePromptResponse?> taskCompletionSource)
|
|
||||||
{
|
|
||||||
_taskCompletionSource = taskCompletionSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnCancel(IDialogInterface dialog)
|
|
||||||
{
|
|
||||||
_taskCompletionSource?.TrySetResult(null);
|
|
||||||
dialog?.Dismiss();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Java.Lang;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Utilities
|
namespace Bit.Droid.Utilities
|
||||||
{
|
{
|
||||||
@@ -14,12 +13,7 @@ namespace Bit.Droid.Utilities
|
|||||||
// Note: getting the bundle like this will cause to call unparcel() internally
|
// Note: getting the bundle like this will cause to call unparcel() internally
|
||||||
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
|
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
|
||||||
}
|
}
|
||||||
catch (Exception ex) when
|
catch (BadParcelableException)
|
||||||
(
|
|
||||||
ex is BadParcelableException ||
|
|
||||||
ex is ClassNotFoundException ||
|
|
||||||
ex is RuntimeException
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
intent.ReplaceExtras((Bundle)null);
|
intent.ReplaceExtras((Bundle)null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Utilities.Prompts;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ namespace Bit.App.Abstractions
|
|||||||
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
|
||||||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||||
bool autofocus = true, bool password = false);
|
bool autofocus = true, bool password = false);
|
||||||
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
|
|
||||||
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
|
||||||
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.App.Abstractions
|
namespace Bit.App.Abstractions
|
||||||
{
|
{
|
||||||
@@ -7,8 +6,10 @@ namespace Bit.App.Abstractions
|
|||||||
{
|
{
|
||||||
string[] ProtectedFields { get; }
|
string[] ProtectedFields { get; }
|
||||||
|
|
||||||
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
|
Task<bool> ShowPasswordPromptAsync();
|
||||||
|
|
||||||
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
|
||||||
|
|
||||||
|
Task<bool> Enabled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.3" />
|
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.3" />
|
||||||
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.6" />
|
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.6" />
|
||||||
<PackageReference Include="Xamarin.Essentials" Version="1.8.0" />
|
<PackageReference Include="Xamarin.Essentials" Version="1.7.5" />
|
||||||
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
|
||||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2612" />
|
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2578" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||||
<PackageReference Include="MessagePack" Version="2.4.59" />
|
<PackageReference Include="MessagePack" Version="2.4.59" />
|
||||||
@@ -145,8 +145,6 @@
|
|||||||
<Folder Include="Controls\DateTime\" />
|
<Folder Include="Controls\DateTime\" />
|
||||||
<Folder Include="Controls\IconLabelButton\" />
|
<Folder Include="Controls\IconLabelButton\" />
|
||||||
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
<Folder Include="Controls\PasswordStrengthProgressBar\" />
|
||||||
<Folder Include="Utilities\Automation\" />
|
|
||||||
<Folder Include="Utilities\Prompts\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -442,7 +440,5 @@
|
|||||||
<None Remove="MessagePack" />
|
<None Remove="MessagePack" />
|
||||||
<None Remove="MessagePack.MSBuild.Tasks" />
|
<None Remove="MessagePack.MSBuild.Tasks" />
|
||||||
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
<None Remove="Controls\PasswordStrengthProgressBar\" />
|
||||||
<None Remove="Utilities\Automation\" />
|
|
||||||
<None Remove="Utilities\Prompts\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
||||||
// Reset delay on every start
|
// Reset delay on every start
|
||||||
_vaultTimeoutService.DelayLockAndLogoutMs = null;
|
_vaultTimeoutService.DelayTimeoutMs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _configService.GetAsync();
|
await _configService.GetAsync();
|
||||||
|
|||||||
@@ -30,15 +30,13 @@
|
|||||||
BackgroundColor="{DynamicResource BackgroundColor}"
|
BackgroundColor="{DynamicResource BackgroundColor}"
|
||||||
VerticalOptions="Start"
|
VerticalOptions="Start"
|
||||||
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
|
||||||
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never"
|
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
|
||||||
AutomationId="AccountListView">
|
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="view:AccountView">
|
<DataTemplate x:DataType="view:AccountView">
|
||||||
<controls:AccountViewCell
|
<controls:AccountViewCell
|
||||||
Account="{Binding .}"
|
Account="{Binding .}"
|
||||||
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
|
||||||
AutomationId="AccountViewCell"
|
|
||||||
/>
|
/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
|
||||||
@@ -60,23 +60,20 @@
|
|||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive}"
|
IsVisible="{Binding IsActive}"
|
||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountEmailLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding AccountView.Email}"
|
Text="{Binding AccountView.Email}"
|
||||||
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
TextColor="{DynamicResource MutedColor}"
|
TextColor="{DynamicResource MutedColor}"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountEmailLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
IsVisible="{Binding ShowHostname}"
|
IsVisible="{Binding ShowHostname}"
|
||||||
Text="{Binding AccountView.Hostname}"
|
Text="{Binding AccountView.Hostname}"
|
||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountHostUrlLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountUnlocked}"
|
Text="{u:I18n AccountUnlocked}"
|
||||||
@@ -84,8 +81,7 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountStatusLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLocked}"
|
Text="{u:I18n AccountLocked}"
|
||||||
@@ -93,8 +89,7 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountStatusLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Text="{u:I18n AccountLoggedOut}"
|
Text="{u:I18n AccountLoggedOut}"
|
||||||
@@ -102,8 +97,7 @@
|
|||||||
StyleClass="accountlist-sub, accountlist-sub-platform"
|
StyleClass="accountlist-sub, accountlist-sub-platform"
|
||||||
FontAttributes="Italic"
|
FontAttributes="Italic"
|
||||||
TextTransform="Lowercase"
|
TextTransform="Lowercase"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation" />
|
||||||
AutomationId="AccountStatusLabel" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
@@ -113,8 +107,7 @@
|
|||||||
Margin="12,0"
|
Margin="12,0"
|
||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform"
|
StyleClass="list-icon, list-icon-platform" />
|
||||||
AutomationId="InactiveVaultIcon" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Text="{Binding AuthStatusIconActive}"
|
Text="{Binding AuthStatusIconActive}"
|
||||||
@@ -123,8 +116,7 @@
|
|||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
StyleClass="list-icon, list-icon-platform"
|
StyleClass="list-icon, list-icon-platform"
|
||||||
TextColor="{DynamicResource TextColor}"
|
TextColor="{DynamicResource TextColor}"/>
|
||||||
AutomationId="ActiveVaultIcon" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
@@ -155,8 +147,7 @@
|
|||||||
StyleClass="accountlist-title, accountlist-title-platform"
|
StyleClass="accountlist-title, accountlist-title-platform"
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
Grid.Column="1"
|
Grid.Column="1" />
|
||||||
AutomationId="AddAccountButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ViewCell>
|
</ViewCell>
|
||||||
@@ -36,7 +36,7 @@ namespace Bit.App.Controls
|
|||||||
|
|
||||||
public bool ShowHostname
|
public bool ShowHostname
|
||||||
{
|
{
|
||||||
get => !string.IsNullOrWhiteSpace(AccountView.Hostname);
|
get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsActive
|
public bool IsActive
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="0"
|
ColumnSpacing="0"
|
||||||
x:DataType="controls:CipherViewCellViewModel"
|
x:DataType="controls:CipherViewCellViewModel">
|
||||||
AutomationId="CipherCell">
|
|
||||||
|
|
||||||
<Grid.Resources>
|
<Grid.Resources>
|
||||||
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
|
||||||
@@ -37,8 +36,7 @@
|
|||||||
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
|
||||||
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
|
||||||
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
AutomationId="CipherTypeIcon" />
|
|
||||||
|
|
||||||
<ff:CachedImage
|
<ff:CachedImage
|
||||||
x:Name="_iconImage"
|
x:Name="_iconImage"
|
||||||
@@ -54,8 +52,7 @@
|
|||||||
Aspect="AspectFit"
|
Aspect="AspectFit"
|
||||||
IsVisible="{Binding ShowIconImage}"
|
IsVisible="{Binding ShowIconImage}"
|
||||||
Source="{Binding IconImageSource, Mode=OneTime}"
|
Source="{Binding IconImageSource, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False" />
|
||||||
AutomationId="CipherWebsiteIcon" />
|
|
||||||
|
|
||||||
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -74,8 +71,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Cipher.Name}"
|
Text="{Binding Cipher.Name}" />
|
||||||
AutomationId="CipherNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -84,8 +80,7 @@
|
|||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Cipher.SubTitle}"
|
Text="{Binding Cipher.SubTitle}"
|
||||||
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
|
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
|
||||||
Converter={StaticResource stringHasValueConverter}}"
|
Converter={StaticResource stringHasValueConverter}}"/>
|
||||||
AutomationId="CipherSubTitleLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -96,8 +91,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
|
||||||
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Shared}"
|
AutomationProperties.Name="{u:I18n Shared}" />
|
||||||
AutomationId="CipherInCollectionIcon" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -108,8 +102,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
|
||||||
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
|
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Attachments}"
|
AutomationProperties.Name="{u:I18n Attachments}" />
|
||||||
AutomationId="CipherWithAttachmentsIcon" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
@@ -121,7 +114,6 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="EndAndExpand"
|
HorizontalOptions="EndAndExpand"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
AutomationId="CipherOptionsButton" />
|
|
||||||
|
|
||||||
</controls:ExtendedGrid>
|
</controls:ExtendedGrid>
|
||||||
@@ -31,7 +31,7 @@ namespace Bit.App.Controls
|
|||||||
public bool ShowIconImage
|
public bool ShowIconImage
|
||||||
{
|
{
|
||||||
get => WebsiteIconsEnabled
|
get => WebsiteIconsEnabled
|
||||||
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
|
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
|
||||||
&& IconImageSource != null;
|
&& IconImageSource != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ namespace Bit.App.Controls
|
|||||||
{
|
{
|
||||||
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
if (_iconImageSource == string.Empty) // default value since icon source can return null
|
||||||
{
|
{
|
||||||
_iconImageSource = IconImageHelper.GetIconImage(Cipher);
|
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
|
||||||
}
|
}
|
||||||
return _iconImageSource;
|
return _iconImageSource;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Controls
|
|
||||||
{
|
|
||||||
public class CustomLabel : Label
|
|
||||||
{
|
|
||||||
public CustomLabel()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public int? FontWeight { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
x:Class="Bit.App.Controls.SendViewCell"
|
x:Class="Bit.App.Controls.SendViewCell"
|
||||||
@@ -54,16 +54,14 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Send.Name}"
|
Text="{Binding Send.Name}" />
|
||||||
AutomationId="SendNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.ColumnSpan="6"
|
Grid.ColumnSpan="6"
|
||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Send.DisplayDate}"
|
Text="{Binding Send.DisplayDate}" />
|
||||||
AutomationId="SendDateLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -74,8 +72,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
|
||||||
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
|
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Disabled}"
|
AutomationProperties.Name="{u:I18n Disabled}" />
|
||||||
AutomationId="DisabledSendLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -86,8 +83,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
|
||||||
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
|
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Password}"
|
AutomationProperties.Name="{u:I18n Password}" />
|
||||||
AutomationId="PasswordProtectedSendLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="3"
|
Grid.Column="3"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -98,8 +94,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
|
||||||
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
|
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n MaxAccessCountReached}"
|
AutomationProperties.Name="{u:I18n MaxAccessCountReached}" />
|
||||||
AutomationId="SendMaxAccessCountReachedLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -110,8 +105,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
|
||||||
IsVisible="{Binding Send.Expired, Mode=OneTime}"
|
IsVisible="{Binding Send.Expired, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Expired}"
|
AutomationProperties.Name="{u:I18n Expired}" />
|
||||||
AutomationId="ExpiredSendLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Grid.Column="5"
|
Grid.Column="5"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@@ -122,8 +116,7 @@
|
|||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||||
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
|
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n PendingDelete}"
|
AutomationProperties.Name="{u:I18n PendingDelete}" />
|
||||||
AutomationId="SendWithPendingDeletionLabel" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<controls:MiButton
|
<controls:MiButton
|
||||||
@@ -136,7 +129,6 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="EndAndExpand"
|
HorizontalOptions="EndAndExpand"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
AutomationId="SendOptionsButton" />
|
|
||||||
|
|
||||||
</controls:ExtendedGrid>
|
</controls:ExtendedGrid>
|
||||||
|
|||||||
@@ -33,8 +33,7 @@
|
|||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
AutomationId="BooleanCustomFieldNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
@@ -50,15 +49,13 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
AutomationId="BooleanCustomFieldValueLabel" />
|
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding BooleanValue}"
|
IsToggled="{Binding BooleanValue}"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2" />
|
||||||
AutomationId="BooleanCustomFieldValueToggle" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="HiddenCustomFieldNameLabel" />
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -40,8 +39,7 @@
|
|||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
IsVisible="{Binding ShowHiddenValue}"
|
IsVisible="{Binding ShowHiddenValue}" />
|
||||||
AutomationId="HiddenCustomFieldValueLabel" />
|
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
Text="{Binding Field.MaskedValue, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
@@ -58,8 +56,7 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{Binding Field.Name}"
|
AutomationProperties.Name="{Binding Field.Name}">
|
||||||
AutomationId="HiddenCustomFieldValueEntry">
|
|
||||||
<Entry.Keyboard>
|
<Entry.Keyboard>
|
||||||
<Keyboard x:FactoryMethod="Create">
|
<Keyboard x:FactoryMethod="Create">
|
||||||
<x:Arguments>
|
<x:Arguments>
|
||||||
@@ -77,8 +74,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
AutomationId="HiddenCustomFieldShowValueButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
|
|||||||
@@ -29,15 +29,13 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="LinkedCustomFieldNameLabel" />
|
|
||||||
<controls:IconLabel
|
<controls:IconLabel
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
AutomationId="LinkedCustomFieldValueLabel" />
|
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row, box-row-input"
|
StyleClass="box-row, box-row-input"
|
||||||
IsVisible="{Binding IsEditing}">
|
IsVisible="{Binding IsEditing}">
|
||||||
@@ -46,8 +44,7 @@
|
|||||||
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
|
||||||
ItemDisplayBinding="{Binding Key}"
|
ItemDisplayBinding="{Binding Key}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="LinkedCustomFieldValuePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
@@ -58,8 +55,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
AutomationId="LinkedCustomFieldOptionsButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -29,15 +29,13 @@
|
|||||||
Text="{Binding Field.Name, Mode=OneWay}"
|
Text="{Binding Field.Name, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="TextCustomFieldNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding ValueText, Mode=OneWay}"
|
Text="{Binding ValueText, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
AutomationId="TextCustomFieldValueLabel" />
|
|
||||||
<Entry
|
<Entry
|
||||||
Text="{Binding Field.Value}"
|
Text="{Binding Field.Value}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
@@ -45,8 +43,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
IsVisible="{Binding IsEditing}"
|
IsVisible="{Binding IsEditing}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{Binding Field.Name}"
|
AutomationProperties.Name="{Binding Field.Name}" />
|
||||||
AutomationId="TextCustomFieldValueEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -56,8 +53,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Copy}"
|
AutomationProperties.Name="{u:I18n Copy}" />
|
||||||
AutomationId="TextCustomFieldCopyValue" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
@@ -67,8 +63,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
AutomationId="TextCustomFieldOptionsButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -95,6 +95,14 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CustomEnvironmentFooter}"
|
Text="{u:I18n CustomEnvironmentFooter}"
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
|
<StackLayout StyleClass="box-row">
|
||||||
|
<Button Text="{u:I18n LoadFromFile}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding LoadFromFileCommand}" />
|
||||||
|
<Button Text="{u:I18n Clear}"
|
||||||
|
StyleClass="btn-secondary"
|
||||||
|
Command="{Binding ClearCommand}" />
|
||||||
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
@@ -27,9 +31,13 @@ namespace Bit.App.Pages
|
|||||||
IconsUrl = _environmentService.IconsUrl;
|
IconsUrl = _environmentService.IconsUrl;
|
||||||
NotificationsUrls = _environmentService.NotificationsUrl;
|
NotificationsUrls = _environmentService.NotificationsUrl;
|
||||||
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
SubmitCommand = new AsyncCommand(SubmitAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
|
LoadFromFileCommand = new AsyncCommand(LoadEnvironmentsFromFile, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
|
ClearCommand = new Command(ClearAllUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand SubmitCommand { get; }
|
public ICommand SubmitCommand { get; }
|
||||||
|
public ICommand LoadFromFileCommand { get; }
|
||||||
|
public ICommand ClearCommand { get; }
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; }
|
||||||
public string ApiUrl { get; set; }
|
public string ApiUrl { get; set; }
|
||||||
public string IdentityUrl { get; set; }
|
public string IdentityUrl { get; set; }
|
||||||
@@ -87,5 +95,75 @@ namespace Bit.App.Pages
|
|||||||
_logger.Value.Exception(ex);
|
_logger.Value.Exception(ex);
|
||||||
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadEnvironmentsFromFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string jsonString;
|
||||||
|
var result = await FilePicker.PickAsync(new PickOptions
|
||||||
|
{
|
||||||
|
PickerTitle = "This a test to pick files"
|
||||||
|
});
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
if (result.FileName.EndsWith("json", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
result.FileName.EndsWith("txt", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var stream = await result.OpenReadAsync();
|
||||||
|
using (var reader = new System.IO.StreamReader(stream))
|
||||||
|
{
|
||||||
|
jsonString = reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
var envUrls = JsonConvert.DeserializeObject<EnvironmentsData>(jsonString);
|
||||||
|
BaseUrl = envUrls.Base;
|
||||||
|
ApiUrl = envUrls.Api;
|
||||||
|
IdentityUrl = envUrls.Identity;
|
||||||
|
WebVaultUrl = envUrls.Vault;
|
||||||
|
IconsUrl = envUrls.Icons;
|
||||||
|
NotificationsUrls = envUrls.Notifications;
|
||||||
|
NotifyUrlsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearAllUrls()
|
||||||
|
{
|
||||||
|
BaseUrl = string.Empty;
|
||||||
|
ApiUrl = string.Empty;
|
||||||
|
IdentityUrl = string.Empty;
|
||||||
|
WebVaultUrl = string.Empty;
|
||||||
|
IconsUrl = string.Empty;
|
||||||
|
NotificationsUrls = string.Empty;
|
||||||
|
NotifyUrlsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyUrlsChanged() {
|
||||||
|
TriggerPropertyChanged(nameof(BaseUrl), new[]
|
||||||
|
{
|
||||||
|
nameof(ApiUrl),
|
||||||
|
nameof(IdentityUrl),
|
||||||
|
nameof(WebVaultUrl),
|
||||||
|
nameof(IconsUrl),
|
||||||
|
nameof(NotificationsUrls)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EnvironmentsData
|
||||||
|
{
|
||||||
|
public string Base { get; set; }
|
||||||
|
public string Admin { get; set; }
|
||||||
|
public string Api { get; set; }
|
||||||
|
public string Identity { get; set; }
|
||||||
|
public string Icons { get; set; }
|
||||||
|
public string Notifications { get; set; }
|
||||||
|
public string Sso { get; set; }
|
||||||
|
public string Vault { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}"
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
AutomationId="AccountIconButton" />
|
|
||||||
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
|
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
{
|
{
|
||||||
public class HomeViewModel : BaseViewModel
|
public class HomeViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private const string LOGGING_IN_ON_US = "bitwarden.com";
|
|
||||||
private const string LOGGING_IN_ON_EU = "bitwarden.eu";
|
|
||||||
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
@@ -30,6 +30,8 @@ namespace Bit.App.Pages
|
|||||||
private bool _rememberEmail;
|
private bool _rememberEmail;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _selectedEnvironmentName;
|
private string _selectedEnvironmentName;
|
||||||
|
private bool _isEmailEnabled;
|
||||||
|
private bool _canLogin;
|
||||||
private bool _displayEuEnvironment;
|
private bool _displayEuEnvironment;
|
||||||
|
|
||||||
public HomeViewModel()
|
public HomeViewModel()
|
||||||
@@ -84,7 +86,7 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _selectedEnvironmentName, value);
|
set => SetProperty(ref _selectedEnvironmentName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RegionText => $"{AppResources.LoggingInOn}:";
|
public string RegionText => $"{AppResources.Region}:";
|
||||||
public bool CanContinue => !string.IsNullOrEmpty(Email);
|
public bool CanContinue => !string.IsNullOrEmpty(Email);
|
||||||
|
|
||||||
public FormattedString CreateAccountText
|
public FormattedString CreateAccountText
|
||||||
@@ -165,12 +167,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
|
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
|
||||||
var options = _displayEuEnvironment
|
var options = _displayEuEnvironment
|
||||||
? new string[] { LOGGING_IN_ON_US, LOGGING_IN_ON_EU, AppResources.SelfHosted }
|
? new string[] { AppResources.US, AppResources.EU, AppResources.SelfHosted }
|
||||||
: new string[] { LOGGING_IN_ON_US, AppResources.SelfHosted };
|
: new string[] { AppResources.US, AppResources.SelfHosted };
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
await Device.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
var result = await Page.DisplayActionSheet(AppResources.LoggingInOn, AppResources.Cancel, null, options);
|
var result = await Page.DisplayActionSheet(AppResources.DataRegion, AppResources.Cancel, null, options);
|
||||||
|
|
||||||
if (result is null || result == AppResources.Cancel)
|
if (result is null || result == AppResources.Cancel)
|
||||||
{
|
{
|
||||||
@@ -183,7 +185,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _environmentService.SetUrlsAsync(result == LOGGING_IN_ON_EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
|
await _environmentService.SetUrlsAsync(result == AppResources.EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
|
||||||
await _configService.GetAsync(true);
|
await _configService.GetAsync(true);
|
||||||
SelectedEnvironmentName = result;
|
SelectedEnvironmentName = result;
|
||||||
});
|
});
|
||||||
@@ -196,17 +198,17 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS);
|
await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS);
|
||||||
environmentsSaved = EnvironmentUrlData.DefaultUS;
|
environmentsSaved = EnvironmentUrlData.DefaultUS;
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_US;
|
SelectedEnvironmentName = AppResources.US;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base)
|
if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base)
|
||||||
{
|
{
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_US;
|
SelectedEnvironmentName = AppResources.US;
|
||||||
}
|
}
|
||||||
else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base)
|
else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base)
|
||||||
{
|
{
|
||||||
SelectedEnvironmentName = LOGGING_IN_ON_EU;
|
SelectedEnvironmentName = AppResources.EU;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}"
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
AutomationId="AccountIconButton" />
|
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<Grid
|
<Grid
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding PinEnabled}"
|
IsVisible="{Binding PinLock}"
|
||||||
Padding="0, 10, 0, 0">
|
Padding="0, 10, 0, 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
AutomationId="PinEntry" />
|
AutomationId="PinEntry"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -84,12 +83,12 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
AutomationId="PinVisibilityToggle" />
|
AutomationId="PinVisibilityToggle"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="_passwordGrid"
|
x:Name="_passwordGrid"
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
|
||||||
Padding="0, 10, 0, 0">
|
Padding="0, 10, 0, 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -115,7 +114,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}"
|
||||||
AutomationId="MasterPasswordEntry" />
|
AutomationId="MasterPasswordEntry"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -154,7 +153,7 @@
|
|||||||
Text="{u:I18n Unlock}"
|
Text="{u:I18n Unlock}"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"
|
||||||
Clicked="Unlock_Clicked"
|
Clicked="Unlock_Clicked"
|
||||||
AutomationId="UnlockVaultButton" />
|
AutomationId="UnlockVaultButton"/>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -20,14 +20,13 @@ namespace Bit.App.Pages
|
|||||||
private bool _promptedAfterResume;
|
private bool _promptedAfterResume;
|
||||||
private bool _appeared;
|
private bool _appeared;
|
||||||
|
|
||||||
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true, bool checkPendingAuthRequests = true)
|
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
|
||||||
{
|
{
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_autoPromptBiometric = autoPromptBiometric;
|
_autoPromptBiometric = autoPromptBiometric;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
|
||||||
_vm = BindingContext as LockPageViewModel;
|
_vm = BindingContext as LockPageViewModel;
|
||||||
_vm.CheckPendingAuthRequests = checkPendingAuthRequests;
|
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_vm?.PinEnabled ?? false)
|
if (_vm?.PinLock ?? false)
|
||||||
{
|
{
|
||||||
return _pin;
|
return _pin;
|
||||||
}
|
}
|
||||||
@@ -55,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task PromptBiometricAfterResumeAsync()
|
public async Task PromptBiometricAfterResumeAsync()
|
||||||
{
|
{
|
||||||
if (_vm.BiometricEnabled)
|
if (_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
if (!_promptedAfterResume)
|
if (!_promptedAfterResume)
|
||||||
@@ -92,13 +91,13 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
_vm.FocusSecretEntry += PerformFocusSecretEntry;
|
||||||
|
|
||||||
if (!_vm.BiometricEnabled)
|
if (!_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
RequestFocus(SecretEntry);
|
RequestFocus(SecretEntry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
|
if (_vm.UsingKeyConnector && !_vm.PinLock)
|
||||||
{
|
{
|
||||||
_passwordGrid.IsVisible = false;
|
_passwordGrid.IsVisible = false;
|
||||||
_unlockButton.IsVisible = false;
|
_unlockButton.IsVisible = false;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@@ -28,27 +27,27 @@ namespace Bit.App.Pages
|
|||||||
private readonly IEnvironmentService _environmentService;
|
private readonly IEnvironmentService _environmentService;
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IBiometricService _biometricService;
|
private readonly IBiometricService _biometricService;
|
||||||
private readonly IUserVerificationService _userVerificationService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IWatchDeviceService _watchDeviceService;
|
private readonly IWatchDeviceService _watchDeviceService;
|
||||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
|
||||||
private readonly ISyncService _syncService;
|
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private string _pin;
|
private string _pin;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private PinLockType _pinStatus;
|
private bool _pinLock;
|
||||||
private bool _pinEnabled;
|
private bool _biometricLock;
|
||||||
private bool _biometricEnabled;
|
|
||||||
private bool _biometricIntegrityValid = true;
|
private bool _biometricIntegrityValid = true;
|
||||||
private bool _biometricButtonVisible;
|
private bool _biometricButtonVisible;
|
||||||
private bool _hasMasterPassword;
|
private bool _usingKeyConnector;
|
||||||
private string _biometricButtonText;
|
private string _biometricButtonText;
|
||||||
private string _loggedInAsText;
|
private string _loggedInAsText;
|
||||||
private string _lockedVerifyText;
|
private string _lockedVerifyText;
|
||||||
|
private bool _isPinProtected;
|
||||||
|
private bool _isPinProtectedWithKey;
|
||||||
|
|
||||||
public LockPageViewModel()
|
public LockPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -61,24 +60,21 @@ namespace Bit.App.Pages
|
|||||||
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
|
||||||
_syncService = ServiceContainer.Resolve<ISyncService>();
|
|
||||||
|
|
||||||
PageTitle = AppResources.VerifyMasterPassword;
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel =
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
{
|
||||||
{
|
AllowAddAccountRow = true,
|
||||||
AllowAddAccountRow = true,
|
AllowActiveAccountSelection = true
|
||||||
AllowActiveAccountSelection = true
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MasterPassword
|
public string MasterPassword
|
||||||
@@ -104,21 +100,21 @@ namespace Bit.App.Pages
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool PinEnabled
|
public bool PinLock
|
||||||
{
|
{
|
||||||
get => _pinEnabled;
|
get => _pinLock;
|
||||||
set => SetProperty(ref _pinEnabled, value);
|
set => SetProperty(ref _pinLock, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasMasterPassword
|
public bool UsingKeyConnector
|
||||||
{
|
{
|
||||||
get => _hasMasterPassword;
|
get => _usingKeyConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricEnabled
|
public bool BiometricLock
|
||||||
{
|
{
|
||||||
get => _biometricEnabled;
|
get => _biometricLock;
|
||||||
set => SetProperty(ref _biometricEnabled, value);
|
set => SetProperty(ref _biometricLock, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricIntegrityValid
|
public bool BiometricIntegrityValid
|
||||||
@@ -151,18 +147,12 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _lockedVerifyText, value);
|
set => SetProperty(ref _lockedVerifyText, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckPendingAuthRequests { get; set; }
|
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
? AppResources.PasswordIsVisibleTapToHide
|
|
||||||
: AppResources.PasswordIsNotVisibleTapToShow;
|
|
||||||
|
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
public event Action<int?> FocusSecretEntry
|
public event Action<int?> FocusSecretEntry
|
||||||
{
|
{
|
||||||
@@ -172,33 +162,18 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
if (pendingRequest != null && CheckPendingAuthRequests)
|
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||||
|
_isPinProtectedWithKey;
|
||||||
|
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
||||||
|
|
||||||
|
// Users with key connector and without biometric or pin has no MP to unlock with
|
||||||
|
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
if (_usingKeyConnector && !(BiometricLock || PinLock))
|
||||||
{
|
{
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
await _vaultTimeoutService.LogOutAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
|
||||||
|
|
||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
|
||||||
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
|
||||||
_pinStatus == PinLockType.Persistent;
|
|
||||||
|
|
||||||
BiometricEnabled = await IsBiometricsEnabledAsync();
|
|
||||||
|
|
||||||
// Users without MP and without biometric or pin has no MP to unlock with
|
|
||||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
|
||||||
if (await _stateService.IsAuthenticatedAsync()
|
|
||||||
&& !_hasMasterPassword
|
|
||||||
&& !BiometricEnabled
|
|
||||||
&& !PinEnabled)
|
|
||||||
{
|
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_email = await _stateService.GetEmailAsync();
|
_email = await _stateService.GetEmailAsync();
|
||||||
if (string.IsNullOrWhiteSpace(_email))
|
if (string.IsNullOrWhiteSpace(_email))
|
||||||
{
|
{
|
||||||
@@ -213,20 +188,26 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
var webVaultHostname = CoreHelpers.GetHostname(webVault);
|
||||||
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
|
||||||
if (PinEnabled)
|
if (PinLock)
|
||||||
{
|
{
|
||||||
PageTitle = AppResources.VerifyPIN;
|
PageTitle = AppResources.VerifyPIN;
|
||||||
LockedVerifyText = AppResources.VaultLockedPIN;
|
LockedVerifyText = AppResources.VaultLockedPIN;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
|
if (_usingKeyConnector)
|
||||||
LockedVerifyText = _hasMasterPassword
|
{
|
||||||
? AppResources.VaultLockedMasterPassword
|
PageTitle = AppResources.UnlockVault;
|
||||||
: AppResources.VaultLockedIdentity;
|
LockedVerifyText = AppResources.VaultLockedIdentity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
|
LockedVerifyText = AppResources.VaultLockedMasterPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BiometricEnabled)
|
if (BiometricLock)
|
||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
if (!_biometricIntegrityValid)
|
if (!_biometricIntegrityValid)
|
||||||
@@ -242,119 +223,20 @@ namespace Bit.App.Pages
|
|||||||
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
||||||
AppResources.UseFingerprintToUnlock;
|
AppResources.UseFingerprintToUnlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
ShowPassword = false;
|
if (PinLock && string.IsNullOrWhiteSpace(Pin))
|
||||||
try
|
|
||||||
{
|
|
||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
|
||||||
if (PinEnabled)
|
|
||||||
{
|
|
||||||
await UnlockWithPinAsync(kdfConfig);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await UnlockWithMasterPasswordAsync(kdfConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
|
|
||||||
{
|
|
||||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
|
||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
|
||||||
var failed = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EncString userKeyPin;
|
|
||||||
EncString oldPinProtected;
|
|
||||||
switch (_pinStatus)
|
|
||||||
{
|
|
||||||
case PinLockType.Persistent:
|
|
||||||
{
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
|
||||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
|
||||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PinLockType.Transient:
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
|
||||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
|
||||||
break;
|
|
||||||
case PinLockType.Disabled:
|
|
||||||
default:
|
|
||||||
throw new Exception("Pin is disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserKey userKey;
|
|
||||||
if (oldPinProtected != null)
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
|
||||||
_pinStatus == PinLockType.Transient,
|
|
||||||
Pin,
|
|
||||||
_email,
|
|
||||||
kdfConfig,
|
|
||||||
oldPinProtected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
|
||||||
Pin,
|
|
||||||
_email,
|
|
||||||
kdfConfig,
|
|
||||||
userKeyPin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
|
||||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
|
||||||
failed = decryptedPin != Pin;
|
|
||||||
if (!failed)
|
|
||||||
{
|
|
||||||
Pin = string.Empty;
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
|
||||||
if (invalidUnlockAttempts >= 5)
|
|
||||||
{
|
|
||||||
_messagingService.Send("logout");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
|
||||||
AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UnlockWithMasterPasswordAsync(KdfConfig kdfConfig)
|
|
||||||
{
|
|
||||||
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
|
||||||
@@ -362,78 +244,126 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
ShowPassword = false;
|
||||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
{
|
|
||||||
throw new LegacyUserException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
if (PinLock)
|
||||||
var passwordValid = false;
|
|
||||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
|
||||||
|
|
||||||
if (storedKeyHash != null)
|
|
||||||
{
|
{
|
||||||
// Offline unlock possible
|
var failed = true;
|
||||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Online unlock required
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
|
||||||
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
|
||||||
HashPurpose.ServerAuthorization);
|
|
||||||
var request = new PasswordVerificationRequest();
|
|
||||||
request.MasterPasswordHash = keyHash;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
if (_isPinProtected)
|
||||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
{
|
||||||
passwordValid = true;
|
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||||
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
kdfConfig,
|
||||||
HashPurpose.LocalAuthorization);
|
await _stateService.GetPinProtectedKeyAsync());
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
|
failed = decPin != Pin;
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
|
Pin = string.Empty;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
|
||||||
|
failed = false;
|
||||||
|
Pin = string.Empty;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
failed = true;
|
||||||
}
|
}
|
||||||
await _deviceActionService.HideLoadingAsync();
|
if (failed)
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordValid)
|
|
||||||
{
|
|
||||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
|
||||||
{
|
{
|
||||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
await _stateService.SetForcePasswordResetReasonAsync(
|
if (invalidUnlockAttempts >= 5)
|
||||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
{
|
||||||
}
|
_messagingService.Send("logout");
|
||||||
|
return;
|
||||||
MasterPassword = string.Empty;
|
}
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
|
||||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
|
|
||||||
// Re-enable biometrics
|
|
||||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
|
||||||
{
|
|
||||||
await _biometricService.SetupBiometricAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
||||||
if (invalidUnlockAttempts >= 5)
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
|
var passwordValid = false;
|
||||||
|
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||||
|
|
||||||
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
_messagingService.Send("logout");
|
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
||||||
return;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||||
|
var request = new PasswordVerificationRequest();
|
||||||
|
request.MasterPasswordHash = keyHash;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||||
|
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||||
|
passwordValid = true;
|
||||||
|
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||||
|
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
||||||
|
}
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
|
if (passwordValid)
|
||||||
|
{
|
||||||
|
if (_isPinProtected)
|
||||||
|
{
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
|
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
|
||||||
|
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||||
|
{
|
||||||
|
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||||
|
await _stateService.SetForcePasswordResetReasonAsync(
|
||||||
|
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterPassword = string.Empty;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(key);
|
||||||
|
|
||||||
|
// Re-enable biometrics
|
||||||
|
if (BiometricLock & !BiometricIntegrityValid)
|
||||||
|
{
|
||||||
|
await _biometricService.SetupBiometricAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
_messagingService.Send("logout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
}
|
}
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
|
||||||
AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,82 +425,44 @@ namespace Bit.App.Pages
|
|||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var secret = PinEnabled ? Pin : MasterPassword;
|
var secret = PinLock ? Pin : MasterPassword;
|
||||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length,
|
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||||
nameof(FocusSecretEntry));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
try
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
|
BiometricButtonVisible = BiometricIntegrityValid;
|
||||||
|
if (!BiometricLock || !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
return;
|
||||||
BiometricButtonVisible = BiometricIntegrityValid;
|
|
||||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
|
||||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
|
||||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
|
|
||||||
!PinEnabled && !HasMasterPassword);
|
|
||||||
|
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (LegacyUserException)
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
|
PinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
|
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||||
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
|
if (success)
|
||||||
{
|
{
|
||||||
await HandleLegacyUserAsync();
|
await DoContinueAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetUserKeyAndContinueAsync(UserKey key)
|
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetUserKeyAsync(key);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
}
|
}
|
||||||
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
|
|
||||||
await DoContinueAsync();
|
await DoContinueAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoContinueAsync()
|
private async Task DoContinueAsync()
|
||||||
{
|
{
|
||||||
_syncService.FullSyncAsync(false).FireAndForget();
|
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
|
||||||
_messagingService.Send("unlocked");
|
_messagingService.Send("unlocked");
|
||||||
UnlockedAction?.Invoke();
|
UnlockedAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> IsBiometricsEnabledAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
|
||||||
await _biometricService.CanUseBiometricsUnlockAsync();
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleLegacyUserAsync()
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault.
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
|
|
||||||
AppResources.AnErrorHasOccurred,
|
|
||||||
AppResources.Ok);
|
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<pages:BaseContentPage
|
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Pages.LoginApproveDevicePage"
|
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
x:DataType="pages:LoginApproveDeviceViewModel"
|
|
||||||
x:Name="_page"
|
|
||||||
Title="{Binding PageTitle}">
|
|
||||||
|
|
||||||
<ContentPage.BindingContext>
|
|
||||||
<pages:LoginApproveDeviceViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
|
|
||||||
<StackLayout Padding="10, 10">
|
|
||||||
<StackLayout Padding="5, 10" Orientation="Horizontal">
|
|
||||||
<StackLayout HorizontalOptions="FillAndExpand">
|
|
||||||
<Label
|
|
||||||
StyleClass="text-md"
|
|
||||||
Text="{u:I18n RememberThisDevice}"/>
|
|
||||||
<Label
|
|
||||||
StyleClass="box-sub-label"
|
|
||||||
Text="{u:I18n TurnOffUsingPublicDevice}"/>
|
|
||||||
</StackLayout>
|
|
||||||
<Switch
|
|
||||||
Scale="0.8"
|
|
||||||
IsToggled="{Binding RememberThisDevice}"
|
|
||||||
VerticalOptions="Center"/>
|
|
||||||
</StackLayout>
|
|
||||||
<StackLayout Margin="0, 20, 0, 0">
|
|
||||||
<Button
|
|
||||||
x:Name="_continue"
|
|
||||||
Text="{u:I18n Continue}"
|
|
||||||
StyleClass="btn-primary"
|
|
||||||
Command="{Binding ContinueCommand}"
|
|
||||||
IsVisible="{Binding IsNewUser}"/>
|
|
||||||
<Button
|
|
||||||
x:Name="_approveWithMyOtherDevice"
|
|
||||||
Text="{u:I18n ApproveWithMyOtherDevice}"
|
|
||||||
StyleClass="btn-primary"
|
|
||||||
Command="{Binding ApproveWithMyOtherDeviceCommand}"
|
|
||||||
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
|
|
||||||
<Button
|
|
||||||
x:Name="_requestAdminApproval"
|
|
||||||
Text="{u:I18n RequestAdminApproval}"
|
|
||||||
StyleClass="box-button-row"
|
|
||||||
Command="{Binding RequestAdminApprovalCommand}"
|
|
||||||
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
|
|
||||||
<Button
|
|
||||||
x:Name="_approveWithMasterPassword"
|
|
||||||
Text="{u:I18n ApproveWithMasterPassword}"
|
|
||||||
StyleClass="box-button-row"
|
|
||||||
Command="{Binding ApproveWithMasterPasswordCommand}"
|
|
||||||
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
|
|
||||||
<Label
|
|
||||||
Text="{Binding LoggingInAsText}"
|
|
||||||
StyleClass="text-sm"
|
|
||||||
Margin="0,40,0,0"
|
|
||||||
AutomationId="LoggingInAsLabel"
|
|
||||||
/>
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n NotYou}"
|
|
||||||
StyleClass="text-md"
|
|
||||||
HorizontalOptions="Start"
|
|
||||||
TextColor="{DynamicResource HyperlinkColor}"
|
|
||||||
AutomationId="NotYouLabel">
|
|
||||||
<Label.GestureRecognizers>
|
|
||||||
<TapGestureRecognizer Command="{Binding LogoutCommand}" />
|
|
||||||
</Label.GestureRecognizers>
|
|
||||||
</Label>
|
|
||||||
</StackLayout>
|
|
||||||
</StackLayout>
|
|
||||||
</pages:BaseContentPage>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Models;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public partial class LoginApproveDevicePage : BaseContentPage
|
|
||||||
{
|
|
||||||
|
|
||||||
private readonly LoginApproveDeviceViewModel _vm;
|
|
||||||
private readonly AppOptions _appOptions;
|
|
||||||
|
|
||||||
public LoginApproveDevicePage(AppOptions appOptions = null)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
_vm = BindingContext as LoginApproveDeviceViewModel;
|
|
||||||
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPasswordAsync().FireAndForget();
|
|
||||||
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
|
||||||
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
|
||||||
_vm.ContinueToVaultAction = () => ContinueToVaultAsync().FireAndForget();
|
|
||||||
_vm.Page = this;
|
|
||||||
_appOptions = appOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAppearing()
|
|
||||||
{
|
|
||||||
_vm.InitAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ContinueToVaultAsync()
|
|
||||||
{
|
|
||||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartLogInWithMasterPasswordAsync()
|
|
||||||
{
|
|
||||||
var page = new LockPage(_appOptions, checkPendingAuthRequests: false);
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartLoginWithDeviceAsync()
|
|
||||||
{
|
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions, true);
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RequestAdminApprovalAsync()
|
|
||||||
{
|
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions, true);
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.App.Utilities.AccountManagement;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
using Bit.Core.Models.Request;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Essentials;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public class LoginApproveDeviceViewModel : BaseViewModel
|
|
||||||
{
|
|
||||||
private bool _rememberThisDevice;
|
|
||||||
private bool _approveWithMyOtherDeviceEnabled;
|
|
||||||
private bool _requestAdminApprovalEnabled;
|
|
||||||
private bool _approveWithMasterPasswordEnabled;
|
|
||||||
private string _email;
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
private readonly IApiService _apiService;
|
|
||||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
|
||||||
private readonly IAuthService _authService;
|
|
||||||
private readonly ISyncService _syncService;
|
|
||||||
private readonly IMessagingService _messagingService;
|
|
||||||
|
|
||||||
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
|
||||||
public ICommand RequestAdminApprovalCommand { get; }
|
|
||||||
public ICommand ApproveWithMasterPasswordCommand { get; }
|
|
||||||
public ICommand ContinueCommand { get; }
|
|
||||||
public ICommand LogoutCommand { get; }
|
|
||||||
|
|
||||||
public Action LogInWithMasterPasswordAction { get; set; }
|
|
||||||
public Action LogInWithDeviceAction { get; set; }
|
|
||||||
public Action RequestAdminApprovalAction { get; set; }
|
|
||||||
public Action ContinueToVaultAction { get; set; }
|
|
||||||
|
|
||||||
public LoginApproveDeviceViewModel()
|
|
||||||
{
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>();
|
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
|
||||||
_authService = ServiceContainer.Resolve<IAuthService>();
|
|
||||||
_syncService = ServiceContainer.Resolve<ISyncService>();
|
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>();
|
|
||||||
|
|
||||||
PageTitle = AppResources.LogInInitiated;
|
|
||||||
RememberThisDevice = true;
|
|
||||||
|
|
||||||
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync,
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
LogoutCommand = new Command(() => _messagingService.Send(AccountsManagerMessageCommands.LOGOUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
|
||||||
|
|
||||||
public bool RememberThisDevice
|
|
||||||
{
|
|
||||||
get => _rememberThisDevice;
|
|
||||||
set => SetProperty(ref _rememberThisDevice, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ApproveWithMyOtherDeviceEnabled
|
|
||||||
{
|
|
||||||
get => _approveWithMyOtherDeviceEnabled;
|
|
||||||
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RequestAdminApprovalEnabled
|
|
||||||
{
|
|
||||||
get => _requestAdminApprovalEnabled;
|
|
||||||
set => SetProperty(ref _requestAdminApprovalEnabled, value,
|
|
||||||
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ApproveWithMasterPasswordEnabled
|
|
||||||
{
|
|
||||||
get => _approveWithMasterPasswordEnabled;
|
|
||||||
set => SetProperty(ref _approveWithMasterPasswordEnabled, value,
|
|
||||||
additionalPropertyNames: new[] { nameof(IsNewUser) });
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsNewUser => !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled;
|
|
||||||
|
|
||||||
public string Email
|
|
||||||
{
|
|
||||||
get => _email;
|
|
||||||
set => SetProperty(ref _email, value, additionalPropertyNames:
|
|
||||||
new string[] {
|
|
||||||
nameof(LoggingInAsText)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Email = await _stateService.GetActiveUserEmailAsync();
|
|
||||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
|
||||||
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
|
|
||||||
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
|
|
||||||
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
HandleException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateNewSsoUserAsync()
|
|
||||||
{
|
|
||||||
await _authService.CreateNewSsoUserAsync(await _stateService.GetRememberedOrgIdentifierAsync());
|
|
||||||
if (RememberThisDevice)
|
|
||||||
{
|
|
||||||
await _deviceTrustCryptoService.TrustDeviceAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
await Device.InvokeOnMainThreadAsync(ContinueToVaultAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetDeviceTrustAndInvokeAsync(Action action)
|
|
||||||
{
|
|
||||||
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
|
|
||||||
await Device.InvokeOnMainThreadAsync(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -24,8 +24,7 @@
|
|||||||
Priority="-1"
|
Priority="-1"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}"
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
AutomationId="AccountIconButton" />
|
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
<ContentPage.Resources>
|
<ContentPage.Resources>
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
x:Name="_moreItem" x:Key="moreItem"
|
x:Name="_moreItem" x:Key="moreItem"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
AutomationId="OptionsButton" />
|
AutomationId="OptionsButton"/>
|
||||||
<ToolbarItem Text="{u:I18n GetPasswordHint}"
|
<ToolbarItem Text="{u:I18n GetPasswordHint}"
|
||||||
x:Key="getPasswordHint"
|
x:Key="getPasswordHint"
|
||||||
x:Name="_getPasswordHint"
|
x:Name="_getPasswordHint"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Bit.App.Models;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -136,7 +135,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task StartLoginWithDeviceAsync()
|
private async Task StartLoginWithDeviceAsync()
|
||||||
{
|
{
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,14 +248,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
if (response.RequiresEncryptionKeyMigration)
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault.
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong, AppResources.AnErrorHasOccurred,
|
|
||||||
AppResources.Ok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.TwoFactor)
|
if (response.TwoFactor)
|
||||||
{
|
{
|
||||||
StartTwoFactorAction?.Invoke();
|
StartTwoFactorAction?.Invoke();
|
||||||
|
|||||||
@@ -32,8 +32,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding LogInAttemptByLabel}"
|
Text="{Binding LogInAttemptByLabel}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"
|
Margin="0,0,0,24"/>
|
||||||
AutomationId="LogInAttemptByLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n FingerprintPhrase}"
|
Text="{u:I18n FingerprintPhrase}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -42,8 +41,7 @@
|
|||||||
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
|
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
|
||||||
FontSize="Medium"
|
FontSize="Medium"
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"
|
TextColor="{DynamicResource FingerprintPhrase}"
|
||||||
Margin="0,0,0,27"
|
Margin="0,0,0,27"/>
|
||||||
AutomationId="FingerprintValueLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DeviceType}"
|
Text="{u:I18n DeviceType}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -51,8 +49,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding LoginRequest.DeviceType}"
|
Text="{Binding LoginRequest.DeviceType}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,21"
|
Margin="0,0,0,21"/>
|
||||||
AutomationId="DeviceTypeValueLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n IpAddress}"
|
Text="{u:I18n IpAddress}"
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
IsVisible="{Binding ShowIpAddress}"
|
||||||
@@ -62,8 +59,7 @@
|
|||||||
Text="{Binding LoginRequest.IpAddress}"
|
Text="{Binding LoginRequest.IpAddress}"
|
||||||
IsVisible="{Binding ShowIpAddress}"
|
IsVisible="{Binding ShowIpAddress}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,21"
|
Margin="0,0,0,21"/>
|
||||||
AutomationId="IpAddressValueLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Time}"
|
Text="{u:I18n Time}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -71,8 +67,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding TimeOfRequestText}"
|
Text="{Binding TimeOfRequestText}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,57"
|
Margin="0,0,0,57"/>
|
||||||
AutomationId="TimeOfRequestValueLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
@@ -80,13 +75,11 @@
|
|||||||
Text="{u:I18n ConfirmLogIn}"
|
Text="{u:I18n ConfirmLogIn}"
|
||||||
Command="{Binding AcceptRequestCommand}"
|
Command="{Binding AcceptRequestCommand}"
|
||||||
Margin="0,0,0,17"
|
Margin="0,0,0,17"
|
||||||
StyleClass="btn-primary"
|
StyleClass="btn-primary"/>
|
||||||
AutomationId="ConfirmLoginButton" />
|
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n DenyLogIn}"
|
Text="{u:I18n DenyLogIn}"
|
||||||
Command="{Binding RejectRequestCommand}"
|
Command="{Binding RejectRequestCommand}"
|
||||||
StyleClass="btn-secundary"
|
StyleClass="btn-secundary"/>
|
||||||
AutomationId="DenyLoginButton" />
|
|
||||||
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -21,17 +21,16 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Padding="7, 0, 7, 20">
|
Padding="7, 0, 7, 20">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Title}"
|
Text="{u:I18n LogInInitiated}"
|
||||||
FontSize="Title"
|
FontSize="Title"
|
||||||
FontAttributes="Bold"
|
FontAttributes="Bold"
|
||||||
Margin="0,14,0,21"
|
Margin="0,14,0,21"/>
|
||||||
AutomationId="LogInInitiatedLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding SubTitle}"
|
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,10"/>
|
Margin="0,0,0,10"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Description}"
|
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"/>
|
Margin="0,0,0,24"/>
|
||||||
<Label
|
<Label
|
||||||
@@ -40,39 +39,38 @@
|
|||||||
FontAttributes="Bold"/>
|
FontAttributes="Bold"/>
|
||||||
<controls:MonoLabel
|
<controls:MonoLabel
|
||||||
FormattedText="{Binding FingerprintPhrase}"
|
FormattedText="{Binding FingerprintPhrase}"
|
||||||
FontSize="Small"
|
FontSize="Medium"
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"
|
TextColor="{DynamicResource FingerprintPhrase}"/>
|
||||||
AutomationId="FingerprintPhraseValue" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ResendNotification}"
|
Text="{u:I18n ResendNotification}"
|
||||||
IsVisible="{Binding ResendNotificationVisible}"
|
StyleClass="text-md"
|
||||||
StyleClass="text-sm"
|
|
||||||
FontAttributes="Bold"
|
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
Margin="0,24,0,0"
|
Margin="0,40,0,0"
|
||||||
TextColor="{DynamicResource HyperlinkColor}"
|
TextColor="{DynamicResource HyperlinkColor}">
|
||||||
AutomationId="ResendNotificationButton">
|
|
||||||
<Label.GestureRecognizers>
|
<Label.GestureRecognizers>
|
||||||
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
|
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
|
||||||
</Label.GestureRecognizers>
|
</Label.GestureRecognizers>
|
||||||
</Label>
|
</Label>
|
||||||
<BoxView
|
<StackLayout
|
||||||
HeightRequest="1"
|
Orientation="Horizontal"
|
||||||
Margin="0,24,0,24"
|
Margin="0,30,0,0">
|
||||||
Color="{DynamicResource DisabledIconColor}" />
|
<Label
|
||||||
<Label
|
Text="{u:I18n NeedAnotherOption}"
|
||||||
Text="{Binding OtherOptions}"
|
FontSize="Small"
|
||||||
FontSize="Small"/>
|
VerticalTextAlignment="End"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ViewAllLoginOptions}"
|
Text="{u:I18n ViewAllLoginOptions}"
|
||||||
StyleClass="text-sm"
|
StyleClass="text-md"
|
||||||
FontAttributes="Bold"
|
VerticalTextAlignment="End"
|
||||||
TextColor="{DynamicResource HyperlinkColor}"
|
VerticalOptions="CenterAndExpand"
|
||||||
AutomationId="ViewAllLoginOptionsButton">
|
Margin="5, 0"
|
||||||
<Label.GestureRecognizers>
|
TextColor="{DynamicResource HyperlinkColor}">
|
||||||
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
<Label.GestureRecognizers>
|
||||||
</Label.GestureRecognizers>
|
<TapGestureRecognizer Command="{Binding CloseCommand}" />
|
||||||
</Label>
|
</Label.GestureRecognizers>
|
||||||
|
</Label>
|
||||||
|
</StackLayout>
|
||||||
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -13,15 +12,13 @@ namespace Bit.App.Pages
|
|||||||
private LoginPasswordlessRequestViewModel _vm;
|
private LoginPasswordlessRequestViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null, bool authingWithSso = false)
|
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.Email = email;
|
_vm.Email = email;
|
||||||
_vm.AuthRequestType = authRequestType;
|
|
||||||
_vm.AuthingWithSso = authingWithSso;
|
|
||||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -12,9 +11,7 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Response;
|
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -35,9 +32,6 @@ namespace Bit.App.Pages
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IEnvironmentService _environmentService;
|
private IEnvironmentService _environmentService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
|
||||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
|
||||||
private readonly ICryptoService _cryptoService;
|
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
@@ -50,7 +44,6 @@ namespace Bit.App.Pages
|
|||||||
private string _email;
|
private string _email;
|
||||||
private string _requestId;
|
private string _requestId;
|
||||||
private string _requestAccessCode;
|
private string _requestAccessCode;
|
||||||
private AuthRequestType _authRequestType;
|
|
||||||
// Item1 publicKey, Item2 privateKey
|
// Item1 publicKey, Item2 privateKey
|
||||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||||
|
|
||||||
@@ -64,9 +57,8 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
|
||||||
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
|
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
|
||||||
|
|
||||||
CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync,
|
CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync,
|
||||||
onException: ex => HandleException(ex),
|
onException: ex => HandleException(ex),
|
||||||
@@ -81,91 +73,10 @@ namespace Bit.App.Pages
|
|||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public bool AuthingWithSso { get; set; }
|
|
||||||
|
|
||||||
public ICommand CreatePasswordlessLoginCommand { get; }
|
public ICommand CreatePasswordlessLoginCommand { get; }
|
||||||
public ICommand CloseCommand { get; }
|
public ICommand CloseCommand { get; }
|
||||||
|
|
||||||
public string HeaderTitle
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (_authRequestType)
|
|
||||||
{
|
|
||||||
case AuthRequestType.AuthenticateAndUnlock:
|
|
||||||
return AppResources.LogInWithDevice;
|
|
||||||
case AuthRequestType.AdminApproval:
|
|
||||||
return AppResources.LogInInitiated;
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Title
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (_authRequestType)
|
|
||||||
{
|
|
||||||
case AuthRequestType.AuthenticateAndUnlock:
|
|
||||||
return AppResources.LogInInitiated;
|
|
||||||
case AuthRequestType.AdminApproval:
|
|
||||||
return AppResources.AdminApprovalRequested;
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SubTitle
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (_authRequestType)
|
|
||||||
{
|
|
||||||
case AuthRequestType.AuthenticateAndUnlock:
|
|
||||||
return AppResources.ANotificationHasBeenSentToYourDevice;
|
|
||||||
case AuthRequestType.AdminApproval:
|
|
||||||
return AppResources.YourRequestHasBeenSentToYourAdmin;
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Description
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (_authRequestType)
|
|
||||||
{
|
|
||||||
case AuthRequestType.AuthenticateAndUnlock:
|
|
||||||
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
|
|
||||||
case AuthRequestType.AdminApproval:
|
|
||||||
return AppResources.YouWillBeNotifiedOnceApproved;
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string OtherOptions
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
switch (_authRequestType)
|
|
||||||
{
|
|
||||||
case AuthRequestType.AuthenticateAndUnlock:
|
|
||||||
return AppResources.LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption;
|
|
||||||
case AuthRequestType.AdminApproval:
|
|
||||||
return AppResources.TroubleLoggingIn;
|
|
||||||
default:
|
|
||||||
return string.Empty;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FingerprintPhrase
|
public string FingerprintPhrase
|
||||||
{
|
{
|
||||||
get => _fingerprintPhrase;
|
get => _fingerprintPhrase;
|
||||||
@@ -178,25 +89,6 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _email, value);
|
set => SetProperty(ref _email, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthRequestType AuthRequestType
|
|
||||||
{
|
|
||||||
get => _authRequestType;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
|
|
||||||
{
|
|
||||||
nameof(Title),
|
|
||||||
nameof(SubTitle),
|
|
||||||
nameof(Description),
|
|
||||||
nameof(OtherOptions),
|
|
||||||
nameof(ResendNotificationVisible)
|
|
||||||
});
|
|
||||||
PageTitle = HeaderTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
|
|
||||||
|
|
||||||
public void StartCheckLoginRequestStatus()
|
public void StartCheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -227,39 +119,25 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task CheckLoginRequestStatus()
|
private async Task CheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_requestId))
|
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PasswordlessLoginResponse response = null;
|
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
|
||||||
if (AuthingWithSso)
|
|
||||||
{
|
|
||||||
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response?.RequestApproved != true)
|
if (response.RequestApproved == null || !response.RequestApproved.Value)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StopCheckLoginRequestStatus();
|
StopCheckLoginRequestStatus();
|
||||||
|
|
||||||
var authResult = await _authService.LogInPasswordlessAsync(AuthingWithSso, Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
|
||||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
|
||||||
{
|
|
||||||
await HandleLoginCompleteAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -275,13 +153,10 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await HandleLoginCompleteAsync();
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
LogInSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ApiException ex) when (ex.Error?.StatusCode == System.Net.HttpStatusCode.BadRequest)
|
|
||||||
{
|
|
||||||
HandleException(ex);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
StartCheckLoginRequestStatus();
|
StartCheckLoginRequestStatus();
|
||||||
@@ -289,65 +164,30 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleLoginCompleteAsync()
|
|
||||||
{
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
LogInSuccessAction?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreatePasswordlessLoginAsync()
|
private async Task CreatePasswordlessLoginAsync()
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||||
|
|
||||||
PasswordlessLoginResponse response = null;
|
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
||||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
if (response != null)
|
||||||
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
|
|
||||||
{
|
{
|
||||||
response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
FingerprintPhrase = response.FingerprintPhrase;
|
||||||
if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
|
_requestId = response.Id;
|
||||||
{
|
_requestAccessCode = response.RequestAccessCode;
|
||||||
// handle pending auth request not valid remove it from state
|
_requestKeyPair = response.RequestKeyPair;
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
pendingRequest = null;
|
|
||||||
response = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Derive pubKey from privKey in state to avoid MITM attacks
|
|
||||||
// Also generate FingerprintPhrase locally for the same reason
|
|
||||||
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
|
|
||||||
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
|
|
||||||
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response == null)
|
|
||||||
{
|
|
||||||
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
|
||||||
}
|
|
||||||
|
|
||||||
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
|
private void HandleException(Exception ex)
|
||||||
{
|
{
|
||||||
if (response == null)
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(response));
|
await _deviceActionService.HideLoadingAsync();
|
||||||
}
|
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
|
||||||
|
}).FireAndForget();
|
||||||
if (createPendingAdminRequest)
|
_logger.Exception(ex);
|
||||||
{
|
|
||||||
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
FingerprintPhrase = response.FingerprintPhrase;
|
|
||||||
_requestId = response.Id;
|
|
||||||
_requestAccessCode = response.RequestAccessCode;
|
|
||||||
_requestKeyPair = response.RequestKeyPair;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ namespace Bit.App.Pages
|
|||||||
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
_vm.StartDeviceApprovalOptionsAction =
|
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
|
||||||
_vm.CloseAction = async () =>
|
_vm.CloseAction = async () =>
|
||||||
{
|
{
|
||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
@@ -108,17 +106,10 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartDeviceApprovalOptionsAsync()
|
|
||||||
{
|
|
||||||
var page = new LoginApproveDevicePage();
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SsoAuthSuccessAsync()
|
private async Task SsoAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
RestoreAppOptionsFromCopy();
|
RestoreAppOptionsFromCopy();
|
||||||
await AppHelpers.ClearPreviousPage();
|
await AppHelpers.ClearPreviousPage();
|
||||||
|
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
@@ -30,11 +29,8 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
|
||||||
private readonly ICryptoService _cryptoService;
|
|
||||||
|
|
||||||
private string _orgIdentifier;
|
private string _orgIdentifier;
|
||||||
private bool _useEphemeralWebBrowserSession;
|
|
||||||
|
|
||||||
public LoginSsoPageViewModel()
|
public LoginSsoPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -49,8 +45,7 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
|
||||||
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
|
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
|
||||||
@@ -66,7 +61,6 @@ namespace Bit.App.Pages
|
|||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action SsoAuthSuccessAction { get; set; }
|
public Action SsoAuthSuccessAction { get; set; }
|
||||||
public Action StartDeviceApprovalOptionsAction { get; set; }
|
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
|
|
||||||
@@ -115,7 +109,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
|
||||||
|
|
||||||
var response = await _apiService.PreValidateSsoAsync(OrgIdentifier);
|
var response = await _apiService.PreValidateSso(OrgIdentifier);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(response?.Token))
|
if (string.IsNullOrWhiteSpace(response?.Token))
|
||||||
{
|
{
|
||||||
@@ -146,12 +140,10 @@ namespace Bit.App.Pages
|
|||||||
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
"ssoToken=" + Uri.EscapeDataString(ssoToken);
|
||||||
|
|
||||||
WebAuthenticatorResult authResult = null;
|
WebAuthenticatorResult authResult = null;
|
||||||
authResult = await WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions()
|
|
||||||
{
|
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
|
||||||
CallbackUrl = new Uri(REDIRECT_URI),
|
new Uri(REDIRECT_URI));
|
||||||
Url = new Uri(url),
|
|
||||||
PrefersEphemeralWebBrowserSession = _useEphemeralWebBrowserSession,
|
|
||||||
});
|
|
||||||
|
|
||||||
var code = GetResultCode(authResult, state);
|
var code = GetResultCode(authResult, state);
|
||||||
if (!string.IsNullOrEmpty(code))
|
if (!string.IsNullOrEmpty(code))
|
||||||
@@ -176,8 +168,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
// user canceled
|
// user canceled
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
// Workaroung for cached expired sso token PM-3551
|
|
||||||
_useEphemeralWebBrowserSession = true;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -207,93 +197,28 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
|
||||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
if (response.TwoFactor)
|
if (response.TwoFactor)
|
||||||
{
|
{
|
||||||
StartTwoFactorAction?.Invoke();
|
StartTwoFactorAction?.Invoke();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if (response.ResetMasterPassword)
|
||||||
// Trusted device option is sent regardless if this is a trusted device or not
|
|
||||||
// If it is trusted, it will have the necessary keys
|
|
||||||
if (decryptOptions?.TrustedDeviceOption != null)
|
|
||||||
{
|
{
|
||||||
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
|
||||||
{
|
|
||||||
// If we have a device key but no keys on server, we need to remove the device key
|
|
||||||
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
|
|
||||||
{
|
|
||||||
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
|
||||||
if (!decryptOptions.HasMasterPassword &&
|
|
||||||
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
|
||||||
{
|
|
||||||
StartSetPasswordAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
|
|
||||||
if (response.ForcePasswordReset)
|
|
||||||
{
|
|
||||||
UpdateTempPasswordAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Device is trusted and has keys, so we can decrypt
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
SsoAuthSuccessAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for pending Admin Auth requests before navigating to device approval options
|
|
||||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
|
||||||
if (pendingRequest != null)
|
|
||||||
{
|
|
||||||
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
|
||||||
if (authRequest?.RequestApproved == true)
|
|
||||||
{
|
|
||||||
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
|
|
||||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
|
||||||
{
|
|
||||||
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
|
|
||||||
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
SsoAuthSuccessAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the standard, non TDE case, a user must set password if they don't
|
|
||||||
// have one and they aren't using key connector.
|
|
||||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
|
||||||
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword == true))
|
|
||||||
{
|
|
||||||
// TODO: We need to look into how to handle this when Org removes TDE
|
|
||||||
// Will we have the User Key by now to set a new password?
|
|
||||||
StartSetPasswordAction?.Invoke();
|
StartSetPasswordAction?.Invoke();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if (response.ForcePasswordReset)
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
{
|
||||||
SsoAuthSuccessAction?.Invoke();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
|
||||||
|
SsoAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,
|
||||||
|
|||||||
@@ -177,28 +177,25 @@ namespace Bit.App.Pages
|
|||||||
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
|
||||||
Email = Email.Trim().ToLower();
|
Email = Email.Trim().ToLower();
|
||||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
|
var encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
newMasterKey,
|
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
await _cryptoService.MakeUserKeyAsync()
|
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||||
);
|
|
||||||
var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
|
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
|
||||||
var request = new RegisterRequest
|
var request = new RegisterRequest
|
||||||
{
|
{
|
||||||
Email = Email,
|
Email = Email,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
MasterPasswordHash = hashedPassword,
|
MasterPasswordHash = hashedPassword,
|
||||||
MasterPasswordHint = Hint,
|
MasterPasswordHint = Hint,
|
||||||
Key = newProtectedUserKey.EncryptedString,
|
Key = encKey.Item2.EncryptedString,
|
||||||
Kdf = kdfConfig.Type,
|
Kdf = kdfConfig.Type,
|
||||||
KdfIterations = kdfConfig.Iterations,
|
KdfIterations = kdfConfig.Iterations,
|
||||||
KdfMemory = kdfConfig.Memory,
|
KdfMemory = kdfConfig.Memory,
|
||||||
KdfParallelism = kdfConfig.Parallelism,
|
KdfParallelism = kdfConfig.Parallelism,
|
||||||
Keys = new KeysRequest
|
Keys = new KeysRequest
|
||||||
{
|
{
|
||||||
PublicKey = newPublicKey,
|
PublicKey = keys.Item1,
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||||
},
|
},
|
||||||
CaptchaResponse = _captchaToken,
|
CaptchaResponse = _captchaToken,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
{
|
{
|
||||||
Organization = await _keyConnectorService.GetManagingOrganizationAsync();
|
Organization = await _keyConnectorService.GetManagingOrganization();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MigrateAccount()
|
public async Task MigrateAccount()
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
|
||||||
await _keyConnectorService.MigrateUserAsync();
|
await _keyConnectorService.MigrateUser();
|
||||||
await _syncService.FullSyncAsync(true);
|
await _syncService.FullSyncAsync(true);
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
|
||||||
await _apiService.PostLeaveOrganizationAsync(Organization.Id);
|
await _apiService.PostLeaveOrganization(Organization.Id);
|
||||||
await _syncService.FullSyncAsync(true);
|
await _syncService.FullSyncAsync(true);
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|||||||
@@ -51,8 +51,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
|
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Start" />
|
||||||
AutomationId="ResetPasswordAutoEnrollInviteWarningLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||||
@@ -74,8 +73,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding PolicySummary}"
|
Text="{Binding PolicySummary}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Start" />
|
||||||
AutomationId="PolicyInEffectLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid StyleClass="box-row">
|
<Grid StyleClass="box-row">
|
||||||
@@ -100,8 +98,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="MasterPasswordField" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -111,8 +108,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="ToggleMasterPasswordVisibilityButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordDescription}"
|
Text="{u:I18n MasterPasswordDescription}"
|
||||||
@@ -141,8 +137,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="RetypePasswordField" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -152,8 +147,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="ToggleRetypePasswordVisibilityButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
@@ -164,8 +158,7 @@
|
|||||||
Text="{Binding Hint}"
|
Text="{Binding Hint}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}" />
|
||||||
AutomationId="MasterPasswordHintLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordHintDescription}"
|
Text="{u:I18n MasterPasswordHintDescription}"
|
||||||
|
|||||||
@@ -165,18 +165,26 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||||
var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
|
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||||
|
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
|
Tuple<SymmetricCryptoKey, EncString> encKey;
|
||||||
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
|
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
|
if (existingEncKey == null)
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||||
var request = new SetPasswordRequest
|
var request = new SetPasswordRequest
|
||||||
{
|
{
|
||||||
MasterPasswordHash = masterPasswordHash,
|
MasterPasswordHash = masterPasswordHash,
|
||||||
Key = newProtectedUserKey.EncryptedString,
|
Key = encKey.Item2.EncryptedString,
|
||||||
MasterPasswordHint = Hint,
|
MasterPasswordHint = Hint,
|
||||||
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
|
||||||
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
|
||||||
@@ -185,8 +193,8 @@ namespace Bit.App.Pages
|
|||||||
OrgIdentifier = OrgIdentifier,
|
OrgIdentifier = OrgIdentifier,
|
||||||
Keys = new KeysRequest
|
Keys = new KeysRequest
|
||||||
{
|
{
|
||||||
PublicKey = newPublicKey,
|
PublicKey = keys.Item1,
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -196,20 +204,19 @@ namespace Bit.App.Pages
|
|||||||
// Set Password and relevant information
|
// Set Password and relevant information
|
||||||
await _apiService.SetPasswordAsync(request);
|
await _apiService.SetPasswordAsync(request);
|
||||||
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
await _stateService.SetKdfConfigurationAsync(kdfConfig);
|
||||||
await _cryptoService.SetUserKeyAsync(newUserKey);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash);
|
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
|
||||||
await _cryptoService.SetUserPrivateKeyAsync(newProtectedPrivateKey.EncryptedString);
|
|
||||||
|
|
||||||
if (ResetPasswordAutoEnroll)
|
if (ResetPasswordAutoEnroll)
|
||||||
{
|
{
|
||||||
// Grab Organization Keys
|
// Grab Organization Keys
|
||||||
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
|
||||||
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
|
||||||
// Grab User Key and encrypt with Org Public Key
|
// Grab user's Encryption Key and encrypt with Org Public Key
|
||||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Bit.App.Controls;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
@@ -18,29 +17,26 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private TwoFactorPageViewModel _vm;
|
private TwoFactorPageViewModel _vm;
|
||||||
private bool _inited;
|
private bool _inited;
|
||||||
|
private bool _authingWithSso;
|
||||||
private string _orgIdentifier;
|
private string _orgIdentifier;
|
||||||
|
|
||||||
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null, string orgIdentifier = null)
|
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null, string orgIdentifier = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
SetActivityIndicator();
|
SetActivityIndicator();
|
||||||
|
_authingWithSso = authingWithSso ?? false;
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_orgIdentifier = orgIdentifier;
|
_orgIdentifier = orgIdentifier;
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
_vm = BindingContext as TwoFactorPageViewModel;
|
_vm = BindingContext as TwoFactorPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.AuthingWithSso = authingWithSso ?? false;
|
|
||||||
_vm.StartSetPasswordAction = () =>
|
_vm.StartSetPasswordAction = () =>
|
||||||
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
|
||||||
_vm.TwoFactorAuthSuccessAction = () =>
|
_vm.TwoFactorAuthSuccessAction = () =>
|
||||||
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessToMainAsync());
|
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
|
||||||
_vm.LockAction = () =>
|
|
||||||
Device.BeginInvokeOnMainThread(TwoFactorAuthSuccessWithSSOLocked);
|
|
||||||
_vm.UpdateTempPasswordAction =
|
_vm.UpdateTempPasswordAction =
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
_vm.StartDeviceApprovalOptionsAction =
|
|
||||||
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
|
|
||||||
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
_vm.CloseAction = async () => await Navigation.PopModalAsync();
|
||||||
DuoWebView = _duoWebView;
|
DuoWebView = _duoWebView;
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
@@ -184,25 +180,21 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartDeviceApprovalOptionsAsync()
|
private async Task TwoFactorAuthSuccessAsync()
|
||||||
{
|
{
|
||||||
var page = new LoginApproveDevicePage();
|
if (_authingWithSso)
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TwoFactorAuthSuccessWithSSOLocked()
|
|
||||||
{
|
|
||||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TwoFactorAuthSuccessToMainAsync()
|
|
||||||
{
|
|
||||||
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
|
||||||
{
|
{
|
||||||
return;
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (AppHelpers.SetAlternateMainPage(_appOptions))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var previousPage = await AppHelpers.ClearPreviousPage();
|
||||||
|
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
||||||
}
|
}
|
||||||
var previousPage = await AppHelpers.ClearPreviousPage();
|
|
||||||
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Token_TextChanged(object sender, TextChangedEventArgs e)
|
private void Token_TextChanged(object sender, TextChangedEventArgs e)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -33,12 +32,12 @@ namespace Bit.App.Pages
|
|||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IAppIdService _appIdService;
|
private readonly IAppIdService _appIdService;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
|
|
||||||
private TwoFactorProviderType? _selectedProviderType;
|
private TwoFactorProviderType? _selectedProviderType;
|
||||||
private string _totpInstruction;
|
private string _totpInstruction;
|
||||||
private string _webVaultUrl = "https://vault.bitwarden.com";
|
private string _webVaultUrl = "https://vault.bitwarden.com";
|
||||||
|
private bool _authingWithSso = false;
|
||||||
private bool _enableContinue = false;
|
private bool _enableContinue = false;
|
||||||
private bool _showContinue = true;
|
private bool _showContinue = true;
|
||||||
|
|
||||||
@@ -55,9 +54,7 @@ namespace Bit.App.Pages
|
|||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
|
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
|
||||||
|
|
||||||
PageTitle = AppResources.TwoStepLogin;
|
PageTitle = AppResources.TwoStepLogin;
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
@@ -72,8 +69,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public bool Remember { get; set; }
|
public bool Remember { get; set; }
|
||||||
|
|
||||||
public bool AuthingWithSso { get; set; }
|
|
||||||
|
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
|
||||||
@@ -123,8 +118,6 @@ namespace Bit.App.Pages
|
|||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public ICommand MoreCommand { get; }
|
public ICommand MoreCommand { get; }
|
||||||
public Action TwoFactorAuthSuccessAction { get; set; }
|
public Action TwoFactorAuthSuccessAction { get; set; }
|
||||||
public Action LockAction { get; set; }
|
|
||||||
public Action StartDeviceApprovalOptionsAction { get; set; }
|
|
||||||
public Action StartSetPasswordAction { get; set; }
|
public Action StartSetPasswordAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
@@ -143,6 +136,8 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_authingWithSso = _authService.AuthingWithSso();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
|
||||||
{
|
{
|
||||||
_webVaultUrl = _environmentService.BaseUrl;
|
_webVaultUrl = _environmentService.BaseUrl;
|
||||||
@@ -320,84 +315,21 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
var task = Task.Run(() => _syncService.FullSyncAsync(true));
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
|
||||||
_messagingService.Send("listenYubiKeyOTP", false);
|
_messagingService.Send("listenYubiKeyOTP", false);
|
||||||
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
|
||||||
|
|
||||||
if (decryptOptions?.TrustedDeviceOption != null)
|
if (_authingWithSso && result.ResetMasterPassword)
|
||||||
{
|
{
|
||||||
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
|
|
||||||
{
|
|
||||||
// If we have a device key but no keys on server, we need to remove the device key
|
|
||||||
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
|
|
||||||
{
|
|
||||||
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If user doesn't have a MP, but has reset password permission, they must set a MP
|
|
||||||
if (!decryptOptions.HasMasterPassword &&
|
|
||||||
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
|
|
||||||
{
|
|
||||||
StartSetPasswordAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
|
|
||||||
if (result.ForcePasswordReset)
|
|
||||||
{
|
|
||||||
UpdateTempPasswordAction?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device is trusted and has keys, so we can decrypt
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
await TwoFactorAuthSuccessAsync();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for pending Admin Auth requests before navigating to device approval options
|
|
||||||
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
|
|
||||||
if (pendingRequest != null)
|
|
||||||
{
|
|
||||||
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
|
|
||||||
if (authRequest?.RequestApproved == true)
|
|
||||||
{
|
|
||||||
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
|
|
||||||
if (authResult == null && await _stateService.IsAuthenticatedAsync())
|
|
||||||
{
|
|
||||||
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
|
|
||||||
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
|
||||||
await TwoFactorAuthSuccessAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _stateService.SetPendingAdminAuthRequestAsync(null);
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StartDeviceApprovalOptionsAction?.Invoke();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the standard, non TDE case, a user must set password if they don't
|
|
||||||
// have one and they aren't using key connector.
|
|
||||||
// Note: TDE & Key connector are mutually exclusive org config options.
|
|
||||||
if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
|
|
||||||
{
|
|
||||||
// TODO: We need to look into how to handle this when Org removes TDE
|
|
||||||
// Will we have the User Key by now to set a new password?
|
|
||||||
StartSetPasswordAction?.Invoke();
|
StartSetPasswordAction?.Invoke();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else if (result.ForcePasswordReset)
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
{
|
||||||
await TwoFactorAuthSuccessAsync();
|
UpdateTempPasswordAction?.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TwoFactorAuthSuccessAction?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
{
|
{
|
||||||
@@ -466,8 +398,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Email = _authService.Email,
|
Email = _authService.Email,
|
||||||
MasterPasswordHash = _authService.MasterPasswordHash,
|
MasterPasswordHash = _authService.MasterPasswordHash,
|
||||||
DeviceIdentifier = await _appIdService.GetAppIdAsync(),
|
DeviceIdentifier = await _appIdService.GetAppIdAsync()
|
||||||
SsoEmail2FaSessionToken = _authService.SsoEmail2FaSessionToken
|
|
||||||
};
|
};
|
||||||
await _apiService.PostTwoFactorEmailAsync(request);
|
await _apiService.PostTwoFactorEmailAsync(request);
|
||||||
if (showLoading)
|
if (showLoading)
|
||||||
@@ -491,17 +422,5 @@ namespace Bit.App.Pages
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TwoFactorAuthSuccessAsync()
|
|
||||||
{
|
|
||||||
if (AuthingWithSso && await _vaultTimeoutService.IsLockedAsync())
|
|
||||||
{
|
|
||||||
LockAction?.Invoke();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TwoFactorAuthSuccessAction?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<pages:BaseContentPage
|
<pages:BaseContentPage
|
||||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
@@ -48,8 +48,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding UpdateMasterPasswordWarningText }"
|
Text="{Binding UpdateMasterPasswordWarningText }"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="UpdatePasswordWarningLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
<Grid IsVisible="{Binding IsPolicyInEffect}"
|
||||||
@@ -72,8 +71,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding PolicySummary}"
|
Text="{Binding PolicySummary}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Start" />
|
||||||
AutomationId="PolicySummaryLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid StyleClass="box-row" IsVisible="{Binding RequireCurrentPassword }">
|
<Grid StyleClass="box-row" IsVisible="{Binding RequireCurrentPassword }">
|
||||||
@@ -98,8 +96,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="MasterPasswordField" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -109,8 +106,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="ToggleMasterPasswordVisibilityButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid StyleClass="box-row">
|
<Grid StyleClass="box-row">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -134,8 +130,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="NewPasswordField" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -145,8 +140,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="NewPasswordVisibilityButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
@@ -172,8 +166,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="RetypePasswordField" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -183,8 +176,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="ToggleRetypePasswordVisibilityButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
@@ -195,8 +187,7 @@
|
|||||||
Text="{Binding Hint}"
|
Text="{Binding Hint}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}" />
|
||||||
AutomationId="MasterPasswordHintLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MasterPasswordHintDescription}"
|
Text="{u:I18n MasterPasswordHintDescription}"
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ namespace Bit.App.Pages
|
|||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
|
|
||||||
// Create new master key and hash new password
|
// Create new key and hash new password
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
|
||||||
// Encrypt user key with new master key
|
// Create new encKey for the User
|
||||||
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
|
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
|
||||||
// Initiate API action
|
// Initiate API action
|
||||||
try
|
try
|
||||||
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
|
|||||||
switch (_reason)
|
switch (_reason)
|
||||||
{
|
{
|
||||||
case ForcePasswordResetReason.AdminForcePasswordReset:
|
case ForcePasswordResetReason.AdminForcePasswordReset:
|
||||||
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||||
break;
|
break;
|
||||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||||
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
@@ -155,7 +155,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
|
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
|
||||||
{
|
{
|
||||||
var currentPasswordHash = await _cryptoService.HashMasterKeyAsync(CurrentMasterPassword, null);
|
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
|
||||||
|
|
||||||
var request = new PasswordRequest
|
var request = new PasswordRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,12 +150,6 @@ namespace Bit.App.Pages
|
|||||||
private async Task SaveActivityAsync()
|
private async Task SaveActivityAsync()
|
||||||
{
|
{
|
||||||
SetServices();
|
SetServices();
|
||||||
if (await _stateService.GetActiveUserIdAsync() == null)
|
|
||||||
{
|
|
||||||
// Fresh install and/or all users logged out won't have an active user, skip saving last active time
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,7 @@
|
|||||||
Clicked="Clear_Clicked"
|
Clicked="Clear_Clicked"
|
||||||
Order="Secondary"
|
Order="Secondary"
|
||||||
x:Name="_clearItem"
|
x:Name="_clearItem"
|
||||||
x:Key="clearItem"
|
x:Key="clearItem" />
|
||||||
AutomationId="ClearPasswordList" />
|
|
||||||
<ToolbarItem Icon="more_vert.png"
|
<ToolbarItem Icon="more_vert.png"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}"
|
||||||
@@ -44,8 +43,7 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center"></Label>
|
||||||
AutomationId="NoPasswordsDisplayedLabel"></Label>
|
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding History}"
|
ItemsSource="{Binding History}"
|
||||||
@@ -58,8 +56,7 @@
|
|||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
Padding="10"
|
Padding="10"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
ColumnSpacing="10"
|
ColumnSpacing="10">
|
||||||
AutomationId="GeneratedPasswordRow">
|
|
||||||
|
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -74,14 +71,12 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
StyleClass="list-title, list-title-platform, text-html"
|
StyleClass="list-title, list-title-platform, text-html"
|
||||||
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}"
|
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
|
||||||
AutomationId="GeneratedPasswordValue" />
|
|
||||||
<Label LineBreakMode="TailTruncation"
|
<Label LineBreakMode="TailTruncation"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
StyleClass="list-subtitle, list-subtitle-platform"
|
StyleClass="list-subtitle, list-subtitle-platform"
|
||||||
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}"
|
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
|
||||||
AutomationId="GeneratedPasswordDateLabel" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="list-row-button, list-row-button-platform"
|
StyleClass="list-row-button, list-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Paste}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Paste}}"
|
||||||
@@ -91,8 +86,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}"
|
AutomationProperties.Name="{u:I18n CopyPassword}" />
|
||||||
AutomationId="CopyPasswordValueButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -71,8 +71,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
|
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="PasswordGeneratorPolicyInEffectLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
|
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
|
||||||
@@ -83,24 +82,21 @@
|
|||||||
x:Name="lblPassword"
|
x:Name="lblPassword"
|
||||||
StyleClass="text-lg, text-html"
|
StyleClass="text-lg, text-html"
|
||||||
Text="{Binding ColoredPassword, Mode=OneWay}"
|
Text="{Binding ColoredPassword, Mode=OneWay}"
|
||||||
Margin="0, 20"
|
Margin="0, 20" />
|
||||||
AutomationId="GeneratedPasswordLabel" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
Command="{Binding CopyCommand}"
|
Command="{Binding CopyCommand}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyPassword}"
|
AutomationProperties.Name="{u:I18n CopyPassword}" />
|
||||||
AutomationId="CopyValueButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
Command="{Binding RegenerateCommand}"
|
Command="{Binding RegenerateCommand}"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
AutomationProperties.Name="{u:I18n GeneratePassword}" />
|
||||||
AutomationId="RegenerateValueButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid IsVisible="{Binding IsUsername}"
|
<Grid IsVisible="{Binding IsUsername}"
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
@@ -111,24 +107,21 @@
|
|||||||
StyleClass="text-lg, text-html"
|
StyleClass="text-lg, text-html"
|
||||||
Text="{Binding ColoredUsername, Mode=OneWay}"
|
Text="{Binding ColoredUsername, Mode=OneWay}"
|
||||||
Margin="0, 20"
|
Margin="0, 20"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start" />
|
||||||
AutomationId="GeneratedPasswordLabel" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
Command="{Binding CopyCommand}"
|
Command="{Binding CopyCommand}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyUsername}"
|
AutomationProperties.Name="{u:I18n CopyUsername}" />
|
||||||
AutomationId="CopyValueButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
Command="{Binding RegenerateUsernameCommand}"
|
Command="{Binding RegenerateUsernameCommand}"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}"
|
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
||||||
AutomationId="RegenerateValueButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView StyleClass="box-row-separator"/>
|
<BoxView StyleClass="box-row-separator"/>
|
||||||
<StackLayout StyleClass="box"
|
<StackLayout StyleClass="box"
|
||||||
@@ -142,8 +135,7 @@
|
|||||||
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding GeneratorTypeSelected}"
|
SelectedItem="{Binding GeneratorTypeSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="GeneratorTypePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label Text="{u:I18n Options, Header=True}"
|
<Label Text="{u:I18n Options, Header=True}"
|
||||||
StyleClass="box-header, box-header-platform"
|
StyleClass="box-header, box-header-platform"
|
||||||
@@ -169,8 +161,7 @@
|
|||||||
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding UsernameTypeSelected}"
|
SelectedItem="{Binding UsernameTypeSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="UsernameTypePicker" />
|
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Text="{Binding UsernameTypeDescriptionLabel}" />
|
Text="{Binding UsernameTypeDescriptionLabel}" />
|
||||||
@@ -181,8 +172,7 @@
|
|||||||
StyleClass="box-label" />
|
StyleClass="box-label" />
|
||||||
<Entry x:Name="_plusAddressedEmailEntry"
|
<Entry x:Name="_plusAddressedEmailEntry"
|
||||||
Text="{Binding PlusAddressedEmail}"
|
Text="{Binding PlusAddressedEmail}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="PlusAddressedEmailEntry" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
||||||
Text="{u:I18n EmailType}"
|
Text="{u:I18n EmailType}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
@@ -213,8 +203,7 @@
|
|||||||
<Entry
|
<Entry
|
||||||
x:Name="_catchAllEmailDomainNameEntry"
|
x:Name="_catchAllEmailDomainNameEntry"
|
||||||
Text="{Binding CatchAllEmailDomain}"
|
Text="{Binding CatchAllEmailDomain}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="CatchAllEmailDomainEntry" />
|
|
||||||
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
<Label IsVisible="{Binding ShowUsernameEmailType}"
|
||||||
Text="{u:I18n EmailType}"
|
Text="{u:I18n EmailType}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
@@ -247,27 +236,26 @@
|
|||||||
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding ForwardedEmailServiceSelected}"
|
SelectedItem="{Binding ForwardedEmailServiceSelected}"
|
||||||
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="ServiceTypePicker" />
|
<!--ANONADDY OPTIONS-->
|
||||||
<Grid
|
<Grid IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||||
Grid.RowDefinitions="Auto,*"
|
Grid.RowDefinitions="Auto,*"
|
||||||
Grid.ColumnDefinitions="*,Auto">
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
<Label
|
<Label
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Text="{Binding ForwardedEmailApiSecretLabel}"
|
Text="{u:I18n APIAccessToken}"
|
||||||
StyleClass="box-label"/>
|
StyleClass="box-label"/>
|
||||||
<Entry
|
<Entry
|
||||||
Text="{Binding ForwardedEmailApiSecret}"
|
x:Name="_anonAddyApiAccessTokenEntry"
|
||||||
IsPassword="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool}}"
|
Text="{Binding AnonAddyApiAccessToken}"
|
||||||
Grid.Row="1"
|
IsPassword="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool}}"
|
||||||
AutomationId="ForwardedEmailApiSecretEntry" />
|
Grid.Row="1"/>
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"/>
|
||||||
AutomationId="ShowForwardedEmailApiSecretButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||||
Text="{u:I18n DomainNameRequiredParenthesis}"
|
Text="{u:I18n DomainNameRequiredParenthesis}"
|
||||||
@@ -275,9 +263,92 @@
|
|||||||
Margin="0,10,0,0"/>
|
Margin="0,10,0,0"/>
|
||||||
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
|
||||||
x:Name="_anonAddyDomainNameEntry"
|
x:Name="_anonAddyDomainNameEntry"
|
||||||
Text="{Binding AddyIoDomainName}"
|
Text="{Binding AnonAddyDomainName}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"/>
|
||||||
AutomationId="AnonAddyDomainNameEntry" />
|
<!--FIREFOX RELAY OPTIONS-->
|
||||||
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
|
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.FirefoxRelay}}"
|
||||||
|
Grid.RowDefinitions="Auto,*"
|
||||||
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n APIAccessToken}"
|
||||||
|
StyleClass="box-label"/>
|
||||||
|
<Entry
|
||||||
|
x:Name="_firefoxRelayApiAccessTokenEntry"
|
||||||
|
Text="{Binding FirefoxRelayApiAccessToken}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"/>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
|
<!--SIMPLELOGIN OPTIONS-->
|
||||||
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
|
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.SimpleLogin}}"
|
||||||
|
Grid.RowDefinitions="Auto,*"
|
||||||
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||||
|
StyleClass="box-label"/>
|
||||||
|
<Entry
|
||||||
|
x:Name="_simpleLoginApiKeyEntry"
|
||||||
|
Text="{Binding SimpleLoginApiKey}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"/>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
|
<!--DUCKDUCKGO OPTIONS-->
|
||||||
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
|
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.DuckDuckGo}}"
|
||||||
|
Grid.RowDefinitions="Auto,*"
|
||||||
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||||
|
StyleClass="box-label"/>
|
||||||
|
<Entry
|
||||||
|
x:Name="_duckDuckGoApiAccessTokenEntry"
|
||||||
|
Text="{Binding DuckDuckGoApiKey}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsPassword="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool}}"/>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
|
<!--FASTMAIL OPTIONS-->
|
||||||
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
|
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.Fastmail}}"
|
||||||
|
Grid.RowDefinitions="Auto,*"
|
||||||
|
Grid.ColumnDefinitions="*,Auto">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n APIKeyRequiredParenthesis}"
|
||||||
|
StyleClass="box-label"/>
|
||||||
|
<Entry
|
||||||
|
x:Name="_fastmailApiAccessTokenEntry"
|
||||||
|
Text="{Binding FastmailApiKey}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsPassword="{Binding ShowFastmailApiKey, Converter={StaticResource inverseBool}}"/>
|
||||||
|
<controls:IconButton
|
||||||
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
|
Text="{Binding ShowFastmailApiKey, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
|
||||||
|
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<!--RANDOM WORD OPTIONS-->
|
<!--RANDOM WORD OPTIONS-->
|
||||||
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
|
||||||
@@ -288,8 +359,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding CapitalizeRandomWordUsername}"
|
IsToggled="{Binding CapitalizeRandomWordUsername}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="CapitalizeRandomWordUsernameToggle" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
||||||
StyleClass="box-row-separator" />
|
StyleClass="box-row-separator" />
|
||||||
@@ -301,8 +371,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding IncludeNumberRandomWordUsername}"
|
IsToggled="{Binding IncludeNumberRandomWordUsername}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="IncludeNumberRandomWordUsernameToggle" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
|
||||||
StyleClass="box-row-separator" />
|
StyleClass="box-row-separator" />
|
||||||
@@ -317,8 +386,7 @@
|
|||||||
x:Name="_passwordTypePicker"
|
x:Name="_passwordTypePicker"
|
||||||
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
|
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="PasswordTypePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Spacing="0"
|
<StackLayout Spacing="0"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
@@ -335,14 +403,12 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="NumberOfWordsLabel" />
|
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding NumWords}"
|
Value="{Binding NumWords}"
|
||||||
Maximum="20"
|
Maximum="20"
|
||||||
Minimum="3"
|
Minimum="3"
|
||||||
Increment="1"
|
Increment="1" />
|
||||||
AutomationId="NumberOfWordsStepper" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
@@ -353,8 +419,7 @@
|
|||||||
Text="{Binding WordSeparator}"
|
Text="{Binding WordSeparator}"
|
||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value">
|
||||||
AutomationId="WordSeparatorEntry">
|
|
||||||
<Entry.Effects>
|
<Entry.Effects>
|
||||||
<effects:NoEmojiKeyboardEffect />
|
<effects:NoEmojiKeyboardEffect />
|
||||||
</Entry.Effects>
|
</Entry.Effects>
|
||||||
@@ -370,8 +435,7 @@
|
|||||||
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
|
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="CapitalizePassphraseToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -384,8 +448,7 @@
|
|||||||
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
|
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
|
||||||
Converter={StaticResource inverseBool}}"
|
Converter={StaticResource inverseBool}}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="IncludeNumbersToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
|
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
|
||||||
@@ -399,8 +462,7 @@
|
|||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
WidthRequest="50"
|
WidthRequest="50" />
|
||||||
AutomationId="PasswordLengthLabel" />
|
|
||||||
<controls:ExtendedSlider
|
<controls:ExtendedSlider
|
||||||
DragCompleted="LengthSlider_DragCompleted"
|
DragCompleted="LengthSlider_DragCompleted"
|
||||||
Value="{Binding Length}"
|
Value="{Binding Length}"
|
||||||
@@ -409,8 +471,7 @@
|
|||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
Maximum="128"
|
Maximum="128"
|
||||||
Minimum="5"
|
Minimum="5" />
|
||||||
AutomationId="PasswordLengthSlider" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -427,8 +488,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"
|
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
|
||||||
AutomationId="UppercaseAtoZToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -445,8 +505,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"
|
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
|
||||||
AutomationId="LowercaseAtoZToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -463,8 +522,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"
|
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
|
||||||
AutomationId="NumbersZeroToNineToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -481,8 +539,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SpecialCharacters}"
|
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
|
||||||
AutomationId="SpecialCharactersToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -497,14 +554,12 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="MinNumberValueLabel" />
|
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding MinNumber}"
|
Value="{Binding MinNumber}"
|
||||||
Maximum="5"
|
Maximum="5"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
Increment="1"
|
Increment="1" />
|
||||||
AutomationId="MinNumberStepper" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-stepper">
|
<StackLayout StyleClass="box-row, box-row-stepper">
|
||||||
@@ -519,14 +574,12 @@
|
|||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="MinSpecialValueLabel" />
|
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
Value="{Binding MinSpecial}"
|
Value="{Binding MinSpecial}"
|
||||||
Maximum="5"
|
Maximum="5"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
Increment="1"
|
Increment="1" />
|
||||||
AutomationId="MinSpecialStepper" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
@@ -537,8 +590,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AvoidAmbiguousChars}"
|
IsToggled="{Binding AvoidAmbiguousChars}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="AvoidAmbiguousCharsToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -24,7 +23,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IUsernameGenerationService _usernameGenerationService;
|
private readonly IUsernameGenerationService _usernameGenerationService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||||
|
|
||||||
private PasswordGenerationOptions _options;
|
private PasswordGenerationOptions _options;
|
||||||
private UsernameGenerationOptions _usernameOptions;
|
private UsernameGenerationOptions _usernameOptions;
|
||||||
@@ -50,7 +49,11 @@ namespace Bit.App.Pages
|
|||||||
private bool _doneIniting;
|
private bool _doneIniting;
|
||||||
private bool _showTypePicker;
|
private bool _showTypePicker;
|
||||||
private string _emailWebsite;
|
private string _emailWebsite;
|
||||||
private bool _showForwardedEmailApiSecret;
|
private bool _showFirefoxRelayApiAccessToken;
|
||||||
|
private bool _showAnonAddyApiAccessToken;
|
||||||
|
private bool _showSimpleLoginApiKey;
|
||||||
|
private bool _showDuckDuckGoApiKey;
|
||||||
|
private bool _showFastmailApiKey;
|
||||||
private bool _editMode;
|
private bool _editMode;
|
||||||
|
|
||||||
public GeneratorPageViewModel()
|
public GeneratorPageViewModel()
|
||||||
@@ -93,7 +96,7 @@ namespace Bit.App.Pages
|
|||||||
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
|
||||||
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
|
||||||
ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret);
|
ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||||
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||||
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
@@ -412,6 +415,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
|
||||||
|
|
||||||
|
|
||||||
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
public ForwardedEmailServiceType ForwardedEmailServiceSelected
|
||||||
{
|
{
|
||||||
get => _usernameOptions.ServiceType;
|
get => _usernameOptions.ServiceType;
|
||||||
@@ -421,11 +425,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_usernameOptions.ServiceType = value;
|
_usernameOptions.ServiceType = value;
|
||||||
Username = Constants.DefaultUsernameGenerated;
|
Username = Constants.DefaultUsernameGenerated;
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected), new string[]
|
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||||
{
|
|
||||||
nameof(ForwardedEmailApiSecret),
|
|
||||||
nameof(ForwardedEmailApiSecretLabel)
|
|
||||||
});
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,107 +445,30 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ForwardedEmailApiSecret
|
public string AnonAddyApiAccessToken
|
||||||
{
|
{
|
||||||
get
|
get => _usernameOptions.AnonAddyApiAccessToken;
|
||||||
{
|
|
||||||
switch (ForwardedEmailServiceSelected)
|
|
||||||
{
|
|
||||||
case ForwardedEmailServiceType.AnonAddy:
|
|
||||||
return _usernameOptions.AnonAddyApiAccessToken;
|
|
||||||
case ForwardedEmailServiceType.DuckDuckGo:
|
|
||||||
return _usernameOptions.DuckDuckGoApiKey;
|
|
||||||
case ForwardedEmailServiceType.Fastmail:
|
|
||||||
return _usernameOptions.FastMailApiKey;
|
|
||||||
case ForwardedEmailServiceType.FirefoxRelay:
|
|
||||||
return _usernameOptions.FirefoxRelayApiAccessToken;
|
|
||||||
case ForwardedEmailServiceType.SimpleLogin:
|
|
||||||
return _usernameOptions.SimpleLoginApiKey;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
bool changed = false;
|
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
||||||
switch (ForwardedEmailServiceSelected)
|
|
||||||
{
|
{
|
||||||
case ForwardedEmailServiceType.AnonAddy:
|
_usernameOptions.AnonAddyApiAccessToken = value;
|
||||||
if (_usernameOptions.AnonAddyApiAccessToken != value)
|
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
||||||
{
|
|
||||||
_usernameOptions.AnonAddyApiAccessToken = value;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.DuckDuckGo:
|
|
||||||
if (_usernameOptions.DuckDuckGoApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.DuckDuckGoApiKey = value;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.Fastmail:
|
|
||||||
if (_usernameOptions.FastMailApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.FastMailApiKey = value;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.FirefoxRelay:
|
|
||||||
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ForwardedEmailServiceType.SimpleLogin:
|
|
||||||
if (_usernameOptions.SimpleLoginApiKey != value)
|
|
||||||
{
|
|
||||||
_usernameOptions.SimpleLoginApiKey = value;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed)
|
|
||||||
{
|
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ForwardedEmailApiSecretLabel
|
public bool ShowAnonAddyApiAccessToken
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
switch (ForwardedEmailServiceSelected)
|
return _showAnonAddyApiAccessToken;
|
||||||
{
|
|
||||||
case ForwardedEmailServiceType.AnonAddy:
|
|
||||||
case ForwardedEmailServiceType.FirefoxRelay:
|
|
||||||
return AppResources.APIAccessToken;
|
|
||||||
case ForwardedEmailServiceType.DuckDuckGo:
|
|
||||||
case ForwardedEmailServiceType.Fastmail:
|
|
||||||
case ForwardedEmailServiceType.SimpleLogin:
|
|
||||||
return AppResources.APIKeyRequiredParenthesis;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowForwardedEmailApiSecret
|
public string AnonAddyDomainName
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _showForwardedEmailApiSecret;
|
|
||||||
}
|
|
||||||
set => SetProperty(ref _showForwardedEmailApiSecret, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AddyIoDomainName
|
|
||||||
{
|
{
|
||||||
get => _usernameOptions.AnonAddyDomainName;
|
get => _usernameOptions.AnonAddyDomainName;
|
||||||
set
|
set
|
||||||
@@ -553,12 +476,105 @@ namespace Bit.App.Pages
|
|||||||
if (_usernameOptions.AnonAddyDomainName != value)
|
if (_usernameOptions.AnonAddyDomainName != value)
|
||||||
{
|
{
|
||||||
_usernameOptions.AnonAddyDomainName = value;
|
_usernameOptions.AnonAddyDomainName = value;
|
||||||
TriggerPropertyChanged(nameof(AddyIoDomainName));
|
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
||||||
SaveUsernameOptionsAsync(false).FireAndForget();
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string FirefoxRelayApiAccessToken
|
||||||
|
{
|
||||||
|
get => _usernameOptions.FirefoxRelayApiAccessToken;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.FirefoxRelayApiAccessToken = value;
|
||||||
|
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
||||||
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowFirefoxRelayApiAccessToken
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showFirefoxRelayApiAccessToken;
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SimpleLoginApiKey
|
||||||
|
{
|
||||||
|
get => _usernameOptions.SimpleLoginApiKey;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_usernameOptions.SimpleLoginApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.SimpleLoginApiKey = value;
|
||||||
|
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
||||||
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowSimpleLoginApiKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showSimpleLoginApiKey;
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _showSimpleLoginApiKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DuckDuckGoApiKey
|
||||||
|
{
|
||||||
|
get => _usernameOptions.DuckDuckGoApiKey;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_usernameOptions.DuckDuckGoApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.DuckDuckGoApiKey = value;
|
||||||
|
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||||
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowDuckDuckGoApiKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showDuckDuckGoApiKey;
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _showDuckDuckGoApiKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string FastmailApiKey
|
||||||
|
{
|
||||||
|
get => _usernameOptions.FastMailApiKey;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_usernameOptions.FastMailApiKey != value)
|
||||||
|
{
|
||||||
|
_usernameOptions.FastMailApiKey = value;
|
||||||
|
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||||
|
SaveUsernameOptionsAsync(false).FireAndForget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowFastmailApiKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _showFastmailApiKey;
|
||||||
|
}
|
||||||
|
set => SetProperty(ref _showFastmailApiKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool CapitalizeRandomWordUsername
|
public bool CapitalizeRandomWordUsername
|
||||||
{
|
{
|
||||||
get => _usernameOptions.CapitalizeRandomWordUsername;
|
get => _usernameOptions.CapitalizeRandomWordUsername;
|
||||||
@@ -791,9 +807,12 @@ namespace Bit.App.Pages
|
|||||||
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
|
||||||
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
|
||||||
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
|
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailApiSecretLabel));
|
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
|
||||||
TriggerPropertyChanged(nameof(AddyIoDomainName));
|
TriggerPropertyChanged(nameof(AnonAddyDomainName));
|
||||||
|
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
|
||||||
|
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
|
||||||
|
TriggerPropertyChanged(nameof(FastmailApiKey));
|
||||||
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
|
||||||
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
|
||||||
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
TriggerPropertyChanged(nameof(UsernameTypeSelected));
|
||||||
@@ -826,23 +845,15 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
_logger.Value.Exception(ex);
|
_logger.Value.Exception(ex);
|
||||||
|
|
||||||
string message = AppResources.GenericErrorMessage;
|
|
||||||
|
|
||||||
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
|
||||||
{
|
{
|
||||||
if (ex is ForwardedEmailInvalidSecretException)
|
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(
|
||||||
{
|
AppResources.AnErrorHasOccurred, string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected), AppResources.Ok));
|
||||||
message = ForwardedEmailServiceSelected == ForwardedEmailServiceType.AnonAddy || ForwardedEmailServiceSelected == ForwardedEmailServiceType.FirefoxRelay
|
}
|
||||||
? AppResources.InvalidAPIToken
|
else
|
||||||
: AppResources.InvalidAPIKey;
|
{
|
||||||
}
|
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok));
|
||||||
else
|
|
||||||
{
|
|
||||||
message = string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUsernameTypeLabelDescription(UsernameType value)
|
private string GetUsernameTypeLabelDescription(UsernameType value)
|
||||||
@@ -859,5 +870,27 @@ namespace Bit.App.Pages
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ToggleForwardedEmailHiddenValueAsync()
|
||||||
|
{
|
||||||
|
switch (ForwardedEmailServiceSelected)
|
||||||
|
{
|
||||||
|
case ForwardedEmailServiceType.AnonAddy:
|
||||||
|
ShowAnonAddyApiAccessToken = !ShowAnonAddyApiAccessToken;
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.FirefoxRelay:
|
||||||
|
ShowFirefoxRelayApiAccessToken = !ShowFirefoxRelayApiAccessToken;
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.SimpleLogin:
|
||||||
|
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.DuckDuckGo:
|
||||||
|
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
|
||||||
|
break;
|
||||||
|
case ForwardedEmailServiceType.Fastmail:
|
||||||
|
ShowFastmailApiKey = !ShowFastmailApiKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SendDisabledWarning}"
|
Text="{u:I18n SendDisabledWarning}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="SendDisabledWarningMessageLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
<Frame
|
<Frame
|
||||||
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
||||||
@@ -84,8 +83,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n SendOptionsPolicyInEffect}"
|
Text="{u:I18n SendOptionsPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="SendOptionsPolicyInEffectLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
@@ -95,8 +93,7 @@
|
|||||||
x:Name="_nameEntry"
|
x:Name="_nameEntry"
|
||||||
Text="{Binding Send.Name}"
|
Text="{Binding Send.Name}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="SendNameEntry" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n NameInfo}"
|
Text="{u:I18n NameInfo}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -126,7 +123,6 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n File}"
|
AutomationProperties.Name="{u:I18n File}"
|
||||||
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
|
||||||
AutomationId="SendFileButton"
|
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -139,7 +135,6 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Text}"
|
AutomationProperties.Name="{u:I18n Text}"
|
||||||
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
|
||||||
AutomationId="SendTextButton"
|
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -157,14 +152,12 @@
|
|||||||
Text="{Binding Send.File.FileName, Mode=OneWay}"
|
Text="{Binding Send.File.FileName, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
HorizontalOptions="StartAndExpand"
|
HorizontalOptions="StartAndExpand" />
|
||||||
AutomationId="SendFileNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Send.File.SizeName, Mode=OneWay}"
|
Text="{Binding Send.File.SizeName, Mode=OneWay}"
|
||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="SendFileSizeLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
|
||||||
@@ -175,29 +168,26 @@
|
|||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="SendNoFileChosenLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
||||||
Text="{Binding FileName}"
|
Text="{Binding FileName}"
|
||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="SendCurrentFileNameLabel" />
|
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n ChooseFile}"
|
Text="{u:I18n ChooseFile}"
|
||||||
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-button-row"
|
StyleClass="box-button-row"
|
||||||
Clicked="ChooseFile_Clicked"
|
Clicked="ChooseFile_Clicked" />
|
||||||
AutomationId="SendChooseFileButton" />
|
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
Text="{u:I18n MaxFileSize}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Start" />
|
HorizontalTextAlignment="Center" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n TypeFileInfo}"
|
Text="{u:I18n TypeFileInfo}"
|
||||||
@@ -217,8 +207,7 @@
|
|||||||
Text="{Binding Send.Text.Text}"
|
Text="{Binding Send.Text.Text}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Margin="{Binding EditorMargins}"
|
Margin="{Binding EditorMargins}"
|
||||||
AutomationId="SendTextContentEntry"
|
|
||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
@@ -246,10 +235,22 @@
|
|||||||
IsToggled="{Binding Send.Text.Hidden}"
|
IsToggled="{Binding Send.Text.Hidden}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0" />
|
||||||
AutomationId="SendHideTextByDefaultToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch">
|
||||||
|
<Label
|
||||||
|
Text="{Binding ShareOnSaveText}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding ShareOnSave}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="0"
|
Spacing="0"
|
||||||
@@ -262,24 +263,21 @@
|
|||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
AutomationId="SendShowHideOptionsButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsUp"
|
x:Name="_btnOptionsUp"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
IsVisible="{Binding ShowOptions}"
|
IsVisible="{Binding ShowOptions}"
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
AutomationId="SendOptionsDisplayed" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
x:Name="_btnOptionsDown"
|
x:Name="_btnOptionsDown"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
TextColor="{DynamicResource PrimaryColor}"
|
TextColor="{DynamicResource PrimaryColor}"
|
||||||
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
|
||||||
AutomationProperties.IsInAccessibleTree="False"
|
AutomationProperties.IsInAccessibleTree="False"/>
|
||||||
AutomationId="SendOptionsHidden" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding ShowOptions}">
|
<StackLayout IsVisible="{Binding ShowOptions}">
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -296,8 +294,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionTime}"
|
AutomationProperties.Name="{u:I18n DeletionTime}" />
|
||||||
AutomationId="SendDeletionOptionsPicker" />
|
|
||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding ShowDeletionCustomPickers}"
|
IsVisible="{Binding ShowDeletionCustomPickers}"
|
||||||
Margin="0,5,0,0">
|
Margin="0,5,0,0">
|
||||||
@@ -311,16 +308,14 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionDate}"
|
AutomationProperties.Name="{u:I18n DeletionDate}"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="SendCustomDeletionDatePicker" />
|
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
Format="t"
|
Format="t"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n DeletionTime}"
|
AutomationProperties.Name="{u:I18n DeletionTime}"
|
||||||
Grid.Column="1"
|
Grid.Column="1" />
|
||||||
AutomationId="SendCustomDeletionTimePicker" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DeletionDateInfo}"
|
Text="{u:I18n DeletionDateInfo}"
|
||||||
@@ -339,8 +334,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
AutomationProperties.Name="{u:I18n ExpirationTime}" />
|
||||||
AutomationId="SendExpirationOptionsPicker" />
|
|
||||||
<Grid
|
<Grid
|
||||||
IsVisible="{Binding ShowExpirationCustomPickers}"
|
IsVisible="{Binding ShowExpirationCustomPickers}"
|
||||||
Margin="0,5,0,0">
|
Margin="0,5,0,0">
|
||||||
@@ -355,8 +349,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
AutomationProperties.Name="{u:I18n ExpirationDate}"
|
||||||
Grid.Column="0"
|
Grid.Column="0" />
|
||||||
AutomationId="SendCustomExpirationDatePicker" />
|
|
||||||
<controls:ExtendedTimePicker
|
<controls:ExtendedTimePicker
|
||||||
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
|
||||||
PlaceHolder="--:-- --"
|
PlaceHolder="--:-- --"
|
||||||
@@ -364,8 +357,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
AutomationProperties.Name="{u:I18n ExpirationTime}"
|
||||||
Grid.Column="1"
|
Grid.Column="1" />
|
||||||
AutomationId="SendCustomExpirationTimePicker" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
@@ -382,8 +374,7 @@
|
|||||||
FontSize="{Binding SegmentedButtonFontSize}"
|
FontSize="{Binding SegmentedButtonFontSize}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-row-button"
|
StyleClass="box-row-button"
|
||||||
Clicked="ClearExpirationDate_Clicked"
|
Clicked="ClearExpirationDate_Clicked" />
|
||||||
AutomationId="SendClearExpirationDateButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -402,15 +393,13 @@
|
|||||||
Keyboard="Numeric"
|
Keyboard="Numeric"
|
||||||
MaxLength="9"
|
MaxLength="9"
|
||||||
TextChanged="OnMaxAccessCountTextChanged"
|
TextChanged="OnMaxAccessCountTextChanged"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand" />
|
||||||
AutomationId="SendMaxAccessCountEntry" />
|
|
||||||
<controls:ExtendedStepper
|
<controls:ExtendedStepper
|
||||||
x:Name="_maxAccessCountStepper"
|
x:Name="_maxAccessCountStepper"
|
||||||
Value="{Binding MaxAccessCount}"
|
Value="{Binding MaxAccessCount}"
|
||||||
Maximum="999999999"
|
Maximum="999999999"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0" />
|
||||||
AutomationId="SendMaxAccessCountStepper" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n MaximumAccessCountInfo}"
|
Text="{u:I18n MaximumAccessCountInfo}"
|
||||||
@@ -430,8 +419,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{Binding Send.AccessCount, Mode=OneWay}"
|
Text="{Binding Send.AccessCount, Mode=OneWay}"
|
||||||
StyleClass="box-label"
|
StyleClass="box-label"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="SendCurrentAccessCountLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -448,8 +436,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand" />
|
||||||
AutomationId="SendNewPasswordEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
@@ -458,8 +445,7 @@
|
|||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
|
||||||
AutomationId="SendShowHidePasswordButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PasswordInfo}"
|
Text="{u:I18n PasswordInfo}"
|
||||||
@@ -478,8 +464,7 @@
|
|||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Margin="{Binding EditorMargins}"
|
Margin="{Binding EditorMargins}"
|
||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
|
||||||
AutomationId="SendNotesEntry">
|
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
</Editor.Behaviors>
|
</Editor.Behaviors>
|
||||||
@@ -507,8 +492,7 @@
|
|||||||
IsToggled="{Binding Send.HideEmail}"
|
IsToggled="{Binding Send.HideEmail}"
|
||||||
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0" />
|
||||||
AutomationId="SendHideEmailSwitch" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row, box-row-switch"
|
StyleClass="box-row, box-row-switch"
|
||||||
@@ -522,8 +506,7 @@
|
|||||||
IsToggled="{Binding Send.Disabled}"
|
IsToggled="{Binding Send.Disabled}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0" />
|
||||||
AutomationId="SendDeactivateSwitch" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -127,9 +127,10 @@ namespace Bit.App.Pages
|
|||||||
public SendType? Type { get; set; }
|
public SendType? Type { get; set; }
|
||||||
public byte[] FileData { get; set; }
|
public byte[] FileData { get; set; }
|
||||||
public string NewPassword { get; set; }
|
public string NewPassword { get; set; }
|
||||||
|
public bool ShareOnSave { get; set; }
|
||||||
public bool DisableHideEmailControl { get; set; }
|
public bool DisableHideEmailControl { get; set; }
|
||||||
public bool IsAddFromShare { get; set; }
|
public bool IsAddFromShare { get; set; }
|
||||||
public bool CopyInsteadOfShareAfterSaving { get; set; }
|
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
|
||||||
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
|
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
|
||||||
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
||||||
@@ -183,6 +184,15 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public bool CopyInsteadOfShareAfterSaving
|
||||||
|
{
|
||||||
|
get => _copyInsteadOfShareAfterSaving;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetProperty(ref _copyInsteadOfShareAfterSaving, value);
|
||||||
|
TriggerPropertyChanged(nameof(ShareOnSaveText));
|
||||||
|
}
|
||||||
|
}
|
||||||
public SendView Send
|
public SendView Send
|
||||||
{
|
{
|
||||||
get => _send;
|
get => _send;
|
||||||
@@ -402,25 +412,34 @@ namespace Bit.App.Pages
|
|||||||
_messagingService.Send("sendUpdated");
|
_messagingService.Send("sendUpdated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ShareOnSave)
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("success", null,
|
||||||
|
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
|
||||||
|
}
|
||||||
|
|
||||||
if (!CopyInsteadOfShareAfterSaving)
|
if (!CopyInsteadOfShareAfterSaving)
|
||||||
{
|
{
|
||||||
await CloseAsync();
|
await CloseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var savedSend = await _sendService.GetAsync(sendId);
|
if (ShareOnSave)
|
||||||
if (savedSend != null)
|
|
||||||
{
|
{
|
||||||
var savedSendView = await savedSend.DecryptAsync();
|
var savedSend = await _sendService.GetAsync(sendId);
|
||||||
if (CopyInsteadOfShareAfterSaving)
|
if (savedSend != null)
|
||||||
{
|
{
|
||||||
await AppHelpers.CopySendUrlAsync(savedSendView);
|
var savedSendView = await savedSend.DecryptAsync();
|
||||||
|
if (CopyInsteadOfShareAfterSaving)
|
||||||
|
{
|
||||||
|
await AppHelpers.CopySendUrlAsync(savedSendView);
|
||||||
|
|
||||||
// wait so that the user sees the message before the view gets dismissed
|
// wait so that the user sees the message before the view gets dismissed
|
||||||
await Task.Delay(1300);
|
await Task.Delay(1300);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await AppHelpers.ShareSendUrlAsync(savedSendView);
|
await AppHelpers.ShareSendUrlAsync(savedSendView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
Priority="-2"
|
Priority="-2"
|
||||||
UseOriginalImage="True"
|
UseOriginalImage="True"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Account}"
|
AutomationProperties.Name="{u:I18n Account}" />
|
||||||
AutomationId="AccountIconButton" />
|
|
||||||
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
|
||||||
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
|
||||||
</ContentPage.ToolbarItems>
|
</ContentPage.ToolbarItems>
|
||||||
@@ -94,13 +93,13 @@
|
|||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Start" />
|
HorizontalTextAlignment="Center" />
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 5, 0, 0"
|
Margin="0, 5, 0, 0"
|
||||||
Text="{u:I18n MaxFileSize}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Start" />
|
HorizontalTextAlignment="Center" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
@@ -145,6 +144,19 @@
|
|||||||
Margin="10,0,0,0" />
|
Margin="10,0,0,0" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch">
|
||||||
|
<Label
|
||||||
|
Text="{Binding ShareOnSaveText}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding ShareOnSave}"
|
||||||
|
IsEnabled="{Binding SendEnabled}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="0"
|
Spacing="0"
|
||||||
|
|||||||
@@ -44,15 +44,13 @@
|
|||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
Send="{Binding Send}"
|
Send="{Binding Send}"
|
||||||
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
||||||
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}"
|
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
|
||||||
AutomationId="SendCell" />
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate x:Key="sendGroupTemplate"
|
<DataTemplate x:Key="sendGroupTemplate"
|
||||||
x:DataType="pages:SendGroupingsPageListItem">
|
x:DataType="pages:SendGroupingsPageListItem">
|
||||||
<controls:ExtendedStackLayout Orientation="Horizontal"
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform">
|
||||||
AutomationId="{Binding AutomationId}">
|
|
||||||
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
<controls:IconLabel Text="{Binding Icon, Mode=OneWay}"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
@@ -66,14 +64,12 @@
|
|||||||
LineBreakMode="TailTruncation"
|
LineBreakMode="TailTruncation"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"
|
StyleClass="list-title" />
|
||||||
AutomationId="SendFilterNameLabel" />
|
|
||||||
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
<Label Text="{Binding ItemCount, Mode=OneWay}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
StyleClass="list-sub"
|
StyleClass="list-sub" />
|
||||||
AutomationId="SendFilterCountLabel" />
|
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
|
|||||||
@@ -66,27 +66,5 @@ namespace Bit.App.Pages
|
|||||||
return _icon;
|
return _icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AutomationId
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_name != null)
|
|
||||||
{
|
|
||||||
return "SendItem";
|
|
||||||
}
|
|
||||||
if (Type != null)
|
|
||||||
{
|
|
||||||
switch (Type.Value)
|
|
||||||
{
|
|
||||||
case SendType.Text:
|
|
||||||
return "SendTextFilter";
|
|
||||||
case SendType.File:
|
|
||||||
return "SendFileFilter";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,7 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="NoSendDisplayedLabel" />
|
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowList}"
|
IsVisible="{Binding ShowList}"
|
||||||
ItemsSource="{Binding Sends}"
|
ItemsSource="{Binding Sends}"
|
||||||
@@ -68,15 +67,13 @@
|
|||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="RowSelected"
|
SelectionChanged="RowSelected"
|
||||||
StyleClass="list, list-platform"
|
StyleClass="list, list-platform"
|
||||||
ExtraDataForLogging="Sends Page"
|
ExtraDataForLogging="Sends Page">
|
||||||
AutomationId="SendCellList">
|
|
||||||
<CollectionView.ItemTemplate>
|
<CollectionView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:SendView">
|
<DataTemplate x:DataType="views:SendView">
|
||||||
<controls:SendViewCell
|
<controls:SendViewCell
|
||||||
Send="{Binding .}"
|
Send="{Binding .}"
|
||||||
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
ButtonCommand="{Binding BindingContext.SendOptionsCommand, Source={x:Reference _page}}"
|
||||||
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}"
|
ShowOptions="{Binding BindingContext.SendEnabled, Source={x:Reference _page}}" />
|
||||||
AutomationId="SendCell" />
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
</controls:ExtendedCollectionView>
|
</controls:ExtendedCollectionView>
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
|
||||||
x:Class="Bit.App.Pages.BlockAutofillUrisPage"
|
|
||||||
xmlns:pages="clr-namespace:Bit.App.Pages"
|
|
||||||
xmlns:controls="clr-namespace:Bit.App.Controls"
|
|
||||||
xmlns:u="clr-namespace:Bit.App.Utilities"
|
|
||||||
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
|
|
||||||
x:DataType="pages:BlockAutofillUrisPageViewModel"
|
|
||||||
NavigationPage.HasBackButton="False"
|
|
||||||
Title="{u:I18n BlockAutoFill}">
|
|
||||||
<ContentPage.BindingContext>
|
|
||||||
<pages:BlockAutofillUrisPageViewModel />
|
|
||||||
</ContentPage.BindingContext>
|
|
||||||
<ContentPage.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<u:InverseBoolConverter x:Key="inverseBool" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
</ContentPage.Resources>
|
|
||||||
<StackLayout Orientation="Vertical">
|
|
||||||
<Image
|
|
||||||
x:Name="_emptyUrisPlaceholder"
|
|
||||||
HorizontalOptions="Center"
|
|
||||||
WidthRequest="120"
|
|
||||||
HeightRequest="120"
|
|
||||||
Margin="0,100,0,0"
|
|
||||||
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n ThereAreNoBlockedURIs}" />
|
|
||||||
<controls:CustomLabel
|
|
||||||
StyleClass="box-label-regular"
|
|
||||||
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
|
||||||
FontWeight="500"
|
|
||||||
HorizontalTextAlignment="Center"
|
|
||||||
Margin="14,10,14,0"/>
|
|
||||||
<controls:ExtendedCollectionView
|
|
||||||
ItemsSource="{Binding BlockedUris}"
|
|
||||||
IsVisible="{Binding ShowList}"
|
|
||||||
VerticalOptions="FillAndExpand"
|
|
||||||
Margin="0,5,0,0"
|
|
||||||
SelectionMode="None"
|
|
||||||
StyleClass="list, list-platform"
|
|
||||||
ExtraDataForLogging="Blocked Autofill Uris"
|
|
||||||
AutomationId="BlockedUrisCellList">
|
|
||||||
<CollectionView.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="pages:BlockAutofillUriItemViewModel">
|
|
||||||
<StackLayout
|
|
||||||
Orientation="Vertical"
|
|
||||||
AutomationId="BlockedUriCell">
|
|
||||||
<StackLayout
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<controls:CustomLabel
|
|
||||||
VerticalOptions="Center"
|
|
||||||
StyleClass="box-label-regular"
|
|
||||||
Text="{Binding Uri}"
|
|
||||||
MaxLines="2"
|
|
||||||
LineBreakMode="TailTruncation"
|
|
||||||
FontWeight="500"
|
|
||||||
Margin="15,0,0,0"
|
|
||||||
HorizontalOptions="StartAndExpand"/>
|
|
||||||
<controls:IconButton
|
|
||||||
StyleClass="box-row-button-muted, box-row-button-platform"
|
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.PencilSquare}}"
|
|
||||||
Command="{Binding EditUriCommand}"
|
|
||||||
Margin="5,0,15,0"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n EditURI}"
|
|
||||||
AutomationId="EditUriButton" />
|
|
||||||
</StackLayout>
|
|
||||||
<BoxView StyleClass="box-row-separator" />
|
|
||||||
</StackLayout>
|
|
||||||
</DataTemplate>
|
|
||||||
</CollectionView.ItemTemplate>
|
|
||||||
</controls:ExtendedCollectionView>
|
|
||||||
<Button
|
|
||||||
Text="{u:I18n NewBlockedURI}"
|
|
||||||
Command="{Binding AddUriCommand}"
|
|
||||||
VerticalOptions="End"
|
|
||||||
HeightRequest="40"
|
|
||||||
Opacity="0.8"
|
|
||||||
Margin="14,5,14,10"
|
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
|
||||||
AutomationProperties.Name="{u:I18n NewBlockedURI}"
|
|
||||||
AutomationId="NewBlockedUriButton" />
|
|
||||||
</StackLayout>
|
|
||||||
</pages:BaseContentPage>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Styles;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.Essentials;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public partial class BlockAutofillUrisPage : BaseContentPage, IThemeDirtablePage
|
|
||||||
{
|
|
||||||
private readonly BlockAutofillUrisPageViewModel _vm;
|
|
||||||
|
|
||||||
public BlockAutofillUrisPage()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
_vm = BindingContext as BlockAutofillUrisPageViewModel;
|
|
||||||
_vm.Page = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAppearing()
|
|
||||||
{
|
|
||||||
base.OnAppearing();
|
|
||||||
|
|
||||||
_vm.InitAsync().FireAndForget(_ => Navigation.PopAsync());
|
|
||||||
|
|
||||||
UpdatePlaceholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task UpdateOnThemeChanged()
|
|
||||||
{
|
|
||||||
await base.UpdateOnThemeChanged();
|
|
||||||
|
|
||||||
UpdatePlaceholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePlaceholder()
|
|
||||||
{
|
|
||||||
MainThread.BeginInvokeOnMainThread(() =>
|
|
||||||
_emptyUrisPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_uris_placeholder" : "empty_uris_placeholder_dark"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Essentials;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
|
||||||
{
|
|
||||||
public class BlockAutofillUrisPageViewModel : BaseViewModel
|
|
||||||
{
|
|
||||||
private const char URI_SEPARARTOR = ',';
|
|
||||||
private const string URI_FORMAT = "https://domain.com";
|
|
||||||
|
|
||||||
private readonly IStateService _stateService;
|
|
||||||
private readonly IDeviceActionService _deviceActionService;
|
|
||||||
|
|
||||||
public BlockAutofillUrisPageViewModel()
|
|
||||||
{
|
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
|
|
||||||
|
|
||||||
AddUriCommand = new AsyncCommand(AddUriAsync,
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
|
|
||||||
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableRangeCollection<BlockAutofillUriItemViewModel> BlockedUris { get; set; } = new ObservableRangeCollection<BlockAutofillUriItemViewModel>();
|
|
||||||
|
|
||||||
public bool ShowList => BlockedUris.Any();
|
|
||||||
|
|
||||||
public ICommand AddUriCommand { get; }
|
|
||||||
|
|
||||||
public ICommand EditUriCommand { get; }
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
|
||||||
{
|
|
||||||
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
|
||||||
if (blockedUrisList?.Any() != true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
|
||||||
{
|
|
||||||
BlockedUris.AddRange(blockedUrisList.OrderBy(uri => uri).Select(u => new BlockAutofillUriItemViewModel(u, EditUriCommand)).ToList());
|
|
||||||
TriggerPropertyChanged(nameof(ShowList));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddUriAsync()
|
|
||||||
{
|
|
||||||
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
|
||||||
{
|
|
||||||
Title = AppResources.NewUri,
|
|
||||||
Subtitle = AppResources.EnterURI,
|
|
||||||
ValueSubInfo = string.Format(AppResources.FormatXSeparateMultipleURIsWithAComma, URI_FORMAT),
|
|
||||||
OkButtonText = AppResources.Save,
|
|
||||||
ValidateText = text => ValidateUris(text, true)
|
|
||||||
});
|
|
||||||
if (response?.Text is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
|
||||||
{
|
|
||||||
foreach (var uri in response.Value.Text.Split(URI_SEPARARTOR).Where(s => !string.IsNullOrEmpty(s)))
|
|
||||||
{
|
|
||||||
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
|
||||||
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
|
||||||
TriggerPropertyChanged(nameof(BlockedUris));
|
|
||||||
TriggerPropertyChanged(nameof(ShowList));
|
|
||||||
});
|
|
||||||
await UpdateAutofillBlacklistedUrisAsync();
|
|
||||||
_deviceActionService.Toast(AppResources.URISaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EditUriAsync(BlockAutofillUriItemViewModel uriItemViewModel)
|
|
||||||
{
|
|
||||||
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
|
|
||||||
{
|
|
||||||
Title = AppResources.EditURI,
|
|
||||||
Subtitle = AppResources.EnterURI,
|
|
||||||
Text = uriItemViewModel.Uri,
|
|
||||||
ValueSubInfo = string.Format(AppResources.FormatX, URI_FORMAT),
|
|
||||||
OkButtonText = AppResources.Save,
|
|
||||||
ThirdButtonText = AppResources.Remove,
|
|
||||||
ValidateText = text => ValidateUris(text, false)
|
|
||||||
});
|
|
||||||
if (response is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.Value.ExecuteThirdAction)
|
|
||||||
{
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
|
||||||
{
|
|
||||||
BlockedUris.Remove(uriItemViewModel);
|
|
||||||
TriggerPropertyChanged(nameof(ShowList));
|
|
||||||
});
|
|
||||||
await UpdateAutofillBlacklistedUrisAsync();
|
|
||||||
_deviceActionService.Toast(AppResources.URIRemoved);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cleanedUri = response.Value.Text.Replace(Environment.NewLine, string.Empty).Trim();
|
|
||||||
await MainThread.InvokeOnMainThreadAsync(() =>
|
|
||||||
{
|
|
||||||
BlockedUris.Remove(uriItemViewModel);
|
|
||||||
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
|
|
||||||
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
|
|
||||||
TriggerPropertyChanged(nameof(BlockedUris));
|
|
||||||
TriggerPropertyChanged(nameof(ShowList));
|
|
||||||
});
|
|
||||||
await UpdateAutofillBlacklistedUrisAsync();
|
|
||||||
_deviceActionService.Toast(AppResources.URISaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ValidateUris(string uris, bool allowMultipleUris)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(uris))
|
|
||||||
{
|
|
||||||
return string.Format(AppResources.FormatX, URI_FORMAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allowMultipleUris && uris.Contains(URI_SEPARARTOR))
|
|
||||||
{
|
|
||||||
return AppResources.CannotEditMultipleURIsAtOnce;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var uri in uris.Split(URI_SEPARARTOR).Where(u => !string.IsNullOrWhiteSpace(u)))
|
|
||||||
{
|
|
||||||
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
|
|
||||||
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
|
||||||
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
|
||||||
{
|
|
||||||
return AppResources.InvalidFormatUseHttpsHttpOrAndroidApp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Uri.TryCreate(cleanedUri, UriKind.Absolute, out var _))
|
|
||||||
{
|
|
||||||
return AppResources.InvalidURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BlockedUris.Any(uriItem => uriItem.Uri == cleanedUri))
|
|
||||||
{
|
|
||||||
return string.Format(AppResources.TheURIXIsAlreadyBlocked, cleanedUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateAutofillBlacklistedUrisAsync()
|
|
||||||
{
|
|
||||||
await _stateService.SetAutofillBlacklistedUrisAsync(BlockedUris.Any() ? BlockedUris.Select(bu => bu.Uri).ToList() : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BlockAutofillUriItemViewModel : ExtendedViewModel
|
|
||||||
{
|
|
||||||
public BlockAutofillUriItemViewModel(string uri, ICommand editUriCommand)
|
|
||||||
{
|
|
||||||
Uri = uri;
|
|
||||||
EditUriCommand = new Command(() => editUriCommand.Execute(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Uri { get; }
|
|
||||||
|
|
||||||
public ICommand EditUriCommand { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -38,8 +38,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
|
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="DisablePrivateVaultPolicyLabel" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
<Grid
|
<Grid
|
||||||
RowSpacing="10"
|
RowSpacing="10"
|
||||||
@@ -56,8 +55,7 @@
|
|||||||
SelectedIndex="{Binding FileFormatSelectedIndex}"
|
SelectedIndex="{Binding FileFormatSelectedIndex}"
|
||||||
SelectedIndexChanged="FileFormat_Changed"
|
SelectedIndexChanged="FileFormat_Changed"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}" />
|
||||||
AutomationId="FileFormatPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
@@ -74,8 +72,7 @@
|
|||||||
HorizontalOptions="Fill"
|
HorizontalOptions="Fill"
|
||||||
VerticalOptions="End"
|
VerticalOptions="End"
|
||||||
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
||||||
Margin="0,0,0,10"
|
Margin="0,0,0,10"/>
|
||||||
AutomationId="SendTOTPCodeButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Grid
|
<Grid
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
@@ -99,8 +96,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding ExportVaultCommand}"
|
ReturnCommand="{Binding ExportVaultCommand}" />
|
||||||
AutomationId="MasterPasswordEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -110,8 +106,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
|
||||||
AutomationId="TogglePasswordVisibilityButton" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ConfirmYourIdentity}"
|
Text="{u:I18n ConfirmYourIdentity}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -133,8 +128,7 @@
|
|||||||
Clicked="ExportVault_Clicked"
|
Clicked="ExportVault_Clicked"
|
||||||
HorizontalOptions="Fill"
|
HorizontalOptions="Fill"
|
||||||
VerticalOptions="End"
|
VerticalOptions="End"
|
||||||
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
|
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
|
||||||
AutomationId="ExportVaultButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly IExportService _exportService;
|
private readonly IExportService _exportService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IUserVerificationService _userVerificationService;
|
private readonly IUserVerificationService _userVerificationService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@@ -44,7 +45,8 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||||
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ namespace Bit.App.Pages
|
|||||||
_initialized = true;
|
_initialized = true;
|
||||||
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
|
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
|
||||||
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
|
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
|
||||||
UseOTPVerification = !await _userVerificationService.HasMasterPasswordAsync(true);
|
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
|
||||||
if (UseOTPVerification)
|
if (UseOTPVerification)
|
||||||
{
|
{
|
||||||
@@ -163,9 +165,9 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
|
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||||
? VerificationType.MasterPassword
|
? VerificationType.OTP
|
||||||
: VerificationType.OTP;
|
: VerificationType.MasterPassword;
|
||||||
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,13 +13,8 @@
|
|||||||
</ContentPage.BindingContext>
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
<ContentPage.ToolbarItems>
|
<ContentPage.ToolbarItems>
|
||||||
<ToolbarItem Text="{u:I18n Cancel}"
|
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
|
||||||
Clicked="Close_Clicked"
|
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" />
|
||||||
Order="Primary"
|
|
||||||
Priority="-1" />
|
|
||||||
<ToolbarItem Text="{u:I18n Save}"
|
|
||||||
Clicked="Save_Clicked"
|
|
||||||
Order="Primary" />
|
|
||||||
<ToolbarItem Text="{u:I18n Delete}"
|
<ToolbarItem Text="{u:I18n Delete}"
|
||||||
Clicked="Delete_Clicked"
|
Clicked="Delete_Clicked"
|
||||||
Order="Secondary"
|
Order="Secondary"
|
||||||
@@ -48,8 +43,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
x:Name="_nameEntry"
|
x:Name="_nameEntry"
|
||||||
ReturnType="Go"
|
ReturnType="Go"
|
||||||
ReturnCommand="{Binding SubmitCommand}"
|
ReturnCommand="{Binding SubmitCommand}" />
|
||||||
AutomationId="FolderNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
Margin="20, 0"
|
Margin="20, 0"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
HorizontalOptions="CenterAndExpand"
|
HorizontalOptions="CenterAndExpand"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center"></Label>
|
||||||
AutomationId="NoFoldersLabel"></Label>
|
|
||||||
<controls:ExtendedCollectionView
|
<controls:ExtendedCollectionView
|
||||||
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
|
||||||
ItemsSource="{Binding Folders}"
|
ItemsSource="{Binding Folders}"
|
||||||
@@ -45,12 +44,10 @@
|
|||||||
<DataTemplate x:DataType="views:FolderView">
|
<DataTemplate x:DataType="views:FolderView">
|
||||||
<controls:ExtendedStackLayout
|
<controls:ExtendedStackLayout
|
||||||
StyleClass="list-row, list-row-platform"
|
StyleClass="list-row, list-row-platform"
|
||||||
Padding="10"
|
Padding="10">
|
||||||
AutomationId="FolderCell">
|
|
||||||
<Label LineBreakMode="TailTruncation"
|
<Label LineBreakMode="TailTruncation"
|
||||||
StyleClass="list-title, list-title-platform"
|
StyleClass="list-title, list-title-platform"
|
||||||
Text="{Binding Name, Mode=OneWay}"
|
Text="{Binding Name, Mode=OneWay}" />
|
||||||
AutomationId="FolderName" />
|
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</CollectionView.ItemTemplate>
|
</CollectionView.ItemTemplate>
|
||||||
|
|||||||
@@ -32,8 +32,7 @@
|
|||||||
Padding="10, 0"
|
Padding="10, 0"
|
||||||
RowSpacing="0"
|
RowSpacing="0"
|
||||||
RowDefinitions="*, Auto, *, 10"
|
RowDefinitions="*, Auto, *, 10"
|
||||||
ColumnDefinitions="*, *"
|
ColumnDefinitions="*, *">
|
||||||
AutomationId="LoginRequestCell">
|
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n FingerprintPhrase}"
|
Text="{u:I18n FingerprintPhrase}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
@@ -46,23 +45,20 @@
|
|||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Padding="0, 5, 0, 10"
|
Padding="0, 5, 0, 10"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
TextColor="{DynamicResource FingerprintPhrase}"
|
TextColor="{DynamicResource FingerprintPhrase}"/>
|
||||||
AutomationId="FingerprintPhraseLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
HorizontalTextAlignment="Start"
|
HorizontalTextAlignment="Start"
|
||||||
Text="{Binding RequestDeviceType}"
|
Text="{Binding RequestDeviceType}"
|
||||||
StyleClass="list-header-sub"
|
StyleClass="list-header-sub" />
|
||||||
AutomationId="RequestDeviceLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
Text="{Binding CreationDate, Converter={StaticResource dateTime}}"
|
Text="{Binding CreationDate, Converter={StaticResource dateTime}}"
|
||||||
StyleClass="list-header-sub"
|
StyleClass="list-header-sub" />
|
||||||
AutomationId="RequestDateLabel" />
|
|
||||||
<BoxView
|
<BoxView
|
||||||
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
StyleClass="list-section-separator-top, list-section-separator-top-platform"
|
||||||
VerticalOptions="End"
|
VerticalOptions="End"
|
||||||
@@ -98,8 +94,7 @@
|
|||||||
Margin="10,0"
|
Margin="10,0"
|
||||||
Icon="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
Icon="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||||
Label="{u:I18n DeclineAllRequests}"
|
Label="{u:I18n DeclineAllRequests}"
|
||||||
ButtonCommand="{Binding DeclineAllRequestsCommand}"
|
ButtonCommand="{Binding DeclineAllRequestsCommand}"/>
|
||||||
AutomationId="DeleteAllRequestsButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|||||||
@@ -27,8 +27,7 @@
|
|||||||
x:Name="_themePicker"
|
x:Name="_themePicker"
|
||||||
ItemsSource="{Binding ThemeOptions, Mode=OneTime}"
|
ItemsSource="{Binding ThemeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding ThemeSelectedIndex}"
|
SelectedIndex="{Binding ThemeSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="ThemeSelectorPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -45,8 +44,7 @@
|
|||||||
x:Name="_autoDarkThemePicker"
|
x:Name="_autoDarkThemePicker"
|
||||||
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
ItemsSource="{Binding AutoDarkThemeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
SelectedIndex="{Binding AutoDarkThemeSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="DefaultDarkThemePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
@@ -61,8 +59,7 @@
|
|||||||
x:Name="_uriMatchPicker"
|
x:Name="_uriMatchPicker"
|
||||||
ItemsSource="{Binding UriMatchOptions, Mode=OneTime}"
|
ItemsSource="{Binding UriMatchOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding UriMatchSelectedIndex}"
|
SelectedIndex="{Binding UriMatchSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="DefaultUriMatchDetectionPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n DefaultUriMatchDetectionDescription}"
|
Text="{u:I18n DefaultUriMatchDetectionDescription}"
|
||||||
@@ -77,8 +74,7 @@
|
|||||||
x:Name="_clearClipboardPicker"
|
x:Name="_clearClipboardPicker"
|
||||||
ItemsSource="{Binding ClearClipboardOptions, Mode=OneTime}"
|
ItemsSource="{Binding ClearClipboardOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding ClearClipboardSelectedIndex}"
|
SelectedIndex="{Binding ClearClipboardSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="ClearClipboardPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ClearClipboardDescription}"
|
Text="{u:I18n ClearClipboardDescription}"
|
||||||
@@ -94,8 +90,7 @@
|
|||||||
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
|
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
|
||||||
SelectedItem="{Binding SelectedLocale}"
|
SelectedItem="{Binding SelectedLocale}"
|
||||||
ItemDisplayBinding="{Binding Value}"
|
ItemDisplayBinding="{Binding Value}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="LanguagePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LanguageChangeRequiresAppRestart}"
|
Text="{u:I18n LanguageChangeRequiresAppRestart}"
|
||||||
@@ -110,8 +105,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding AutoTotpCopy}"
|
IsToggled="{Binding AutoTotpCopy}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="CopyTotpAutomaticallyToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
Text="{u:I18n CopyTotpAutomaticallyDescription}"
|
||||||
@@ -126,8 +120,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Favicon}"
|
IsToggled="{Binding Favicon}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="ShowWebsiteIconsToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ShowWebsiteIconsDescription}"
|
Text="{u:I18n ShowWebsiteIconsDescription}"
|
||||||
@@ -153,14 +146,22 @@
|
|||||||
StyleClass="box-footer-label, box-footer-label-switch" />
|
StyleClass="box-footer-label, box-footer-label-switch" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
|
||||||
<StackLayout.GestureRecognizers>
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
|
<Label
|
||||||
</StackLayout.GestureRecognizers>
|
Text="{u:I18n AutofillBlockedUris}"
|
||||||
|
StyleClass="box-label" />
|
||||||
|
<Editor
|
||||||
|
x:Name="_autofillBlockedUrisEditor"
|
||||||
|
Text="{Binding AutofillBlockedUris}"
|
||||||
|
StyleClass="box-value"
|
||||||
|
AutoSize="TextChanges"
|
||||||
|
IsSpellCheckEnabled="False"
|
||||||
|
IsTextPredictionEnabled="False"
|
||||||
|
Keyboard="Url"
|
||||||
|
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
|
||||||
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n BlockAutoFill}"
|
Text="{u:I18n AutofillBlockedUrisDescription}"
|
||||||
StyleClass="box-label-regular" />
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.PlatformConfiguration;
|
using Xamarin.Forms.PlatformConfiguration;
|
||||||
@@ -42,6 +44,17 @@ namespace Bit.App.Pages
|
|||||||
await _vm.InitAsync();
|
await _vm.InitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async override void OnDisappearing()
|
||||||
|
{
|
||||||
|
base.OnDisappearing();
|
||||||
|
await _vm.UpdateAutofillBlockedUris();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
|
||||||
|
{
|
||||||
|
await _vm.UpdateAutofillBlockedUris();
|
||||||
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -20,6 +19,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IPlatformUtilsService _platformUtilsService;
|
private readonly IPlatformUtilsService _platformUtilsService;
|
||||||
|
|
||||||
private bool _autofillSavePrompt;
|
private bool _autofillSavePrompt;
|
||||||
|
private string _autofillBlockedUris;
|
||||||
private bool _favicon;
|
private bool _favicon;
|
||||||
private bool _autoTotpCopy;
|
private bool _autoTotpCopy;
|
||||||
private int _clearClipboardSelectedIndex;
|
private int _clearClipboardSelectedIndex;
|
||||||
@@ -84,10 +84,6 @@ namespace Bit.App.Pages
|
|||||||
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
|
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
|
||||||
};
|
};
|
||||||
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
|
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
|
||||||
|
|
||||||
GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
|
|
||||||
onException: ex => HandleException(ex),
|
|
||||||
allowsMultipleExecutions: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
|
||||||
@@ -196,18 +192,25 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string AutofillBlockedUris
|
||||||
|
{
|
||||||
|
get => _autofillBlockedUris;
|
||||||
|
set => SetProperty(ref _autofillBlockedUris, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowAndroidAutofillSettings
|
public bool ShowAndroidAutofillSettings
|
||||||
{
|
{
|
||||||
get => _showAndroidAutofillSettings;
|
get => _showAndroidAutofillSettings;
|
||||||
set => SetProperty(ref _showAndroidAutofillSettings, value);
|
set => SetProperty(ref _showAndroidAutofillSettings, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand GoToBlockAutofillUrisCommand { get; }
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
|
||||||
|
|
||||||
|
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
|
||||||
|
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
|
||||||
|
|
||||||
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
|
||||||
|
|
||||||
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
|
||||||
@@ -285,6 +288,41 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAutofillBlockedUris()
|
||||||
|
{
|
||||||
|
if (_inited)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
|
||||||
|
{
|
||||||
|
await _stateService.SetAutofillBlacklistedUrisAsync(null);
|
||||||
|
AutofillBlockedUris = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var csv = AutofillBlockedUris;
|
||||||
|
var urisList = new List<string>();
|
||||||
|
foreach (var uri in csv.Split(','))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(uri))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var cleanedUri = uri.Replace(System.Environment.NewLine, string.Empty).Trim();
|
||||||
|
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
|
||||||
|
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
urisList.Add(cleanedUri);
|
||||||
|
}
|
||||||
|
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
|
||||||
|
AutofillBlockedUris = string.Join(", ", urisList);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateCurrentLocaleAsync()
|
private async Task UpdateCurrentLocaleAsync()
|
||||||
{
|
{
|
||||||
if (!_inited)
|
if (!_inited)
|
||||||
|
|||||||
@@ -32,21 +32,19 @@
|
|||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center" />
|
||||||
</Frame>
|
</Frame>
|
||||||
<controls:CustomLabel IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
|
<Label IsVisible="{Binding UseFrame, Converter={StaticResource inverseBool}}"
|
||||||
Text="{Binding Name, Mode=OneWay}"
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
LineBreakMode="{Binding LineBreakMode}"
|
LineBreakMode="{Binding LineBreakMode}"
|
||||||
HorizontalOptions="StartAndExpand"
|
HorizontalOptions="StartAndExpand"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
StyleClass="list-title"
|
StyleClass="list-title"/>
|
||||||
AutomationId="{Binding AutomationIdSettingName}" />
|
<Label Text="{Binding SubLabel, Mode=OneWay}"
|
||||||
<controls:CustomLabel Text="{Binding SubLabel, Mode=OneWay}"
|
|
||||||
IsVisible="{Binding ShowSubLabel}"
|
IsVisible="{Binding ShowSubLabel}"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalOptions="CenterAndExpand"
|
VerticalOptions="CenterAndExpand"
|
||||||
TextColor="{Binding SubLabelColor}"
|
TextColor="{Binding SubLabelColor}"
|
||||||
StyleClass="list-sub"
|
StyleClass="list-sub" />
|
||||||
AutomationId="{Binding AutomationIdSettingStatus}" />
|
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate
|
<DataTemplate
|
||||||
@@ -59,8 +57,7 @@
|
|||||||
Padding="10"
|
Padding="10"
|
||||||
HasShadow="False"
|
HasShadow="False"
|
||||||
BackgroundColor="Transparent"
|
BackgroundColor="Transparent"
|
||||||
BorderColor="{DynamicResource PrimaryColor}"
|
BorderColor="{DynamicResource PrimaryColor}">
|
||||||
AutomationId="SettingActivePolicyTextLabel">
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Name, Mode=OneWay}"
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
@@ -78,8 +75,7 @@
|
|||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
TextColor="{Binding SubLabelColor}"
|
TextColor="{Binding SubLabelColor}"
|
||||||
StyleClass="list-sub" Margin="-5"
|
StyleClass="list-sub" Margin="-5"/>
|
||||||
AutomationId="SettingCustomVaultTimeoutPicker" />
|
|
||||||
<controls:ExtendedStackLayout.GestureRecognizers>
|
<controls:ExtendedStackLayout.GestureRecognizers>
|
||||||
<TapGestureRecognizer Tapped="ActivateTimePicker"/>
|
<TapGestureRecognizer Tapped="ActivateTimePicker"/>
|
||||||
</controls:ExtendedStackLayout.GestureRecognizers>
|
</controls:ExtendedStackLayout.GestureRecognizers>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.App.Utilities.Automation;
|
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -24,29 +22,5 @@ namespace Bit.App.Pages
|
|||||||
public Color SubLabelColor => SubLabelTextEnabled ?
|
public Color SubLabelColor => SubLabelTextEnabled ?
|
||||||
ThemeManager.GetResourceColor("SuccessColor") :
|
ThemeManager.GetResourceColor("SuccessColor") :
|
||||||
ThemeManager.GetResourceColor("MutedColor");
|
ThemeManager.GetResourceColor("MutedColor");
|
||||||
|
|
||||||
public string AutomationIdSettingName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return AutomationIdsHelper.AddSuffixFor(
|
|
||||||
UseFrame ? "EnabledPolicy"
|
|
||||||
: AutomationIdsHelper.ToEnglishTitleCase(Name)
|
|
||||||
, SuffixType.Cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string AutomationIdSettingStatus
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (UseFrame)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AutomationIdsHelper.AddSuffixFor(AutomationIdsHelper.ToEnglishTitleCase(Name), SuffixType.SettingValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IBiometricService _biometricService;
|
private readonly IBiometricService _biometricService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ILocalizeService _localizeService;
|
private readonly ILocalizeService _localizeService;
|
||||||
private readonly IUserVerificationService _userVerificationService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
private readonly IClipboardService _clipboardService;
|
private readonly IClipboardService _clipboardService;
|
||||||
private readonly ILogger _loggerService;
|
private readonly ILogger _loggerService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
@@ -48,7 +48,6 @@ namespace Bit.App.Pages
|
|||||||
private bool _reportLoggingEnabled;
|
private bool _reportLoggingEnabled;
|
||||||
private bool _approvePasswordlessLoginRequests;
|
private bool _approvePasswordlessLoginRequests;
|
||||||
private bool _shouldConnectToWatch;
|
private bool _shouldConnectToWatch;
|
||||||
private bool _hasMasterPassword;
|
|
||||||
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
|
||||||
new List<KeyValuePair<string, int?>>
|
new List<KeyValuePair<string, int?>>
|
||||||
{
|
{
|
||||||
@@ -89,7 +88,7 @@ namespace Bit.App.Pages
|
|||||||
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
|
||||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
|
||||||
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
|
||||||
@@ -101,17 +100,12 @@ namespace Bit.App.Pages
|
|||||||
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin;
|
|
||||||
|
|
||||||
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
|
||||||
|
|
||||||
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
|
|
||||||
// set has true for backwards compatibility
|
|
||||||
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
|
|
||||||
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
|
||||||
var lastSync = await _syncService.GetLastSyncAsync();
|
var lastSync = await _syncService.GetLastSyncAsync();
|
||||||
if (lastSync != null)
|
if (lastSync != null)
|
||||||
@@ -130,17 +124,8 @@ namespace Bit.App.Pages
|
|||||||
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
|
||||||
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
|
||||||
|
|
||||||
|
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
||||||
var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync();
|
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
|
||||||
_pin = pinSet != PinLockType.Disabled;
|
|
||||||
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
|
||||||
var timeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
|
|
||||||
if (!IsVaultTimeoutActionLockAllowed && timeoutAction == VaultTimeoutAction.Lock)
|
|
||||||
{
|
|
||||||
timeoutAction = VaultTimeoutAction.Logout;
|
|
||||||
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
|
||||||
}
|
|
||||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == timeoutAction).Key;
|
|
||||||
|
|
||||||
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
|
||||||
{
|
{
|
||||||
@@ -152,6 +137,10 @@ namespace Bit.App.Pages
|
|||||||
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
|
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
|
||||||
t.Value != null).ToList();
|
t.Value != null).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
_pin = pinSet.Item1 || pinSet.Item2;
|
||||||
|
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
|
||||||
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
|
||||||
|
|
||||||
if (_vaultTimeoutDisplayValue == null)
|
if (_vaultTimeoutDisplayValue == null)
|
||||||
@@ -159,7 +148,8 @@ namespace Bit.App.Pages
|
|||||||
_vaultTimeoutDisplayValue = AppResources.Custom;
|
_vaultTimeoutDisplayValue = AppResources.Custom;
|
||||||
}
|
}
|
||||||
|
|
||||||
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && await _userVerificationService.HasMasterPasswordAsync();
|
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
|
||||||
|
!await _keyConnectorService.GetUsesKeyConnector();
|
||||||
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
_reportLoggingEnabled = await _loggerService.IsEnabled();
|
||||||
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
|
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
|
||||||
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
|
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
|
||||||
@@ -333,7 +323,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (oldTimeout != newTimeout)
|
if (oldTimeout != newTimeout)
|
||||||
{
|
{
|
||||||
await _cryptoService.RefreshKeysAsync();
|
|
||||||
await Device.InvokeOnMainThreadAsync(BuildList);
|
await Device.InvokeOnMainThreadAsync(BuildList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,11 +387,8 @@ namespace Bit.App.Pages
|
|||||||
// do nothing if we have a policy set
|
// do nothing if we have a policy set
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var options = _vaultTimeoutActionOptions.Select(o =>
|
||||||
var options = IsVaultTimeoutActionLockAllowed
|
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
|
||||||
? _vaultTimeoutActionOptions.Select(o => CreateSelectableOption(o.Key, _vaultTimeoutActionDisplayValue == o.Key)).ToArray()
|
|
||||||
: _vaultTimeoutActionOptions.Where(o => o.Value == VaultTimeoutAction.Logout).Select(v => ToSelectedOption(v.Key)).ToArray();
|
|
||||||
|
|
||||||
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
|
||||||
AppResources.Cancel, null, options);
|
AppResources.Cancel, null, options);
|
||||||
if (selection == null || selection == AppResources.Cancel)
|
if (selection == null || selection == AppResources.Cancel)
|
||||||
@@ -442,7 +428,7 @@ namespace Bit.App.Pages
|
|||||||
if (!string.IsNullOrWhiteSpace(pin))
|
if (!string.IsNullOrWhiteSpace(pin))
|
||||||
{
|
{
|
||||||
var masterPassOnRestart = false;
|
var masterPassOnRestart = false;
|
||||||
if (await _userVerificationService.HasMasterPasswordAsync())
|
if (!await _keyConnectorService.GetUsesKeyConnector())
|
||||||
{
|
{
|
||||||
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
|
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
|
||||||
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
|
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
|
||||||
@@ -451,20 +437,19 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
|
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
|
||||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
var key = await _cryptoService.GetKeyAsync();
|
||||||
var protectedPinKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
|
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
||||||
|
|
||||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
|
||||||
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
|
||||||
|
|
||||||
if (masterPassOnRestart)
|
if (masterPassOnRestart)
|
||||||
{
|
{
|
||||||
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(protectedPinKey);
|
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||||
|
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
||||||
|
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
|
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -474,8 +459,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (!_pin)
|
if (!_pin)
|
||||||
{
|
{
|
||||||
|
await _cryptoService.ClearPinProtectedKeyAsync();
|
||||||
await _vaultTimeoutService.ClearAsync();
|
await _vaultTimeoutService.ClearAsync();
|
||||||
await UpdateVaultTimeoutActionIfNeededAsync();
|
|
||||||
}
|
}
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
@@ -504,10 +489,9 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetBiometricUnlockAsync(null);
|
await _stateService.SetBiometricUnlockAsync(null);
|
||||||
await UpdateVaultTimeoutActionIfNeededAsync();
|
|
||||||
}
|
}
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
await _cryptoService.RefreshKeysAsync();
|
await _cryptoService.ToggleKeyAsync();
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,11 +835,9 @@ namespace Bit.App.Pages
|
|||||||
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string CreateSelectableOption(string option, bool selected) => selected ? ToSelectedOption(option) : option;
|
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
|
||||||
|
|
||||||
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == ToSelectedOption(compareTo);
|
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
|
||||||
|
|
||||||
private string ToSelectedOption(string option) => $"✓ {option}";
|
|
||||||
|
|
||||||
public async Task SetScreenCaptureAllowedAsync()
|
public async Task SetScreenCaptureAllowedAsync()
|
||||||
{
|
{
|
||||||
@@ -887,17 +869,5 @@ namespace Bit.App.Pages
|
|||||||
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateVaultTimeoutActionIfNeededAsync()
|
|
||||||
{
|
|
||||||
if (IsVaultTimeoutActionLockAllowed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.First(o => o.Value == VaultTimeoutAction.Logout).Key;
|
|
||||||
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
|
|
||||||
_deviceActionService.Toast(AppResources.VaultTimeoutActionChangedToLogOut);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await UpdateVaultButtonTitleAsync();
|
await UpdateVaultButtonTitleAsync();
|
||||||
if (await _keyConnectorService.UserNeedsMigrationAsync())
|
if (await _keyConnectorService.UserNeedsMigration())
|
||||||
{
|
{
|
||||||
_messagingService.Send("convertAccountToKeyConnector");
|
_messagingService.Send("convertAccountToKeyConnector");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,25 +33,23 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row" Padding="10, 20"
|
<StackLayout StyleClass="box-row" Padding="10, 20"
|
||||||
IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding HasAttachments, Converter={StaticResource inverseBool}}">
|
||||||
<Label Text="{u:I18n NoAttachments}" HorizontalTextAlignment="Center" AutomationId="NoAttachmentsLabel" />
|
<Label Text="{u:I18n NoAttachments}" HorizontalTextAlignment="Center" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<controls:RepeaterView ItemsSource="{Binding Attachments}" IsVisible="{Binding HasAttachments}" AutomationId="AttachmentsList">
|
<controls:RepeaterView ItemsSource="{Binding Attachments}" IsVisible="{Binding HasAttachments}">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:AttachmentView">
|
<DataTemplate x:DataType="views:AttachmentView">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10" AutomationId="AttachmentRow">
|
<StackLayout Orientation="Horizontal" StyleClass="box-row" Spacing="10">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding FileName, Mode=OneWay}"
|
Text="{Binding FileName, Mode=OneWay}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center"
|
||||||
HorizontalOptions="StartAndExpand"
|
HorizontalOptions="StartAndExpand" />
|
||||||
AutomationId="AttachmentFileNameLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding SizeName, Mode=OneWay}"
|
Text="{Binding SizeName, Mode=OneWay}"
|
||||||
StyleClass="box-sub-label"
|
StyleClass="box-sub-label"
|
||||||
HorizontalTextAlignment="End"
|
HorizontalTextAlignment="End"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="AttachmentFileSizeLabel" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
|
||||||
@@ -59,8 +57,7 @@
|
|||||||
CommandParameter="{Binding .}"
|
CommandParameter="{Binding .}"
|
||||||
VerticalOptions="Center"
|
VerticalOptions="Center"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Delete}"
|
AutomationProperties.Name="{u:I18n Delete}" />
|
||||||
AutomationId="AttachmentDeleteButton" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -80,20 +77,17 @@
|
|||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="NoFileChosenLabel" />
|
|
||||||
<Label
|
<Label
|
||||||
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
|
||||||
Text="{Binding FileName}"
|
Text="{Binding FileName}"
|
||||||
LineBreakMode="CharacterWrap"
|
LineBreakMode="CharacterWrap"
|
||||||
StyleClass="text-sm, text-muted"
|
StyleClass="text-sm, text-muted"
|
||||||
HorizontalOptions="FillAndExpand"
|
HorizontalOptions="FillAndExpand"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="NewAttachmentNameLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Button Text="{u:I18n ChooseFile}" StyleClass="box-button-row"
|
<Button Text="{u:I18n ChooseFile}" StyleClass="box-button-row"
|
||||||
Clicked="ChooseFile_Clicked"
|
Clicked="ChooseFile_Clicked"></Button>
|
||||||
AutomationId="ChooseFileButton"></Button>
|
|
||||||
<Label
|
<Label
|
||||||
Margin="0, 10, 0, 0"
|
Margin="0, 10, 0, 0"
|
||||||
Text="{u:I18n MaxFileSize}"
|
Text="{u:I18n MaxFileSize}"
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Bit.App.Pages
|
|||||||
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
_cipherDomain = await _cipherService.GetAsync(CipherId);
|
||||||
Cipher = await _cipherDomain.DecryptAsync();
|
Cipher = await _cipherDomain.DecryptAsync();
|
||||||
LoadAttachments();
|
LoadAttachments();
|
||||||
_hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
|
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
||||||
if (!_canAccessAttachments)
|
if (!_canAccessAttachments)
|
||||||
@@ -156,7 +156,7 @@ namespace Bit.App.Pages
|
|||||||
// Prevent Android from locking if vault timeout set to "immediate"
|
// Prevent Android from locking if vault timeout set to "immediate"
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
|
_vaultTimeoutService.DelayTimeoutMs = 60000;
|
||||||
}
|
}
|
||||||
await _fileService.SelectFileAsync();
|
await _fileService.SelectFileAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
|
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _cipher, value, additionalPropertyNames: AdditionalPropertiesToRaiseOnCipherChanged);
|
set => SetProperty(ref _cipher, value, additionalPropertyNames: AdditionalPropertiesToRaiseOnCipherChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreationDate => string.Format(AppResources.CreatedXY, Cipher.CreationDate.ToShortDateString(), Cipher.CreationDate.ToShortTimeString());
|
|
||||||
|
|
||||||
public AsyncCommand CheckPasswordCommand { get; }
|
public AsyncCommand CheckPasswordCommand { get; }
|
||||||
|
|
||||||
protected async Task CheckPasswordAsync()
|
protected async Task CheckPasswordAsync()
|
||||||
|
|||||||
@@ -57,16 +57,16 @@
|
|||||||
x:Key="deleteItem" />
|
x:Key="deleteItem" />
|
||||||
|
|
||||||
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
<DataTemplate x:Key="TextCustomFieldDataTemplate">
|
||||||
<il:TextCustomFieldItemLayout AutomationId="TextCustomFieldItem" />
|
<il:TextCustomFieldItemLayout />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
<DataTemplate x:Key="BooleanCustomFieldDataTemplate">
|
||||||
<il:BooleanCustomFieldItemLayout AutomationId="BooleanCustomFieldItem" />
|
<il:BooleanCustomFieldItemLayout />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
<DataTemplate x:Key="HiddenCustomFieldDataTemplate">
|
||||||
<il:HiddenCustomFieldItemLayout AutomationId="HiddenCustomFieldItem" />
|
<il:HiddenCustomFieldItemLayout />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
<DataTemplate x:Key="LinkedCustomFieldDataTemplate">
|
||||||
<il:LinkedCustomFieldItemLayout AutomationId="LinkedCustomFieldItem" />
|
<il:LinkedCustomFieldItemLayout />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
<dts:CustomFieldItemTemplateSelector x:Key="CustomFieldItemTemplateSelector"
|
||||||
@@ -100,8 +100,7 @@
|
|||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PersonalOwnershipPolicyInEffect}"
|
Text="{u:I18n PersonalOwnershipPolicyInEffect}"
|
||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center"
|
HorizontalTextAlignment="Center" />
|
||||||
AutomationId="PersonalOwnershipPolicyLabel"/>
|
|
||||||
</Frame>
|
</Frame>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -117,8 +116,7 @@
|
|||||||
x:Name="_typePicker"
|
x:Name="_typePicker"
|
||||||
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
|
ItemsSource="{Binding TypeOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding TypeSelectedIndex}"
|
SelectedIndex="{Binding TypeSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="ItemTypePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -129,8 +127,7 @@
|
|||||||
Text="{Binding Cipher.Name}"
|
Text="{Binding Cipher.Name}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Name}"
|
AutomationProperties.Name="{u:I18n Name}" />
|
||||||
AutomationId="ItemNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsLogin}" Spacing="0" Padding="0">
|
||||||
<Grid StyleClass="box-row, box-row-input"
|
<Grid StyleClass="box-row, box-row-input"
|
||||||
@@ -145,8 +142,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Username}"
|
AutomationProperties.Name="{u:I18n Username}"/>
|
||||||
AutomationId="LoginUsernameEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
@@ -154,8 +150,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GenerateUsername}"
|
AutomationProperties.Name="{u:I18n GenerateUsername}" />
|
||||||
AutomationId="GenerateUsernameButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -185,8 +180,7 @@
|
|||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
IsEnabled="{Binding Cipher.ViewPassword}"
|
IsEnabled="{Binding Cipher.ViewPassword}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Password}"
|
AutomationProperties.Name="{u:I18n Password}"/>
|
||||||
AutomationId="LoginPasswordEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.CheckCircle}}"
|
||||||
@@ -196,8 +190,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CheckPassword}"
|
AutomationProperties.Name="{u:I18n CheckPassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}"
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
AutomationId="CheckPasswordButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowPasswordIcon}"
|
Text="{Binding ShowPasswordIcon}"
|
||||||
@@ -208,8 +201,7 @@
|
|||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
||||||
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}"
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
AutomationId="ViewPasswordButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
|
||||||
@@ -219,21 +211,9 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
AutomationProperties.Name="{u:I18n GeneratePassword}"
|
||||||
IsVisible="{Binding Cipher.ViewPassword}"
|
IsVisible="{Binding Cipher.ViewPassword}" />
|
||||||
AutomationId="RegeneratePasswordButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n Passkey}"
|
|
||||||
StyleClass="box-label"
|
|
||||||
Margin="0,10,0,0"
|
|
||||||
IsVisible="{Binding ShowPasskeyInfo}"/>
|
|
||||||
<Entry
|
|
||||||
Text="{Binding CreationDate}"
|
|
||||||
IsEnabled="False"
|
|
||||||
StyleClass="box-value,text-muted"
|
|
||||||
IsVisible="{Binding ShowPasskeyInfo}" />
|
|
||||||
|
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -267,8 +247,7 @@
|
|||||||
Padding="0,15"
|
Padding="0,15"
|
||||||
HorizontalOptions="Center"
|
HorizontalOptions="Center"
|
||||||
VerticalOptions="FillAndExpand"
|
VerticalOptions="FillAndExpand"
|
||||||
VerticalTextAlignment="Center"
|
VerticalTextAlignment="Center" />
|
||||||
AutomationId="SetupTotpButton" />
|
|
||||||
</Frame>
|
</Frame>
|
||||||
<controls:MonoEntry
|
<controls:MonoEntry
|
||||||
x:Name="_loginTotpEntry"
|
x:Name="_loginTotpEntry"
|
||||||
@@ -283,8 +262,7 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="{Binding TotpColumnSpan}"
|
Grid.ColumnSpan="{Binding TotpColumnSpan}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n AuthenticatorKey}"
|
AutomationProperties.Name="{u:I18n AuthenticatorKey}" />
|
||||||
AutomationId="LoginTotpEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
|
||||||
@@ -294,8 +272,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CopyTotp}"
|
AutomationProperties.Name="{u:I18n CopyTotp}" />
|
||||||
AutomationId="CopyTotpValueButton" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Camera}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Camera}}"
|
||||||
@@ -305,8 +282,7 @@
|
|||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
IsVisible="{Binding HasTotpValue}"
|
IsVisible="{Binding HasTotpValue}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ScanQrTitle}"
|
AutomationProperties.Name="{u:I18n ScanQrTitle}" />
|
||||||
/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsCard}" Spacing="0" Padding="0">
|
||||||
@@ -317,8 +293,7 @@
|
|||||||
<Entry
|
<Entry
|
||||||
x:Name="_cardholderNameEntry"
|
x:Name="_cardholderNameEntry"
|
||||||
Text="{Binding Cipher.Card.CardholderName}"
|
Text="{Binding Cipher.Card.CardholderName}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="CardholderNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -344,8 +319,7 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Number}"
|
AutomationProperties.Name="{u:I18n Number}" />
|
||||||
AutomationId="CardNumberEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardNumberIcon}"
|
Text="{Binding ShowCardNumberIcon}"
|
||||||
@@ -354,8 +328,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
AutomationId="ShowCardNumberButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -365,8 +338,7 @@
|
|||||||
x:Name="_cardBrandPicker"
|
x:Name="_cardBrandPicker"
|
||||||
ItemsSource="{Binding CardBrandOptions, Mode=OneTime}"
|
ItemsSource="{Binding CardBrandOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding CardBrandSelectedIndex}"
|
SelectedIndex="{Binding CardBrandSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="CardBrandPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -376,8 +348,7 @@
|
|||||||
x:Name="_cardExpMonthPicker"
|
x:Name="_cardExpMonthPicker"
|
||||||
ItemsSource="{Binding CardExpMonthOptions, Mode=OneTime}"
|
ItemsSource="{Binding CardExpMonthOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding CardExpMonthSelectedIndex}"
|
SelectedIndex="{Binding CardExpMonthSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="CardExpirationMonthPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -389,8 +360,7 @@
|
|||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
Keyboard="Numeric"
|
Keyboard="Numeric"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ExpirationYear}"
|
AutomationProperties.Name="{u:I18n ExpirationYear}" />
|
||||||
AutomationId="CardExpirationYearEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Grid StyleClass="box-row, box-row-input">
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -417,8 +387,7 @@
|
|||||||
IsSpellCheckEnabled="False"
|
IsSpellCheckEnabled="False"
|
||||||
IsTextPredictionEnabled="False"
|
IsTextPredictionEnabled="False"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SecurityCode}"
|
AutomationProperties.Name="{u:I18n SecurityCode}" />
|
||||||
AutomationId="CardSecurityCodeEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding ShowCardCodeIcon}"
|
Text="{Binding ShowCardCodeIcon}"
|
||||||
@@ -427,8 +396,7 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ToggleVisibility}"
|
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
|
||||||
AutomationId="CardShowSecurityCodeButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
<StackLayout IsVisible="{Binding IsIdentity}" Spacing="0" Padding="0">
|
||||||
@@ -440,8 +408,7 @@
|
|||||||
x:Name="_identityTitlePicker"
|
x:Name="_identityTitlePicker"
|
||||||
ItemsSource="{Binding IdentityTitleOptions, Mode=OneTime}"
|
ItemsSource="{Binding IdentityTitleOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding IdentityTitleSelectedIndex}"
|
SelectedIndex="{Binding IdentityTitleSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="IdentityTitlePicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -452,8 +419,7 @@
|
|||||||
Text="{Binding Cipher.Identity.FirstName}"
|
Text="{Binding Cipher.Identity.FirstName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n FirstName}"
|
AutomationProperties.Name="{u:I18n FirstName}"/>
|
||||||
AutomationId="IdentityFirstNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -464,8 +430,7 @@
|
|||||||
Text="{Binding Cipher.Identity.MiddleName}"
|
Text="{Binding Cipher.Identity.MiddleName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n MiddleName}"
|
AutomationProperties.Name="{u:I18n MiddleName}" />
|
||||||
AutomationId="IdentityMiddleNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -476,8 +441,7 @@
|
|||||||
Text="{Binding Cipher.Identity.LastName}"
|
Text="{Binding Cipher.Identity.LastName}"
|
||||||
StyleClass="box-value,capitalize-word-input"
|
StyleClass="box-value,capitalize-word-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LastName}"
|
AutomationProperties.Name="{u:I18n LastName}" />
|
||||||
AutomationId="IdentityLastNameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -488,8 +452,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Username}"
|
Text="{Binding Cipher.Identity.Username}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Username}"
|
AutomationProperties.Name="{u:I18n Username}" />
|
||||||
AutomationId="IdentityUsernameEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -500,8 +463,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Company}"
|
Text="{Binding Cipher.Identity.Company}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Company}"
|
AutomationProperties.Name="{u:I18n Company}"/>
|
||||||
AutomationId="IdentityCompanyEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -512,8 +474,7 @@
|
|||||||
Text="{Binding Cipher.Identity.SSN}"
|
Text="{Binding Cipher.Identity.SSN}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n SSN}"
|
AutomationProperties.Name="{u:I18n SSN}"/>
|
||||||
AutomationId="IdentitySsnEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -524,8 +485,7 @@
|
|||||||
Text="{Binding Cipher.Identity.PassportNumber}"
|
Text="{Binding Cipher.Identity.PassportNumber}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n PassportNumber}"
|
AutomationProperties.Name="{u:I18n PassportNumber}"/>
|
||||||
AutomationId="IdentityPassportNumberEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -536,8 +496,7 @@
|
|||||||
Text="{Binding Cipher.Identity.LicenseNumber}"
|
Text="{Binding Cipher.Identity.LicenseNumber}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n LicenseNumber}"
|
AutomationProperties.Name="{u:I18n LicenseNumber}" />
|
||||||
AutomationId="IdentityLicenseNumberEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -549,8 +508,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Email}"
|
Text="{Binding Cipher.Identity.Email}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Email}"
|
AutomationProperties.Name="{u:I18n Email}"/>
|
||||||
AutomationId="IdentityEmailEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -562,8 +520,7 @@
|
|||||||
Keyboard="Telephone"
|
Keyboard="Telephone"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Phone}"
|
AutomationProperties.Name="{u:I18n Phone}" />
|
||||||
AutomationId="IdentityPhoneEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -574,8 +531,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Address1}"
|
Text="{Binding Cipher.Identity.Address1}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address1}"
|
AutomationProperties.Name="{u:I18n Address1}"/>
|
||||||
AutomationId="IdentityAddressOneEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -586,8 +542,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Address2}"
|
Text="{Binding Cipher.Identity.Address2}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address2}"
|
AutomationProperties.Name="{u:I18n Address2}" />
|
||||||
AutomationId="IdentityAddressTwoEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -598,8 +553,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Address3}"
|
Text="{Binding Cipher.Identity.Address3}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Address3}"
|
AutomationProperties.Name="{u:I18n Address3}" />
|
||||||
AutomationId="IdentityAddressThreeEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -610,8 +564,7 @@
|
|||||||
Text="{Binding Cipher.Identity.City}"
|
Text="{Binding Cipher.Identity.City}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n CityTown}"
|
AutomationProperties.Name="{u:I18n CityTown}" />
|
||||||
AutomationId="IdentityCityEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -622,8 +575,7 @@
|
|||||||
Text="{Binding Cipher.Identity.State}"
|
Text="{Binding Cipher.Identity.State}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n StateProvince}"
|
AutomationProperties.Name="{u:I18n StateProvince}" />
|
||||||
AutomationId="IdentityStateEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -634,8 +586,7 @@
|
|||||||
Text="{Binding Cipher.Identity.PostalCode}"
|
Text="{Binding Cipher.Identity.PostalCode}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n ZipPostalCode}"
|
AutomationProperties.Name="{u:I18n ZipPostalCode}" />
|
||||||
AutomationId="IdentityPostalCodeEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-input">
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<Label
|
<Label
|
||||||
@@ -646,8 +597,7 @@
|
|||||||
Text="{Binding Cipher.Identity.Country}"
|
Text="{Binding Cipher.Identity.Country}"
|
||||||
StyleClass="box-value,capitalize-sentence-input"
|
StyleClass="box-value,capitalize-sentence-input"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Country}"
|
AutomationProperties.Name="{u:I18n Country}" />
|
||||||
AutomationId="IdentityCountryEntry" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -659,7 +609,7 @@
|
|||||||
<controls:RepeaterView ItemsSource="{Binding Uris}">
|
<controls:RepeaterView ItemsSource="{Binding Uris}">
|
||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="views:LoginUriView">
|
<DataTemplate x:DataType="views:LoginUriView">
|
||||||
<Grid StyleClass="box-row, box-row-input" AutomationId="UriListGrid" >
|
<Grid StyleClass="box-row, box-row-input">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
@@ -680,8 +630,7 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n URI}"
|
AutomationProperties.Name="{u:I18n URI}" />
|
||||||
AutomationId="LoginUriEntry" />
|
|
||||||
<controls:IconButton
|
<controls:IconButton
|
||||||
StyleClass="box-row-button, box-row-button-platform"
|
StyleClass="box-row-button, box-row-button-platform"
|
||||||
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
|
||||||
@@ -691,15 +640,13 @@
|
|||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Options}"
|
AutomationProperties.Name="{u:I18n Options}" />
|
||||||
AutomationId="LoginUriOptionsButton" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</controls:RepeaterView.ItemTemplate>
|
</controls:RepeaterView.ItemTemplate>
|
||||||
</controls:RepeaterView>
|
</controls:RepeaterView>
|
||||||
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
|
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
|
||||||
Clicked="NewUri_Clicked"
|
Clicked="NewUri_Clicked"></Button>
|
||||||
AutomationId="LoginAddNewUriButton"></Button>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -714,8 +661,7 @@
|
|||||||
x:Name="_folderPicker"
|
x:Name="_folderPicker"
|
||||||
ItemsSource="{Binding FolderOptions, Mode=OneTime}"
|
ItemsSource="{Binding FolderOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding FolderSelectedIndex}"
|
SelectedIndex="{Binding FolderSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="FolderPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
@@ -725,8 +671,7 @@
|
|||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Cipher.Favorite}"
|
IsToggled="{Binding Cipher.Favorite}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="ItemFavoriteToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout x:Name="_passwordPrompt" StyleClass="box-row, box-row-switch">
|
<StackLayout x:Name="_passwordPrompt" StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
@@ -744,8 +689,7 @@
|
|||||||
IsToggled="{Binding PasswordPrompt}"
|
IsToggled="{Binding PasswordPrompt}"
|
||||||
Toggled="PasswordPrompt_Toggled"
|
Toggled="PasswordPrompt_Toggled"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End" />
|
||||||
AutomationId="MasterPasswordRepromptToggle" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -762,8 +706,7 @@
|
|||||||
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
effects:ScrollEnabledEffect.IsScrollEnabled="false"
|
||||||
Text="{Binding Cipher.Notes}"
|
Text="{Binding Cipher.Notes}"
|
||||||
AutomationProperties.IsInAccessibleTree="True"
|
AutomationProperties.IsInAccessibleTree="True"
|
||||||
AutomationProperties.Name="{u:I18n Notes}"
|
AutomationProperties.Name="{u:I18n Notes}" >
|
||||||
AutomationId="ItemNotesEntry">
|
|
||||||
<Editor.Behaviors>
|
<Editor.Behaviors>
|
||||||
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
|
||||||
</Editor.Behaviors>
|
</Editor.Behaviors>
|
||||||
@@ -782,11 +725,9 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Spacing="0"
|
Spacing="0"
|
||||||
BindableLayout.ItemsSource="{Binding Fields}"
|
BindableLayout.ItemsSource="{Binding Fields}"
|
||||||
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}"
|
BindableLayout.ItemTemplateSelector="{StaticResource CustomFieldItemTemplateSelector}" />
|
||||||
AutomationId="CustomFieldsList" />
|
|
||||||
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
|
<Button Text="{u:I18n NewCustomField}" StyleClass="box-button-row"
|
||||||
Clicked="NewField_Clicked"
|
Clicked="NewField_Clicked"></Button>
|
||||||
AutomationId="NewCustomFieldButton"></Button>
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowOwnershipOptions}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowOwnershipOptions}">
|
||||||
<StackLayout StyleClass="box-row-header">
|
<StackLayout StyleClass="box-row-header">
|
||||||
@@ -801,8 +742,7 @@
|
|||||||
x:Name="_ownershipPicker"
|
x:Name="_ownershipPicker"
|
||||||
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}"
|
ItemsSource="{Binding OwnershipOptions, Mode=OneTime}"
|
||||||
SelectedIndex="{Binding OwnershipSelectedIndex}"
|
SelectedIndex="{Binding OwnershipSelectedIndex}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value" />
|
||||||
AutomationId="ItemOwnershipPicker" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout StyleClass="box" IsVisible="{Binding ShowCollections}">
|
<StackLayout StyleClass="box" IsVisible="{Binding ShowCollections}">
|
||||||
@@ -813,8 +753,7 @@
|
|||||||
<StackLayout Spacing="0" Padding="0"
|
<StackLayout Spacing="0" Padding="0"
|
||||||
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding HasCollections, Converter={StaticResource inverseBool}}">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label Text="{u:I18n NoCollectionsToList}"
|
<Label Text="{u:I18n NoCollectionsToList}" />
|
||||||
AutomationId="NoCollectionsToListLabel" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -824,17 +763,15 @@
|
|||||||
<controls:RepeaterView.ItemTemplate>
|
<controls:RepeaterView.ItemTemplate>
|
||||||
<DataTemplate x:DataType="pages:CollectionViewModel">
|
<DataTemplate x:DataType="pages:CollectionViewModel">
|
||||||
<StackLayout Spacing="0" Padding="0">
|
<StackLayout Spacing="0" Padding="0">
|
||||||
<StackLayout StyleClass="box-row, box-row-switch" AutomationId="CollectionItemCell">
|
<StackLayout StyleClass="box-row, box-row-switch">
|
||||||
<Label
|
<Label
|
||||||
Text="{Binding Collection.Name}"
|
Text="{Binding Collection.Name}"
|
||||||
StyleClass="box-label-regular"
|
StyleClass="box-label-regular"
|
||||||
HorizontalOptions="StartAndExpand"
|
HorizontalOptions="StartAndExpand" />
|
||||||
AutomationId="CollectionItemNameLabel" />
|
|
||||||
<Switch
|
<Switch
|
||||||
IsToggled="{Binding Checked}"
|
IsToggled="{Binding Checked}"
|
||||||
StyleClass="box-value"
|
StyleClass="box-value"
|
||||||
HorizontalOptions="End"
|
HorizontalOptions="End"/>
|
||||||
AutomationId="CollectionItemSwitch" />
|
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
<BoxView StyleClass="box-row-separator" />
|
<BoxView StyleClass="box-row-separator" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -844,4 +781,5 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly IDeviceActionService _deviceActionService;
|
private readonly IDeviceActionService _deviceActionService;
|
||||||
private readonly IAutofillHandler _autofillHandler;
|
private readonly IAutofillHandler _autofillHandler;
|
||||||
private readonly IVaultTimeoutService _vaultTimeoutService;
|
private readonly IVaultTimeoutService _vaultTimeoutService;
|
||||||
private readonly IUserVerificationService _userVerificationService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
|
||||||
private CipherAddEditPageViewModel _vm;
|
private CipherAddEditPageViewModel _vm;
|
||||||
private bool _fromAutofill;
|
private bool _fromAutofill;
|
||||||
@@ -43,7 +43,7 @@ namespace Bit.App.Pages
|
|||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
||||||
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
|
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
|
||||||
|
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_fromAutofill = fromAutofill;
|
_fromAutofill = fromAutofill;
|
||||||
@@ -175,8 +175,8 @@ namespace Bit.App.Pages
|
|||||||
RequestFocus(_nameEntry);
|
RequestFocus(_nameEntry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Hide password reprompt option if using key connector
|
||||||
_passwordPrompt.IsVisible = await _userVerificationService.HasMasterPasswordAsync();
|
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ namespace Bit.App.Pages
|
|||||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||||
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
|
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
|
||||||
|
|
||||||
|
|
||||||
GeneratePasswordCommand = new Command(GeneratePassword);
|
GeneratePasswordCommand = new Command(GeneratePassword);
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
ToggleCardNumberCommand = new Command(ToggleCardNumber);
|
||||||
@@ -308,7 +309,6 @@ namespace Bit.App.Pages
|
|||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
|
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
|
||||||
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
|
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
|
||||||
public bool ShowPasskeyInfo => Cipher?.HasFido2Key == true && !CloneMode;
|
|
||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
@@ -367,11 +367,6 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Cipher.OrganizationId = OrganizationId;
|
Cipher.OrganizationId = OrganizationId;
|
||||||
}
|
}
|
||||||
if (Cipher.Type == CipherType.Login)
|
|
||||||
{
|
|
||||||
// passkeys can't be cloned
|
|
||||||
Cipher.Login.Fido2Keys = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login)
|
if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user