1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-15 15:53:44 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
André Bispo
f75cc95cce [SG-765] Add cancel button to passwordless login modal on android 2022-10-31 14:36:03 +00:00
381 changed files with 2193 additions and 18862 deletions

View File

@@ -7,12 +7,6 @@
"commands": [ "commands": [
"dotnet-format" "dotnet-format"
] ]
},
"cake.tool": {
"version": "2.2.0",
"commands": [
"dotnet-cake"
]
} }
} }
} }

View File

@@ -1,6 +1,6 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Report mobile AUTOFILL failure - name: Report mobile autofill failure
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users! about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users!
- name: Feature Requests - name: Feature Requests

View File

@@ -14,10 +14,6 @@
<string>Dist: Extension 2021</string> <string>Dist: Extension 2021</string>
<key>com.8bit.bitwarden.share-extension</key> <key>com.8bit.bitwarden.share-extension</key>
<string>Dist: Share Extension 2021</string> <string>Dist: Share Extension 2021</string>
<key>com.8bit.bitwarden.watchkitapp</key>
<string>Dist: Bitwarden Watch App</string>
<key>com.8bit.bitwarden.watchkitapp.watchkitextension</key>
<string>Dist: Bitwarden Watch App Extension</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

Binary file not shown.

View File

@@ -4,10 +4,10 @@ name: Build
on: on:
push: push:
branches-ignore: branches-ignore:
- "l10n_master" - 'l10n_master'
- "gh-pages" - 'gh-pages'
paths-ignore: paths-ignore:
- ".github/workflows/**" - '.github/workflows/**'
workflow_dispatch: workflow_dispatch:
inputs: {} inputs: {}
@@ -42,15 +42,15 @@ jobs:
id: branch-check id: branch-check
run: | run: |
if [[ $(git ls-remote --heads origin rc) ]]; then if [[ $(git ls-remote --heads origin rc) ]]; then
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT echo "::set-output name=rc_branch_exists::1"
else else
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT echo "::set-output name=rc_branch_exists::0"
fi fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT echo "::set-output name=hotfix_branch_exists::1"
else else
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT echo "::set-output name=hotfix_branch_exists::0"
fi fi
shell: bash shell: bash
@@ -59,13 +59,9 @@ jobs:
name: Android name: Android
runs-on: windows-2022 runs-on: windows-2022
needs: setup needs: setup
strategy:
fail-fast: false
matrix:
variant: ["prod", "qa"]
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
@@ -91,6 +87,7 @@ jobs:
Write-Host "components were not installed" Write-Host "components were not installed"
exit 1 exit 1
} }
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
@@ -101,8 +98,7 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
fetch-depth: 0
- name: Decrypt secrets - name: Decrypt secrets
env: env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -113,17 +109,12 @@ jobs:
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg --output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg --output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg --output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash shell: bash
- name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER)) BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
@@ -148,48 +139,29 @@ jobs:
shell: pwsh shell: pwsh
- name: Run Core tests - name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" || true run: dotnet test test/Core.Test/Core.Test.csproj
shell: pwsh
- name: Report test results
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226
if: always()
with:
name: Test Results
path: "**/test-results.trx"
reporter: dotnet-trx
fail-on-error: true
- name: Build Play Store publisher - name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }}
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Setup Android build (${{ matrix.variant }}) - name: Build for Play Store
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build Android
run: | run: |
$configuration = "Release"; $configuration = "Release";
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Build $configuration Configuration" Write-Output "##### Build $configuration Configuration"
Write-Output "########################################" Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh shell: pwsh
- name: Sign Android Build - name: Sign for Play Store
env: env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }} PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: | run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj"); $androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration" Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################" Write-Output "########################################"
@@ -203,8 +175,9 @@ jobs:
Write-Output "##### Copy Google Play Bundle to project root" Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################" Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab"); $signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab"); $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################" Write-Output "########################################"
@@ -220,41 +193,33 @@ jobs:
Write-Output "##### Copy Release APK to project root" Write-Output "##### Copy Release APK to project root"
Write-Output "########################################" Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk"); $signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk"); $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh shell: pwsh
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }} - name: Upload Play Store .aab artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden.aab name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab
if-no-files-found: error if-no-files-found: error
- name: Upload Prod .apk artifact - name: Upload Play Store .apk artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden.apk name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk
if-no-files-found: error if-no-files-found: error
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
if-no-files-found: error
- name: Deploy to Play Store - name: Deploy to Play Store
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master' if: |
&& needs.setup.outputs.rc_branch_exists == 0 (github.ref == 'refs/heads/master'
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.rc_branch_exists == 0
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }} || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: | run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll" PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json" CREDS_PATH="$HOME/secrets/play_creds.json"
@@ -270,7 +235,7 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
@@ -450,11 +415,11 @@ jobs:
ios: ios:
name: Apple iOS name: Apple iOS
runs-on: macos-12 runs-on: macos-11
needs: setup needs: setup
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
@@ -485,7 +450,7 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Decrypt secrets - name: Decrypt secrets
@@ -507,14 +472,6 @@ jobs:
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision \ --output $HOME/secrets/dist_share_extension.mobileprovision \
./.github/secrets/dist_share_extension.mobileprovision.gpg ./.github/secrets/dist_share_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app.mobileprovision \
./.github/secrets/dist_watch_app.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
@@ -529,9 +486,6 @@ jobs:
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
cd ../../..
shell: bash shell: bash
- name: Update Entitlements - name: Update Entitlements
@@ -566,8 +520,6 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH" mkdir -p "$PROFILES_DIR_PATH"
@@ -583,25 +535,6 @@ jobs:
SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}") SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision" cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
WATCH_APP_UUID=$(grep UUID -A1 -a $WATCH_APP_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_UUID.mobileprovision"
WATCH_APP_EXTENSION_UUID=$(grep UUID -A1 -a $WATCH_APP_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_EXTENSION_UUID.mobileprovision"
shell: bash
- name: Bulid WatchApp
run: |
echo "########################################"
echo "##### Build WatchApp with Release Configuration"
echo "########################################"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "########################################"
echo "##### Done"
echo "########################################"
shell: bash shell: bash
- name: Restore packages - name: Restore packages
@@ -639,12 +572,7 @@ jobs:
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs" ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
EXPORT_PATH="./bitwarden-export" EXPORT_PATH="./bitwarden-export"
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/" cp -r $ARCHIVE_DSYMS_PATH $EXPORT_PATH
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
shell: bash shell: bash
- name: Upload App Store .ipa & dSYMs artifacts - name: Upload App Store .ipa & dSYMs artifacts
@@ -667,39 +595,23 @@ jobs:
- name: Upload dSYMs to App Center - name: Upload dSYMs to App Center
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash shell: bash
- name: Upload Watch dSYMs to Firebase Crashlytics
if: |
(github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: |
echo "########################################"
echo "##### Uploading Watch dSYMs to Firebase"
echo "########################################"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
shell: bash
- name: Deploy to App Store - name: Deploy to App Store
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }} APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
@@ -739,7 +651,7 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Upload Sources - name: Upload Sources
@@ -767,9 +679,9 @@ jobs:
steps: steps:
- name: Check if any job failed - name: Check if any job failed
if: | if: |
(github.ref == 'refs/heads/master') (github.ref == 'refs/heads/master')
|| (github.ref == 'refs/heads/rc') || (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix-rc') || (github.ref == 'refs/heads/hotfix-rc')
env: env:
CLOC_STATUS: ${{ needs.cloc.result }} CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }} ANDROID_STATUS: ${{ needs.android.result }}
@@ -807,7 +719,7 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Notify Slack on failure - name: Notify Slack on failure

View File

@@ -12,5 +12,5 @@ jobs:
- name: Enforce Label - name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with: with:
BANNED_LABELS: "hold,needs-qa" BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged" BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@@ -1,6 +1,5 @@
--- ---
name: Release name: Release
run-name: Release ${{ inputs.release_type }}
on: on:
workflow_dispatch: workflow_dispatch:
@@ -52,10 +51,9 @@ jobs:
id: branch id: branch
run: | run: |
BRANCH_NAME=$(basename ${{ github.ref }}) BRANCH_NAME=$(basename ${{ github.ref }})
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Create GitHub deployment - name: Create GitHub deployment
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48 uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
id: deployment id: deployment
with: with:
@@ -74,7 +72,7 @@ jobs:
workflow_conclusion: success workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }} branch: ${{ steps.branch.outputs.branch-name }}
- name: Dry Run - Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
@@ -101,7 +99,7 @@ jobs:
draft: true draft: true
- name: Update deployment status to Success - name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} if: ${{ success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86 uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
@@ -109,7 +107,7 @@ jobs:
deployment-id: ${{ steps.deployment.outputs.deployment_id }} deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure - name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} if: ${{ failure() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86 uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
@@ -135,7 +133,7 @@ jobs:
branch: ${{ needs.release.outputs.branch-name }} branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - 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@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:

View File

@@ -21,15 +21,15 @@ jobs:
env: env:
RELEASE_TAG: ${{ github.ref }} RELEASE_TAG: ${{ github.ref }}
run: | run: |
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/') CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/') CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
echo "Current Major: $CURR_MAJOR" echo "Current Major: $CURR_MAJOR"
echo "Current Patch: $CURR_PATCH" echo "Current Patch: $CURR_PATCH"
NEW_PATCH=$((CURR_PATCH+1)) NEW_PATCH=$((CURR_PATCH+1))
NEW_VER=$CURR_MAJOR.$NEW_PATCH NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo "New Version: $NEW_VER" echo "New Version: $NEW_VER"
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT echo "::set-output name=new-version::$NEW_VER"
trigger_version_bump: trigger_version_bump:
name: "Trigger version bump workflow" name: "Trigger version bump workflow"

View File

@@ -37,7 +37,8 @@ jobs:
git_commit_gpgsign: true git_commit_gpgsign: true
- name: Create Version Branch - name: Create Version Branch
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@03ad9a873c39cdc95dd8d77dbbda67f84db43945 uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
@@ -78,15 +79,16 @@ jobs:
id: version-changed id: version-changed
run: | run: |
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT echo "::set-output name=changes_to_commit::TRUE"
else else
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT echo "::set-output name=changes_to_commit::FALSE"
echo "No changes to commit!"; echo "No changes to commit!";
fi fi
- name: Commit files - name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a run: |
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes - name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}

126
.gitignore vendored
View File

@@ -209,129 +209,3 @@ FakesAssemblies/
project.lock.json project.lock.json
.DS_Store .DS_Store
src/App/Css src/App/Css
tools
# Created by https://www.toptal.com/developers/gitignore/api/swift,objective-c
# Edit at https://www.toptal.com/developers/gitignore?templates=swift,objective-c
### Objective-C ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Objective-C Patch ###
### Swift ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
# Accio dependency management
Dependencies/
.accio/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
# End of https://www.toptal.com/developers/gitignore/api/swift,objective-c

View File

@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
# Build/Run # Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/clients/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. Please refer to the [Mobile section](https://contributing.bitwarden.com/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
# We're Hiring! # We're Hiring!

View File

@@ -1,346 +0,0 @@
#addin nuget:?package=Cake.FileHelpers&version=5.0.0
#addin nuget:?package=Cake.AndroidAppManifest&version=1.1.2
#addin nuget:?package=Cake.Plist&version=0.7.0
#addin nuget:?package=Cake.Incubator&version=7.0.0
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
using Path = System.IO.Path;
var debugScript = Argument<bool>("debugScript", false);
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var variant = Argument("variant", "dev");
abstract record VariantConfig(
string AppName,
string AndroidPackageName,
string iOSBundleId,
string ApsEnvironment
);
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development");
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production");
VariantConfig GetVariant() => variant.ToLower() switch{
"qa" => new QA(),
"beta" => new Beta(),
"prod" => new Prod(),
_ => new Dev()
};
GitVersion _gitVersion; //will be set by GetGitInfo task
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
int CreateBuildNumber(int previousNumber) => ++previousNumber;
Task("GetGitInfo")
.Does(()=> {
_gitVersion = GitVersion(new GitVersionSettings());
if(debugScript)
{
Information($"GitVersion Dump:\n{_gitVersion.Dump()}");
}
Information("Git data Load successfully.");
});
#region Android
Task("UpdateAndroidAppIcon")
.Does(()=>{
//TODO we'll implement variant icons later
//manifest.ApplicationIcon = "@mipmap/ic_launcher";
Information($"Updated Androix App Icon with success");
});
Task("UpdateAndroidManifest")
.IsDependentOn("GetGitInfo")
.Does(()=>
{
var buildVariant = GetVariant();
var manifestPath = Path.Combine(_slnPath, "src", "Android", "Properties", "AndroidManifest.xml");
// Cake.AndroidAppManifest doesn't currently enable us to access nested items so, quick (not ideal) fix:
var manifestText = FileReadText(manifestPath);
manifestText = manifestText.Replace("com.x8bit.bitwarden.", buildVariant.AndroidPackageName + ".");
manifestText = manifestText.Replace("android:label=\"Bitwarden\"", $"android:label=\"{buildVariant.AppName}\"");
FileWriteText(manifestPath, manifestText);
var manifest = DeserializeAppManifest(manifestPath);
var prevVersionCode = manifest.VersionCode;
var prevVersionName = manifest.VersionName;
_androidPackageName = manifest.PackageName;
//manifest.VersionCode = CreateBuildNumber(prevVersionCode);
manifest.VersionName = GetVersionName(prevVersionName, buildVariant, _gitVersion);
manifest.PackageName = buildVariant.AndroidPackageName;
manifest.ApplicationLabel = buildVariant.AppName;
//Information($"AndroidManigest.xml VersionCode from {prevVersionCode} to {manifest.VersionCode}");
Information($"AndroidManigest.xml VersionName from {prevVersionName} to {manifest.VersionName}");
Information($"AndroidManigest.xml PackageName from {_androidPackageName} to {buildVariant.AndroidPackageName}");
Information($"AndroidManigest.xml ApplicationLabel to {buildVariant.AppName}");
SerializeAppManifest(manifestPath, manifest);
Information("AndroidManifest updated with success!");
});
void ReplaceInFile(string filePath, string oldtext, string newtext)
{
var fileText = FileReadText(filePath);
if(string.IsNullOrEmpty(fileText) || !fileText.Contains(oldtext))
{
throw new Exception($"Couldn't find {filePath} or it didn't contain: {oldtext}");
}
fileText = fileText.Replace(oldtext, newtext);
FileWriteText(filePath, fileText);
Information($"{filePath} modified successfully.");
}
Task("UpdateAndroidCodeFiles")
.IsDependentOn("UpdateAndroidManifest")
.Does(()=> {
var buildVariant = GetVariant();
//We're not using _androidPackageName here because the codefile is currently slightly different string than the one in AndroidManifest.xml
var keyName = "com.8bit.bitwarden";
var fixedPackageName = buildVariant.AndroidPackageName.Replace("x8bit", "8bit");
var filePath = Path.Combine(_slnPath, "src", "Android", "Services", "BiometricService.cs");
ReplaceInFile(filePath, keyName, fixedPackageName);
var packageFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "MainActivity.cs"),
Path.Combine(_slnPath, "src", "Android", "MainApplication.cs"),
Path.Combine(_slnPath, "src", "Android", "Constants.cs"),
Path.Combine(_slnPath, "src", "Android", "Accessibility", "AccessibilityService.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillHelpers.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "ClearClipboardAlarmReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "EventUploadReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "PackageReplacedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "RestrictionsChangedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "DeviceActionService.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "FileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "AutofillTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "GeneratorTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "MyVaultTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "google-services.json"),
Path.Combine(_slnPath, "store", "google", "Publisher", "Program.cs"),
};
foreach(string path in packageFileList)
{
ReplaceInFile(path, "com.x8bit.bitwarden", buildVariant.AndroidPackageName);
}
var labelFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
};
foreach(string path in labelFileList)
{
ReplaceInFile(path, "Bitwarden\"", $"{buildVariant.AppName}\"");
}
});
#endregion Android
#region iOS
enum iOSProjectType
{
Null,
MainApp,
Autofill,
Extension,
ShareExtension
}
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
_ => buildVariant.iOSBundleId
};
string GetiOSBundleName(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.AppName} Autofill",
iOSProjectType.Extension => $"{buildVariant.AppName} Extension",
iOSProjectType.ShareExtension => $"{buildVariant.AppName} Share Extension",
_ => buildVariant.AppName
};
private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, GitVersion git, iOSProjectType projectType = iOSProjectType.MainApp)
{
var plistFile = File(plistPath);
dynamic plist = DeserializePlist(plistFile);
var prevVersionName = plist["CFBundleShortVersionString"];
var prevVersionString = plist["CFBundleVersion"];
var prevVersion = int.Parse(plist["CFBundleVersion"]);
var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString();
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType);
plist["CFBundleName"] = newBundleName;
plist["CFBundleDisplayName"] = newBundleName;
//plist["CFBundleVersion"] = newVersion;
plist["CFBundleShortVersionString"] = newVersionName;
plist["CFBundleIdentifier"] = newBundleId;
if(projectType == iOSProjectType.MainApp)
{
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
}
if(projectType == iOSProjectType.Extension)
{
var keyText = plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"];
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
}
SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}");
//Information($"Changed Bundle Version from {prevVersion} to {newVersion}");
Information($"Changed Bundle Short Version name from {prevVersionName} to {newVersionName}");
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
Information($"{plistPath} updated with success!");
}
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
{
var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
Information($"Changed ApsEnvironment name to {buildVariant.ApsEnvironment}");
Information($"Changed keychain-access-groups bundleID to {buildVariant.iOSBundleId}");
SerializePlist(EntitlementlistFile, Entitlements);
Information($"{entitlementsPath} updated with success!");
}
Task("UpdateiOSIcon")
.Does(()=>{
//TODO we'll implement variant icons later
Information($"Updating IOS App Icon");
});
Task("UpdateiOSPlist")
.IsDependentOn("GetGitInfo")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSAutofillPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSShareExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSCodeFiles")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var fileList = new string[] {
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
Path.Combine(".github", "resources", "export-options-app-store.plist"),
};
foreach(string path in fileList)
{
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
}
});
#endregion iOS
#region Main Tasks
Task("Android")
//.IsDependentOn("UpdateAndroidAppIcon")
.IsDependentOn("UpdateAndroidManifest")
.IsDependentOn("UpdateAndroidCodeFiles")
.Does(()=>
{
Information("Android app updated");
});
Task("iOS")
//.IsDependentOn("UpdateiOSIcon")
.IsDependentOn("UpdateiOSPlist")
.IsDependentOn("UpdateiOSAutofillPlist")
.IsDependentOn("UpdateiOSExtensionPlist")
.IsDependentOn("UpdateiOSShareExtensionPlist")
.IsDependentOn("UpdateiOSCodeFiles")
.Does(()=>
{
Information("iOS app updated");
});
Task("Default")
.Does(() => {
var usage = @"Missing target.
Usage:
dotnet cake build.cake --target (Android | iOS) --variant (dev | qa | beta | prod)
Options:
--debugScript=<bool> Script debug mode.
";
Information(usage);
});
#endregion Main Tasks
RunTarget(target);

View File

@@ -33,7 +33,6 @@ namespace Bit.Droid.Accessibility
// - Resources/xml/autofillservice.xml // - Resources/xml/autofillservice.xml
new Browser("alook.browser", "search_fragment_input_view"), new Browser("alook.browser", "search_fragment_input_view"),
new Browser("alook.browser.google", "search_fragment_input_view"), new Browser("alook.browser.google", "search_fragment_input_view"),
new Browser("app.vanadium.browser", "url_bar"),
new Browser("com.amazon.cloud9", "url"), new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"), new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"), new Browser("com.android.chrome", "url_bar"),
@@ -67,7 +66,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.mmbox.xbrowser", "search_box"), new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"), new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"), new Browser("com.naver.whale", "url_bar"),
new Browser("com.neeva.app", "full_url_text_view"),
new Browser("com.opera.browser", "url_field"), new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"), new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.gx", "addressbarEdit"), new Browser("com.opera.gx", "addressbarEdit"),
@@ -76,7 +74,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.opera.touch", "addressbarEdit"), new Browser("com.opera.touch", "addressbarEdit"),
new Browser("com.qflair.browserq", "url"), new Browser("com.qflair.browserq", "url"),
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4) new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
new Browser("com.rainsee.create", "search_box"),
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"), new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"), new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
new Browser("com.stoutner.privacybrowser.free", "url_edittext"), new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
@@ -86,9 +83,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.vivaldi.browser.sopranos", "url_bar"), new Browser("com.vivaldi.browser.sopranos", "url_bar"),
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title", new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0) (s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
new Browser("com.yjllq.internet", "search_box"),
new Browser("com.yjllq.kito", "search_box"),
new Browser("com.yujian.ResideMenuDemo", "search_box"),
new Browser("com.z28j.feel", "g2"), new Browser("com.z28j.feel", "g2"),
new Browser("idm.internet.download.manager", "search"), new Browser("idm.internet.download.manager", "search"),
new Browser("idm.internet.download.manager.adm.lite", "search"), new Browser("idm.internet.download.manager.adm.lite", "search"),
@@ -96,7 +90,6 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"), new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"), new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"), new Browser("mark.via.gp", "as"),
new Browser("net.dezor.browser", "url_bar"),
new Browser("net.slions.fulguris.full.download", "search"), new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "search"), new Browser("net.slions.fulguris.full.download.debug", "search"),
new Browser("net.slions.fulguris.full.playstore", "search"), new Browser("net.slions.fulguris.full.playstore", "search"),
@@ -136,7 +129,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.htc.sense.browser", "title"), new Browser("com.htc.sense.browser", "title"),
new Browser("com.jerky.browser2", "enterUrl"), new Browser("com.jerky.browser2", "enterUrl"),
new Browser("com.ksmobile.cb", "address_bar_edit_text"), new Browser("com.ksmobile.cb", "address_bar_edit_text"),
new Browser("com.lemurbrowser.exts","url_bar"),
new Browser("com.linkbubble.playstore", "url_text"), new Browser("com.linkbubble.playstore", "url_text"),
new Browser("com.mx.browser", "address_editor_with_progress"), new Browser("com.mx.browser", "address_editor_with_progress"),
new Browser("com.mx.browser.tablet", "address_editor_with_progress"), new Browser("com.mx.browser.tablet", "address_editor_with_progress"),

View File

@@ -15,7 +15,7 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix> <MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix> <MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion> <TargetFrameworkVersion>v12.1</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
@@ -77,12 +77,12 @@
<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.5.1.1" /> <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.14" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.13" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.17" /> <PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.16" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0.1" /> <PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.15" /> <PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.14" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" /> <PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1" />
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version> <Version>1.7.3</Version>
</PackageReference> </PackageReference>
@@ -103,10 +103,8 @@
<Compile Include="Accessibility\Browser.cs" /> <Compile Include="Accessibility\Browser.cs" />
<Compile Include="Accessibility\NodeList.cs" /> <Compile Include="Accessibility\NodeList.cs" />
<Compile Include="Accessibility\KnownUsernameField.cs" /> <Compile Include="Accessibility\KnownUsernameField.cs" />
<Compile Include="Autofill\AutofillConstants.cs" />
<Compile Include="Autofill\AutofillHelpers.cs" /> <Compile Include="Autofill\AutofillHelpers.cs" />
<Compile Include="Autofill\AutofillService.cs" /> <Compile Include="Autofill\AutofillService.cs" />
<Compile Include="Autofill\AutofillExternalSelectionActivity.cs" />
<Compile Include="Autofill\Field.cs" /> <Compile Include="Autofill\Field.cs" />
<Compile Include="Autofill\FieldCollection.cs" /> <Compile Include="Autofill\FieldCollection.cs" />
<Compile Include="Autofill\FilledItem.cs" /> <Compile Include="Autofill\FilledItem.cs" />
@@ -158,8 +156,6 @@
<Compile Include="Services\FileService.cs" /> <Compile Include="Services\FileService.cs" />
<Compile Include="Services\AutofillHandler.cs" /> <Compile Include="Services\AutofillHandler.cs" />
<Compile Include="Constants.cs" /> <Compile Include="Constants.cs" />
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
<Compile Include="Services\WatchDeviceService.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" /> <AndroidAsset Include="Assets\bwi-font.ttf" />

Binary file not shown.

View File

@@ -1,10 +0,0 @@
namespace Bit.Droid.Autofill
{
public class AutofillConstants
{
public const string AutofillFramework = "autofillFramework";
public const string AutofillFrameworkFillType = "autofillFrameworkFillType";
public const string AutofillFrameworkUri = "autofillFrameworkUri";
public const string AutofillFrameworkCipherId = "autofillFrameworkCipherId";
}
}

View File

@@ -1,42 +0,0 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
public class AutofillExternalSelectionActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(AutofillConstants.AutofillFrameworkCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformAutofillAsync(cipherId).FireAndForget();
}
private async Task GetCipherAndPerformAutofillAsync(string cipherId)
{
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@@ -54,7 +54,6 @@ namespace Bit.Droid.Autofill
{ {
"alook.browser", "alook.browser",
"alook.browser.google", "alook.browser.google",
"app.vanadium.browser",
"com.amazon.cloud9", "com.amazon.cloud9",
"com.android.browser", "com.android.browser",
"com.android.chrome", "com.android.chrome",
@@ -79,7 +78,6 @@ namespace Bit.Droid.Autofill
"com.jamal2367.styx", "com.jamal2367.styx",
"com.kiwibrowser.browser", "com.kiwibrowser.browser",
"com.kiwibrowser.browser.dev", "com.kiwibrowser.browser.dev",
"com.lemurbrowser.exts",
"com.microsoft.emmx", "com.microsoft.emmx",
"com.microsoft.emmx.beta", "com.microsoft.emmx.beta",
"com.microsoft.emmx.canary", "com.microsoft.emmx.canary",
@@ -88,7 +86,6 @@ namespace Bit.Droid.Autofill
"com.mmbox.xbrowser", "com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser", "com.mycompany.app.soulbrowser",
"com.naver.whale", "com.naver.whale",
"com.neeva.app",
"com.opera.browser", "com.opera.browser",
"com.opera.browser.beta", "com.opera.browser.beta",
"com.opera.gx", "com.opera.gx",
@@ -97,7 +94,6 @@ namespace Bit.Droid.Autofill
"com.opera.touch", "com.opera.touch",
"com.qflair.browserq", "com.qflair.browserq",
"com.qwant.liberty", "com.qwant.liberty",
"com.rainsee.create",
"com.sec.android.app.sbrowser", "com.sec.android.app.sbrowser",
"com.sec.android.app.sbrowser.beta", "com.sec.android.app.sbrowser.beta",
"com.stoutner.privacybrowser.free", "com.stoutner.privacybrowser.free",
@@ -106,9 +102,6 @@ namespace Bit.Droid.Autofill
"com.vivaldi.browser.snapshot", "com.vivaldi.browser.snapshot",
"com.vivaldi.browser.sopranos", "com.vivaldi.browser.sopranos",
"com.yandex.browser", "com.yandex.browser",
"com.yjllq.internet",
"com.yjllq.kito",
"com.yujian.ResideMenuDemo",
"com.z28j.feel", "com.z28j.feel",
"idm.internet.download.manager", "idm.internet.download.manager",
"idm.internet.download.manager.adm.lite", "idm.internet.download.manager.adm.lite",
@@ -116,7 +109,6 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven", "io.github.forkmaintainers.iceraven",
"mark.via", "mark.via",
"mark.via.gp", "mark.via.gp",
"net.dezor.browser",
"net.slions.fulguris.full.download", "net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug", "net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore", "net.slions.fulguris.full.playstore",
@@ -215,7 +207,7 @@ namespace Bit.Droid.Autofill
} }
} }
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i], var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
true, inlinePresentationSpec); inlinePresentationSpec);
if (dataset != null) if (dataset != null)
{ {
responseBuilder.AddDataset(dataset); responseBuilder.AddDataset(dataset);
@@ -229,7 +221,7 @@ namespace Bit.Droid.Autofill
} }
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem, public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null) InlinePresentationSpec inlinePresentationSpec = null)
{ {
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
filledItem.Name, filledItem.Name,
@@ -250,15 +242,6 @@ namespace Bit.Droid.Autofill
{ {
datasetBuilder.SetInlinePresentation(inlinePresentation); datasetBuilder.SetInlinePresentation(inlinePresentation);
} }
if (includeAuthIntent)
{
var intent = new Intent(context, typeof(AutofillExternalSelectionActivity));
intent.PutExtra(AutofillConstants.AutofillFramework, true);
intent.PutExtra(AutofillConstants.AutofillFrameworkCipherId, filledItem.Id);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
}
if (filledItem.ApplyToFields(fields, datasetBuilder)) if (filledItem.ApplyToFields(fields, datasetBuilder))
{ {
return datasetBuilder.Build(); return datasetBuilder.Build();
@@ -270,26 +253,25 @@ namespace Bit.Droid.Autofill
IList<InlinePresentationSpec> inlinePresentationSpecs = null) IList<InlinePresentationSpec> inlinePresentationSpecs = null)
{ {
var intent = new Intent(context, typeof(MainActivity)); var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra(AutofillConstants.AutofillFramework, true); intent.PutExtra("autofillFramework", true);
if (fields.FillableForLogin) if (fields.FillableForLogin)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
} }
else if (fields.FillableForCard) else if (fields.FillableForCard)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
} }
else if (fields.FillableForIdentity) else if (fields.FillableForIdentity)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
} }
else else
{ {
return null; return null;
} }
intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri); intent.PutExtra("autofillFrameworkUri", uri);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden, AppResources.AutofillWithBitwarden,

View File

@@ -23,7 +23,6 @@ namespace Bit.Droid.Autofill
public FilledItem(CipherView cipher) public FilledItem(CipherView cipher)
{ {
Id = cipher.Id;
Name = cipher.Name; Name = cipher.Name;
Type = cipher.Type; Type = cipher.Type;
Subtitle = cipher.SubTitle; Subtitle = cipher.SubTitle;
@@ -56,7 +55,6 @@ namespace Bit.Droid.Autofill
} }
} }
public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Subtitle { get; set; } = string.Empty; public string Subtitle { get; set; } = string.Empty;
public int Icon { get; set; } = Resource.Drawable.login; public int Icon { get; set; } = Resource.Drawable.login;

View File

@@ -1,23 +0,0 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(RemoveFontPaddingEffect), nameof(RemoveFontPaddingEffect))]
namespace Bit.Droid.Effects
{
public class RemoveFontPaddingEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetIncludeFontPadding(false);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -18,7 +18,6 @@ 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 Bit.Droid.Autofill;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -323,13 +322,13 @@ namespace Bit.Droid
{ {
var options = new AppOptions var options = new AppOptions
{ {
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri), Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false), MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false), GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false), FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
CreateSend = GetCreateSendRequest(Intent) CreateSend = GetCreateSendRequest(Intent)
}; };
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0); var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if (fillType > 0) if (fillType > 0)
{ {
options.FillType = (CipherType)fillType; options.FillType = (CipherType)fillType;

View File

@@ -45,16 +45,9 @@ namespace Bit.Droid
if (ServiceContainer.RegisteredServices.Count == 0) if (ServiceContainer.RegisteredServices.Count == 0)
{ {
RegisterLocalServices(); RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey, ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey,
Core.Constants.AndroidAllClearCipherCacheKeys); Core.Constants.AndroidAllClearCipherCacheKeys);
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>()));
InitializeAppSetup(); InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged // TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
@@ -80,9 +73,8 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"), ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"), ServiceContainer.Resolve<IAuthService>("authService"),
ServiceContainer.Resolve<ILogger>("logger"), ServiceContainer.Resolve<ILogger>("logger"),
ServiceContainer.Resolve<IMessagingService>("messagingService"), ServiceContainer.Resolve<IMessagingService>("messagingService"));
ServiceContainer.Resolve<IWatchDeviceService>()); ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -149,10 +141,9 @@ namespace Bit.Droid
var clipboardService = new ClipboardService(stateService); var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(stateService, messagingService); var deviceActionService = new DeviceActionService(stateService, messagingService);
var fileService = new FileService(stateService, broadcasterService); var fileService = new FileService(stateService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, new LazyResolve<IEventService>());
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService); messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(); var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.1.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="2022.10.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -40,16 +40,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) --> <!-- Package visibility (for Android 11+) -->
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="*" />
<data android:scheme="http"/>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
</intent> </intent>
</queries> </queries>
</manifest> </manifest>

View File

@@ -1,5 +1,4 @@
using Android.Content; using Android.Content;
using Android.OS;
namespace Bit.Droid.Receivers namespace Bit.Droid.Receivers
{ {
@@ -9,17 +8,7 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent) public override void OnReceive(Context context, Intent intent)
{ {
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager; var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
if (clipboardManager == null) clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
{
return;
}
// ClearPrimaryClip is supported down to API 28 with mixed results, so we're requiring 33+ instead
if ((int)Build.VERSION.SdkInt < 33)
{
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
return;
}
clipboardManager.ClearPrimaryClip();
} }
} }
} }

View File

@@ -44,7 +44,6 @@ namespace Bit.Droid.Renderers
} }
if (Control != null) if (Control != null)
{ {
Control.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null); var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb) if (t is GradientDrawable thumb)
{ {

View File

@@ -17,9 +17,6 @@
<compatibility-package <compatibility-package
android:name="alook.browser.google" android:name="alook.browser.google"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="app.vanadium.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.amazon.cloud9" android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -92,9 +89,6 @@
<compatibility-package <compatibility-package
android:name="com.kiwibrowser.browser.dev" android:name="com.kiwibrowser.browser.dev"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.lemurbrowser.exts"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.microsoft.emmx" android:name="com.microsoft.emmx"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -119,9 +113,6 @@
<compatibility-package <compatibility-package
android:name="com.naver.whale" android:name="com.naver.whale"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.neeva.app"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.opera.browser" android:name="com.opera.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -146,9 +137,6 @@
<compatibility-package <compatibility-package
android:name="com.qwant.liberty" android:name="com.qwant.liberty"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.rainsee.create"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.sec.android.app.sbrowser" android:name="com.sec.android.app.sbrowser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -173,15 +161,6 @@
<compatibility-package <compatibility-package
android:name="com.yandex.browser" android:name="com.yandex.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.internet"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.kito"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yujian.ResideMenuDemo"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.z28j.feel" android:name="com.z28j.feel"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -203,9 +182,6 @@
<compatibility-package <compatibility-package
android:name="mark.via.gp" android:name="mark.via.gp"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.dezor.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="net.slions.fulguris.full.download" android:name="net.slions.fulguris.full.download"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>

View File

@@ -6,7 +6,6 @@ using Android.Content;
using Android.OS; using Android.OS;
using Android.Provider; using Android.Provider;
using Android.Views.Autofill; using Android.Views.Autofill;
using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.View; using Bit.Core.Models.View;
@@ -21,19 +20,16 @@ namespace Bit.Droid.Services
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly LazyResolve<IEventService> _eventService; private readonly LazyResolve<IEventService> _eventService;
public AutofillHandler(IStateService stateService, public AutofillHandler(IStateService stateService,
IMessagingService messagingService, IMessagingService messagingService,
IClipboardService clipboardService, IClipboardService clipboardService,
IPlatformUtilsService platformUtilsService,
LazyResolve<IEventService> eventService) LazyResolve<IEventService> eventService)
{ {
_stateService = stateService; _stateService = stateService;
_messagingService = messagingService; _messagingService = messagingService;
_clipboardService = clipboardService; _clipboardService = clipboardService;
_platformUtilsService = platformUtilsService;
_eventService = eventService; _eventService = eventService;
} }
@@ -77,12 +73,12 @@ namespace Bit.Droid.Services
public void Autofill(CipherView cipher) public void Autofill(CipherView cipher)
{ {
var activity = CrossCurrentActivity.Current.Activity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null) if (activity == null)
{ {
return; return;
} }
if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false) if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
{ {
if (cipher == null) if (cipher == null)
{ {
@@ -107,7 +103,7 @@ namespace Bit.Droid.Services
return; return;
} }
var task = CopyTotpAsync(cipher); var task = CopyTotpAsync(cipher);
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher), false); var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
var replyIntent = new Intent(); var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset); replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent); activity.SetResult(Result.Ok, replyIntent);
@@ -206,7 +202,6 @@ namespace Bit.Droid.Services
if (totp != null) if (totp != null)
{ {
await _clipboardService.CopyTextAsync(totp); await _clipboardService.CopyTextAsync(totp);
_platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
} }
} }
} }

View File

@@ -6,6 +6,7 @@ using Android.OS;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Xamarin.Essentials; using Xamarin.Essentials;
namespace Bit.Droid.Services namespace Bit.Droid.Services
@@ -20,9 +21,9 @@ namespace Bit.Droid.Services
_stateService = stateService; _stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() => _clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(Application.Context, PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
0, 0,
new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)), new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false))); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
} }
@@ -44,7 +45,7 @@ namespace Bit.Droid.Services
} }
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to")) catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{ {
// #1962 Just ignore, the content is copied either way but there is some app interfering in the process // #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
// that the OS catches and just throws this exception. // that the OS catches and just throws this exception.
} }
} }
@@ -57,7 +58,9 @@ namespace Bit.Droid.Services
private void CopyToClipboard(string text, bool isSensitive = true) private void CopyToClipboard(string text, bool isSensitive = true)
{ {
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text); var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive) if (isSensitive)
{ {
@@ -84,7 +87,7 @@ namespace Bit.Droid.Services
return; return;
} }
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs; var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager; var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value); alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
} }
} }

View File

@@ -18,7 +18,6 @@ using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using static Bit.App.Pages.SettingsPageViewModel;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
@@ -70,17 +69,14 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName) public bool LaunchApp(string appName)
{ {
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = CrossCurrentActivity.Current.Activity; var activity = CrossCurrentActivity.Current.Activity;
appName = appName.Replace("androidapp://", string.Empty); appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName); var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null); if (launchIntent != null)
return launchIntentSender != null; {
activity.StartActivity(launchIntent);
}
return launchIntent != null;
} }
public async Task ShowLoadingAsync(string text) public async Task ShowLoadingAsync(string text)

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@@ -26,13 +25,13 @@ namespace Bit.Droid.Services
try try
{ {
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage)); var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
Debug.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")"); Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
ci = new CultureInfo(fallback); ci = new CultureInfo(fallback);
} }
catch (CultureNotFoundException e2) catch (CultureNotFoundException e2)
{ {
// iOS language not valid .NET culture, falling back to English // iOS language not valid .NET culture, falling back to English
Debug.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")"); Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
ci = new CultureInfo("en"); ci = new CultureInfo("en");
} }
} }
@@ -41,7 +40,7 @@ namespace Bit.Droid.Services
private string AndroidToDotnetLanguage(string androidLanguage) private string AndroidToDotnetLanguage(string androidLanguage)
{ {
Debug.WriteLine("Android Language:" + androidLanguage); Console.WriteLine("Android Language:" + androidLanguage);
var netLanguage = androidLanguage; var netLanguage = androidLanguage;
if (androidLanguage.StartsWith("zh")) if (androidLanguage.StartsWith("zh"))
{ {
@@ -80,13 +79,13 @@ namespace Bit.Droid.Services
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
} }
Debug.WriteLine(".NET Language/Locale:" + netLanguage); Console.WriteLine(".NET Language/Locale:" + netLanguage);
return netLanguage; return netLanguage;
} }
private string ToDotnetFallbackLanguage(PlatformCulture platCulture) private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
{ {
Debug.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode); Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually); var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
switch (platCulture.LanguageCode) switch (platCulture.LanguageCode)
{ {
@@ -96,7 +95,7 @@ namespace Bit.Droid.Services
// add more application-specific cases here (if required) // add more application-specific cases here (if required)
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
Debug.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)"); Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
return netLanguage; return netLanguage;
} }

View File

@@ -1,29 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Models;
namespace Bit.Droid.Services
{
public class WatchDeviceService : BaseWatchDeviceService
{
public WatchDeviceService(ICipherService cipherService,
IEnvironmentService environmentService,
IStateService stateService,
IVaultTimeoutService vaultTimeoutService)
: base(cipherService, environmentService, stateService, vaultTimeoutService)
{
}
protected override bool IsSupported => false;
public override bool IsConnected => false;
protected override bool CanSendData => false;
protected override Task SendDataToWatchAsync(WatchDTO watchDto) => throw new NotImplementedException();
protected override void ConnectToWatch() => throw new NotImplementedException();
}
}

View File

@@ -62,7 +62,7 @@ namespace Bit.Droid.Utilities
theme = ThemeManager.Dark; theme = ThemeManager.Dark;
} }
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord || theme == ThemeManager.SolarizedDark) if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
{ {
LightTheme = false; LightTheme = false;
} }

View File

@@ -9,6 +9,5 @@ namespace Bit.App.Abstractions
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost); void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null); Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task LogOutAsync(string userId, bool userInitiated, bool expired); Task LogOutAsync(string userId, bool userInitiated, bool expired);
Task PromptToSwitchToExistingAccountAsync(string userId);
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {

View File

@@ -125,9 +125,6 @@
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs"> <Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon> <DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Accounts\LoginPasswordlessRequestPage.xaml.cs">
<DependentUpon>LoginPasswordlessRequestPage.xaml</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -142,8 +139,6 @@
<Folder Include="Controls\AccountSwitchingOverlay\" /> <Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" /> <Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" /> <Folder Include="Controls\DateTime\" />
<Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -435,7 +430,5 @@
<None Remove="Controls\AccountSwitchingOverlay\" /> <None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" /> <None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" /> <None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@@ -34,9 +33,8 @@ namespace Bit.App
private readonly IAccountsManager _accountsManager; private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private static bool _isResumed; private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App. // this variable is static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests; private static bool _pendingCheckPasswordlessLoginRequests;
private static object _processingLoginRequestLock = new object();
public App(AppOptions appOptions) public App(AppOptions appOptions)
{ {
@@ -145,15 +143,9 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
}); });
} }
else if (message.Command == Constants.PasswordlessLoginRequestKey else if (message.Command == "passwordlessLoginRequest" || message.Command == "unlocked" || message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{ {
lock (_processingLoginRequestLock) CheckPasswordlessLoginRequestsAsync().FireAndForget();
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -170,6 +162,7 @@ namespace Bit.App
_pendingCheckPasswordlessLoginRequests = true; _pendingCheckPasswordlessLoginRequests = true;
return; return;
} }
_pendingCheckPasswordlessLoginRequests = false; _pendingCheckPasswordlessLoginRequests = false;
if (await _vaultTimeoutService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
@@ -189,11 +182,6 @@ namespace Bit.App
// Delay to wait for the vault page to appear // Delay to wait for the vault page to appear
await Task.Delay(2000); await Task.Delay(2000);
// if there is a request modal opened ignore all incoming requests
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
{
return;
}
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id); var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails() var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
{ {
@@ -204,11 +192,11 @@ namespace Bit.App
FingerprintPhrase = loginRequestData.RequestFingerprint, FingerprintPhrase = loginRequestData.RequestFingerprint,
RequestDate = loginRequestData.CreationDate, RequestDate = loginRequestData.CreationDate,
DeviceType = loginRequestData.RequestDeviceType, DeviceType = loginRequestData.RequestDeviceType,
Origin = loginRequestData.Origin Origin = loginRequestData.Origin,
}); });
await _stateService.SetPasswordlessLoginNotificationAsync(null); await _stateService.SetPasswordlessLoginNotificationAsync(null);
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId); _pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
if (!loginRequestData.IsExpired) if (loginRequestData.CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) > DateTime.UtcNow)
{ {
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page))); await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
} }
@@ -223,20 +211,13 @@ namespace Bit.App
} }
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId); var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
Device.BeginInvokeOnMainThread(async () => await Device.InvokeOnMainThreadAsync(async () =>
{ {
try var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
if (result == AppResources.Ok)
{ {
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok); await _stateService.SetActiveUserAsync(notification.UserId);
if (result == AppResources.Ok) _messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
{
await _stateService.SetActiveUserAsync(notification.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
} }
}); });
return true; return true;
@@ -261,7 +242,7 @@ namespace Bit.App
} }
if (_pendingCheckPasswordlessLoginRequests) if (_pendingCheckPasswordlessLoginRequests)
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); CheckPasswordlessLoginRequestsAsync().FireAndForget();
} }
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@@ -297,7 +278,7 @@ namespace Bit.App
_isResumed = true; _isResumed = true;
if (_pendingCheckPasswordlessLoginRequests) if (_pendingCheckPasswordlessLoginRequests)
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); CheckPasswordlessLoginRequestsAsync().FireAndForget();
} }
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@@ -459,14 +440,7 @@ namespace Bit.App
switch (navTarget) switch (navTarget)
{ {
case NavigationTarget.HomeLogin: case NavigationTarget.HomeLogin:
if (navParams is HomeNavigationParams homeParams) Current.MainPage = new NavigationPage(new HomePage(Options));
{
Current.MainPage = new NavigationPage(new HomePage(Options, homeParams.ShouldCheckRememberEmail));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
break; break;
case NavigationTarget.Login: case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams) if (navParams is LoginNavigationParams loginParams)

View File

@@ -14,7 +14,7 @@ namespace Bit.App.Controls
{ {
AccountView = accountView; AccountView = accountView;
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool") AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor); ?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email);
} }
public AccountView AccountView public AccountView AccountView

View File

@@ -13,7 +13,6 @@ namespace Bit.App.Controls
{ {
private readonly string _text; private readonly string _text;
private readonly string _id; private readonly string _id;
private readonly string _color;
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
@@ -24,7 +23,7 @@ namespace Bit.App.Controls
if (obj is AvatarImageSource avatar) if (obj is AvatarImageSource avatar)
{ {
return avatar._id == _id && avatar._text == _text && avatar._color == _color; return avatar._id == _id && avatar._text == _text;
} }
return base.Equals(obj); return base.Equals(obj);
@@ -32,7 +31,7 @@ namespace Bit.App.Controls
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1; public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null) public AvatarImageSource(string userId = null, string name = null, string email = null)
{ {
_id = userId; _id = userId;
_text = name; _text = name;
@@ -40,7 +39,6 @@ namespace Bit.App.Controls
{ {
_text = email; _text = email;
} }
_color = color;
} }
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync; public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
@@ -73,7 +71,7 @@ namespace Bit.App.Controls
chars = upperCaseText = _text.ToUpper(); chars = upperCaseText = _text.ToUpper();
} }
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); var bgColor = CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff");
var textColor = CoreHelpers.TextColorFromBgColor(bgColor); var textColor = CoreHelpers.TextColorFromBgColor(bgColor);
var size = 50; var size = 50;

View File

@@ -5,19 +5,19 @@ namespace Bit.App.Controls
{ {
public interface IAvatarImageSourcePool public interface IAvatarImageSourcePool
{ {
AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color); AvatarImageSource GetOrCreateAvatar(string userId, string name, string email);
} }
public class AvatarImageSourcePool : IAvatarImageSourcePool public class AvatarImageSourcePool : IAvatarImageSourcePool
{ {
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>(); private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color) public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email)
{ {
var key = $"{userId}{name}{email}{color}"; var key = $"{userId}{name}{email}";
if (!_cache.TryGetValue(key, out var avatar)) if (!_cache.TryGetValue(key, out var avatar))
{ {
avatar = new AvatarImageSource(userId, name, email, color); avatar = new AvatarImageSource(userId, name, email);
if (!_cache.TryAdd(key, avatar) if (!_cache.TryAdd(key, avatar)
&& &&
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add. !_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.

View File

@@ -21,13 +21,13 @@ namespace Bit.App.Controls
nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f); nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("175DDC")); nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("dd4b39")); nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.White); nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public double Progress public double Progress
{ {

View File

@@ -5,7 +5,7 @@ namespace Bit.App.Controls
public class ExtendedSlider : Slider public class ExtendedSlider : Slider
{ {
public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create( public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create(
nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.FromHex("b5b5b5")); nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.Default);
public Color ThumbBorderColor public Color ThumbBorderColor
{ {

View File

@@ -5,10 +5,10 @@ namespace Bit.App.Controls
public class ExtendedStepper : Stepper public class ExtendedStepper : Stepper
{ {
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create( public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.White); nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create( public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Black); nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public Color StepperBackgroundColor public Color StepperBackgroundColor
{ {

View File

@@ -1,5 +1,4 @@
using Bit.App.Effects; using Xamarin.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
@@ -17,8 +16,6 @@ namespace Bit.App.Controls
FontFamily = "bwi-font.ttf#bwi-font"; FontFamily = "bwi-font.ttf#bwi-font";
break; break;
} }
Effects.Add(new RemoveFontPaddingEffect());
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using Bit.App.Effects; using Xamarin.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
@@ -18,8 +17,6 @@ namespace Bit.App.Controls
FontFamily = "bwi-font.ttf#bwi-font"; FontFamily = "bwi-font.ttf#bwi-font";
break; break;
} }
Effects.Add(new RemoveFontPaddingEffect());
} }
} }
} }

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Frame xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.IconLabelButton"
xmlns:controls="clr-namespace:Bit.App.Controls"
x:Name="_iconLabelButton"
HeightRequest="45"
Padding="1"
StyleClass="btn-icon-secondary"
BackgroundColor="{Binding IconLabelBorderColor, Source={x:Reference _iconLabelButton}}"
BorderColor="Transparent"
HasShadow="False">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ButtonCommand, Source={x:Reference _iconLabelButton}}" />
</Frame.GestureRecognizers>
<Frame
Margin="0"
Padding="0"
CornerRadius="{Binding CornerRadius, Source={x:Reference _iconLabelButton}}"
BackgroundColor="{Binding IconLabelBackgroundColor, Source={x:Reference _iconLabelButton}}"
BorderColor="Transparent"
IsClippedToBounds="True"
HasShadow="False">
<StackLayout
Orientation="Horizontal"
HorizontalOptions="Center">
<controls:IconLabel
VerticalOptions="Center"
HorizontalTextAlignment="Center"
FontSize="Large"
TextColor="{Binding IconLabelColor, Source={x:Reference _iconLabelButton}}"
Text="{Binding Icon, Source={x:Reference _iconLabelButton}}">
</controls:IconLabel>
<Label
VerticalOptions="Center"
HorizontalTextAlignment="Center"
TextColor="{Binding IconLabelColor, Source={x:Reference _iconLabelButton}}"
FontSize="Medium"
Text="{Binding Label, Source={x:Reference _iconLabelButton}}"/>
</StackLayout>
</Frame>
</Frame>

View File

@@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.Core.Models.Domain;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Bit.App.Controls
{
public partial class IconLabelButton : Frame
{
public static readonly BindableProperty IconProperty = BindableProperty.Create(
nameof(Icon), typeof(string), typeof(IconLabelButton));
public static readonly BindableProperty LabelProperty = BindableProperty.Create(
nameof(Label), typeof(string), typeof(IconLabelButton));
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(ICommand), typeof(IconLabelButton));
public static readonly BindableProperty IconLabelColorProperty = BindableProperty.Create(
nameof(IconLabelColor), typeof(Color), typeof(IconLabelButton), Color.White);
public static readonly BindableProperty IconLabelBackgroundColorProperty = BindableProperty.Create(
nameof(IconLabelBackgroundColor), typeof(Color), typeof(IconLabelButton), Color.White);
public static readonly BindableProperty IconLabelBorderColorProperty = BindableProperty.Create(
nameof(IconLabelBorderColor), typeof(Color), typeof(IconLabelButton), Color.White);
public IconLabelButton()
{
InitializeComponent();
}
public string Icon
{
get => GetValue(IconProperty) as string;
set => SetValue(IconProperty, value);
}
public string Label
{
get => GetValue(LabelProperty) as string;
set => SetValue(LabelProperty, value);
}
public ICommand ButtonCommand
{
get => GetValue(ButtonCommandProperty) as ICommand;
set => SetValue(ButtonCommandProperty, value);
}
public Color IconLabelColor
{
get { return (Color)GetValue(IconLabelColorProperty); }
set { SetValue(IconLabelColorProperty, value); }
}
public Color IconLabelBackgroundColor
{
get { return (Color)GetValue(IconLabelBackgroundColorProperty); }
set { SetValue(IconLabelBackgroundColorProperty, value); }
}
public Color IconLabelBorderColor
{
get { return (Color)GetValue(IconLabelBorderColorProperty); }
set { SetValue(IconLabelBorderColorProperty, value); }
}
}
}

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace Bit.App.Controls
{
public interface IPasswordStrengthable
{
string Password { get; }
List<string> UserInputs { get; }
}
}

View File

@@ -1,17 +0,0 @@
using Bit.Core.Attributes;
namespace Bit.App.Controls
{
public enum PasswordStrengthLevel
{
[LocalizableEnum("Weak")]
VeryWeak,
[LocalizableEnum("Weak")]
Weak,
[LocalizableEnum("Good")]
Good,
[LocalizableEnum("Strong")]
Strong
}
}

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<StackLayout
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="controls:PasswordStrengthViewModel"
x:Class="Bit.App.Controls.PasswordStrengthProgressBar"
StyleClass="box">
<StackLayout.Resources>
<ResourceDictionary>
<u:LocalizableEnumConverter x:Key="localizableEnum" />
</ResourceDictionary>
</StackLayout.Resources>
<ProgressBar
x:Name="_progressBar"
u:ProgressBarExtensions.AnimatedProgress="{Binding PasswordStrength}"
ScaleY="2" />
<Label
x:Name="_progressLabel"
Text="{Binding PasswordStrengthLevel, Converter={StaticResource localizableEnum}, TargetNullValue=' ' }"
StyleClass="box-footer-label" />
</StackLayout>

View File

@@ -1,107 +0,0 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public partial class PasswordStrengthProgressBar : StackLayout
{
public static readonly BindableProperty PasswordStrengthLevelProperty = BindableProperty.Create(
nameof(PasswordStrengthLevel),
typeof(PasswordStrengthLevel),
typeof(PasswordStrengthProgressBar),
propertyChanged: OnControlPropertyChanged);
public static readonly BindableProperty VeryWeakColorProperty = BindableProperty.Create(
nameof(VeryWeakColor),
typeof(Color),
typeof(PasswordStrengthProgressBar),
propertyChanged: OnControlPropertyChanged);
public static readonly BindableProperty WeakColorProperty = BindableProperty.Create(
nameof(WeakColor),
typeof(Color),
typeof(PasswordStrengthProgressBar),
propertyChanged: OnControlPropertyChanged);
public static readonly BindableProperty GoodColorProperty = BindableProperty.Create(
nameof(GoodColor),
typeof(Color),
typeof(PasswordStrengthProgressBar),
propertyChanged: OnControlPropertyChanged);
public static readonly BindableProperty StrongColorProperty = BindableProperty.Create(
nameof(StrongColor),
typeof(Color),
typeof(PasswordStrengthProgressBar),
propertyChanged: OnControlPropertyChanged);
public PasswordStrengthLevel? PasswordStrengthLevel
{
get { return (PasswordStrengthLevel?)GetValue(PasswordStrengthLevelProperty); }
set { SetValue(PasswordStrengthLevelProperty, value); }
}
public Color VeryWeakColor
{
get { return (Color)GetValue(VeryWeakColorProperty); }
set { SetValue(VeryWeakColorProperty, value); }
}
public Color WeakColor
{
get { return (Color)GetValue(WeakColorProperty); }
set { SetValue(WeakColorProperty, value); }
}
public Color GoodColor
{
get { return (Color)GetValue(GoodColorProperty); }
set { SetValue(GoodColorProperty, value); }
}
public Color StrongColor
{
get { return (Color)GetValue(StrongColorProperty); }
set { SetValue(StrongColorProperty, value); }
}
public PasswordStrengthProgressBar()
{
InitializeComponent();
SetBinding(PasswordStrengthProgressBar.PasswordStrengthLevelProperty, new Binding() { Path = nameof(PasswordStrengthViewModel.PasswordStrengthLevel) });
}
private static void OnControlPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as PasswordStrengthProgressBar)?.UpdateColors();
}
public void UpdateColors()
{
if (_progressBar == null || _progressLabel == null)
{
return;
}
_progressBar.ProgressColor = GetColorForStrength();
_progressLabel.TextColor = _progressBar.ProgressColor;
}
private Color GetColorForStrength()
{
switch (PasswordStrengthLevel)
{
case Controls.PasswordStrengthLevel.VeryWeak:
return VeryWeakColor;
case Controls.PasswordStrengthLevel.Weak:
return WeakColor;
case Controls.PasswordStrengthLevel.Good:
return GoodColor;
case Controls.PasswordStrengthLevel.Strong:
return StrongColor;
default:
return Color.Transparent;
}
}
}
}

View File

@@ -1,67 +0,0 @@
using System.Collections.Generic;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class PasswordStrengthViewModel : ExtendedViewModel
{
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPasswordStrengthable _passwordStrengthable;
private double _passwordStrength;
private Color _passwordColor;
private PasswordStrengthLevel? _passwordStrengthLevel;
public PasswordStrengthViewModel(IPasswordStrengthable passwordStrengthable)
{
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
_passwordStrengthable = passwordStrengthable;
}
public double PasswordStrength
{
get => _passwordStrength;
set => SetProperty(ref _passwordStrength, value);
}
public PasswordStrengthLevel? PasswordStrengthLevel
{
get => _passwordStrengthLevel;
set => SetProperty(ref _passwordStrengthLevel, value);
}
public List<string> GetPasswordStrengthUserInput(string email) => _passwordGenerationService.GetPasswordStrengthUserInput(email);
public void CalculatePasswordStrength()
{
if (string.IsNullOrEmpty(_passwordStrengthable.Password))
{
PasswordStrength = 0;
PasswordStrengthLevel = null;
return;
}
var passwordStrength = _passwordGenerationService.PasswordStrength(_passwordStrengthable.Password, _passwordStrengthable.UserInputs);
// The passwordStrength.Score is 0..4, convertion was made to be used as a progress directly by the control 0..1
PasswordStrength = (passwordStrength.Score + 1f) / 5f;
if (PasswordStrength <= 0.4f)
{
PasswordStrengthLevel = Controls.PasswordStrengthLevel.VeryWeak;
}
else if (PasswordStrength <= 0.6f)
{
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Weak;
}
else if (PasswordStrength <= 0.8f)
{
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Good;
}
else
{
PasswordStrengthLevel = Controls.PasswordStrengthLevel.Strong;
}
}
}
}

View File

@@ -1,13 +0,0 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Effects
{
public class RemoveFontPaddingEffect : RoutingEffect
{
public RemoveFontPaddingEffect()
: base("Bitwarden.RemoveFontPaddingEffect")
{ }
}
}

View File

@@ -4,8 +4,6 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -149,8 +147,8 @@ namespace Bit.App.Pages
} }
if (IsPolicyInEffect) if (IsPolicyInEffect)
{ {
var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync()); var userInput = await GetPasswordStrengthUserInput();
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs); var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy)) if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage, await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordPolicyValidationMessage,
@@ -160,7 +158,7 @@ namespace Bit.App.Pages
} }
else else
{ {
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) if (MasterPassword.Length < 8)
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok); AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
@@ -176,5 +174,19 @@ namespace Bit.App.Pages
return true; return true;
} }
private async Task<List<string>> GetPasswordStrengthUserInput()
{
var email = await _stateService.GetEmailAsync();
List<string> userInput = null;
var atPosition = email.IndexOf('@');
if (atPosition > -1)
{
var rx = new Regex("/[^A-Za-z0-9]/", RegexOptions.Compiled);
var data = rx.Split(email.Substring(0, atPosition).Trim().ToLower());
userInput = new List<string>(data);
}
return userInput;
}
} }
} }

View File

@@ -6,12 +6,11 @@ namespace Bit.App.Pages
{ {
private HintPageViewModel _vm; private HintPageViewModel _vm;
public HintPage(string email = null) public HintPage()
{ {
InitializeComponent(); InitializeComponent();
_vm = BindingContext as HintPageViewModel; _vm = BindingContext as HintPageViewModel;
_vm.Page = this; _vm.Page = this;
_vm.Email = email;
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
ToolbarItems.RemoveAt(0); ToolbarItems.RemoveAt(0);

View File

@@ -15,7 +15,6 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
private readonly ILogger _logger; private readonly ILogger _logger;
private string _email;
public HintPageViewModel() public HintPageViewModel()
{ {
@@ -35,12 +34,7 @@ namespace Bit.App.Pages
} }
public ICommand SubmitCommand { get; } public ICommand SubmitCommand { get; }
public string Email { get; set; }
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
public async Task SubmitAsync() public async Task SubmitAsync()
{ {

View File

@@ -24,7 +24,6 @@
UseOriginalImage="True" UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" /> AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
<ToolbarItem <ToolbarItem
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary" Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
@@ -33,68 +32,30 @@
<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" /> <StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="0" Padding="10, 5">
<StackLayout x:Name="_mainLayout" x:Key="mainLayout" Spacing="30" Padding="20, 50, 20, 0"> <StackLayout VerticalOptions="CenterAndExpand" Spacing="20">
<Image <Image
x:Name="_logo" x:Name="_logo"
Source="logo.png" Source="logo.png"
VerticalOptions="Center" /> VerticalOptions="Center" />
<Label Text="{u:I18n LoginOrCreateNewAccount}" <Label Text="{u:I18n LoginOrCreateNewAccount}"
StyleClass="text-lg" StyleClass="text-lg"
HorizontalTextAlignment="Center"/> HorizontalTextAlignment="Center">
<StackLayout </Label>
StyleClass="box-row"> <StackLayout Spacing="5">
<Label <Button Text="{u:I18n LogIn}"
Text="{u:I18n EmailAddress}" StyleClass="btn-primary"
StyleClass="box-label" /> Clicked="LogIn_Clicked" />
<Entry <Button Text="{u:I18n CreateAccount}"
x:Name="_email" Clicked="Register_Clicked" />
Text="{Binding Email}" <Button Text="{u:I18n LogInSso}"
Keyboard="Email" Clicked="LogInSso_Clicked" />
StyleClass="box-value" <Button Text="{u:I18n Cancel}"
ReturnType="Go" IsVisible="{Binding ShowCancelButton}"
ReturnCommand="{Binding ContinueCommand}"> Margin="0,10,0,0"
<VisualStateManager.VisualStateGroups> Clicked="Cancel_Clicked" />
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{DynamicResource MutedColor}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>
<StackLayout
Orientation="Horizontal"
Margin="0, 16, 0 ,0">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding RememberEmailCommand}" />
</StackLayout.GestureRecognizers>
<Label
Text="{u:I18n RememberMe}"
StyleClass="text-sm"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
VerticalTextAlignment="Center"/>
<Switch
Scale="0.8"
IsToggled="{Binding RememberEmail}"
VerticalOptions="Center"/>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
<Button Text="{u:I18n Continue}"
StyleClass="btn-primary"
IsEnabled="{Binding CanContinue}"
Command="{Binding ContinueCommand}" />
<Label FormattedText="{Binding CreateAccountText}"
Margin="0, 10"
StyleClass="box-footer-label">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CreateAccountCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout> </StackLayout>
</ResourceDictionary> </ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>

View File

@@ -10,35 +10,28 @@ namespace Bit.App.Pages
{ {
public partial class HomePage : BaseContentPage public partial class HomePage : BaseContentPage
{ {
private bool _checkRememberedEmail;
private readonly HomeViewModel _vm; private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null, bool shouldCheckRememberEmail = true) public HomePage(AppOptions appOptions = null)
{ {
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions; _appOptions = appOptions;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as HomeViewModel; _vm = BindingContext as HomeViewModel;
_vm.Page = this; _vm.Page = this;
_vm.ShouldCheckRememberEmail = shouldCheckRememberEmail; _vm.StartLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartLoginAsync());
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
_vm.StartLoginAction = async () => await StartLoginAsync();
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync()); _vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync()); _vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync());
_vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync()); _vm.StartEnvironmentAction = () => Device.BeginInvokeOnMainThread(async () => await StartEnvironmentAsync());
_vm.CloseAction = async () =>
{
await _accountListOverlay.HideAsync();
await Navigation.PopModalAsync();
};
UpdateLogo(); UpdateLogo();
if (!_vm.ShowCancelButton) if (_appOptions?.IosExtension ?? false)
{ {
ToolbarItems.Remove(_closeButton); _vm.ShowCancelButton = true;
} }
if (_appOptions?.HideAccountSwitcher ?? false) if (_appOptions?.HideAccountSwitcher ?? false)
{ {
ToolbarItems.Remove(_accountAvatar); ToolbarItems.Remove(_accountAvatar);
@@ -59,7 +52,7 @@ namespace Bit.App.Pages
if (!_appOptions?.HideAccountSwitcher ?? false) if (!_appOptions?.HideAccountSwitcher ?? false)
{ {
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(false); _vm.AvatarImageSource = await GetAvatarImageSourceAsync();
} }
_broadcasterService.Subscribe(nameof(HomePage), (message) => _broadcasterService.Subscribe(nameof(HomePage), (message) =>
{ {
@@ -71,8 +64,6 @@ namespace Bit.App.Pages
}); });
} }
}); });
_vm.CheckNavigateLoginStep();
} }
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
@@ -105,12 +96,28 @@ namespace Bit.App.Pages
} }
} }
private void LogIn_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.StartLoginAction();
}
}
private async Task StartLoginAsync() private async Task StartLoginAsync()
{ {
var page = new LoginPage(_vm.Email, _appOptions); var page = new LoginPage(null, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page)); await Navigation.PushModalAsync(new NavigationPage(page));
} }
private void Register_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.StartRegisterAction();
}
}
private async Task StartRegisterAsync() private async Task StartRegisterAsync()
{ {
var page = new RegisterPage(this); var page = new RegisterPage(this);

View File

@@ -1,15 +1,8 @@
using System; using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Resources; using Bit.App.Resources;
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.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -19,37 +12,19 @@ namespace Bit.App.Pages
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private bool _showCancelButton; private bool _showCancelButton;
private bool _rememberEmail;
private string _email;
private bool _isEmailEnabled;
private bool _canLogin;
private IPlatformUtilsService _platformUtilsService;
private ILogger _logger;
private IEnvironmentService _environmentService;
private IAccountsManager _accountManager;
public HomeViewModel() public HomeViewModel()
{ {
_stateService = ServiceContainer.Resolve<IStateService>(); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>(); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>(); var logger = ServiceContainer.Resolve<ILogger>("logger");
_logger = ServiceContainer.Resolve<ILogger>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, logger)
{ {
AllowActiveAccountSelection = true AllowActiveAccountSelection = true
}; };
RememberEmailCommand = new Command(() => RememberEmail = !RememberEmail);
ContinueCommand = new AsyncCommand(ContinueToLoginStepAsync, allowsMultipleExecutions: false);
CreateAccountCommand = new AsyncCommand(async () => await Device.InvokeOnMainThreadAsync(StartRegisterAction),
onException: _logger.Exception, allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(async () => await Device.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception, allowsMultipleExecutions: false);
InitAsync().FireAndForget();
} }
public bool ShowCancelButton public bool ShowCancelButton
@@ -58,102 +33,11 @@ namespace Bit.App.Pages
set => SetProperty(ref _showCancelButton, value); set => SetProperty(ref _showCancelButton, value);
} }
public bool RememberEmail
{
get => _rememberEmail;
set => SetProperty(ref _rememberEmail, value);
}
public string Email
{
get => _email;
set => SetProperty(ref _email, value,
additionalPropertyNames: new[] { nameof(CanContinue) });
}
public bool CanContinue => !string.IsNullOrEmpty(Email);
public bool ShouldCheckRememberEmail { get; set; }
public FormattedString CreateAccountText
{
get
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = $"{AppResources.NewAroundHere} "
});
fs.Spans.Add(new Span
{
Text = AppResources.CreateAccount,
TextColor = ThemeManager.GetResourceColor("PrimaryColor")
});
return fs;
}
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Action StartLoginAction { get; set; } public Action StartLoginAction { get; set; }
public Action StartRegisterAction { get; set; } public Action StartRegisterAction { get; set; }
public Action StartSsoLoginAction { get; set; } public Action StartSsoLoginAction { get; set; }
public Action StartEnvironmentAction { get; set; } public Action StartEnvironmentAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public Command RememberEmailCommand { get; set; }
public AsyncCommand ContinueCommand { get; }
public AsyncCommand CloseCommand { get; }
public AsyncCommand CreateAccountCommand { get; }
public async Task InitAsync()
{
Email = await _stateService.GetRememberedEmailAsync();
RememberEmail = !string.IsNullOrEmpty(Email);
}
public void CheckNavigateLoginStep()
{
if (ShouldCheckRememberEmail && RememberEmail)
{
StartLoginAction();
}
ShouldCheckRememberEmail = false;
}
public async Task ContinueToLoginStepAsync()
{
try
{
if (string.IsNullOrWhiteSpace(Email))
{
await _platformUtilsService.ShowDialogAsync(
string.Format(AppResources.ValidationFieldRequired, AppResources.EmailAddress),
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
if (!Email.Contains("@"))
{
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidEmail, AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
await _stateService.SetRememberedEmailAsync(RememberEmail ? Email : null);
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
{
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl)
{
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
return;
}
}
StartLoginAction();
}
catch (Exception ex)
{
_logger.Exception(ex);
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
} }
} }

View File

@@ -28,7 +28,6 @@ namespace Bit.App.Pages
private readonly IBiometricService _biometricService; private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService; private readonly IKeyConnectorService _keyConnectorService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IWatchDeviceService _watchDeviceService;
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>(); private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private string _email; private string _email;
@@ -57,7 +56,6 @@ namespace Bit.App.Pages
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService"); _biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"); _keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
PageTitle = AppResources.VerifyMasterPassword; PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@@ -389,7 +387,6 @@ namespace Bit.App.Pages
private async Task DoContinueAsync() private async Task DoContinueAsync()
{ {
await _stateService.SetBiometricLockedAsync(false); await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");
UnlockedAction?.Invoke(); UnlockedAction?.Invoke();
} }

View File

@@ -4,7 +4,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.LoginPage" x:Class="Bit.App.Pages.LoginPage"
xmlns:pages="clr-namespace:Bit.App.Pages" xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
xmlns:controls="clr-namespace:Bit.App.Controls" xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities" xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPageViewModel" x:DataType="pages:LoginPageViewModel"
@@ -47,13 +46,33 @@
Order="Secondary" /> Order="Secondary" />
<ScrollView x:Name="_mainLayout" x:Key="mainLayout"> <ScrollView x:Name="_mainLayout" x:Key="mainLayout">
<StackLayout Spacing="0"> <StackLayout Spacing="20">
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<StackLayout StyleClass="box-row">
<Label
Text="{u:I18n EmailAddress}"
StyleClass="box-label" />
<Entry
x:Name="_email"
Text="{Binding Email}"
IsEnabled="{Binding IsEmailEnabled}"
Keyboard="Email"
StyleClass="box-value">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{DynamicResource MutedColor}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Entry>
</StackLayout>
<Grid StyleClass="box-row"> <Grid StyleClass="box-row">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -62,7 +81,6 @@
<Label <Label
Text="{u:I18n MasterPassword}" Text="{u:I18n MasterPassword}"
StyleClass="box-label" StyleClass="box-label"
Padding="0, 10, 0, 0"
Grid.Row="0" Grid.Row="0"
Grid.Column="0" /> Grid.Column="0" />
<controls:MonoEntry <controls:MonoEntry
@@ -80,60 +98,21 @@
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}" Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}" Command="{Binding TogglePasswordCommand}"
Grid.Row="1" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
Grid.RowSpan="1" 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}"/>
<Label
Text="{u:I18n GetMasterPasswordwordHint}"
StyleClass="box-footer-label"
TextColor="{DynamicResource HyperlinkColor}"
Padding="0,5,0,0"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Hint_Clicked" />
</Label.GestureRecognizers>
</Label>
</Grid> </Grid>
</StackLayout> </StackLayout>
<StackLayout Padding="10, 10"> <StackLayout Padding="10, 0">
<Button x:Name="_loginWithMasterPassword" <Button Text="{u:I18n LogIn}"
Text="{u:I18n LogInWithMasterPassword}"
StyleClass="btn-primary" StyleClass="btn-primary"
Clicked="LogIn_Clicked" /> Clicked="LogIn_Clicked" />
<controls:IconLabelButton <Button Text="{u:I18n Cancel}"
HorizontalOptions="Fill" IsVisible="{Binding ShowCancelButton}"
VerticalOptions="CenterAndExpand" Clicked="Cancel_Clicked" />
Icon="{Binding Source={x:Static core:BitwardenIcons.Device}}"
Label="{u:I18n LogInWithAnotherDevice}"
ButtonCommand="{Binding LogInWithDeviceCommand}"
IsVisible="{Binding IsKnownDevice}"/>
<controls:IconLabelButton
HorizontalOptions="Fill"
VerticalOptions="CenterAndExpand"
Icon="{Binding Source={x:Static core:BitwardenIcons.Suitcase}}"
Label="{u:I18n LogInSso}">
<controls:IconLabelButton.GestureRecognizers>
<TapGestureRecognizer Tapped="LogInSSO_Clicked" />
</controls:IconLabelButton.GestureRecognizers>
</controls:IconLabelButton>
<Label
Text="{Binding LoggingInAsText}"
StyleClass="text-sm"
Margin="0,40,0,0"/>
<Label
Text="{u:I18n NotYou}"
StyleClass="text-md"
HorizontalOptions="Start"
TextColor="{DynamicResource HyperlinkColor}">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Cancel_Clicked" />
</Label.GestureRecognizers>
</Label>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>
</ScrollView> </ScrollView>

View File

@@ -3,9 +3,7 @@ using System.Threading.Tasks;
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.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -27,9 +25,8 @@ namespace Bit.App.Pages
_vm.Page = this; _vm.Page = this;
_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.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget(); _vm.UpdateTempPasswordAction =
_vm.StartSsoLoginAction = () => Device.BeginInvokeOnMainThread(async () => await StartSsoLoginAsync()); () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = async () => _vm.CloseAction = async () =>
{ {
await _accountListOverlay.HideAsync(); await _accountListOverlay.HideAsync();
@@ -45,6 +42,9 @@ namespace Bit.App.Pages
_vm.Email = email; _vm.Email = email;
MasterPasswordEntry = _masterPassword; MasterPasswordEntry = _masterPassword;
_email.ReturnType = ReturnType.Next;
_email.ReturnCommand = new Command(() => _masterPassword.Focus());
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
ToolbarItems.Add(_moreItem); ToolbarItems.Add(_moreItem);
@@ -54,6 +54,11 @@ namespace Bit.App.Pages
ToolbarItems.Add(_getPasswordHint); ToolbarItems.Add(_getPasswordHint);
} }
if (Device.RuntimePlatform == Device.Android && !_vm.IsEmailEnabled)
{
ToolbarItems.Add(_removeAccount);
}
if (_appOptions?.IosExtension ?? false) if (_appOptions?.IosExtension ?? false)
{ {
_vm.ShowCancelButton = true; _vm.ShowCancelButton = true;
@@ -73,20 +78,16 @@ namespace Bit.App.Pages
_mainContent.Content = _mainLayout; _mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing(); _accountAvatar?.OnAppearing();
await _vm.InitAsync();
if (!_appOptions?.HideAccountSwitcher ?? false) if (!_appOptions?.HideAccountSwitcher ?? false)
{ {
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(_vm.EmailIsInSavedAccounts); _vm.AvatarImageSource = await GetAvatarImageSourceAsync();
} }
await _vm.InitAsync();
if (!_inputFocused) if (!_inputFocused)
{ {
RequestFocus(_masterPassword); RequestFocus(string.IsNullOrWhiteSpace(_vm.Email) ? _email : _masterPassword);
_inputFocused = true; _inputFocused = true;
} }
if (Device.RuntimePlatform == Device.Android && !_vm.CanRemoveAccount)
{
ToolbarItems.Add(_removeAccount);
}
} }
protected override bool OnBackButtonPressed() protected override bool OnBackButtonPressed()
@@ -114,31 +115,11 @@ namespace Bit.App.Pages
} }
} }
private void LogInSSO_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.StartSsoLoginAction();
}
}
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartSsoLoginAsync()
{
var page = new LoginSsoPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private void Hint_Clicked(object sender, EventArgs e) private void Hint_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())
{ {
_vm.ShowMasterPasswordHintAsync().FireAndForget(); Navigation.PushModalAsync(new NavigationPage(new HintPage()));
} }
} }

View File

@@ -1,19 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
@@ -31,15 +25,12 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IApiService _apiService;
private readonly IAppIdService _appIdService;
private readonly IAccountsManager _accountManager;
private bool _showPassword; private bool _showPassword;
private bool _showCancelButton; private bool _showCancelButton;
private string _email; private string _email;
private string _masterPassword; private string _masterPassword;
private bool _isEmailEnabled; private bool _isEmailEnabled;
private bool _isKnownDevice;
public LoginPageViewModel() public LoginPageViewModel()
{ {
@@ -52,15 +43,11 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
_apiService = ServiceContainer.Resolve<IApiService>();
_appIdService = ServiceContainer.Resolve<IAppIdService>();
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
PageTitle = AppResources.Bitwarden; PageTitle = AppResources.Bitwarden;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
LogInCommand = new Command(async () => await LogInAsync()); LogInCommand = new Command(async () => await LogInAsync());
MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false); MoreCommand = new AsyncCommand(MoreAsync, onException: _logger.Exception, allowsMultipleExecutions: false);
LogInWithDeviceCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(LogInWithDeviceAction), onException: _logger.Exception, allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger) AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{ {
@@ -89,11 +76,7 @@ namespace Bit.App.Pages
public string Email public string Email
{ {
get => _email; get => _email;
set => SetProperty(ref _email, value, set => SetProperty(ref _email, value);
additionalPropertyNames: new string[]
{
nameof(LoggingInAsText)
});
} }
public string MasterPassword public string MasterPassword
@@ -108,31 +91,20 @@ namespace Bit.App.Pages
set => SetProperty(ref _isEmailEnabled, value); set => SetProperty(ref _isEmailEnabled, value);
} }
public bool IsKnownDevice public bool IsIosExtension { get; set; }
{
get => _isKnownDevice;
set => SetProperty(ref _isKnownDevice, value);
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; } public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; } public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
public ICommand MoreCommand { get; internal set; } public ICommand MoreCommand { get; internal set; }
public ICommand LogInWithDeviceCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public bool IsIosExtension { get; set; }
public bool CanRemoveAccount { get; set; }
public Action StartTwoFactorAction { get; set; } public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; } public Action LogInSuccessAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
public Action UpdateTempPasswordAction { get; set; } public Action UpdateTempPasswordAction { get; set; }
public Action StartSsoLoginAction { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
public bool EmailIsInSavedAccounts => _stateService.AccountViews != null && _stateService.AccountViews.Any(e => e.Email == Email);
protected override II18nService i18nService => _i18nService; protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService; protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService; protected override IDeviceActionService deviceActionService => _deviceActionService;
@@ -140,22 +112,9 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
try if (string.IsNullOrWhiteSpace(Email))
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); Email = await _stateService.GetRememberedEmailAsync();
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _stateService.GetRememberedEmailAsync();
}
var deviceIdentifier = await _appIdService.GetAppIdAsync();
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
await _deviceActionService.HideLoadingAsync();
}
catch (Exception ex)
{
HandleException(ex);
} }
} }
@@ -199,7 +158,7 @@ namespace Bit.App.Pages
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId); var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl) if (userEnvUrls?.Base == _environmentService.BaseUrl)
{ {
await _accountManager.PromptToSwitchToExistingAccountAsync(userId); await PromptToSwitchToExistingAccountAsync(userId);
return; return;
} }
} }
@@ -211,6 +170,7 @@ namespace Bit.App.Pages
} }
var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken); var response = await _authService.LogInAsync(Email, MasterPassword, _captchaToken);
await _stateService.SetRememberedEmailAsync(Email);
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (response.CaptchaNeeded) if (response.CaptchaNeeded)
@@ -256,14 +216,19 @@ namespace Bit.App.Pages
private async Task MoreAsync() private async Task MoreAsync()
{ {
var buttons = IsEmailEnabled || CanRemoveAccount var buttons = IsEmailEnabled
? new[] { AppResources.GetPasswordHint } ? new[] { AppResources.GetPasswordHint }
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount }; : new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons); var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons);
if (selection == AppResources.GetPasswordHint) if (selection == AppResources.GetPasswordHint)
{ {
await ShowMasterPasswordHintAsync(); var hintNavigationPage = new NavigationPage(new HintPage());
if (IsIosExtension)
{
ThemeManager.ApplyResourcesTo(hintNavigationPage);
}
await Page.Navigation.PushModalAsync(hintNavigationPage);
} }
else if (selection == AppResources.RemoveAccount) else if (selection == AppResources.RemoveAccount)
{ {
@@ -271,16 +236,6 @@ namespace Bit.App.Pages
} }
} }
public async Task ShowMasterPasswordHintAsync()
{
var hintNavigationPage = new NavigationPage(new HintPage(Email));
if (IsIosExtension)
{
ThemeManager.ApplyResourcesTo(hintNavigationPage);
}
await Page.Navigation.PushModalAsync(hintNavigationPage);
}
public void TogglePassword() public void TogglePassword()
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
@@ -305,5 +260,17 @@ namespace Bit.App.Pages
_logger.Exception(e); _logger.Exception(e);
} }
} }
private async Task PromptToSwitchToExistingAccountAsync(string userId)
{
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
AppResources.SwitchToAlreadyAddedAccountConfirmation,
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
if (switchToAccount)
{
await _stateService.SetActiveUserAsync(userId);
_messagingService.Send("switchedAccount");
}
}
} }
} }

View File

@@ -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.LoginPasswordlessRequestPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPasswordlessRequestViewModel"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LoginPasswordlessRequestViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1" x:Name="_closeItem"/>
</ContentPage.ToolbarItems>
<ScrollView>
<StackLayout
Padding="7, 0, 7, 20">
<Label
Text="{u:I18n LogInInitiated}"
FontSize="Title"
FontAttributes="Bold"
Margin="0,14,0,21"/>
<Label
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
FontSize="Small"
Margin="0,0,0,10"/>
<Label
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
FontSize="Small"
Margin="0,0,0,24"/>
<Label
Text="{u:I18n FingerprintPhrase}"
FontSize="Small"
FontAttributes="Bold"/>
<controls:MonoLabel
FormattedText="{Binding FingerprintPhrase}"
FontSize="Medium"
TextColor="{DynamicResource FingerprintPhrase}"/>
<Label
Text="{u:I18n ResendNotification}"
StyleClass="text-md"
HorizontalOptions="Start"
Margin="0,40,0,0"
TextColor="{DynamicResource HyperlinkColor}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
</Label.GestureRecognizers>
</Label>
<StackLayout
Orientation="Horizontal"
Margin="0,30,0,0">
<Label
Text="{u:I18n NeedAnotherOption}"
FontSize="Small"
VerticalTextAlignment="End"/>
<Label
Text="{u:I18n ViewAllLoginOptions}"
StyleClass="text-md"
VerticalTextAlignment="End"
VerticalOptions="CenterAndExpand"
Margin="5, 0"
TextColor="{DynamicResource HyperlinkColor}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CloseCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

@@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginPasswordlessRequestPage : BaseContentPage
{
private LoginPasswordlessRequestViewModel _vm;
private readonly AppOptions _appOptions;
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
{
InitializeComponent();
_appOptions = appOptions;
_vm = BindingContext as LoginPasswordlessRequestViewModel;
_vm.Page = this;
_vm.Email = email;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.CreatePasswordlessLoginCommand.Execute(null);
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.StartCheckLoginRequestStatus();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
_vm.StopCheckLoginRequestStatus();
}
private async Task StartTwoFactorAsync()
{
var page = new TwoFactorPage(false, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task LogInSuccessAsync()
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task UpdateTempPasswordAsync()
{
var page = new UpdateTempPasswordPage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -1,194 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginPasswordlessRequestViewModel : CaptchaProtectedViewModel
{
private const int REQUEST_TIME_UPDATE_PERIOD_IN_SECONDS = 4;
private IDeviceActionService _deviceActionService;
private IAuthService _authService;
private ISyncService _syncService;
private II18nService _i18nService;
private IStateService _stateService;
private IPlatformUtilsService _platformUtilsService;
private IEnvironmentService _environmentService;
private ILogger _logger;
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService;
protected override IPlatformUtilsService platformUtilsService => _platformUtilsService;
private CancellationTokenSource _checkLoginRequestStatusCts;
private Task _checkLoginRequestStatusTask;
private string _fingerprintPhrase;
private string _email;
private string _requestId;
private string _requestAccessCode;
// Item1 publicKey, Item2 privateKey
private Tuple<byte[], byte[]> _requestKeyPair;
public LoginPasswordlessRequestViewModel()
{
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_authService = ServiceContainer.Resolve<IAuthService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
_i18nService = ServiceContainer.Resolve<II18nService>();
_stateService = ServiceContainer.Resolve<IStateService>();
_logger = ServiceContainer.Resolve<ILogger>();
PageTitle = AppResources.LogInWithAnotherDevice;
CreatePasswordlessLoginCommand = new AsyncCommand(CreatePasswordlessLoginAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(() => Device.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception,
allowsMultipleExecutions: false);
}
public Action StartTwoFactorAction { get; set; }
public Action LogInSuccessAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public Action CloseAction { get; set; }
public ICommand CreatePasswordlessLoginCommand { get; }
public ICommand CloseCommand { get; }
public string FingerprintPhrase
{
get => _fingerprintPhrase;
set => SetProperty(ref _fingerprintPhrase, value);
}
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
public void StartCheckLoginRequestStatus()
{
try
{
_checkLoginRequestStatusCts?.Cancel();
_checkLoginRequestStatusCts = new CancellationTokenSource();
_checkLoginRequestStatusTask = new TimerTask(_logger, CheckLoginRequestStatus, _checkLoginRequestStatusCts).RunPeriodic(TimeSpan.FromSeconds(REQUEST_TIME_UPDATE_PERIOD_IN_SECONDS));
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
public void StopCheckLoginRequestStatus()
{
try
{
_checkLoginRequestStatusCts?.Cancel();
_checkLoginRequestStatusCts?.Dispose();
_checkLoginRequestStatusCts = null;
}
catch (Exception ex)
{
_logger.Exception(ex);
}
}
private async Task CheckLoginRequestStatus()
{
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
{
return;
}
try
{
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
if (response.RequestApproved == null || !response.RequestApproved.Value)
{
return;
}
StopCheckLoginRequestStatus();
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
{
return;
}
if (authResult.TwoFactor)
{
StartTwoFactorAction?.Invoke();
}
else if (authResult.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
_syncService.FullSyncAsync(true).FireAndForget();
LogInSuccessAction?.Invoke();
}
}
catch (Exception ex)
{
StartCheckLoginRequestStatus();
HandleException(ex);
}
}
private async Task CreatePasswordlessLoginAsync()
{
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
if (response != null)
{
FingerprintPhrase = response.RequestFingerprint;
_requestId = response.Id;
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;
}
await _deviceActionService.HideLoadingAsync();
}
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
}).FireAndForget();
_logger.Exception(ex);
}
}
}

View File

@@ -100,7 +100,7 @@ namespace Bit.App.Pages
private async Task UpdateRequestTime() private async Task UpdateRequestTime()
{ {
TriggerPropertyChanged(nameof(TimeOfRequestText)); TriggerPropertyChanged(nameof(TimeOfRequestText));
if (LoginRequest?.IsExpired ?? false) if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
{ {
StopRequestTimeUpdater(); StopRequestTimeUpdater();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired); await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
@@ -110,21 +110,13 @@ namespace Bit.App.Pages
private async Task PasswordlessLoginAsync(bool approveRequest) private async Task PasswordlessLoginAsync(bool approveRequest)
{ {
if (LoginRequest.IsExpired) if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired); await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
await Page.Navigation.PopModalAsync(); await Page.Navigation.PopModalAsync();
return; return;
} }
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(LoginRequest.Id);
if (loginRequestData.RequestApproved.HasValue && loginRequestData.ResponseDate.HasValue)
{
await _platformUtilsService.ShowDialogAsync(AppResources.ThisRequestIsNoLongerValid);
await Page.Navigation.PopModalAsync();
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _authService.PasswordlessLoginAsync(LoginRequest.Id, LoginRequest.PubKey, approveRequest); await _authService.PasswordlessLoginAsync(LoginRequest.Id, LoginRequest.PubKey, approveRequest);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
@@ -148,6 +140,16 @@ namespace Bit.App.Pages
return string.Format(AppResources.XMinutesAgo, DateTime.UtcNow.Minute - requestDate.Value.ToUniversalTime().Minute); return string.Format(AppResources.XMinutesAgo, DateTime.UtcNow.Minute - requestDate.Value.ToUniversalTime().Minute);
} }
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
}).FireAndForget();
_logger.Exception(ex);
}
} }
public class LoginPasswordlessDetails public class LoginPasswordlessDetails
@@ -169,7 +171,5 @@ namespace Bit.App.Pages
public string DeviceType { get; set; } public string DeviceType { get; set; }
public string IpAddress { get; set; } public string IpAddress { get; set; }
public bool IsExpired => RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
} }
} }

View File

@@ -25,7 +25,7 @@
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<ScrollView> <ScrollView>
<StackLayout Spacing="10"> <StackLayout Spacing="20">
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<StackLayout StyleClass="box-row"> <StackLayout StyleClass="box-row">
<Label <Label
@@ -72,19 +72,8 @@
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/> AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
</Grid> </Grid>
<Label <Label
StyleClass="box-sub-label" Text="{u:I18n MasterPasswordDescription}"
Margin="0,0,0,10"> StyleClass="box-footer-label" />
<Label.FormattedText>
<FormattedString>
<Span Text="{u:I18n Important}" TextColor="{DynamicResource InfoColor}"/>
<Span Text=": " TextColor="{DynamicResource InfoColor}"/>
<Span Text="{Binding MasterPasswordMininumCharactersDescription}" TextColor="{DynamicResource MutedColor}"/>
</FormattedString>
</Label.FormattedText>
</Label>
<controls:PasswordStrengthProgressBar
BindingContext="{Binding PasswordStrengthViewModel}"
Margin="0,0"/>
</StackLayout> </StackLayout>
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<Grid StyleClass="box-row"> <Grid StyleClass="box-row">
@@ -137,17 +126,6 @@
StyleClass="box-footer-label" /> StyleClass="box-footer-label" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Switch
IsToggled="{Binding CheckExposedMasterPassword}"
StyleClass="box-value"
HorizontalOptions="Start"
Margin="0, 0, 10, 0"/>
<Label
Text="{u:I18n CheckKnownDataBreachesForThisPassword}"
StyleClass="box-footer-label"
VerticalOptions="Center"/>
</StackLayout>
<StackLayout StyleClass="box-row, box-row-switch" <StackLayout StyleClass="box-row, box-row-switch"
IsVisible="{Binding ShowTerms}"> IsVisible="{Binding ShowTerms}">
<Switch <Switch

View File

@@ -1,36 +1,28 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources; using Bit.App.Resources;
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.Exceptions;
using Bit.Core.Models.Request; using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class RegisterPageViewModel : CaptchaProtectedViewModel, IPasswordStrengthable public class RegisterPageViewModel : CaptchaProtectedViewModel
{ {
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly II18nService _i18nService; private readonly II18nService _i18nService;
private readonly IEnvironmentService _environmentService; private readonly IEnvironmentService _environmentService;
private readonly IAuditService _auditService;
private readonly IApiService _apiService; private readonly IApiService _apiService;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private string _email;
private string _masterPassword;
private bool _showPassword; private bool _showPassword;
private bool _acceptPolicies; private bool _acceptPolicies;
private bool _checkExposedMasterPassword;
public RegisterPageViewModel() public RegisterPageViewModel()
{ {
@@ -40,14 +32,12 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"); _platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService"); _i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService"); _environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_auditService = ServiceContainer.Resolve<IAuditService>();
PageTitle = AppResources.CreateAccount; PageTitle = AppResources.CreateAccount;
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword); ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync()); SubmitCommand = new Command(async () => await SubmitAsync());
ShowTerms = !_platformUtilsService.IsSelfHost(); ShowTerms = !_platformUtilsService.IsSelfHost();
PasswordStrengthViewModel = new PasswordStrengthViewModel(this);
} }
public ICommand PoliciesClickCommand => new Command<string>((url) => public ICommand PoliciesClickCommand => new Command<string>((url) =>
@@ -71,34 +61,6 @@ namespace Bit.App.Pages
get => _acceptPolicies; get => _acceptPolicies;
set => SetProperty(ref _acceptPolicies, value); set => SetProperty(ref _acceptPolicies, value);
} }
public bool CheckExposedMasterPassword
{
get => _checkExposedMasterPassword;
set => SetProperty(ref _checkExposedMasterPassword, value);
}
public string MasterPassword
{
get => _masterPassword;
set
{
SetProperty(ref _masterPassword, value);
PasswordStrengthViewModel.CalculatePasswordStrength();
}
}
public string Email
{
get => _email;
set => SetProperty(ref _email, value);
}
public string Password => MasterPassword;
public List<string> UserInputs => PasswordStrengthViewModel.GetPasswordStrengthUserInput(Email);
public string MasterPasswordMininumCharactersDescription => string.Format(AppResources.YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum,
Constants.MasterPasswordMinimumChars);
public PasswordStrengthViewModel PasswordStrengthViewModel { get; }
public bool ShowTerms { get; set; } public bool ShowTerms { get; set; }
public Command SubmitCommand { get; } public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; } public Command TogglePasswordCommand { get; }
@@ -106,10 +68,13 @@ namespace Bit.App.Pages
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye; public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow; public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string Name { get; set; } public string Name { get; set; }
public string Email { get; set; }
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; } public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; } public string Hint { get; set; }
public Action RegistrationSuccess { get; set; } public Action RegistrationSuccess { get; set; }
public Action CloseAction { get; set; } public Action CloseAction { get; set; }
protected override II18nService i18nService => _i18nService; protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService; protected override IEnvironmentService environmentService => _environmentService;
protected override IDeviceActionService deviceActionService => _deviceActionService; protected override IDeviceActionService deviceActionService => _deviceActionService;
@@ -145,7 +110,7 @@ namespace Bit.App.Pages
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) if (MasterPassword.Length < 8)
{ {
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage, await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
AppResources.AnErrorHasOccurred, AppResources.Ok); AppResources.AnErrorHasOccurred, AppResources.Ok);
@@ -163,10 +128,8 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred, AppResources.Ok); AppResources.AnErrorHasOccurred, AppResources.Ok);
return; return;
} }
if (await IsPasswordWeakOrExposed())
{ // TODO: Password strength check?
return;
}
if (showLoading) if (showLoading)
{ {
@@ -176,7 +139,8 @@ 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 kdf = KdfType.PBKDF2_SHA256; var kdf = KdfType.PBKDF2_SHA256;
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, Constants.KdfIterations); var kdfIterations = 100_000;
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, kdfIterations);
var encKey = await _cryptoService.MakeEncKeyAsync(key); var encKey = await _cryptoService.MakeEncKeyAsync(key);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
@@ -188,7 +152,7 @@ namespace Bit.App.Pages
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString, Key = encKey.Item2.EncryptedString,
Kdf = kdf, Kdf = kdf,
KdfIterations = Constants.KdfIterations, KdfIterations = kdfIterations,
Keys = new KeysRequest Keys = new KeysRequest
{ {
PublicKey = keys.Item1, PublicKey = keys.Item1,
@@ -196,7 +160,6 @@ namespace Bit.App.Pages
}, },
CaptchaResponse = _captchaToken, CaptchaResponse = _captchaToken,
}; };
// TODO: org invite? // TODO: org invite?
try try
@@ -245,43 +208,5 @@ namespace Bit.App.Pages
entry.Focus(); entry.Focus();
entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length; entry.CursorPosition = String.IsNullOrEmpty(ConfirmMasterPassword) ? 0 : ConfirmMasterPassword.Length;
} }
private async Task<bool> IsPasswordWeakOrExposed()
{
try
{
var title = string.Empty;
var message = string.Empty;
var exposedPassword = CheckExposedMasterPassword ? await _auditService.PasswordLeakedAsync(MasterPassword) > 0 : false;
var weakPassword = PasswordStrengthViewModel.PasswordStrengthLevel <= PasswordStrengthLevel.Weak;
if (exposedPassword && weakPassword)
{
title = AppResources.WeakAndExposedMasterPassword;
message = AppResources.WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription;
}
else if (exposedPassword)
{
title = AppResources.ExposedMasterPassword;
message = AppResources.PasswordFoundInADataBreachAlertDescription;
}
else if (weakPassword)
{
title = AppResources.WeakMasterPassword;
message = AppResources.WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount;
}
if (exposedPassword || weakPassword)
{
return !await _platformUtilsService.ShowDialogAsync(message, title, AppResources.Yes, AppResources.No);
}
}
catch (Exception ex)
{
HandleException(ex);
}
return false;
}
} }
} }

View File

@@ -47,7 +47,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}" Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
@@ -69,7 +69,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{Binding PolicySummary}" Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -5,7 +5,6 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
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;
@@ -138,8 +137,8 @@ namespace Bit.App.Pages
} }
if (IsPolicyInEffect) if (IsPolicyInEffect)
{ {
var userInputs = _passwordGenerationService.GetPasswordStrengthUserInput(await _stateService.GetEmailAsync()); var userInput = await GetPasswordStrengthUserInput();
var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInputs); var passwordStrength = _passwordGenerationService.PasswordStrength(MasterPassword, userInput);
if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy)) if (!await _policyService.EvaluateMasterPassword(passwordStrength.Score, MasterPassword, Policy))
{ {
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle, await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
@@ -149,7 +148,7 @@ namespace Bit.App.Pages
} }
else else
{ {
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars) if (MasterPassword.Length < 8)
{ {
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle, await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
AppResources.MasterPasswordLengthValMessage, AppResources.Ok); AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
@@ -164,8 +163,9 @@ namespace Bit.App.Pages
} }
var kdf = KdfType.PBKDF2_SHA256; var kdf = KdfType.PBKDF2_SHA256;
var kdfIterations = 100000;
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, Constants.KdfIterations); var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
@@ -187,7 +187,7 @@ namespace Bit.App.Pages
Key = encKey.Item2.EncryptedString, Key = encKey.Item2.EncryptedString,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Kdf = kdf, Kdf = kdf,
KdfIterations = Constants.KdfIterations, KdfIterations = kdfIterations,
OrgIdentifier = OrgIdentifier, OrgIdentifier = OrgIdentifier,
Keys = new KeysRequest Keys = new KeysRequest
{ {
@@ -202,7 +202,7 @@ namespace Bit.App.Pages
// Set Password and relevant information // Set Password and relevant information
await _apiService.SetPasswordAsync(request); await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfTypeAsync(kdf); await _stateService.SetKdfTypeAsync(kdf);
await _stateService.SetKdfIterationsAsync(Constants.KdfIterations); await _stateService.SetKdfIterationsAsync(kdfIterations);
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);

View File

@@ -44,7 +44,7 @@
Margin="0" Margin="0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n UpdateMasterPasswordWarning}" Text="{u:I18n UpdateMasterPasswordWarning}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
@@ -67,7 +67,7 @@
Margin="0" Margin="0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{Binding PolicySummary}" Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -129,8 +129,8 @@ namespace Bit.App.Pages
{ {
if (useCurrentActiveAccount) if (useCurrentActiveAccount)
{ {
var user = await _stateService.GetActiveUserCustomDataAsync(a => (a?.Profile?.UserId, a?.Profile?.Name, a?.Profile?.Email, a?.Profile?.AvatarColor)); return new AvatarImageSource(await _stateService.GetActiveUserIdAsync(),
return new AvatarImageSource(user.UserId, user.Name, user.Email, user.AvatarColor); await _stateService.GetNameAsync(), await _stateService.GetEmailAsync());
} }
return new AvatarImageSource(); return new AvatarImageSource();
} }

View File

@@ -1,10 +1,4 @@
using System; using Bit.App.Controls;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
@@ -14,9 +8,6 @@ namespace Bit.App.Pages
{ {
private string _pageTitle = string.Empty; private string _pageTitle = string.Empty;
private AvatarImageSource _avatar; private AvatarImageSource _avatar;
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
private LazyResolve<IPlatformUtilsService> _platformUtilsService = new LazyResolve<IPlatformUtilsService>();
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public string PageTitle public string PageTitle
{ {
@@ -31,21 +22,5 @@ namespace Bit.App.Pages
} }
public ContentPage Page { get; set; } public ContentPage Page { get; set; }
protected void HandleException(Exception ex, string message = null)
{
if (ex is ApiException apiException && apiException.Error != null)
{
message = apiException.Error.GetSingleMessage();
}
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.HideLoadingAsync();
await _platformUtilsService.Value.ShowDialogAsync(message ?? AppResources.GenericErrorMessage);
}).FireAndForget();
_logger.Value.Exception(ex);
}
} }
} }

View File

@@ -16,22 +16,6 @@ namespace Bit.App.Pages
protected abstract IPlatformUtilsService platformUtilsService { get; } protected abstract IPlatformUtilsService platformUtilsService { get; }
protected string _captchaToken = null; protected string _captchaToken = null;
protected async Task<bool> HandleCaptchaAsync(string captchaSiteKey, bool needsCaptcha, Func<Task> onSuccess)
{
if (!needsCaptcha)
{
_captchaToken = null;
return false;
}
if (await HandleCaptchaAsync(captchaSiteKey))
{
await onSuccess();
_captchaToken = null;
}
return true;
}
protected async Task<bool> HandleCaptchaAsync(string CaptchaSiteKey) protected async Task<bool> HandleCaptchaAsync(string CaptchaSiteKey)
{ {
var callbackUri = "bitwarden://captcha-callback"; var callbackUri = "bitwarden://captcha-callback";

View File

@@ -66,7 +66,7 @@
Margin="0" Margin="0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n PasswordGeneratorPolicyInEffect}" Text="{u:I18n PasswordGeneratorPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
@@ -462,7 +462,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}" />
</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">

View File

@@ -67,7 +67,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n SendDisabledWarning}" Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
@@ -79,7 +79,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n SendOptionsPolicyInEffect}" Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -48,7 +48,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n SendDisabledWarning}" Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"
@@ -60,7 +60,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n SendOptionsPolicyInEffect}" Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -108,7 +108,7 @@
Margin="0, 12, 0, 6" Margin="0, 12, 0, 6"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n SendDisabledWarning}" Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -34,7 +34,7 @@
Margin="0, 12, 0, 0" Margin="0, 12, 0, 0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}" Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -26,7 +26,7 @@
Padding="10" Padding="10"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<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"
@@ -57,7 +57,7 @@
Padding="10" Padding="10"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<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"

View File

@@ -7,10 +7,7 @@ using Bit.App.Pages.Accounts;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel; using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
@@ -35,7 +32,6 @@ namespace Bit.App.Pages
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService; private readonly ILogger _loggerService;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IWatchDeviceService _watchDeviceService;
private const int CustomVaultTimeoutValue = -100; private const int CustomVaultTimeoutValue = -100;
private bool _supportsBiometric; private bool _supportsBiometric;
@@ -48,7 +44,6 @@ namespace Bit.App.Pages
private bool _showChangeMasterPassword; private bool _showChangeMasterPassword;
private bool _reportLoggingEnabled; private bool _reportLoggingEnabled;
private bool _approvePasswordlessLoginRequests; private bool _approvePasswordlessLoginRequests;
private bool _shouldConnectToWatch;
private List<KeyValuePair<string, int?>> _vaultTimeouts = private List<KeyValuePair<string, int?>> _vaultTimeouts =
new List<KeyValuePair<string, int?>> new List<KeyValuePair<string, int?>>
@@ -92,7 +87,6 @@ namespace Bit.App.Pages
_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>();
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>(); GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
PageTitle = AppResources.Settings; PageTitle = AppResources.Settings;
@@ -144,9 +138,6 @@ namespace Bit.App.Pages
!await _keyConnectorService.GetUsesKeyConnector(); !await _keyConnectorService.GetUsesKeyConnector();
_reportLoggingEnabled = await _loggerService.IsEnabled(); _reportLoggingEnabled = await _loggerService.IsEnabled();
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync(); _approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
BuildList(); BuildList();
} }
@@ -610,26 +601,19 @@ namespace Bit.App.Pages
ExecuteAsync = () => SetScreenCaptureAllowedAsync() ExecuteAsync = () => SetScreenCaptureAllowedAsync()
}); });
} }
var accountItems = new List<SettingsPageListItem>(); var accountItems = new List<SettingsPageListItem>
if (Device.RuntimePlatform == Device.iOS)
{ {
accountItems.Add(new SettingsPageListItem new SettingsPageListItem
{ {
Name = AppResources.ConnectToWatch, Name = AppResources.FingerprintPhrase,
SubLabel = _shouldConnectToWatch ? AppResources.On : AppResources.Off, ExecuteAsync = () => FingerprintAsync()
ExecuteAsync = () => ToggleWatchConnectionAsync() },
}); new SettingsPageListItem
} {
accountItems.Add(new SettingsPageListItem Name = AppResources.LogOut,
{ ExecuteAsync = () => LogOutAsync()
Name = AppResources.FingerprintPhrase, }
ExecuteAsync = () => FingerprintAsync() };
});
accountItems.Add(new SettingsPageListItem
{
Name = AppResources.LogOut,
ExecuteAsync = () => LogOutAsync()
});
if (_showChangeMasterPassword) if (_showChangeMasterPassword)
{ {
accountItems.Insert(0, new SettingsPageListItem accountItems.Insert(0, new SettingsPageListItem
@@ -807,13 +791,5 @@ namespace Bit.App.Pages
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok); await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok);
} }
} }
private async Task ToggleWatchConnectionAsync()
{
_shouldConnectToWatch = !_shouldConnectToWatch;
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
BuildList();
}
} }
} }

View File

@@ -90,7 +90,6 @@ namespace Bit.App.Pages
try try
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing); await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
await _syncService.SyncPasswordlessLoginRequestsAsync();
var success = await _syncService.FullSyncAsync(true); var success = await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
if (success) if (success)

View File

@@ -96,7 +96,7 @@
Margin="0" Margin="0"
HasShadow="False" HasShadow="False"
BackgroundColor="Transparent" BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}"> BorderColor="Accent">
<Label <Label
Text="{u:I18n PersonalOwnershipPolicyInEffect}" Text="{u:I18n PersonalOwnershipPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold" StyleClass="text-muted, text-sm, text-bold"

View File

@@ -263,6 +263,12 @@ namespace Bit.App.Pages
{ {
if (DoOnce()) if (DoOnce())
{ {
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
if (cameraPermission != PermissionStatus.Granted)
{
return;
}
var page = new ScanPage(key => var page = new ScanPage(key =>
{ {
Device.BeginInvokeOnMainThread(async () => Device.BeginInvokeOnMainThread(async () =>
@@ -271,7 +277,6 @@ namespace Bit.App.Pages
await _vm.UpdateTotpKeyAsync(key); await _vm.UpdateTotpKeyAsync(key);
}); });
}); });
await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page)); await Navigation.PushModalAsync(new Xamarin.Forms.NavigationPage(page));
} }
} }

View File

@@ -29,7 +29,6 @@ namespace Bit.App.Pages
private readonly ICustomFieldItemFactory _customFieldItemFactory; private readonly ICustomFieldItemFactory _customFieldItemFactory;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IAutofillHandler _autofillHandler; private readonly IAutofillHandler _autofillHandler;
private readonly IWatchDeviceService _watchDeviceService;
private bool _showNotesSeparator; private bool _showNotesSeparator;
private bool _showPassword; private bool _showPassword;
@@ -81,7 +80,6 @@ namespace Bit.App.Pages
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory"); _customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService"); _clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>(); _autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
GeneratePasswordCommand = new Command(GeneratePassword); GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword); TogglePasswordCommand = new Command(TogglePassword);
@@ -139,7 +137,6 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(AppResources.Mr, AppResources.Mr), new KeyValuePair<string, string>(AppResources.Mr, AppResources.Mr),
new KeyValuePair<string, string>(AppResources.Mrs, AppResources.Mrs), new KeyValuePair<string, string>(AppResources.Mrs, AppResources.Mrs),
new KeyValuePair<string, string>(AppResources.Ms, AppResources.Ms), new KeyValuePair<string, string>(AppResources.Ms, AppResources.Ms),
new KeyValuePair<string, string>(AppResources.Mx, AppResources.Mx),
new KeyValuePair<string, string>(AppResources.Dr, AppResources.Dr), new KeyValuePair<string, string>(AppResources.Dr, AppResources.Dr),
}; };
FolderOptions = new List<KeyValuePair<string, string>>(); FolderOptions = new List<KeyValuePair<string, string>>();
@@ -510,8 +507,6 @@ namespace Bit.App.Pages
EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated); EditMode && !CloneMode ? AppResources.ItemUpdated : AppResources.NewItemCreated);
_messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id); _messagingService.Send(EditMode && !CloneMode ? "editedCipher" : "addedCipher", Cipher.Id);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
if (Page is CipherAddEditPage page && page.FromAutofillFramework) if (Page is CipherAddEditPage page && page.FromAutofillFramework)
{ {
// Close and go back to app // Close and go back to app

View File

@@ -31,7 +31,6 @@ namespace Bit.App.Pages
private readonly ILocalizeService _localizeService; private readonly ILocalizeService _localizeService;
private readonly ICustomFieldItemFactory _customFieldItemFactory; private readonly ICustomFieldItemFactory _customFieldItemFactory;
private readonly IClipboardService _clipboardService; private readonly IClipboardService _clipboardService;
private readonly IWatchDeviceService _watchDeviceService;
private List<ICustomFieldItemViewModel> _fields; private List<ICustomFieldItemViewModel> _fields;
private bool _canAccessPremium; private bool _canAccessPremium;
@@ -63,7 +62,6 @@ namespace Bit.App.Pages
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService"); _localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory"); _customFieldItemFactory = ServiceContainer.Resolve<ICustomFieldItemFactory>("customFieldItemFactory");
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService"); _clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false); CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
@@ -373,9 +371,6 @@ namespace Bit.App.Pages
await _cipherService.SoftDeleteWithServerAsync(Cipher.Id); await _cipherService.SoftDeleteWithServerAsync(Cipher.Id);
} }
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_platformUtilsService.ShowToast("success", null, _platformUtilsService.ShowToast("success", null,
Cipher.IsDeleted ? AppResources.ItemDeleted : AppResources.ItemSoftDeleted); Cipher.IsDeleted ? AppResources.ItemDeleted : AppResources.ItemSoftDeleted);
_messagingService.Send(Cipher.IsDeleted ? "deletedCipher" : "softDeletedCipher", Cipher); _messagingService.Send(Cipher.IsDeleted ? "deletedCipher" : "softDeletedCipher", Cipher);

View File

@@ -108,10 +108,6 @@ namespace Bit.App.Pages
else if (message.Command == "syncCompleted") else if (message.Command == "syncCompleted")
{ {
await Task.Delay(500); await Task.Delay(500);
if (_vm.MainPage)
{
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
Device.BeginInvokeOnMainThread(() => Device.BeginInvokeOnMainThread(() =>
{ {
IsBusy = false; IsBusy = false;

View File

@@ -189,7 +189,6 @@ namespace Bit.App.Pages
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing) if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
{ {
SyncRefreshing = true; SyncRefreshing = true;
await _syncService.SyncPasswordlessLoginRequestsAsync();
await _syncService.FullSyncAsync(false); await _syncService.FullSyncAsync(false);
return; return;
} }

View File

@@ -34,13 +34,16 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ContentView <zxing:ZXingScannerView
x:Name="_scannerContainer" x:Name="_zxing"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
AutomationId="zxingScannerView" AutomationId="zxingScannerView"
IsVisible="{Binding ShowScanner}" IsVisible="{Binding ShowScanner}"
Grid.Column="0" Grid.Column="0"
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="3"/> Grid.RowSpan="3"
OnScanResult="OnScanResult"/>
<StackLayout <StackLayout
VerticalOptions="Center" VerticalOptions="Center"
HorizontalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"

View File

@@ -8,10 +8,8 @@ using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using SkiaSharp; using SkiaSharp;
using SkiaSharp.Views.Forms; using SkiaSharp.Views.Forms;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials; using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
using ZXing.Net.Mobile.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@@ -28,15 +26,20 @@ namespace Bit.App.Pages
private bool _pageIsActive; private bool _pageIsActive;
private bool _qrcodeFound; private bool _qrcodeFound;
private float _scale; private float _scale;
private ZXingScannerView _zxing;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger"); private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
public ScanPage(Action<string> callback) public ScanPage(Action<string> callback)
{ {
InitializeComponent();
_callback = callback; _callback = callback;
ViewModel.InitScannerCommand = new Command(() => InitScanner()); InitializeComponent();
_zxing.Options = new ZXing.Mobile.MobileBarcodeScanningOptions
{
UseNativeScanning = true,
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
AutoRotate = false,
TryInverted = true
};
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
ToolbarItems.RemoveAt(0); ToolbarItems.RemoveAt(0);
@@ -52,53 +55,6 @@ namespace Bit.App.Pages
protected override void OnAppearing() protected override void OnAppearing()
{ {
base.OnAppearing(); base.OnAppearing();
StartScanner();
}
protected override void OnDisappearing()
{
StopScanner().FireAndForget();
base.OnDisappearing();
}
// Fix known bug with DelayBetweenAnalyzingFrames & DelayBetweenContinuousScans: https://github.com/Redth/ZXing.Net.Mobile/issues/721
private void InitScanner()
{
try
{
if (!ViewModel.HasCameraPermission || !ViewModel.ShowScanner || _zxing != null)
{
return;
}
_zxing = new ZXingScannerView();
_zxing.Options = new ZXing.Mobile.MobileBarcodeScanningOptions
{
UseNativeScanning = true,
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
AutoRotate = false,
TryInverted = true,
DelayBetweenAnalyzingFrames = 5,
DelayBetweenContinuousScans = 5
};
_scannerContainer.Content = _zxing;
StartScanner();
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
}
private void StartScanner()
{
if (_zxing == null)
{
return;
}
_zxing.OnScanResult -= OnScanResult;
_zxing.OnScanResult += OnScanResult;
_zxing.IsScanning = true; _zxing.IsScanning = true;
// Fix for Autofocus, now it's done every 2 seconds so that the user does't have to do it // Fix for Autofocus, now it's done every 2 seconds so that the user does't have to do it
@@ -142,21 +98,16 @@ namespace Bit.App.Pages
AnimationLoopAsync(); AnimationLoopAsync();
} }
private async Task StopScanner() protected override async void OnDisappearing()
{ {
if (_zxing == null)
{
return;
}
_autofocusCts?.Cancel(); _autofocusCts?.Cancel();
if (_continuousAutofocusTask != null) if (_continuousAutofocusTask != null)
{ {
await _continuousAutofocusTask; await _continuousAutofocusTask;
} }
_zxing.IsScanning = false; _zxing.IsScanning = false;
_zxing.OnScanResult -= OnScanResult;
_pageIsActive = false; _pageIsActive = false;
base.OnDisappearing();
} }
private async void OnScanResult(ZXing.Result result) private async void OnScanResult(ZXing.Result result)

View File

@@ -1,14 +1,6 @@
using System; using Bit.App.Resources;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
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 Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -17,46 +9,13 @@ namespace Bit.App.Pages
{ {
private bool _showScanner = true; private bool _showScanner = true;
private string _totpAuthenticationKey; private string _totpAuthenticationKey;
private IPlatformUtilsService _platformUtilsService;
private IDeviceActionService _deviceActionService;
private ILogger _logger;
public ScanPageViewModel() public ScanPageViewModel()
{ {
ToggleScanModeCommand = new AsyncCommand(ToggleScanMode, onException: HandleException); ToggleScanModeCommand = new Command(() => ShowScanner = !ShowScanner);
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_logger = ServiceContainer.Resolve<ILogger>();
InitAsync().FireAndForget();
} }
public async Task InitAsync() public Command ToggleScanModeCommand { get; set; }
{
try
{
await Device.InvokeOnMainThreadAsync(async () =>
{
var hasCameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
HasCameraPermission = hasCameraPermission == PermissionStatus.Granted;
ShowScanner = hasCameraPermission == PermissionStatus.Granted;
});
if (!HasCameraPermission)
{
return;
}
InitScannerCommand.Execute(null);
}
catch (System.Exception ex)
{
HandleException(ex);
}
}
public ICommand ToggleScanModeCommand { get; set; }
public ICommand InitScannerCommand { get; set; }
public bool HasCameraPermission { get; set; }
public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner; public string ScanQrPageTitle => ShowScanner ? AppResources.ScanQrTitle : AppResources.AuthenticatorKeyScanner;
public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered; public string CameraInstructionTop => ShowScanner ? AppResources.PointYourCameraAtTheQRCode : AppResources.OnceTheKeyIsSuccessfullyEntered;
public string TotpAuthenticationKey public string TotpAuthenticationKey
@@ -80,23 +39,6 @@ namespace Bit.App.Pages
}); });
} }
private async Task ToggleScanMode()
{
var cameraPermission = await PermissionManager.CheckAndRequestPermissionAsync(new Permissions.Camera());
HasCameraPermission = cameraPermission == PermissionStatus.Granted;
if (!HasCameraPermission)
{
var openAppSettingsResult = await _platformUtilsService.ShowDialogAsync(AppResources.EnableCamerPermissionToUseTheScanner, title: string.Empty, confirmText: AppResources.Settings, cancelText: AppResources.NoThanks);
if (openAppSettingsResult)
{
_deviceActionService.OpenAppSettings();
}
return;
}
ShowScanner = !ShowScanner;
InitScannerCommand.Execute(null);
}
public FormattedString ToggleScanModeLabel public FormattedString ToggleScanModeLabel
{ {
get get
@@ -115,15 +57,5 @@ namespace Bit.App.Pages
return fs; return fs;
} }
} }
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
}).FireAndForget();
_logger.Exception(ex);
}
} }
} }

View File

@@ -481,15 +481,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to A notification has been sent to your device..
/// </summary>
public static string ANotificationHasBeenSentToYourDevice {
get {
return ResourceManager.GetString("ANotificationHasBeenSentToYourDevice", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to API access token. /// Looks up a localized string similar to API access token.
/// </summary> /// </summary>
@@ -1372,15 +1363,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Check known data breaches for this password.
/// </summary>
public static string CheckKnownDataBreachesForThisPassword {
get {
return ResourceManager.GetString("CheckKnownDataBreachesForThisPassword", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Check if password has been exposed.. /// Looks up a localized string similar to Check if password has been exposed..
/// </summary> /// </summary>
@@ -1507,15 +1489,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Connect to Watch.
/// </summary>
public static string ConnectToWatch {
get {
return ResourceManager.GetString("ConnectToWatch", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Continue. /// Looks up a localized string similar to Continue.
/// </summary> /// </summary>
@@ -2146,15 +2119,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Enable camera permission to use the scanner.
/// </summary>
public static string EnableCamerPermissionToUseTheScanner {
get {
return ResourceManager.GetString("EnableCamerPermissionToUseTheScanner", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Enabled. /// Looks up a localized string similar to Enabled.
/// </summary> /// </summary>
@@ -2425,15 +2389,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Exposed Master Password.
/// </summary>
public static string ExposedMasterPassword {
get {
return ResourceManager.GetString("ExposedMasterPassword", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Extension activated!. /// Looks up a localized string similar to Extension activated!.
/// </summary> /// </summary>
@@ -2938,15 +2893,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Get master password hint.
/// </summary>
public static string GetMasterPasswordwordHint {
get {
return ResourceManager.GetString("GetMasterPasswordwordHint", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Get your master password hint. /// Looks up a localized string similar to Get your master password hint.
/// </summary> /// </summary>
@@ -2956,15 +2902,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Good.
/// </summary>
public static string Good {
get {
return ResourceManager.GetString("Good", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Go to my vault. /// Looks up a localized string similar to Go to my vault.
/// </summary> /// </summary>
@@ -3100,15 +3037,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Important.
/// </summary>
public static string Important {
get {
return ResourceManager.GetString("Important", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Import items. /// Looks up a localized string similar to Import items.
/// </summary> /// </summary>
@@ -3478,15 +3406,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Logging in as {0}.
/// </summary>
public static string LoggingInAsX {
get {
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Log In. /// Looks up a localized string similar to Log In.
/// </summary> /// </summary>
@@ -3515,11 +3434,10 @@ namespace Bit.App.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Login attempt from: /// Looks up a localized string similar to Login attempt from {0}. Do you want to switch to this account?.
///{0}
///Do you want to switch to this account?.
/// </summary> /// </summary>
public static string LoginAttemptFromXDoYouWantToSwitchToThisAccount { public static string LoginAttemptFromXDoYouWantToSwitchToThisAccount
{
get { get {
return ResourceManager.GetString("LoginAttemptFromXDoYouWantToSwitchToThisAccount", resourceCulture); return ResourceManager.GetString("LoginAttemptFromXDoYouWantToSwitchToThisAccount", resourceCulture);
} }
@@ -3543,15 +3461,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Log in initiated.
/// </summary>
public static string LogInInitiated {
get {
return ResourceManager.GetString("LogInInitiated", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Login. /// Looks up a localized string similar to Login.
/// </summary> /// </summary>
@@ -3598,7 +3507,7 @@ namespace Bit.App.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Enterprise single sign-on. /// Looks up a localized string similar to Enterprise Single Sign-On.
/// </summary> /// </summary>
public static string LogInSso { public static string LogInSso {
get { get {
@@ -3633,24 +3542,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Log In with another device.
/// </summary>
public static string LogInWithAnotherDevice {
get {
return ResourceManager.GetString("LogInWithAnotherDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log in with master password.
/// </summary>
public static string LogInWithMasterPassword {
get {
return ResourceManager.GetString("LogInWithMasterPassword", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Log out. /// Looks up a localized string similar to Log out.
/// </summary> /// </summary>
@@ -3984,17 +3875,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Mx.
/// </summary>
public static string Mx
{
get
{
return ResourceManager.GetString("Mx", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to You must log into the main Bitwarden app before you can use the extension.. /// Looks up a localized string similar to You must log into the main Bitwarden app before you can use the extension..
/// </summary> /// </summary>
@@ -4049,15 +3929,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Need another option?.
/// </summary>
public static string NeedAnotherOption {
get {
return ResourceManager.GetString("NeedAnotherOption", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Never. /// Looks up a localized string similar to Never.
/// </summary> /// </summary>
@@ -4076,15 +3947,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to New around here?.
/// </summary>
public static string NewAroundHere {
get {
return ResourceManager.GetString("NewAroundHere", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to New custom field. /// Looks up a localized string similar to New custom field.
/// </summary> /// </summary>
@@ -4319,15 +4181,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Not you?.
/// </summary>
public static string NotYou {
get {
return ResourceManager.GetString("NotYou", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to This login does not have a username or password configured.. /// Looks up a localized string similar to This login does not have a username or password configured..
/// </summary> /// </summary>
@@ -4617,15 +4470,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?.
/// </summary>
public static string PasswordFoundInADataBreachAlertDescription {
get {
return ResourceManager.GetString("PasswordFoundInADataBreachAlertDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Password generated. /// Looks up a localized string similar to Password generated.
/// </summary> /// </summary>
@@ -4806,15 +4650,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device..
/// </summary>
public static string PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice {
get {
return ResourceManager.GetString("PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Plus addressed email. /// Looks up a localized string similar to Plus addressed email.
/// </summary> /// </summary>
@@ -5113,15 +4948,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Resend notification.
/// </summary>
public static string ResendNotification {
get {
return ResourceManager.GetString("ResendNotification", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password.. /// Looks up a localized string similar to This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password..
/// </summary> /// </summary>
@@ -5311,6 +5137,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
public static string SelectAddTotpToStoreTheKeySafely {
get {
return ResourceManager.GetString("SelectAddTotpToStoreTheKeySafely", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to You must select at least one collection.. /// Looks up a localized string similar to You must select at least one collection..
/// </summary> /// </summary>
@@ -5725,15 +5560,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Strong.
/// </summary>
public static string Strong {
get {
return ResourceManager.GetString("Strong", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Submit. /// Looks up a localized string similar to Submit.
/// </summary> /// </summary>
@@ -5950,15 +5776,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to This request is no longer valid.
/// </summary>
public static string ThisRequestIsNoLongerValid {
get {
return ResourceManager.GetString("ThisRequestIsNoLongerValid", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to 3 days. /// Looks up a localized string similar to 3 days.
/// </summary> /// </summary>
@@ -6670,15 +6487,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to View all log in options.
/// </summary>
public static string ViewAllLoginOptions {
get {
return ResourceManager.GetString("ViewAllLoginOptions", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to View item. /// Looks up a localized string similar to View item.
/// </summary> /// </summary>
@@ -6715,51 +6523,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Weak.
/// </summary>
public static string Weak {
get {
return ResourceManager.GetString("Weak", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Weak and Exposed Master Password.
/// </summary>
public static string WeakAndExposedMasterPassword {
get {
return ResourceManager.GetString("WeakAndExposedMasterPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Weak Master Password.
/// </summary>
public static string WeakMasterPassword {
get {
return ResourceManager.GetString("WeakMasterPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?.
/// </summary>
public static string WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription {
get {
return ResourceManager.GetString("WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password?.
/// </summary>
public static string WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount {
get {
return ResourceManager.GetString("WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Website. /// Looks up a localized string similar to Website.
/// </summary> /// </summary>
@@ -6877,15 +6640,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Your master password cannot be recovered if you forget it! {0} characters minimum..
/// </summary>
public static string YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum {
get {
return ResourceManager.GetString("YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device&apos;s USB port, then touch its button.. /// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device&apos;s USB port, then touch its button..
/// </summary> /// </summary>

View File

@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} is gekopieer.</value> <value>{0} is gekopieer.</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>Bevestig vingerafdruk</value> <value>Bevestig vingerafdruk</value>
@@ -1576,7 +1576,7 @@ Skandering gebeur outomaties.</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="SolarizedDark" xml:space="preserve"> <data name="SolarizedDark" xml:space="preserve">
<value>'Solarized' Donker</value> <value>Solarized Dark</value>
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="AutofillBlockedUris" xml:space="preserve"> <data name="AutofillBlockedUris" xml:space="preserve">
@@ -2299,6 +2299,9 @@ Skandering gebeur outomaties.</value>
<value>Sodra u die sleutel reg ingevoer het, <value>Sodra u die sleutel reg ingevoer het,
kies u Voeg TOTP toe om die sleutel veilig te bewaar</value> kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Deur u vergrendelopsies na “Nooit” te stel, is u kluis beskikbaar aan enigeen met toegang tot u toestel. Indien u hierdie opsie gebruik moet u seker maak dat u u toestel voldoende beskerm.</value> <value>Deur u vergrendelopsies na “Nooit” te stel, is u kluis beskikbaar aan enigeen met toegang tot u toestel. Indien u hierdie opsie gebruik moet u seker maak dat u u toestel voldoende beskerm.</value>
</data> </data>
@@ -2449,9 +2452,6 @@ kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>Lukraak</value> <value>Lukraak</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>Koppel aan horlosie</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>Toeganklikheidsdiensopenbaarmaking</value> <value>Toeganklikheidsdiensopenbaarmaking</value>
</data> </data>
@@ -2468,50 +2468,8 @@ kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
<value>Aantekenversoek het reeds verstryk.</value> <value>Aantekenversoek het reeds verstryk.</value>
</data> </data>
<data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve"> <data name="LoginAttemptFromXDoYouWantToSwitchToThisAccount" xml:space="preserve">
<value>Teken Aan probeerslag van: <value>Login attempt from:
{0} {0}
Wil u na die rekening omskakel?</value> Do you want to switch to this account?</value>
</data>
<data name="NewAroundHere" xml:space="preserve">
<value>Nuut hier?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Kry hoofwagwoord wenk</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Teken in as {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Nie jy nie?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Teken aan met hoofwagwoord</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Teken Aan met 'n ander toestel</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Aanmelding begin</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>'n Kennisgewing was gestuur na u toestel.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>Asseblief maak seker jou kluis is oopgesluit en die vingerafdruk frase stem ooreen op die ander toestel.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>Stuur kennisgewing weer</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>Is daar nog 'n opsie nodig?</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>Wys alle aanmeldings opsies</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>Hierdie versoek is nie langer gelding nie</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Laat die kamera versoek toe om die skandeerder te gebruik</value>
</data> </data>
</root> </root>

View File

@@ -118,17 +118,17 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="About" xml:space="preserve"> <data name="About" xml:space="preserve">
<value>عن التطبيق</value> <value>حول</value>
</data> </data>
<data name="Add" xml:space="preserve"> <data name="Add" xml:space="preserve">
<value>أضِف</value> <value>إضافة</value>
<comment>Add/create a new entity (verb).</comment> <comment>Add/create a new entity (verb).</comment>
</data> </data>
<data name="AddFolder" xml:space="preserve"> <data name="AddFolder" xml:space="preserve">
<value>مجلد مضاف</value> <value>إضافة مجلد</value>
</data> </data>
<data name="AddItem" xml:space="preserve"> <data name="AddItem" xml:space="preserve">
<value>تمت إضافة العنصر</value> <value>إضافة عنصر</value>
<comment>The title for the add item page.</comment> <comment>The title for the add item page.</comment>
</data> </data>
<data name="AnErrorHasOccurred" xml:space="preserve"> <data name="AnErrorHasOccurred" xml:space="preserve">
@@ -323,14 +323,14 @@
<comment>Label for a password.</comment> <comment>Label for a password.</comment>
</data> </data>
<data name="Save" xml:space="preserve"> <data name="Save" xml:space="preserve">
<value>حفظ</value> <value>تسجيل</value>
<comment>Button text for a save operation (verb).</comment> <comment>Button text for a save operation (verb).</comment>
</data> </data>
<data name="Move" xml:space="preserve"> <data name="Move" xml:space="preserve">
<value>نقل</value> <value>نقل</value>
</data> </data>
<data name="Saving" xml:space="preserve"> <data name="Saving" xml:space="preserve">
<value>حفظ...</value> <value>تسجيل ...</value>
<comment>Message shown when interacting with the server</comment> <comment>Message shown when interacting with the server</comment>
</data> </data>
<data name="Settings" xml:space="preserve"> <data name="Settings" xml:space="preserve">
@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} تم نسخه</value> <value>{0} تم نسخه</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>التحقق من بصمة الإصبع</value> <value>التحقق من بصمة الإصبع</value>
@@ -2300,6 +2300,9 @@
<value>بمجرد إدخال المفتاح بنجاح، <value>بمجرد إدخال المفتاح بنجاح،
حدد إضافة TOTP لتخزين المفتاح بأمان</value> حدد إضافة TOTP لتخزين المفتاح بأمان</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>تعيين خيارات قفل الخاص بك إلى "مطلقا" يبقي خزنتك متاحةً لأي شخص لديه حق الوصول إلى جهازك. إذا كنت تستخدم هذا الخيار، يجب أن تتأكد من الحفاظ على حماية جهازك بشكل صحيح.</value> <value>تعيين خيارات قفل الخاص بك إلى "مطلقا" يبقي خزنتك متاحةً لأي شخص لديه حق الوصول إلى جهازك. إذا كنت تستخدم هذا الخيار، يجب أن تتأكد من الحفاظ على حماية جهازك بشكل صحيح.</value>
</data> </data>
@@ -2450,9 +2453,6 @@
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>عشوائي</value> <value>عشوائي</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>متّصل بالسّاعة</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>كشف خدمة إمكانية الوصول</value> <value>كشف خدمة إمكانية الوصول</value>
</data> </data>
@@ -2473,46 +2473,4 @@
{0} {0}
هل تريد التبديل إلى هذا الحساب؟</value> هل تريد التبديل إلى هذا الحساب؟</value>
</data> </data>
<data name="NewAroundHere" xml:space="preserve">
<value>جديد هنا؟</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>احصل على تلميح كلمة المرور الرئيسية</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>تسجيل الدخول كـ {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>ليس أنت؟</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>تسجيل الدخول باستخدام كلمة المرور الرئيسية</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>تسجيل الدخول باستخدام جهاز آخر</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>بدء تسجيل الدخول</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>تم إرسال إشعار إلى جهازك.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>الرجاء التأكد من أن الخزنة الخاصة بك غير مقفلة وأن عبارة بصمة الإصبع تتطابق على الجهاز الآخر.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>إعادة إرسال الإشعار</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>هل تحتاج إلى خيار آخر؟</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>عرض جميع خيارات تسجيل الدخول</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>هذا الطلب لم يعد صالحًا</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>تمكين إذن الكاميرا لاستخدام الماسح الضوئي</value>
</data>
</root> </root>

View File

@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} kopyalandı.</value> <value>{0} kopyalandı.</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>Barmaq izini təsdiqlə</value> <value>Barmaq izini təsdiqlə</value>
@@ -2137,7 +2137,7 @@ Skan prosesi avtomatik baş tutacaq.</value>
<value>Bu təşkilat, sizi "parol sıfırlama"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat administratorlarına ana parolunuzu dəyişdirmə icazəsi verəcək.</value> <value>Bu təşkilat, sizi "parol sıfırlama"da avtomatik olaraq qeydiyyata alan müəssisə siyasətinə sahibdir. Qeydiyyat, təşkilat administratorlarına ana parolunuzu dəyişdirmə icazəsi verəcək.</value>
</data> </data>
<data name="VaultTimeoutPolicyInEffect" xml:space="preserve"> <data name="VaultTimeoutPolicyInEffect" xml:space="preserve">
<value>Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s)</value> <value>Təşkilatınızın siyasətləri, anbarınızın vaxt bitişinə təsir edir. Anbar vaxt bitişi üçün icazə verilən maksimum vaxt $HOURS$ saat $MINUTES$ dəqiqədir</value>
</data> </data>
<data name="VaultTimeoutToLarge" xml:space="preserve"> <data name="VaultTimeoutToLarge" xml:space="preserve">
<value>Anbar vaxt bitişi, təşkilatınız tərəfindən tənzimlənən məhdudiyyətləri aşır.</value> <value>Anbar vaxt bitişi, təşkilatınız tərəfindən tənzimlənən məhdudiyyətləri aşır.</value>
@@ -2298,6 +2298,9 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve"> <data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Açar uğurla daxil edildikdən sonra, açarı güvənli şəkildə saxlamaq üçün "TOTP əlavə et"i seçin</value> <value>Açar uğurla daxil edildikdən sonra, açarı güvənli şəkildə saxlamaq üçün "TOTP əlavə et"i seçin</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Kilid seçimlərini "Heç vaxt" olaraq tənzimləmək, anbarınızı cihazınıza müraciəti olan hər kəsə əlçatan edir. Bu seçimi istifadə etsəniz, cihazınızı düzgün qoruduğunuza əmin olmalısınız.</value> <value>Kilid seçimlərini "Heç vaxt" olaraq tənzimləmək, anbarınızı cihazınıza müraciəti olan hər kəsə əlçatan edir. Bu seçimi istifadə etsəniz, cihazınızı düzgün qoruduğunuza əmin olmalısınız.</value>
</data> </data>
@@ -2448,9 +2451,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>Təsadüfi</value> <value>Təsadüfi</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>"Watch"a bağlan</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>Əlçatımlılıq Xidməti açıqlaması</value> <value>Əlçatımlılıq Xidməti açıqlaması</value>
</data> </data>
@@ -2471,46 +2471,4 @@ Skan prosesi avtomatik baş tutacaq.</value>
{0} {0}
Bu hesaba keçmək istəyirsiniz?</value> Bu hesaba keçmək istəyirsiniz?</value>
</data> </data>
<data name="NewAroundHere" xml:space="preserve">
<value>Burada yenisiniz?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Ana parol üçün məsləhət alın</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>{0} olaraq giriş edilir</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Siz deyilsiniz?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Ana parolla giriş et</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Başqa cihazla giriş et</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Giriş etmə başladıldı</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>Cihazınıza bir bildiriş göndərildi.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>Zəhmət olmasa anbarınızın kilidinin açıq olduğuna və Barmaq izi ifadəsinin digər cihazda uyğun gəldiyinə əmin olun.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>Bildirişi təkrar göndər</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>Başqa bir seçimə ehtiyacınız var?</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>Bütün giriş etmə seçimlərinə bax</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>Bu tələb artıq yararsızdır</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Skaneri istifadə etmək üçün kamera icazəsini fəallaşdırın</value>
</data>
</root> </root>

View File

@@ -349,7 +349,7 @@
<value>Адправіць</value> <value>Адправіць</value>
</data> </data>
<data name="Sync" xml:space="preserve"> <data name="Sync" xml:space="preserve">
<value>Сінхранізаваць</value> <value>Сінхранізавана</value>
<comment>The title for the sync page.</comment> <comment>The title for the sync page.</comment>
</data> </data>
<data name="ThankYou" xml:space="preserve"> <data name="ThankYou" xml:space="preserve">
@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} скапіяваны</value> <value>{0} скапіяваны</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>Праверка адбітка пальца</value> <value>Праверка адбітка пальца</value>
@@ -1575,7 +1575,7 @@
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="SolarizedDark" xml:space="preserve"> <data name="SolarizedDark" xml:space="preserve">
<value>Solarized Dark</value> <value>Цёмная Solarized</value>
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="AutofillBlockedUris" xml:space="preserve"> <data name="AutofillBlockedUris" xml:space="preserve">
@@ -1622,7 +1622,7 @@
<value>Біяметрычныя праверка</value> <value>Біяметрычныя праверка</value>
</data> </data>
<data name="Biometrics" xml:space="preserve"> <data name="Biometrics" xml:space="preserve">
<value>біяметрыяй</value> <value>Біяметрыяй</value>
</data> </data>
<data name="UseBiometricsToUnlock" xml:space="preserve"> <data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Выкарыстоўваць біяметрычныя даныя для разблакіроўкі</value> <value>Выкарыстоўваць біяметрычныя даныя для разблакіроўкі</value>
@@ -2299,6 +2299,9 @@
<value>Пасля таго, як ваш ключ паспяхова ўведзены, <value>Пасля таго, як ваш ключ паспяхова ўведзены,
выберыце "Дадаць TOTP" для надзейнага захавання ключа</value> выберыце "Дадаць TOTP" для надзейнага захавання ключа</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Прызначыўшы параметр блакіравання "Ніколі", ваша сховішча будзе даступна кожнаму, хто мае доступ да вашай прылады. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена.</value> <value>Прызначыўшы параметр блакіравання "Ніколі", ваша сховішча будзе даступна кожнаму, хто мае доступ да вашай прылады. Калі вы выкарыстоўваеце гэты параметр, вы павінны быць упэўнены ў тым, што ваша прылада надзейна абаронена.</value>
</data> </data>
@@ -2449,9 +2452,6 @@
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>Выпадкова</value> <value>Выпадкова</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>Падлучыцца да гадзінніка</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>Апісанне службы спецыяльных магчымасцей</value> <value>Апісанне службы спецыяльных магчымасцей</value>
</data> </data>
@@ -2472,46 +2472,4 @@
{0} {0}
Вы сапраўды хочаце перайсці на гэты ўліковы запіс?</value> Вы сапраўды хочаце перайсці на гэты ўліковы запіс?</value>
</data> </data>
<data name="NewAroundHere" xml:space="preserve">
<value>Упершыню тут?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Атрымаць падказку да асноўнага пароля</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Увайсці як {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Не вы?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Увайсці з асноўным паролем</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Увайсці з іншай прылады</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Ініцыяваны ўваход</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>Апавяшчэнне было адпраўлена на вашу прыладу.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>Пераканайцеся, што ваша сховішча разблакіравана, а фраза адбітка пальца супадае з іншай прыладай.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>Адправіць апавяшчэнне паўторна</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>Неабходны іншы варыянт?</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>Паглядзець усе варыянты ўваходу</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>Гэты запыт больш не дзейнічае</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Каб выкарыстоўваць сканер дайце дазвол на выкарыстанне камеры</value>
</data>
</root> </root>

View File

@@ -300,7 +300,7 @@
<comment>The title for the vault page.</comment> <comment>The title for the vault page.</comment>
</data> </data>
<data name="Authenticator" xml:space="preserve"> <data name="Authenticator" xml:space="preserve">
<value>Удостоверител</value> <value>Authenticator</value>
<comment>Authenticator TOTP feature</comment> <comment>Authenticator TOTP feature</comment>
</data> </data>
<data name="Name" xml:space="preserve"> <data name="Name" xml:space="preserve">
@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} беше копирано.</value> <value>{0} беше копирано.</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>Потвърждаване на пръстовия отпечатък</value> <value>Потвърждаване на пръстовия отпечатък</value>
@@ -2080,7 +2080,7 @@
<value>Това действие е защитено. За да продължите, въведете отново главната си парола, за да потвърдите самоличността си.</value> <value>Това действие е защитено. За да продължите, въведете отново главната си парола, за да потвърдите самоличността си.</value>
</data> </data>
<data name="CaptchaRequired" xml:space="preserve"> <data name="CaptchaRequired" xml:space="preserve">
<value>Адресът за hCaptcha е задължителен</value> <value>Captcha required</value>
</data> </data>
<data name="CaptchaFailed" xml:space="preserve"> <data name="CaptchaFailed" xml:space="preserve">
<value> <value>
@@ -2267,7 +2267,7 @@
<value>Всички</value> <value>Всички</value>
</data> </data>
<data name="Totp" xml:space="preserve"> <data name="Totp" xml:space="preserve">
<value>Кода за потвърждаване</value> <value>TOTP</value>
</data> </data>
<data name="VerificationCodes" xml:space="preserve"> <data name="VerificationCodes" xml:space="preserve">
<value>Кодове за потвърждаване</value> <value>Кодове за потвърждаване</value>
@@ -2291,15 +2291,18 @@
<value>Ръчно въвеждане на кода</value> <value>Ръчно въвеждане на кода</value>
</data> </data>
<data name="AddTotp" xml:space="preserve"> <data name="AddTotp" xml:space="preserve">
<value>Копиране на кода за потвърждаване</value> <value>Add TOTP</value>
</data> </data>
<data name="SetupTotp" xml:space="preserve"> <data name="SetupTotp" xml:space="preserve">
<value>Копиране на кода за потвърждаване</value> <value>Set up TOTP</value>
</data> </data>
<data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve"> <data name="OnceTheKeyIsSuccessfullyEntered" xml:space="preserve">
<value>Once the key is successfully entered, <value>Once the key is successfully entered,
select Add TOTP to store the key safely</value> select Add TOTP to store the key safely</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Ако изберете „Никога“ като настройка за заключването, трезорът Ви ще бъде достъпен за всеки, който има досег с устройството. Ако използвате тази настройка, трябва да се уверите, че устройството Ви е удачно защитено.</value> <value>Ако изберете „Никога“ като настройка за заключването, трезорът Ви ще бъде достъпен за всеки, който има досег с устройството. Ако използвате тази настройка, трябва да се уверите, че устройството Ви е удачно защитено.</value>
</data> </data>
@@ -2334,7 +2337,7 @@ select Add TOTP to store the key safely</value>
<value>Време</value> <value>Време</value>
</data> </data>
<data name="Near" xml:space="preserve"> <data name="Near" xml:space="preserve">
<value>Близо</value> <value>Near</value>
</data> </data>
<data name="ConfirmLogIn" xml:space="preserve"> <data name="ConfirmLogIn" xml:space="preserve">
<value>Потвърждаване на вписването</value> <value>Потвърждаване на вписването</value>
@@ -2385,13 +2388,13 @@ select Add TOTP to store the key safely</value>
<value>Тип потребителско име</value> <value>Тип потребителско име</value>
</data> </data>
<data name="PlusAddressedEmail" xml:space="preserve"> <data name="PlusAddressedEmail" xml:space="preserve">
<value>Адрес на е-поща с плюс</value> <value>Plus addressed email</value>
</data> </data>
<data name="CatchAllEmail" xml:space="preserve"> <data name="CatchAllEmail" xml:space="preserve">
<value>Хващаща всичко е-поща</value> <value>Catch-all email</value>
</data> </data>
<data name="ForwardedEmailAlias" xml:space="preserve"> <data name="ForwardedEmailAlias" xml:space="preserve">
<value>Псевдоним на препратена е-поща</value> <value>Forwarded email alias</value>
</data> </data>
<data name="RandomWord" xml:space="preserve"> <data name="RandomWord" xml:space="preserve">
<value>Произволна дума</value> <value>Произволна дума</value>
@@ -2430,7 +2433,7 @@ select Add TOTP to store the key safely</value>
<value>Генериране на потр. име</value> <value>Генериране на потр. име</value>
</data> </data>
<data name="EmailType" xml:space="preserve"> <data name="EmailType" xml:space="preserve">
<value>Вид е-поща</value> <value>Email Type</value>
</data> </data>
<data name="WebsiteRequired" xml:space="preserve"> <data name="WebsiteRequired" xml:space="preserve">
<value>Уеб сайт (задължително)</value> <value>Уеб сайт (задължително)</value>
@@ -2442,17 +2445,14 @@ select Add TOTP to store the key safely</value>
<value>Използвайте възможностите за под-адресиране на е-поща на своя доставчик</value> <value>Използвайте възможностите за под-адресиране на е-поща на своя доставчик</value>
</data> </data>
<data name="CatchAllEmailDescription" xml:space="preserve"> <data name="CatchAllEmailDescription" xml:space="preserve">
<value>Използвайте конфигурираната входяща кутия за събиране на всичко.</value> <value>Use your domain's configured catch-all inbox.</value>
</data> </data>
<data name="ForwardedEmailDescription" xml:space="preserve"> <data name="ForwardedEmailDescription" xml:space="preserve">
<value>Създайте псевдоним на е-поща с външна услуга за препращане.</value> <value>Generate an email alias with an external forwarding service.</value>
</data> </data>
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>Произволно</value> <value>Произволно</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>Свързване към часовник</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>Използване на услугата за достъпност</value> <value>Използване на услугата за достъпност</value>
</data> </data>
@@ -2473,46 +2473,4 @@ select Add TOTP to store the key safely</value>
{0} {0}
Искате ли да превключите към тази регистрация?</value> Искате ли да превключите към тази регистрация?</value>
</data> </data>
<data name="NewAroundHere" xml:space="preserve">
<value>За пръв път ли сте тук?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Получете подсказване за главната парола</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Вписване като {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Това не сте Вие?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Вписване с главната парола</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Вписване с друго устройство</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Вписването е стартирано</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>Към устройството Ви е изпратено известие.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>Уверете се, че трезорът Ви е отключен и че Уникалната фраза съвпада с другото устройство.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>Повторно изпращане на известието</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>Предпочитате друг вариант?</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>Вижте всички възможности за вписване</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>Тази зявка вече не е приложима</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Разрешете достъпа до камерата, за да използвате скенера</value>
</data>
</root> </root>

View File

@@ -376,7 +376,7 @@
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} copied</value> <value>{0} copied</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after suceessfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">
<value>Verify fingerprint</value> <value>Verify fingerprint</value>
@@ -1762,7 +1762,7 @@ Scanning will happen automatically.</value>
<value>Syncing vault with pull down gesture.</value> <value>Syncing vault with pull down gesture.</value>
</data> </data>
<data name="LogInSso" xml:space="preserve"> <data name="LogInSso" xml:space="preserve">
<value>Enterprise single sign-on</value> <value>Enterprise Single Sign-On</value>
</data> </data>
<data name="LogInSsoSummary" xml:space="preserve"> <data name="LogInSsoSummary" xml:space="preserve">
<value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value> <value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value>
@@ -2300,6 +2300,9 @@ Scanning will happen automatically.</value>
<value>Once the key is successfully entered, <value>Once the key is successfully entered,
select Add TOTP to store the key safely</value> select Add TOTP to store the key safely</value>
</data> </data>
<data name="SelectAddTotpToStoreTheKeySafely" xml:space="preserve">
<value></value>
</data>
<data name="NeverLockWarning" xml:space="preserve"> <data name="NeverLockWarning" xml:space="preserve">
<value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value> <value>Setting your lock options to “Never” keeps your vault available to anyone with access to your device. If you use this option, you should ensure that you keep your device properly protected.</value>
</data> </data>
@@ -2450,9 +2453,6 @@ select Add TOTP to store the key safely</value>
<data name="Random" xml:space="preserve"> <data name="Random" xml:space="preserve">
<value>Random</value> <value>Random</value>
</data> </data>
<data name="ConnectToWatch" xml:space="preserve">
<value>Connect to Watch</value>
</data>
<data name="AccessibilityServiceDisclosure" xml:space="preserve"> <data name="AccessibilityServiceDisclosure" xml:space="preserve">
<value>Accessibility Service Disclosure</value> <value>Accessibility Service Disclosure</value>
</data> </data>
@@ -2473,46 +2473,4 @@ select Add TOTP to store the key safely</value>
{0} {0}
Do you want to switch to this account?</value> Do you want to switch to this account?</value>
</data> </data>
<data name="NewAroundHere" xml:space="preserve">
<value>New around here?</value>
</data>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Get master password hint</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Not you?</value>
</data>
<data name="LogInWithMasterPassword" xml:space="preserve">
<value>Log in with master password</value>
</data>
<data name="LogInWithAnotherDevice" xml:space="preserve">
<value>Log In with another device</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Log in initiated</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>A notification has been sent to your device.</value>
</data>
<data name="PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice" xml:space="preserve">
<value>Please make sure your vault is unlocked and the Fingerprint phrase matches on the other device.</value>
</data>
<data name="ResendNotification" xml:space="preserve">
<value>Resend notification</value>
</data>
<data name="NeedAnotherOption" xml:space="preserve">
<value>Need another option?</value>
</data>
<data name="ViewAllLoginOptions" xml:space="preserve">
<value>View all log in options</value>
</data>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>This request is no longer valid</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Enable camera permission to use the scanner</value>
</data>
</root> </root>

Some files were not shown because too many files have changed in this diff Show More