1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
Bitwarden DevOps
ffd888bd96 Bumped version to 2024.3.0 (#3080)
(cherry picked from commit e7f9d64edb)
2024-03-14 11:15:28 -04:00
Federico Maccaroni
95f66870c2 [PM-6751]Added missing query intent for CustomTabs that might be responsible for the Exception in WebAuthenticator 2024-03-11 19:37:04 -03:00
Federico Maccaroni
0bd4e62afd [PM-6726] Fix for Android 14 devices crashing when using the Tiles 2024-03-11 18:07:40 -03:00
297 changed files with 1228 additions and 19550 deletions

1
.github/CODEOWNERS vendored
View File

@@ -21,6 +21,7 @@ src/App/Platforms/iOS/Info.plist
## Platform team files ##
appIcons @bitwarden/team-platform-dev
build.cake @bitwarden/team-platform-dev
## Vault team files ##
src/watchOS @bitwarden/team-vault-dev

35
.github/labeler.yml vendored
View File

@@ -1,26 +1,19 @@
android:
- changed-files:
- any-glob-to-any-file:
- src/App/*
- src/Core/*
- src/Android/*
- 'src/Xamarin.AndroidX.Credentials/*'
- src/App/*
- src/Core/*
- src/Android/*
iOS:
- changed-files:
- any-glob-to-any-file:
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
watchOS:
- changed-files:
- any-glob-to-any-file:
- src/watchOS/*
- src/watchOS/*

View File

@@ -7,7 +7,7 @@ on:
jobs:
close-issue:
name: 'Close issue with automatic response'
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
permissions:
issues: write
steps:

View File

@@ -3,348 +3,3 @@ name: Build Beta
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch or tag to build'
required: true
default: 'main'
type: string
env:
main_app_folder_path: src/App
main_app_project_path: src/App/App.csproj
target-net-version: net8.0
jobs:
setup:
name: Setup
runs-on: ubuntu-22.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
submodules: 'true'
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
ios:
name: Apple iOS
runs-on: macos-14
needs: setup
env:
ios_folder_path: src/App/Platforms/iOS
app_output_name: App
app_ci_output_filename: App_x64_Debug
steps:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: 15.1
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '8.0.x'
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Print environment
run: |
nuget help | grep Version
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
fetch-depth: 0
ref: ${{ inputs.ref }}
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: profiles
run: |
mkdir -p $HOME/secrets
profiles=(
"dist_beta_autofill.mobileprovision"
"dist_beta_bitwarden.mobileprovision"
"dist_beta_extension.mobileprovision"
"dist_beta_share_extension.mobileprovision"
"dist_beta_bitwarden_watch_app.mobileprovision"
"dist_beta_bitwarden_watch_app_extension.mobileprovision"
)
for FILE in "${profiles[@]}"
do
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
done
- name: Download Google Services secret
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: GoogleService-Info.plist
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/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.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
- name: Update Entitlements
run: |
echo "##### Updating Entitlements"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>beta<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
- name: Get certificates
run: |
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles
run: |
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_beta_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
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"
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"
- name: Restore packages
run: |
dotnet restore
dotnet tool restore
- name: Setup iOS build CAKE (Testing)
run: dotnet cake build.cake --target iOS --variant beta
- name: Bulid WatchApp
run: |
echo "##### Build WatchApp with Release Configuration"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "##### Done"
- name: Archive Build for App Store
shell: pwsh
run: |
Write-Output "##### Archive for Release ios-arm64"
dotnet publish ${{ env.main_app_project_path }} -c Release -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=ios-arm64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
- name: Archive Build for Mobile Automation
shell: pwsh
run: |
Write-Output "##### Archive Debug for iossimulator-x64"
dotnet build ${{ env.main_app_project_path }} -c Debug -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=iossimulator-x64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
ls ~/Library/Developer/Xcode/Archives
- name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: |
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI
env:
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
EXPORT_PATH: ./bitwarden-export
run: |
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Show Bitwarden Export
shell: bash
run: ls -a -R ./bitwarden-export
- name: Copy all dSYMs files to upload
env:
EXPORT_PATH: ./bitwarden-export
WATCH_ARCHIVE_DSYMS_PATH: ./src/watchOS/bitwarden.xcarchive/dSYMs/
WATCH_DSYMS_EXPORT_PATH: ./bitwarden-export/Watch_dSYMs
run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/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
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: Bitwarden iOS
path: |
./bitwarden-export/Bitwarden*.ipa
./bitwarden-export/dSYMs/*.*
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
if-no-files-found: error
- name: Install AppCenter CLI
run: npm install -g appcenter-cli
- name: Upload dSYMs to App Center
env:
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
- name: Upload Watch dSYMs to Firebase Crashlytics
run: |
echo "##### Uploading Watch dSYMs to Firebase"
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" \;
- name: Validate app in App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-22.04
needs:
- setup
- ios
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
if: failure()
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up CLOC
run: |
@@ -40,7 +40,7 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: 'true'
@@ -73,7 +73,7 @@ jobs:
android_folder_path_bash: src/App/Platforms/Android
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
@@ -83,7 +83,7 @@ jobs:
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
@@ -109,7 +109,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
@@ -146,7 +146,10 @@ jobs:
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "##### Setting Android Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "########################################"
echo "##### Setting Version Code $BUILD_NUMBER"
echo "########################################"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_folder_path_bash }}/AndroidManifest.xml
@@ -226,7 +229,7 @@ jobs:
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
@@ -234,7 +237,7 @@ jobs:
- name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
@@ -242,7 +245,7 @@ jobs:
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
@@ -262,7 +265,7 @@ jobs:
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
@@ -270,7 +273,7 @@ jobs:
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
@@ -300,17 +303,17 @@ jobs:
android_manifest_path: src/App/Platforms/Android/AndroidManifest.xml
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '8.0.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
uses: microsoft/setup-msbuild@ede762b26a2de8d110bb5a3db4d7e0e080c0e917 # v1.3.3
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
@@ -335,7 +338,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -355,7 +358,7 @@ jobs:
- name: Increment version
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
echo "##### Setting F-Droid Version Code to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "##### Setting Version Code $BUILD_NUMBER"
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
./${{ env.android_manifest_path }}
@@ -363,14 +366,15 @@ jobs:
- name: Clean for F-Droid
run: |
$directoryBuildProps = $($env:GITHUB_WORKSPACE + "/Directory.Build.props");
$appPath = $($env:GITHUB_WORKSPACE + "/${{ env.main_app_project_path }}");
$corePath = $($env:GITHUB_WORKSPACE + "/src/Core/Core.csproj");
$androidManifest = $($env:GITHUB_WORKSPACE + "/${{ env.android_manifest_path }}");
Write-Output "##### Back up project files"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $directoryBuildProps $($directoryBuildProps + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "##### Cleanup Android Manifest"
@@ -382,10 +386,6 @@ jobs:
$xml.Save($androidManifest);
Write-Output "##### Enabling FDROID constant"
(Get-Content $directoryBuildProps).Replace('<!-- <CustomConstants>FDROID</CustomConstants> -->', '<CustomConstants>FDROID</CustomConstants>') | Set-Content $directoryBuildProps
- name: Restore packages
run: dotnet restore
@@ -399,22 +399,23 @@ jobs:
Write-Output "##### Sign FDroid"
$signingFdroidKeyStore = "$($env:GITHUB_WORKSPACE)\${{ env.android_folder_path }}\app_fdroid-keystore.jks"
dotnet build $projToBuild -c Release -f ${{ env.target-net-version }}-android `
dotnet publish $projToBuild -c Release -f ${{ env.target-net-version }}-android `
/p:AndroidKeyStore=true `
/p:AndroidSigningKeyStore=$signingFdroidKeyStore `
/p:AndroidSigningKeyAlias=bitwarden `
/p:AndroidSigningKeyPass="$($env:FDROID_KEYSTORE_PASSWORD)" `
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" ` --no-restore
/p:AndroidSigningStorePass="$($env:FDROID_KEYSTORE_PASSWORD)" `
/p:CustomConstants="FDROID" --no-restore
Write-Output "##### Copy FDroid apk to project root"
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\$($packageName)-Signed.apk";
$signedApkPath = "$($env:GITHUB_WORKSPACE)\${{ env.main_app_folder_path }}\bin\Release\${{ env.target-net-version }}-android\publish\$($packageName)-Signed.apk";
$signedApkDestPath = "$($env:GITHUB_WORKSPACE)\com.x8bit.bitwarden-fdroid.apk";
Copy-Item $signedApkPath $signedApkDestPath
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
@@ -426,7 +427,7 @@ jobs:
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
@@ -435,7 +436,7 @@ jobs:
ios:
name: Apple iOS
runs-on: macos-14
runs-on: macos-13
needs: setup
env:
ios_folder_path: src/App/Platforms/iOS
@@ -448,12 +449,12 @@ jobs:
xcode-version: 15.1
- name: Setup NuGet
uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '8.0.x'
@@ -470,7 +471,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: 'true'
@@ -520,7 +521,9 @@ jobs:
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting iOS CFBundleVersion to $BUILD_NUMBER" | tee -a $GITHUB_STEP_SUMMARY
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/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
@@ -631,7 +634,7 @@ jobs:
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: Bitwarden iOS
path: |
@@ -640,7 +643,7 @@ jobs:
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
@@ -718,7 +721,7 @@ jobs:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -733,7 +736,7 @@ jobs:
secrets: "crowdin-api-token"
- name: Upload Sources
uses: crowdin/github-action@67705afb6985401459cd143d5f5f00c9dc212f23 # v1.20.2
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -778,7 +781,7 @@ jobs:
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -1,53 +0,0 @@
---
name: Cleanup RC Branch
on:
push:
tags:
- v**
jobs:
delete-rc:
name: Delete RC Branch
runs-on: ubuntu-22.04
steps:
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve bot secrets
id: retrieve-bot-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout main
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
ref: main
token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
- name: Check if a RC branch exists
id: branch-check
run: |
hotfix_rc_branch_check=$(git ls-remote --heads origin hotfix-rc | wc -l)
rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
if [[ "${hotfix_rc_branch_check}" -gt 0 ]]; then
echo "hotfix-rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=hotfix-rc" >> $GITHUB_OUTPUT
elif [[ "${rc_branch_check}" -gt 0 ]]; then
echo "rc branch exists." | tee -a $GITHUB_STEP_SUMMARY
echo "name=rc" >> $GITHUB_OUTPUT
fi
- name: Delete RC branch
env:
BRANCH_NAME: ${{ steps.branch-check.outputs.name }}
run: |
if ! [[ -z "$BRANCH_NAME" ]]; then
git push --quiet origin --delete $BRANCH_NAME
echo "Deleted $BRANCH_NAME branch." | tee -a $GITHUB_STEP_SUMMARY
fi

View File

@@ -10,12 +10,12 @@ on:
jobs:
crowdin-sync:
name: Autosync
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
env:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
@@ -30,7 +30,7 @@ jobs:
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: crowdin/github-action@67705afb6985401459cd143d5f5f00c9dc212f23 # v1.20.2
uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@@ -7,7 +7,7 @@ on:
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2

View File

@@ -10,8 +10,8 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
with:
sync-labels: true

View File

@@ -23,7 +23,7 @@ on:
jobs:
release:
name: Create Release
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
outputs:
branch-name: ${{ steps.branch.outputs.branch-name }}
steps:
@@ -38,7 +38,7 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Check Release Version
id: version
@@ -68,7 +68,7 @@ jobs:
- name: Download all artifacts
if: ${{ inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build.yml
workflow_conclusion: success
@@ -76,7 +76,7 @@ jobs:
- name: Dry Run - Download all artifacts
if: ${{ inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build.yml
workflow_conclusion: success
@@ -121,16 +121,16 @@ jobs:
f-droid:
name: F-Droid Release
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs: release
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Download F-Droid .apk artifact
if: ${{ inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build.yml
workflow_conclusion: success
@@ -139,7 +139,7 @@ jobs:
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
with:
workflow: build.yml
workflow_conclusion: success

View File

@@ -8,7 +8,7 @@ on:
jobs:
stale:
name: 'Check for stale issues and PRs'
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0

View File

@@ -11,6 +11,24 @@ jobs:
name: Bump Mobile Version
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Calculate bumped version
id: version
env:
RELEASE_TAG: ${{ github.ref }}
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_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
echo "Current Major: $CURR_MAJOR"
echo "Current Patch: $CURR_PATCH"
NEW_PATCH=$((CURR_PATCH+1))
NEW_VER=$CURR_MAJOR.$NEW_PATCH
echo "New Version: $NEW_VER"
echo "new_version=$NEW_VER" >> $GITHUB_OUTPUT
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
@@ -23,9 +41,9 @@ jobs:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Trigger Version Bump workflow
- name: "Bump version to ${{ steps.version.outputs.new_version }}"
env:
GH_TOKEN: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
run: |
echo '{"cut_rc_branch": "false"}' | \
gh workflow run version-bump.yml --json --repo bitwarden/mobile
echo '{"cut_rc_branch": "false", "version_number": "${{ steps.version.outputs.new_version }}"}' | \
gh workflow run version-bump.yml --json --repo bitwarden/mobile

View File

@@ -1,13 +1,13 @@
---
name: Version Bump
run-name: Version Bump - v${{ inputs.version_number }}
on:
workflow_dispatch:
inputs:
version_number_override:
description: "New version override (leave blank for automatic calculation, example: '2024.1.0')"
required: false
type: string
version_number:
description: "New version (example: '2024.1.0')"
required: true
cut_rc_branch:
description: "Cut RC branch?"
default: true
@@ -15,19 +15,25 @@ on:
jobs:
bump_version:
name: Bump Version
name: "Bump Version to v${{ inputs.version_number }}"
runs-on: ubuntu-22.04
outputs:
version: ${{ steps.set-final-version-output.outputs.version }}
steps:
- name: Validate version input
if: ${{ inputs.version_number_override != '' }}
uses: bitwarden/gh-actions/version-check@main
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
version: ${{ inputs.version_number_override }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Checkout Branch
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
@@ -41,20 +47,6 @@ jobs:
exit 1
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key,
github-gpg-private-key-passphrase,
github-pat-bitwarden-devops-bot-repo-scope"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
with:
@@ -63,38 +55,25 @@ jobs:
git_user_signingkey: true
git_commit_gpgsign: true
- name: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Create Version Branch
id: create-branch
run: |
NAME=version_bump_${{ github.ref_name }}_$(date +"%Y-%m-%d")
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
git switch -c $NAME
echo "name=$NAME" >> $GITHUB_OUTPUT
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
- name: Get current version
id: current-version
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
run: sudo apt install -y libxml2-utils
- name: Verify input version
if: ${{ inputs.version_number_override != '' }}
env:
CURRENT_VERSION: ${{ steps.current-version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number_override }}
NEW_VERSION: ${{ inputs.version_number }}
run: |
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)
# Error if version has not changed.
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
echo "Version has not changed."
@@ -110,93 +89,40 @@ jobs:
exit 1
fi
- name: Calculate next release version
if: ${{ inputs.version_number_override == '' }}
id: calculate-next-version
uses: bitwarden/gh-actions/version-next@main
with:
version: ${{ steps.current-version.outputs.version }}
- name: Bump Version - Android XML - Version Override
if: ${{ inputs.version_number_override != '' }}
id: bump-version-override
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ inputs.version_number_override }}
- name: Bump Version - Android XML - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
id: bump-version-automatic
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/Android/AndroidManifest.xml"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Autofill - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Autofill - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Autofill/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.Extension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.Extension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.Extension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.Extension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS.ShareExtension - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS.ShareExtension - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/iOS.ShareExtension/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Bump Version - iOS - Version Override
if: ${{ inputs.version_number_override != '' }}
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@main
with:
version: ${{ inputs.version_number }}
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ inputs.version_number_override }}
- name: Bump Version - iOS - Automatic Calculation
if: ${{ inputs.version_number_override == '' }}
uses: bitwarden/gh-actions/version-bump@main
with:
file_path: "src/App/Platforms/iOS/Info.plist"
version: ${{ steps.calculate-next-version.outputs.version }}
- name: Set Job output
id: set-final-version-output
- name: Setup git
run: |
if [[ "${{ steps.bump-version-override.outcome }}" == "success" ]]; then
echo "version=${{ inputs.version_number_override }}" >> $GITHUB_OUTPUT
elif [[ "${{ steps.bump-version-automatic.outcome }}" == "success" ]]; then
echo "version=${{ steps.calculate-next-version.outputs.version }}" >> $GITHUB_OUTPUT
fi
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot"
- name: Check if version changed
id: version-changed
@@ -210,7 +136,7 @@ jobs:
- name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ steps.set-final-version-output.outputs.version }}" -a
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
- name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
@@ -224,7 +150,7 @@ jobs:
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
TITLE: "Bump version to ${{ steps.set-final-version-output.outputs.version }}"
TITLE: "Bump version to ${{ inputs.version_number }}"
run: |
PR_URL=$(gh pr create --title "$TITLE" \
--base "main" \
@@ -240,18 +166,16 @@ jobs:
- [X] Other
## Objective
Automated version bump to ${{ steps.set-final-version-output.outputs.version }}")
Automated version bump to ${{ inputs.version_number }}")
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
- name: Approve PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
run: gh pr review $PR_NUMBER --approve
- name: Merge PR
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
env:
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
@@ -259,30 +183,28 @@ jobs:
cut_rc:
name: Cut RC branch
if: ${{ inputs.cut_rc_branch == true }}
needs: bump_version
if: ${{ inputs.cut_rc_branch == true }}
runs-on: ubuntu-22.04
steps:
- name: Checkout Branch
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
ref: main
- name: Install xmllint
run: |
sudo apt-get update
sudo apt-get install -y libxml2-utils
run: sudo apt install -y libxml2-utils
- name: Verify version has been updated
env:
NEW_VERSION: ${{ needs.bump_version.outputs.version }}
NEW_VERSION: ${{ inputs.version_number }}
run: |
# Wait for version to change.
while : ; do
echo "Waiting for version to be updated..."
git pull --force
CURRENT_VERSION=$(xmllint --xpath '
string(/manifest/@*[local-name()="versionName"
string(/manifest/@*[local-name()="versionName"
and namespace-uri()="http://schemas.android.com/apk/res/android"])
' src/App/Platforms/Android/AndroidManifest.xml)

1
.gitignore vendored
View File

@@ -148,7 +148,6 @@ publish/
# NuGet Packages
*.nupkg
!**/Xamarin.AndroidX.Credentials.1.0.0.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.

View File

@@ -9,8 +9,5 @@
<!-- Uncomment this when Unit Testing-->
<!-- <CustomConstants>UT</CustomConstants> -->
<!-- Uncomment this when building FDROID-->
<!-- <CustomConstants>FDROID</CustomConstants> -->
</PropertyGroup>
</Project>

View File

@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# using .NET MAUI.
# Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/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/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.
# We're Hiring!

View File

@@ -15,18 +15,16 @@ abstract record VariantConfig(
string AppName,
string AndroidPackageName,
string iOSBundleId,
string ApsEnvironment,
string DistProvisioningProfilePrefix
string ApsEnvironment
);
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
//NOTE: Beta iOS variants have a different ITSEncryptionExportComplianceCode
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development", "Dist:");
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development", "Dist:");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production", "Dist: Beta");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production", "Dist:");
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(),
@@ -199,8 +197,7 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString();
// we need to maintain version formatting here composed of one to three period-separated integers, so we cannot use the GetVersionName method as in Android for non-Prod.
var newVersionName = prevVersionName;
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType);
@@ -222,11 +219,6 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
}
if(buildVariant is Beta)
{
plist["ITSEncryptionExportComplianceCode"] = "3dd3e32f-efa6-4d99-b410-28aa28b1cb77";
}
SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}");
@@ -236,15 +228,12 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
Information($"{plistPath} updated with success!");
}
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant, bool updateApsEnv)
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
{
var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
if (updateApsEnv)
{
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
}
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}" };;
@@ -283,10 +272,9 @@ private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
const string pattern = @"MARKETING_VERSION = [^;]*;";
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified Marketing Version successfully.");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified successfully.");
}
/// <summary>
@@ -339,7 +327,7 @@ Task("UpdateiOSPlist")
var infoPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, true);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSAutofillPlist")
@@ -350,7 +338,7 @@ Task("UpdateiOSAutofillPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSExtensionPlist")
@@ -361,7 +349,7 @@ Task("UpdateiOSExtensionPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSShareExtensionPlist")
@@ -372,7 +360,7 @@ Task("UpdateiOSShareExtensionPlist")
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, false);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSCodeFiles")
@@ -409,22 +397,6 @@ Task("UpdateWatchKitAppInfoPlist")
UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
});
Task("UpdateDistProfiles")
.IsDependentOn("UpdateiOSCodeFiles")
.Does(()=> {
var buildVariant = GetVariant();
var filesToReplace = new string[] {
Path.Combine(".github", "resources", "export-options-app-store.plist"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj")
};
foreach(string path in filesToReplace)
{
ReplaceInFile(path, "Dist:", buildVariant.DistProvisioningProfilePrefix);
}
});
#endregion iOS
#region Main Tasks
@@ -446,7 +418,6 @@ Task("iOS")
.IsDependentOn("UpdateiOSCodeFiles")
.IsDependentOn("UpdateWatchProject")
.IsDependentOn("UpdateWatchKitAppInfoPlist")
.IsDependentOn("UpdateDistProfiles")
.Does(()=>
{
Information("iOS app updated");
@@ -466,4 +437,4 @@ Options:
});
#endregion Main Tasks
RunTarget(target);
RunTarget(target);

View File

@@ -1,8 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Xamarin.AndroidX.Credentials</name>
</assembly>
<members>
</members>
</doc>

View File

@@ -2,6 +2,5 @@
<configuration>
<packageSources>
<add key="MAUI Nightly builds" value="https://pkgs.dev.azure.com/xamarin/public/_packaging/maui-nightly/nuget/v3/index.json" />
<add key="Local AndroidX Credentials" value="lib/android/Xamarin.AndroidX.Credentials" />
</packageSources>
</configuration>

View File

@@ -117,13 +117,10 @@
<Folder Include="Platforms\Android\Services\" />
<Folder Include="Platforms\Android\Tiles\" />
<Folder Include="Platforms\Android\Utilities\" />
<Folder Include="Platforms\Android\Resources\drawable-xxxhdpi\" />
<Folder Include="Resources\Raw\" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
@@ -259,13 +256,5 @@
<None Remove="Platforms\iOS\Resources\more_vert.png" />
<None Remove="Platforms\iOS\Resources\logo_white.png" />
<None Remove="Platforms\iOS\Resources\logo%402x.png" />
<None Remove="Platforms\Android\Resources\drawable-xxxhdpi\" />
<None Remove="Resources\Raw\" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">
<BundleResource Include="Platforms\iOS\PrivacyInfo.xcprivacy" LogicalName="PrivacyInfo.xcprivacy" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<MauiAsset Include="Resources\Raw\fido2_privileged_allow_list.json" LogicalName="fido2_privileged_allow_list.json" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,5 @@
<?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="2024.5.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2024.3.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />

View File

@@ -1,321 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Nodes;
using Android.App;
using Android.Content;
using Android.OS;
using AndroidX.Credentials;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.App.Abstractions;
using Bit.App.Droid.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities.Fido2.Extensions;
using Bit.Droid;
using Org.Json;
using Activity = Android.App.Activity;
using Drawables = Android.Graphics.Drawables;
namespace Bit.App.Platforms.Android.Autofill
{
public static class CredentialHelpers
{
public static async Task<List<CredentialEntry>> PopulatePasskeyDataAsync(CallingAppInfo callingAppInfo,
BeginGetPublicKeyCredentialOption option, Context context, bool hasVaultBeenUnlockedInThisTransaction)
{
var passkeyEntries = new List<CredentialEntry>();
var requestOptions = new PublicKeyCredentialRequestOptions(option.RequestJson);
var authenticator = Bit.Core.Utilities.ServiceContainer.Resolve<IFido2AuthenticatorService>();
var credentials = await authenticator.SilentCredentialDiscoveryAsync(requestOptions.RpId);
// We need to change the request code for every pending intent on mapping the credential so the extras are not overriten by the last
// credential entry created.
int requestCodeAddition = 0;
passkeyEntries = credentials.Select(credential => MapCredential(credential, option, context, hasVaultBeenUnlockedInThisTransaction, Bit.Droid.Autofill.CredentialProviderService.UniqueGetRequestCode + requestCodeAddition++) as CredentialEntry).ToList();
return passkeyEntries;
}
private static PublicKeyCredentialEntry MapCredential(Fido2AuthenticatorDiscoverableCredentialMetadata credential, BeginGetPublicKeyCredentialOption option, Context context, bool hasVaultBeenUnlockedInThisTransaction, int requestCode)
{
var credDataBundle = new Bundle();
credDataBundle.PutByteArray(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialIdIntentExtra, credential.Id);
var intent = new Intent(context, typeof(Bit.Droid.Autofill.CredentialProviderSelectionActivity))
.SetAction(Bit.Droid.Autofill.CredentialProviderService.GetFido2IntentAction).SetPackage(Constants.PACKAGE_NAME);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialDataIntentExtra, credDataBundle);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialProviderCipherId, credential.CipherId);
intent.PutExtra(Bit.Core.Utilities.Fido2.CredentialProviderConstants.CredentialHasVaultBeenUnlockedInThisTransactionExtra, hasVaultBeenUnlockedInThisTransaction);
var pendingIntent = PendingIntent.GetActivity(context, requestCode, intent,
PendingIntentFlags.Mutable | PendingIntentFlags.UpdateCurrent);
return new PublicKeyCredentialEntry.Builder(
context,
credential.UserName ?? "No username",
pendingIntent,
option)
.SetDisplayName(credential.UserName ?? "No username")
.SetIcon(Drawables.Icon.CreateWithResource(context, Microsoft.Maui.Resource.Drawable.icon))
.Build();
}
private static PublicKeyCredentialCreationOptions GetPublicKeyCredentialCreationOptionsFromJson(string json)
{
var request = new PublicKeyCredentialCreationOptions(json);
var jsonObj = new JSONObject(json);
var authenticatorSelection = jsonObj.GetJSONObject("authenticatorSelection");
request.AuthenticatorSelection = new AndroidX.Credentials.WebAuthn.AuthenticatorSelectionCriteria(
authenticatorSelection.OptString("authenticatorAttachment", "platform"),
authenticatorSelection.OptString("residentKey", null),
authenticatorSelection.OptBoolean("requireResidentKey", false),
authenticatorSelection.OptString("userVerification", "preferred"));
return request;
}
public static async Task CreateCipherPasskeyAsync(ProviderCreateCredentialRequest getRequest, Activity activity)
{
var callingRequest = getRequest?.CallingRequest as CreatePublicKeyCredentialRequest;
if (callingRequest is null)
{
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, string.Empty);
FailAndFinish();
return;
}
var credentialCreationOptions = GetPublicKeyCredentialCreationOptionsFromJson(callingRequest.RequestJson);
string origin;
try
{
origin = await ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, credentialCreationOptions.Rp.Id);
}
catch (Core.Exceptions.ValidationException valEx)
{
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message);
FailAndFinish();
return;
}
if (origin is null)
{
await DisplayAlertAsync(AppResources.ErrorCreatingPasskey, AppResources.PasskeysNotSupportedForThisApp);
FailAndFinish();
return;
}
var rp = new Core.Utilities.Fido2.PublicKeyCredentialRpEntity()
{
Id = credentialCreationOptions.Rp.Id,
Name = credentialCreationOptions.Rp.Name
};
var user = new Core.Utilities.Fido2.PublicKeyCredentialUserEntity()
{
Id = credentialCreationOptions.User.GetId(),
Name = credentialCreationOptions.User.Name,
DisplayName = credentialCreationOptions.User.DisplayName
};
var pubKeyCredParams = new List<Core.Utilities.Fido2.PublicKeyCredentialParameters>();
foreach (var pubKeyCredParam in credentialCreationOptions.PubKeyCredParams)
{
pubKeyCredParams.Add(new Core.Utilities.Fido2.PublicKeyCredentialParameters() { Alg = Convert.ToInt32(pubKeyCredParam.Alg), Type = pubKeyCredParam.Type });
}
var excludeCredentials = new List<Core.Utilities.Fido2.PublicKeyCredentialDescriptor>();
foreach (var excludeCred in credentialCreationOptions.ExcludeCredentials)
{
excludeCredentials.Add(new Core.Utilities.Fido2.PublicKeyCredentialDescriptor() { Id = excludeCred.GetId(), Type = excludeCred.Type, Transports = excludeCred.Transports.ToArray() });
}
var authenticatorSelection = new Core.Utilities.Fido2.AuthenticatorSelectionCriteria()
{
UserVerification = credentialCreationOptions.AuthenticatorSelection.UserVerification,
ResidentKey = credentialCreationOptions.AuthenticatorSelection.ResidentKey,
RequireResidentKey = credentialCreationOptions.AuthenticatorSelection.RequireResidentKey
};
var timeout = Convert.ToInt32(credentialCreationOptions.Timeout);
var credentialCreateParams = new Fido2ClientCreateCredentialParams()
{
Challenge = credentialCreationOptions.GetChallenge(),
Origin = origin,
PubKeyCredParams = pubKeyCredParams.ToArray(),
Rp = rp,
User = user,
Timeout = timeout,
Attestation = credentialCreationOptions.Attestation,
AuthenticatorSelection = authenticatorSelection,
ExcludeCredentials = excludeCredentials.ToArray(),
Extensions = MapExtensionsFromJson(credentialCreationOptions),
SameOriginWithAncestors = true
};
var credentialExtraCreateParams = new Fido2ExtraCreateCredentialParams
(
callingRequest.GetClientDataHash(),
getRequest.CallingAppInfo?.PackageName
);
var fido2MediatorService = ServiceContainer.Resolve<IFido2MediatorService>();
var clientCreateCredentialResult = await fido2MediatorService.CreateCredentialAsync(credentialCreateParams, credentialExtraCreateParams);
if (clientCreateCredentialResult == null)
{
FailAndFinish();
return;
}
var transportsArray = new JSONArray();
if (clientCreateCredentialResult.Transports != null)
{
foreach (var transport in clientCreateCredentialResult.Transports)
{
transportsArray.Put(transport);
}
}
var responseInnerAndroidJson = new JSONObject();
if (clientCreateCredentialResult.ClientDataJSON != null)
{
responseInnerAndroidJson.Put("clientDataJSON", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.ClientDataJSON));
}
responseInnerAndroidJson.Put("authenticatorData", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.AuthData));
responseInnerAndroidJson.Put("attestationObject", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.AttestationObject));
responseInnerAndroidJson.Put("transports", transportsArray);
responseInnerAndroidJson.Put("publicKeyAlgorithm", clientCreateCredentialResult.PublicKeyAlgorithm);
responseInnerAndroidJson.Put("publicKey", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.PublicKey));
var rootAndroidJson = new JSONObject();
rootAndroidJson.Put("id", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.CredentialId));
rootAndroidJson.Put("rawId", CoreHelpers.Base64UrlEncode(clientCreateCredentialResult.CredentialId));
rootAndroidJson.Put("authenticatorAttachment", "platform");
rootAndroidJson.Put("type", "public-key");
rootAndroidJson.Put("clientExtensionResults", MapExtensionsToJson(clientCreateCredentialResult.Extensions));
rootAndroidJson.Put("response", responseInnerAndroidJson);
var result = new Intent();
var publicKeyResponse = new CreatePublicKeyCredentialResponse(rootAndroidJson.ToString());
PendingIntentHandler.SetCreateCredentialResponse(result, publicKeyResponse);
activity.SetResult(Result.Ok, result);
activity.Finish();
async Task DisplayAlertAsync(string title, string message)
{
if (ServiceContainer.TryResolve<IDeviceActionService>(out var deviceActionService))
{
await deviceActionService.DisplayAlertAsync(title, message, AppResources.Ok);
}
}
void FailAndFinish()
{
var result = new Intent();
PendingIntentHandler.SetCreateCredentialException(result, new CreateCredentialUnknownException());
activity.SetResult(Result.Ok, result);
activity.Finish();
}
}
private static Fido2CreateCredentialExtensionsParams MapExtensionsFromJson(PublicKeyCredentialCreationOptions options)
{
if (options == null || !options.Json.Has("extensions"))
{
return null;
}
var extensions = options.Json.GetJSONObject("extensions");
return new Fido2CreateCredentialExtensionsParams
{
CredProps = extensions.Has("credProps") && extensions.GetBoolean("credProps")
};
}
private static JSONObject MapExtensionsToJson(Fido2CreateCredentialExtensionsResult extensions)
{
if (extensions == null)
{
return null;
}
var extensionsJson = new JSONObject();
if (extensions.CredProps != null)
{
var credPropsJson = new JSONObject();
credPropsJson.Put("rk", extensions.CredProps.Rk);
extensionsJson.Put("credProps", credPropsJson);
}
return extensionsJson;
}
public static async Task<string> LoadFido2PrivilegedAllowedListAsync()
{
try
{
using var stream = await FileSystem.OpenAppPackageFileAsync("fido2_privileged_allow_list.json");
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
catch
{
return null;
}
}
public static async Task<string> ValidateCallingAppInfoAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
{
if (callingAppInfo.Origin is null)
{
return await ValidateAssetLinksAndGetOriginAsync(callingAppInfo, rpId);
}
var privilegedAllowedList = await LoadFido2PrivilegedAllowedListAsync();
if (privilegedAllowedList is null)
{
throw new InvalidOperationException("Could not load Fido2 privileged allowed list");
}
if (!privilegedAllowedList.Contains($"\"package_name\": \"{callingAppInfo.PackageName}\""))
{
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserIsNotPrivileged);
}
try
{
return callingAppInfo.GetOrigin(privilegedAllowedList);
}
catch (Java.Lang.IllegalStateException)
{
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch);
}
catch (Java.Lang.IllegalArgumentException)
{
return null; // wrong list format
}
}
private static async Task<string> ValidateAssetLinksAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
{
if (!ServiceContainer.TryResolve<IAssetLinksService>(out var assetLinksService))
{
throw new InvalidOperationException("Can't resolve IAssetLinksService");
}
var normalizedFingerprint = callingAppInfo.GetLatestCertificationFingerprint();
var isValid = await assetLinksService.ValidateAssetLinksAsync(rpId, callingAppInfo.PackageName, normalizedFingerprint);
return isValid ? callingAppInfo.GetAndroidOrigin() : null;
}
}
}

View File

@@ -1,217 +0,0 @@
using System.Diagnostics;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using AndroidX.Activity.Result;
using AndroidX.Activity.Result.Contract;
using AndroidX.Credentials;
using AndroidX.Credentials.Exceptions;
using AndroidX.Credentials.Provider;
using AndroidX.Credentials.WebAuthn;
using Bit.App.Abstractions;
using Bit.App.Droid.Utilities;
using Bit.App.Platforms.Android.Autofill;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
using Org.Json;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = false,
LaunchMode = LaunchMode.SingleInstance)]
[Register("com.x8bit.bitwarden.CredentialProviderSelectionActivity")]
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
{
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
private LazyResolve<IFido2AndroidGetAssertionUserInterface> _fido2GetAssertionUserInterface = new LazyResolve<IFido2AndroidGetAssertionUserInterface>();
private LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
private LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
private ActivityResultLauncher _activityResultLauncher;
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(CredentialProviderConstants.CredentialProviderCipherId);
if (string.IsNullOrEmpty(cipherId))
{
Finish();
return;
}
GetCipherAndPerformFido2AuthAsync(cipherId).FireAndForget();
}
//Used to avoid crash on MAUI when doing back
public override void OnBackPressed()
{
Finish();
}
private async Task GetCipherAndPerformFido2AuthAsync(string cipherId)
{
string RpId = string.Empty;
try
{
var getRequest = PendingIntentHandler.RetrieveProviderGetCredentialRequest(Intent);
if (getRequest is null)
{
FailAndFinish();
return;
}
var credentialOption = getRequest.CredentialOptions.FirstOrDefault();
var credentialPublic = credentialOption as GetPublicKeyCredentialOption;
var requestOptions = new PublicKeyCredentialRequestOptions(credentialPublic.RequestJson);
RpId = requestOptions.RpId;
var requestInfo = Intent.GetBundleExtra(CredentialProviderConstants.CredentialDataIntentExtra);
var credentialId = requestInfo?.GetByteArray(CredentialProviderConstants.CredentialIdIntentExtra);
var hasVaultBeenUnlockedInThisTransaction = Intent.GetBooleanExtra(CredentialProviderConstants.CredentialHasVaultBeenUnlockedInThisTransactionExtra, false);
var packageName = getRequest.CallingAppInfo.PackageName;
string origin;
try
{
origin = await CredentialHelpers.ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, RpId);
}
catch (Core.Exceptions.ValidationException valEx)
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message, AppResources.Ok);
FailAndFinish();
return;
}
if (origin is null)
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
FailAndFinish();
return;
}
_fido2GetAssertionUserInterface.Value.Init(
cipherId,
false,
() => hasVaultBeenUnlockedInThisTransaction,
RpId
);
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback(result =>
{
_fido2GetAssertionUserInterface.Value.ConfirmVaultUnlocked(result.ResultCode == (int)Result.Ok);
}));
var clientAssertParams = new Fido2ClientAssertCredentialParams
{
Challenge = requestOptions.GetChallenge(),
RpId = RpId,
AllowCredentials = new Core.Utilities.Fido2.PublicKeyCredentialDescriptor[] { new Core.Utilities.Fido2.PublicKeyCredentialDescriptor { Id = credentialId } },
Origin = origin,
SameOriginWithAncestors = true,
UserVerification = requestOptions.UserVerification
};
var extraAssertParams = new Fido2ExtraAssertCredentialParams
(
getRequest.CallingAppInfo.Origin != null ? credentialPublic.GetClientDataHash() : null,
packageName
);
var assertResult = await _fido2MediatorService.Value.AssertCredentialAsync(clientAssertParams, extraAssertParams);
var result = new Intent();
var responseInnerAndroidJson = new JSONObject();
if (assertResult.ClientDataJSON != null)
{
responseInnerAndroidJson.Put("clientDataJSON", CoreHelpers.Base64UrlEncode(assertResult.ClientDataJSON));
}
responseInnerAndroidJson.Put("authenticatorData", CoreHelpers.Base64UrlEncode(assertResult.AuthenticatorData));
responseInnerAndroidJson.Put("signature", CoreHelpers.Base64UrlEncode(assertResult.Signature));
responseInnerAndroidJson.Put("userHandle", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.UserHandle));
var rootAndroidJson = new JSONObject();
rootAndroidJson.Put("id", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.Id));
rootAndroidJson.Put("rawId", CoreHelpers.Base64UrlEncode(assertResult.SelectedCredential.Id));
rootAndroidJson.Put("authenticatorAttachment", "platform");
rootAndroidJson.Put("type", "public-key");
rootAndroidJson.Put("clientExtensionResults", new JSONObject());
rootAndroidJson.Put("response", responseInnerAndroidJson);
var json = rootAndroidJson.ToString();
var cred = new PublicKeyCredential(json);
var credResponse = new GetCredentialResponse(cred);
PendingIntentHandler.SetGetCredentialResponse(result, credResponse);
await MainThread.InvokeOnMainThreadAsync(() =>
{
SetResult(Result.Ok, result);
Finish();
});
}
catch (NotAllowedError)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
FailAndFinish();
});
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, string.Format(AppResources.ThereWasAProblemReadingAPasskeyForXTryAgainLater, RpId), AppResources.Ok);
FailAndFinish();
});
}
}
public void LaunchToUnlock()
{
if (_activityResultLauncher is null)
{
throw new InvalidOperationException("There is no activity result launcher available");
}
var intent = new Intent(this, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout);
_activityResultLauncher.Launch(intent);
}
private void FailAndFinish()
{
var result = new Intent();
PendingIntentHandler.SetGetCredentialException(result, new GetCredentialUnknownException());
SetResult(Result.Ok, result);
Finish();
}
}
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
readonly Action<ActivityResult> _callback;
public ActivityResultCallback(Action<ActivityResult> callback) => _callback = callback;
public ActivityResultCallback(TaskCompletionSource<ActivityResult> tcs) => _callback = tcs.SetResult;
public void OnActivityResult(Java.Lang.Object p0) => _callback((ActivityResult)p0);
}
}

View File

@@ -1,168 +0,0 @@
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using AndroidX.Credentials.Provider;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using AndroidX.Credentials.Exceptions;
using Bit.App.Droid.Utilities;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities.Fido2;
namespace Bit.Droid.Autofill
{
[Service(Permission = Manifest.Permission.BindCredentialProviderService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.service.credentials.CredentialProviderService" })]
[MetaData("android.credentials.provider", Resource = "@xml/provider")]
[Register("com.x8bit.bitwarden.Autofill.CredentialProviderService")]
public class CredentialProviderService : AndroidX.Credentials.Provider.CredentialProviderService
{
public const string GetFido2IntentAction = "PACKAGE_NAME.GET_PASSKEY";
public const string CreateFido2IntentAction = "PACKAGE_NAME.CREATE_PASSKEY";
public const int UniqueGetRequestCode = 94556023;
public const int UniqueCreateRequestCode = 94556024;
private readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public override async void OnBeginCreateCredentialRequest(BeginCreateCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
var response = await ProcessCreateCredentialsRequestAsync(request);
if (response != null)
{
await MainThread.InvokeOnMainThreadAsync(() => callback.OnResult(response));
return;
}
}
catch (Exception ex)
{
_logger.Value.Exception(ex);
}
MainThread.BeginInvokeOnMainThread(() => callback.OnError(AppResources.ErrorCreatingPasskey));
}
public override async void OnBeginGetCredentialRequest(BeginGetCredentialRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
try
{
await _vaultTimeoutService.Value.CheckVaultTimeoutAsync();
var locked = await _vaultTimeoutService.Value.IsLockedAsync();
if (!locked)
{
var response = await ProcessGetCredentialsRequestAsync(request);
callback.OnResult(response);
return;
}
var intent = new Intent(ApplicationContext, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialGet);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueGetRequestCode, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true));
var unlockAction = new AuthenticationAction(AppResources.Unlock, pendingIntent);
var unlockResponse = new BeginGetCredentialResponse.Builder()
.SetAuthenticationActions(new List<AuthenticationAction>() { unlockAction } )
.Build();
callback.OnResult(unlockResponse);
}
catch (GetCredentialException e)
{
_logger.Value.Exception(e);
callback.OnError(e.ErrorMessage ?? AppResources.ErrorReadingPasskey);
}
catch (Exception e)
{
_logger.Value.Exception(e);
callback.OnError(AppResources.ErrorReadingPasskey);
}
}
private async Task<BeginCreateCredentialResponse> ProcessCreateCredentialsRequestAsync(
BeginCreateCredentialRequest request)
{
if (request == null) { return null; }
if (request is BeginCreatePasswordCredentialRequest beginCreatePasswordCredentialRequest)
{
//This flow can be used if Password flow needs to be implemented
throw new NotImplementedException();
//return HandleCreatePasswordQuery(beginCreatePasswordCredentialRequest);
}
else if (request is BeginCreatePublicKeyCredentialRequest beginCreatePublicKeyCredentialRequest)
{
return await HandleCreatePasskeyQueryAsync(beginCreatePublicKeyCredentialRequest);
}
return null;
}
private async Task<BeginCreateCredentialResponse> HandleCreatePasskeyQueryAsync(BeginCreatePublicKeyCredentialRequest optionRequest)
{
var intent = new Intent(ApplicationContext, typeof(MainActivity));
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialCreate);
var pendingIntent = PendingIntent.GetActivity(ApplicationContext, UniqueCreateRequestCode, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true));
var userEmail = await GetSafeActiveAccountEmailAsync();
var createEntryBuilder = new CreateEntry.Builder(userEmail ?? AppResources.Bitwarden, pendingIntent)
.SetDescription(userEmail != null
? string.Format(AppResources.YourPasskeyWillBeSavedToYourBitwardenVaultForX, userEmail)
: AppResources.YourPasskeyWillBeSavedToYourBitwardenVault)
.Build();
var createCredentialResponse = new BeginCreateCredentialResponse.Builder()
.AddCreateEntry(createEntryBuilder);
return createCredentialResponse.Build();
}
private async Task<BeginGetCredentialResponse> ProcessGetCredentialsRequestAsync(
BeginGetCredentialRequest request)
{
var credentialEntries = new List<CredentialEntry>();
foreach (var option in request.BeginGetCredentialOptions.OfType<BeginGetPublicKeyCredentialOption>())
{
credentialEntries.AddRange(await Bit.App.Platforms.Android.Autofill.CredentialHelpers.PopulatePasskeyDataAsync(request.CallingAppInfo, option, ApplicationContext, false));
}
if (!credentialEntries.Any())
{
return new BeginGetCredentialResponse();
}
return new BeginGetCredentialResponse.Builder()
.SetCredentialEntries(credentialEntries)
.Build();
}
public override void OnClearCredentialStateRequest(ProviderClearCredentialStateRequest request,
CancellationSignal cancellationSignal, IOutcomeReceiver callback)
{
callback.OnResult(null);
}
private async Task<string> GetSafeActiveAccountEmailAsync()
{
try
{
return await _stateService.Value.GetEmailAsync();
}
catch (Exception ex)
{
// if it throws to get the user's email then we log and continue showing a more generic message
_logger.Value.Exception(ex);
return null;
}
}
}
}

View File

@@ -1,112 +0,0 @@
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
using Bit.Droid.Autofill;
namespace Bit.App.Platforms.Android.Autofill
{
public interface IFido2AndroidGetAssertionUserInterface : IFido2GetAssertionUserInterface
{
void Init(string cipherId,
bool userVerified,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
string rpId);
/// <summary>
/// Call this after the vault was unlocked so that Fido2 credential autofill can proceed.
/// </summary>
void ConfirmVaultUnlocked(bool unlocked);
}
public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface
{
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
private TaskCompletionSource<bool> _unlockVaultTcs;
public Fido2GetAssertionUserInterface(IStateService stateService,
IVaultTimeoutService vaultTimeoutService,
ICipherService cipherService,
IUserVerificationMediatorService userVerificationMediatorService)
{
_stateService = stateService;
_vaultTimeoutService = vaultTimeoutService;
_cipherService = cipherService;
_userVerificationMediatorService = userVerificationMediatorService;
}
public void Init(string cipherId,
bool userVerified,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
string rpId)
{
Init(cipherId,
userVerified,
EnsureAuthenAndVaultUnlockedAsync,
hasVaultBeenUnlockedInThisTransaction,
(cipherId, userVerificationPreference) => VerifyUserAsync(cipherId, userVerificationPreference, rpId, hasVaultBeenUnlockedInThisTransaction()));
}
public async Task EnsureAuthenAndVaultUnlockedAsync()
{
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
{
if (await _stateService.GetVaultTimeoutAsync() != 0)
{
// this should never happen but just in case.
throw new InvalidOperationException("Not authed or vault locked");
}
// if vault timeout is immediate, then we need to unlock the vault
if (!await NavigateAndWaitForUnlockAsync())
{
throw new InvalidOperationException("Couldn't unlock with immediate timeout");
}
}
}
public void ConfirmVaultUnlocked(bool unlocked) => _unlockVaultTcs?.TrySetResult(unlocked);
private async Task<bool> NavigateAndWaitForUnlockAsync()
{
var credentialProviderSelectionActivity = Platform.CurrentActivity as CredentialProviderSelectionActivity;
if (credentialProviderSelectionActivity == null)
{
throw new InvalidOperationException("Can't get current activity");
}
_unlockVaultTcs?.TrySetCanceled();
_unlockVaultTcs = new TaskCompletionSource<bool>();
credentialProviderSelectionActivity.LaunchToUnlock();
return await _unlockVaultTcs.Task;
}
private async Task<bool> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction)
{
try
{
var encrypted = await _cipherService.GetAsync(selectedCipherId);
var cipher = await encrypted.DecryptAsync();
var userVerification = await _userVerificationMediatorService.VerifyUserForFido2Async(
new Fido2UserVerificationOptions(
cipher?.Reprompt == Core.Enums.CipherRepromptType.Password,
userVerificationPreference,
vaultUnlockedDuringThisTransaction,
rpId)
);
return !userVerification.IsCancelled && userVerification.Result;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return false;
}
}
}
}

View File

@@ -1,202 +0,0 @@
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.App.Platforms.Android.Autofill
{
public class Fido2MakeCredentialUserInterface : IFido2MakeCredentialConfirmationUserInterface
{
private readonly IStateService _stateService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ICipherService _cipherService;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private LazyResolve<IMessagingService> _messagingService = new LazyResolve<IMessagingService>();
private TaskCompletionSource<(string cipherId, bool? userVerified)> _confirmCredentialTcs;
private TaskCompletionSource<bool> _unlockVaultTcs;
private Fido2UserVerificationOptions? _currentDefaultUserVerificationOptions;
private Func<bool> _checkHasVaultBeenUnlockedInThisTransaction;
public Fido2MakeCredentialUserInterface(IStateService stateService,
IVaultTimeoutService vaultTimeoutService,
ICipherService cipherService,
IUserVerificationMediatorService userVerificationMediatorService,
IDeviceActionService deviceActionService,
IPlatformUtilsService platformUtilsService)
{
_stateService = stateService;
_vaultTimeoutService = vaultTimeoutService;
_cipherService = cipherService;
_userVerificationMediatorService = userVerificationMediatorService;
_deviceActionService = deviceActionService;
_platformUtilsService = platformUtilsService;
}
public bool HasVaultBeenUnlockedInThisTransaction => _checkHasVaultBeenUnlockedInThisTransaction?.Invoke() == true;
public bool IsConfirmingNewCredential => _confirmCredentialTcs?.Task != null && !_confirmCredentialTcs.Task.IsCompleted;
public bool IsWaitingUnlockVault => _unlockVaultTcs?.Task != null && !_unlockVaultTcs.Task.IsCompleted;
public async Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
{
_confirmCredentialTcs?.TrySetCanceled();
_confirmCredentialTcs = null;
_confirmCredentialTcs = new TaskCompletionSource<(string cipherId, bool? userVerified)>();
_currentDefaultUserVerificationOptions = new Fido2UserVerificationOptions(false, confirmNewCredentialParams.UserVerificationPreference, HasVaultBeenUnlockedInThisTransaction, confirmNewCredentialParams.RpId);
_messagingService.Value.Send(Bit.Core.Constants.CredentialNavigateToAutofillCipherMessageCommand, confirmNewCredentialParams);
var (cipherId, isUserVerified) = await _confirmCredentialTcs.Task;
var verified = isUserVerified;
if (verified is null)
{
var userVerification = await VerifyUserAsync(cipherId, confirmNewCredentialParams.UserVerificationPreference, confirmNewCredentialParams.RpId);
// TODO: If cancelled then let the user choose another cipher.
// I think this can be done by showing a message to the uesr and recursive calling of this method ConfirmNewCredentialAsync
verified = !userVerification.IsCancelled && userVerification.Result;
}
if (cipherId is null)
{
return await CreateNewLoginForFido2CredentialAsync(confirmNewCredentialParams, verified.Value);
}
return (cipherId, verified.Value);
}
private async Task<(string CipherId, bool UserVerified)> CreateNewLoginForFido2CredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams, bool userVerified)
{
if (!userVerified && await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions
(
false,
confirmNewCredentialParams.UserVerificationPreference,
true,
confirmNewCredentialParams.RpId
)))
{
return (null, false);
}
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var cipherId = await _cipherService.CreateNewLoginForPasskeyAsync(confirmNewCredentialParams);
await _deviceActionService.HideLoadingAsync();
return (cipherId, userVerified);
}
catch
{
await _deviceActionService.HideLoadingAsync();
throw;
}
}
public async Task EnsureUnlockedVaultAsync()
{
if (!await _stateService.IsAuthenticatedAsync()
||
await _vaultTimeoutService.IsLoggedOutByTimeoutAsync()
||
await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
{
await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.HomeLogin);
return;
}
if (!await _vaultTimeoutService.IsLockedAsync())
{
return;
}
await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.Lock);
}
private async Task NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget navTarget)
{
_unlockVaultTcs?.TrySetCanceled();
_unlockVaultTcs = new TaskCompletionSource<bool>();
_messagingService.Value.Send(Bit.Core.Constants.NavigateToMessageCommand, navTarget);
await _unlockVaultTcs.Task;
}
public Task InformExcludedCredentialAsync(string[] existingCipherIds)
{
// TODO: Show excluded credential to the user in some screen.
return Task.FromResult(true);
}
public void SetCheckHasVaultBeenUnlockedInThisTransaction(Func<bool> checkHasVaultBeenUnlockedInThisTransaction)
{
_checkHasVaultBeenUnlockedInThisTransaction = checkHasVaultBeenUnlockedInThisTransaction;
}
public void Confirm(string cipherId, bool? userVerified) => _confirmCredentialTcs?.TrySetResult((cipherId, userVerified));
public void ConfirmVaultUnlocked() => _unlockVaultTcs?.TrySetResult(true);
public async Task ConfirmAsync(string cipherId, bool alreadyHasFido2Credential, bool? userVerified)
{
if (alreadyHasFido2Credential
&&
!await _platformUtilsService.ShowDialogAsync(
AppResources.ThisItemAlreadyContainsAPasskeyAreYouSureYouWantToOverwriteTheCurrentPasskey,
AppResources.OverwritePasskey,
AppResources.Yes,
AppResources.No))
{
return;
}
Confirm(cipherId, userVerified);
}
public void Cancel() => _confirmCredentialTcs?.TrySetCanceled();
public void OnConfirmationException(Exception ex) => _confirmCredentialTcs?.TrySetException(ex);
private async Task<CancellableResult<bool>> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId)
{
try
{
if (selectedCipherId is null && userVerificationPreference == Fido2UserVerificationPreference.Discouraged)
{
return new CancellableResult<bool>(false);
}
var shouldCheckMasterPasswordReprompt = false;
if (selectedCipherId != null)
{
var encrypted = await _cipherService.GetAsync(selectedCipherId);
var cipher = await encrypted.DecryptAsync();
shouldCheckMasterPasswordReprompt = cipher?.Reprompt == Core.Enums.CipherRepromptType.Password;
}
return await _userVerificationMediatorService.VerifyUserForFido2Async(
new Fido2UserVerificationOptions(
shouldCheckMasterPasswordReprompt,
userVerificationPreference,
HasVaultBeenUnlockedInThisTransaction,
rpId)
);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return new CancellableResult<bool>(false);
}
}
public Fido2UserVerificationOptions? GetCurrentUserVerificationOptions() => _currentDefaultUserVerificationOptions;
}
}

View File

@@ -24,7 +24,6 @@ using Bit.App.Droid.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using FileProvider = AndroidX.Core.Content.FileProvider;
using Bit.Core.Utilities.Fido2;
namespace Bit.Droid
{
@@ -168,13 +167,6 @@ namespace Bit.Droid
base.OnNewIntent(intent);
try
{
if (intent?.GetStringExtra(CredentialProviderConstants.Fido2CredentialAction) == CredentialProviderConstants.Fido2CredentialCreate
&&
_appOptions != null)
{
_appOptions.HasUnlockedInThisTransaction = false;
}
if (intent?.GetStringExtra("uri") is string uri)
{
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE);
@@ -333,15 +325,12 @@ namespace Bit.Droid
private AppOptions GetOptions()
{
var fido2CredentialAction = Intent.GetStringExtra(CredentialProviderConstants.Fido2CredentialAction);
var options = new AppOptions
{
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
Fido2CredentialAction = fido2CredentialAction,
FromFido2Framework = !string.IsNullOrWhiteSpace(fido2CredentialAction),
CreateSend = GetCreateSendRequest(Intent)
};
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);

View File

@@ -20,10 +20,7 @@ using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
using Bit.App.Platforms.Android.Autofill;
using Bit.Core.Enums;
using Bit.Core.Services.UserVerification;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -88,57 +85,6 @@ namespace Bit.Droid
ServiceContainer.Resolve<IWatchDeviceService>(),
ServiceContainer.Resolve<IConditionedAwaiterManager>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
var userPinService = new UserPinService(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<ICryptoService>(),
ServiceContainer.Resolve<IVaultTimeoutService>());
ServiceContainer.Register<IUserPinService>(userPinService);
var userVerificationMediatorService = new UserVerificationMediatorService(
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
userPinService,
deviceActionService,
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IUserVerificationMediatorService>(userVerificationMediatorService);
var fido2AuthenticatorService = new Fido2AuthenticatorService(
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<ISyncService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
userVerificationMediatorService);
ServiceContainer.Register<IFido2AuthenticatorService>(fido2AuthenticatorService);
var fido2GetAssertionUserInterface = new Fido2GetAssertionUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IUserVerificationMediatorService>());
ServiceContainer.Register<IFido2AndroidGetAssertionUserInterface>(fido2GetAssertionUserInterface);
var fido2MakeCredentialUserInterface = new Fido2MakeCredentialUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IUserVerificationMediatorService>(),
ServiceContainer.Resolve<IDeviceActionService>(),
ServiceContainer.Resolve<IPlatformUtilsService>());
ServiceContainer.Register<IFido2MakeCredentialConfirmationUserInterface>(fido2MakeCredentialUserInterface);
var fido2ClientService = new Fido2ClientService(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
ServiceContainer.Resolve<IFido2AuthenticatorService>(),
fido2GetAssertionUserInterface,
fido2MakeCredentialUserInterface);
ServiceContainer.Register<IFido2ClientService>(fido2ClientService);
ServiceContainer.Register<IFido2MediatorService>(new Fido2MediatorService(
fido2AuthenticatorService,
fido2ClientService,
ServiceContainer.Resolve<ICipherService>()));
}
#if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -214,6 +160,7 @@ namespace Bit.Droid
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService, logger);
var biometricService = new BiometricService(stateService, cryptoService);
var userPinService = new UserPinService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
@@ -237,6 +184,7 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
ServiceContainer.Register<IUserPinService>(userPinService);
// Push
#if FDROID

View File

@@ -1,9 +1,8 @@
#if !FDROID
#if !FDROID
using System;
using Android.App;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Firebase.Messaging;
@@ -21,7 +20,7 @@ namespace Bit.Droid.Push
try {
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
await stateService.SetPushRegisteredTokenAsync(token);
await pushNotificationService.RegisterAsync();
}
@@ -39,33 +38,13 @@ namespace Bit.Droid.Push
{
return;
}
JObject obj = null;
if (message.Data.TryGetValue("data", out var data))
{
// Legacy GCM format
obj = JObject.Parse(data);
}
else if (message.Data.TryGetValue("type", out var typeData) &&
Enum.TryParse(typeData, out NotificationType type))
{
// New FCMv1 format
obj = new JObject
{
{ "type", (int)type }
};
if (message.Data.TryGetValue("payload", out var payloadData))
{
obj.Add("payload", payloadData);
}
}
if (obj == null)
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
if (data == null)
{
return;
}
var obj = JObject.Parse(data);
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
"pushNotificationListenerService");
await listener.OnMessageAsync(obj, Device.Android);

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>

View File

@@ -1,11 +1,11 @@
using Android.App;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Credentials;
using Android.OS;
using Android.Provider;
using Android.Views.Autofill;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
@@ -37,42 +37,6 @@ namespace Bit.Droid.Services
_eventService = eventService;
}
public bool CredentialProviderServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.UpsideDownCake)
{
return false;
}
try
{
var activity = (MainActivity)Platform.CurrentActivity;
if (activity == null)
{
return false;
}
var credManager = activity.GetSystemService(Java.Lang.Class.FromType(typeof(CredentialManager))) as CredentialManager;
if (credManager == null)
{
return false;
}
var credentialProviderServiceComponentName = new ComponentName(activity, Java.Lang.Class.FromType(typeof(CredentialProviderService)));
return credManager.IsEnabledCredentialProviderService(credentialProviderServiceComponentName);
}
catch (Java.Lang.NullPointerException)
{
// CredentialManager API is not working fully and may return a NullPointerException even if the CredentialProviderService is working and enabled
// Info Here: https://developer.android.com/reference/android/credentials/CredentialManager#isEnabledCredentialProviderService(android.content.ComponentName)
return false;
}
catch
{
return false;
}
}
public bool AutofillServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
@@ -199,17 +163,7 @@ namespace Bit.Droid.Services
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public void DisableCredentialProviderService()
{
try
{
// We should try to find a way to programmatically disable the provider service when the API allows for it.
// For now we'll take the user to Credential Settings so they can manually disable it
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
deviceActionService.OpenCredentialProviderSettings();
}
catch { }
}
public void DisableAutofillService()
{

View File

@@ -1,4 +1,6 @@
using Android.App;
using System;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Nfc;
@@ -9,28 +11,21 @@ using Android.Text.Method;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using AndroidX.Credentials;
using Bit.App.Abstractions;
using Bit.Core.Resources.Localization;
using Bit.App.Utilities;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.App.Droid.Utilities;
using Bit.App.Models;
using Bit.Droid.Autofill;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;
using Resource = Bit.Core.Resource;
using Application = Android.App.Application;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities;
namespace Bit.Droid.Services
{
public class DeviceActionService : IDeviceActionService
{
public const int DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS = 15000;
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private AlertDialog _progressDialog;
@@ -77,28 +72,17 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName)
{
try
{
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
catch (IntentSender.SendIntentException)
{
return false;
}
catch (Android.Util.AndroidException)
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
return launchIntentSender != null;
}
public async Task ShowLoadingAsync(string text)
@@ -208,7 +192,7 @@ namespace Bit.Droid.Services
string text = null, string okButtonText = null, string cancelButtonText = null,
bool numericKeyboard = false, bool autofocus = true, bool password = false)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<string>(null);
@@ -265,7 +249,7 @@ namespace Bit.Droid.Services
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
@@ -342,7 +326,7 @@ namespace Bit.Droid.Services
public void RateApp()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var rateIntent = RateIntentForUrl("market://details", activity);
@@ -375,14 +359,14 @@ namespace Bit.Droid.Services
public bool SupportsNfc()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var manager = activity.GetSystemService(Context.NfcService) as NfcManager;
return manager.DefaultAdapter?.IsEnabled ?? false;
}
public bool SupportsCamera()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
}
@@ -398,7 +382,7 @@ namespace Bit.Droid.Services
public Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return Task.FromResult<string>(null);
@@ -479,7 +463,7 @@ namespace Bit.Droid.Services
public void OpenAccessibilityOverlayPermissionSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var intent = new Intent(Settings.ActionManageOverlayPermission);
@@ -506,32 +490,11 @@ namespace Bit.Droid.Services
}
}
public void OpenCredentialProviderSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var pendingIntent = ICredentialManager.Create(activity).CreateSettingsPendingIntent();
pendingIntent.Send();
}
catch (ActivityNotFoundException)
{
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetMessage(AppResources.BitwardenCredentialProviderGoToSettings);
alertBuilder.SetCancelable(true);
alertBuilder.SetPositiveButton(AppResources.Ok, (sender, args) =>
{
(sender as AlertDialog)?.Cancel();
});
alertBuilder.Create().Show();
}
}
public void OpenAccessibilitySettings()
{
try
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var intent = new Intent(Settings.ActionAccessibilitySettings);
activity.StartActivity(intent);
}
@@ -540,7 +503,7 @@ namespace Bit.Droid.Services
public void OpenAutofillSettings()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
try
{
var intent = new Intent(Settings.ActionRequestSetAutofillService);
@@ -568,101 +531,10 @@ namespace Bit.Droid.Services
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime();
}
public async Task ExecuteFido2CredentialActionAsync(AppOptions appOptions)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null || string.IsNullOrWhiteSpace(appOptions.Fido2CredentialAction))
{
return;
}
if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialGet)
{
await ExecuteFido2GetCredentialAsync(appOptions);
}
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout
&&
ServiceContainer.TryResolve<IVaultTimeoutService>(out var vaultTimeoutService))
{
vaultTimeoutService.DelayLockAndLogoutMs = DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS;
activity.SetResult(Result.Ok);
activity.Finish();
}
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialCreate)
{
await ExecuteFido2CreateCredentialAsync();
}
// Clear CredentialAction and FromFido2Framework values to avoid erratic behaviors in subsequent navigation/flows
// For Fido2CredentialGet these are no longer needed as a new Activity will be initiated.
// For Fido2CredentialCreate the app will rely on IFido2MakeCredentialConfirmationUserInterface.IsConfirmingNewCredential
appOptions.Fido2CredentialAction = null;
appOptions.FromFido2Framework = false;
}
private async Task ExecuteFido2GetCredentialAsync(AppOptions appOptions)
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return;
}
try
{
var request = AndroidX.Credentials.Provider.PendingIntentHandler.RetrieveBeginGetCredentialRequest(activity.Intent);
var response = new AndroidX.Credentials.Provider.BeginGetCredentialResponse();;
var credentialEntries = new List<AndroidX.Credentials.Provider.CredentialEntry>();
foreach (var option in request.BeginGetCredentialOptions.OfType<AndroidX.Credentials.Provider.BeginGetPublicKeyCredentialOption>())
{
credentialEntries.AddRange(await Bit.App.Platforms.Android.Autofill.CredentialHelpers.PopulatePasskeyDataAsync(request.CallingAppInfo, option, activity, appOptions.HasUnlockedInThisTransaction));
}
if (credentialEntries.Any())
{
response = new AndroidX.Credentials.Provider.BeginGetCredentialResponse.Builder()
.SetCredentialEntries(credentialEntries)
.Build();
}
var result = new Android.Content.Intent();
AndroidX.Credentials.Provider.PendingIntentHandler.SetBeginGetCredentialResponse(result, response);
activity.SetResult(Result.Ok, result);
activity.Finish();
}
catch (Exception ex)
{
Bit.Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
activity.SetResult(Result.Canceled);
activity.Finish();
}
}
private async Task ExecuteFido2CreateCredentialAsync()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null) { return; }
try
{
var getRequest = AndroidX.Credentials.Provider.PendingIntentHandler.RetrieveProviderCreateCredentialRequest(activity.Intent);
await Bit.App.Platforms.Android.Autofill.CredentialHelpers.CreateCipherPasskeyAsync(getRequest, activity);
}
catch (Exception ex)
{
Bit.Core.Services.LoggerHelper.LogEvenIfCantBeResolved(ex);
activity.SetResult(Result.Canceled);
activity.Finish();
}
}
public void CloseMainApp()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = (MainActivity)Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
if (activity == null)
{
return;
@@ -676,8 +548,6 @@ namespace Bit.Droid.Services
return true;
}
public bool SupportsCredentialProviderService() => Build.VERSION.SdkInt >= BuildVersionCodes.UpsideDownCake;
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
@@ -703,7 +573,7 @@ namespace Bit.Droid.Services
public float GetSystemFontSizeScale()
{
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity as MainActivity;
return activity?.Resources?.Configuration?.FontScale ?? 1;
}

View File

@@ -1,37 +0,0 @@
using Android.OS;
using AndroidX.Credentials.Provider;
using Bit.Core.Utilities;
using Java.Security;
namespace Bit.App.Droid.Utilities
{
public static class CallingAppInfoExtensions
{
public static string GetAndroidOrigin(this CallingAppInfo callingAppInfo)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.P || callingAppInfo?.SigningInfo?.GetApkContentsSigners().Any() != true)
{
return null;
}
var cert = callingAppInfo.SigningInfo.GetApkContentsSigners()[0].ToByteArray();
var md = MessageDigest.GetInstance("SHA-256");
var certHash = md.Digest(cert);
return $"android:apk-key-hash:{CoreHelpers.Base64UrlEncode(certHash)}";
}
public static string GetLatestCertificationFingerprint(this CallingAppInfo callingAppInfo)
{
if (callingAppInfo.SigningInfo.HasMultipleSigners)
{
return null;
}
var signature = callingAppInfo.SigningInfo.GetSigningCertificateHistory()[0].ToByteArray();
var md = MessageDigest.GetInstance("SHA-256");
var digestedSignature = md.Digest(signature);
var normalizedFingerprint = string.Join(":", digestedSignature.Select(b => b.ToString("X2")));
return normalizedFingerprint;
}
}
}

View File

@@ -88,7 +88,7 @@ namespace Bit.iOS
Core.Constants.AutofillNeedsIdentityReplacementKey);
if (needsAutofillReplacement.GetValueOrDefault())
{
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
}
else if (message.Command == "showAppExtension")
@@ -102,7 +102,7 @@ namespace Bit.iOS
var success = value as bool?;
if (success.GetValueOrDefault() && _deviceActionService.SystemMajorVersion() >= 12)
{
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
}
}
@@ -114,21 +114,22 @@ namespace Bit.iOS
return;
}
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
if (await ASHelpers.IdentitiesCanIncremental())
{
var cipherId = message.Data as string;
if (message.Command == "addedCipher" && !string.IsNullOrWhiteSpace(cipherId))
{
var identity = await ASHelpers.GetCipherPasswordIdentityAsync(cipherId);
var identity = await ASHelpers.GetCipherIdentityAsync(cipherId);
if (identity == null)
{
return;
}
await ASCredentialIdentityStoreExtensions.SaveCredentialIdentitiesAsync(identity);
await ASCredentialIdentityStore.SharedStore?.SaveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return;
}
}
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == "deletedCipher" || message.Command == "softDeletedCipher")
{
@@ -137,27 +138,28 @@ namespace Bit.iOS
return;
}
if (await ASHelpers.IdentitiesSupportIncrementalAsync())
if (await ASHelpers.IdentitiesCanIncremental())
{
var identity = ASHelpers.ToPasswordCredentialIdentity(
var identity = ASHelpers.ToCredentialIdentity(
message.Data as Bit.Core.Models.View.CipherView);
if (identity == null)
{
return;
}
await ASCredentialIdentityStoreExtensions.RemoveCredentialIdentitiesAsync(identity);
await ASCredentialIdentityStore.SharedStore?.RemoveCredentialIdentitiesAsync(
new ASPasswordCredentialIdentity[] { identity });
return;
}
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == "logout" && UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
else if ((message.Command == "softDeletedCipher" || message.Command == "restoredCipher")
&& UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
else if (message.Command == AppHelpers.VAULT_TIMEOUT_ACTION_CHANGED_MESSAGE_COMMAND)
{
@@ -166,12 +168,12 @@ namespace Bit.iOS
{
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore.RemoveAllCredentialIdentitiesAsync();
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
else
{
await ASHelpers.ReplaceAllIdentitiesAsync();
await ASHelpers.ReplaceAllIdentities();
}
}
}

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key>
<string>2024.5.1</string>
<string>2024.3.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconName</key>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,481 +0,0 @@
{
"apps": [
{
"type": "android",
"info": {
"package_name": "com.android.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "DA:63:3D:34:B6:9E:63:AE:21:03:B4:9D:53:CE:05:2F:C5:F7:F3:C5:3A:AB:94:FD:C2:A2:08:BD:FD:14:24:9C"
},
{
"build": "release",
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.dev",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
},
{
"build": "release",
"cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.chrome.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "20:19:DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.chromium.chrome",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
},
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.google.android.apps.chrome",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fennec_webauthndebug",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.firefox",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.firefox_beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.focus",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.fennec_aurora",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "BC:04:88:83:8D:06:F4:CA:6B:F3:23:86:DA:AB:0D:D8:EB:CF:3E:77:30:78:74:59:F6:2F:B3:CD:14:A1:BA:AA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "org.mozilla.rocket",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "86:3A:46:F0:97:39:32:B7:D0:19:9B:54:91:12:74:1C:2D:27:31:AC:72:EA:11:B7:52:3A:A9:0A:11:BF:56:91"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.dev",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.rolling",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.microsoft.emmx.local",
"signatures": [
{
"build": "userdebug",
"cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser_beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.brave.browser_nightly",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "app.vanadium.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser.snapshot",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.vivaldi.browser.sopranos",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.citrix.Receiver",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "3D:D1:12:67:10:69:AB:36:4E:F9:BE:73:9A:B7:B5:EE:15:E1:CD:E9:D8:75:7B:1B:F0:64:F5:0C:55:68:9A:49"
},
{
"build": "release",
"cert_fingerprint_sha256": "CE:B2:23:D7:77:09:F2:B6:BC:0B:3A:78:36:F5:A5:AF:4C:E1:D3:55:F4:A7:28:86:F7:9D:F8:0D:C9:D6:12:2E"
},
{
"build": "release",
"cert_fingerprint_sha256": "AA:D0:D4:57:E6:33:C3:78:25:77:30:5B:C1:B2:D9:E3:81:41:C7:21:DF:0D:AA:6E:29:07:2F:C4:1D:34:F0:AB"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.android.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C9:00:9D:01:EB:F9:F5:D0:30:2B:C7:1B:2F:E9:AA:9A:47:A4:32:BB:A1:73:08:A3:11:1B:75:D7:B2:14:90:25"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.sec.android.app.sbrowser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
},
{
"build": "release",
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.sec.android.app.sbrowser.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
},
{
"build": "release",
"cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.google.android.gms",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "7C:E8:3C:1B:71:F3:D5:72:FE:D0:4C:8D:40:C5:CB:10:FF:75:E6:D8:7D:9D:F6:FB:D5:3F:04:68:C2:90:50:53"
},
{
"build": "release",
"cert_fingerprint_sha256": "D2:2C:C5:00:29:9F:B2:28:73:A0:1A:01:0D:E1:C8:2F:BE:4D:06:11:19:B9:48:14:DD:30:1D:AB:50:CB:76:78"
},
{
"build": "release",
"cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
},
{
"build": "release",
"cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.beta",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.alpha",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.corp",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.canary",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
}
]
}
},
{
"type": "android",
"info": {
"package_name": "com.yandex.browser.broteam",
"signatures": [
{
"build": "release",
"cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
}
]
}
}
]
}

View File

@@ -1,4 +1,9 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
@@ -41,7 +46,6 @@ namespace Bit.Core.Abstractions
Task<CipherResponse> PutShareCipherAsync(string id, CipherShareRequest request);
Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id);
Task<bool> HasUnassignedCiphersAsync();
Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
@@ -95,6 +99,5 @@ namespace Bit.Core.Abstractions
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey);
Task<List<Utilities.DigitalAssetLinks.Statement>> GetDigitalAssetLinksForRpAsync(string rpId);
}
}

View File

@@ -1,7 +0,0 @@
namespace Bit.Core.Services
{
public interface IAssetLinksService
{
Task<bool> ValidateAssetLinksAsync(string rpId, string packageName, string normalizedFingerprint);
}
}

View File

@@ -4,7 +4,6 @@ namespace Bit.Core.Abstractions
{
public interface IAutofillHandler
{
bool CredentialProviderServiceEnabled();
bool AutofillServicesEnabled();
bool SupportsAutofillService();
void Autofill(CipherView cipher);
@@ -12,7 +11,6 @@ namespace Bit.Core.Abstractions
bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted();
bool AutofillServiceEnabled();
void DisableCredentialProviderService();
void DisableAutofillService();
}
}

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
@@ -34,8 +37,5 @@ namespace Bit.Core.Abstractions
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id);
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
Task<bool> VerifyOrganizationHasUnassignedItemsAsync();
}
}

View File

@@ -1,10 +1,12 @@
namespace Bit.Core.Abstractions
using System;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public enum AwaiterPrecondition
{
EnvironmentUrlsInited,
AndroidWindowCreated,
AutofillIOSExtensionViewDidAppear
AndroidWindowCreated
}
public interface IConditionedAwaiterManager
@@ -12,6 +14,5 @@
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
void Recreate(AwaiterPrecondition awaiterPrecondition);
}
}

View File

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

View File

@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums;
using Bit.Core.Models;
@@ -29,7 +28,6 @@ namespace Bit.App.Abstractions
bool SupportsNfc();
bool SupportsCamera();
bool SupportsFido2();
bool SupportsCredentialProviderService();
bool SupportsAutofillServices();
bool SupportsInlineAutofill();
bool SupportsDrawOver();
@@ -38,10 +36,8 @@ namespace Bit.App.Abstractions
void RateApp();
void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings();
void OpenCredentialProviderSettings();
void OpenAutofillSettings();
long GetActiveTime();
Task ExecuteFido2CredentialActionAsync(AppOptions appOptions);
void CloseMainApp();
float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync();

View File

@@ -1,12 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2AuthenticatorService
{
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
// TODO: Should this return a List? Or maybe IEnumerable?
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
}
}

View File

@@ -1,37 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
/// <summary>
/// This class represents an abstraction of the WebAuthn Client as described by W3C:
/// https://www.w3.org/TR/webauthn-3/#webauthn-client
///
/// The WebAuthn Client is an intermediary entity typically implemented in the user agent
/// (in whole, or in part). Conceptually, it underlies the Web Authentication API and embodies
/// the implementation of the Web Authentication API's operations.
///
/// It is responsible for both marshalling the inputs for the underlying authenticator operations,
/// and for returning the results of the latter operations to the Web Authentication API's callers.
/// </summary>
public interface IFido2ClientService
{
/// <summary>
/// Allows WebAuthn Relying Party scripts to request the creation of a new public key credential source.
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential
/// </summary>
/// <param name="createCredentialParams">The parameters for the credential creation operation</param>
/// <param name="extraParams">Extra parameters for the credential creation operation</param>
/// <returns>The new credential</returns>
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams, Fido2ExtraCreateCredentialParams extraParams);
/// <summary>
/// Allows WebAuthn Relying Party scripts to discover and use an existing public key credential, with the users consent.
/// Relying Party script can optionally specify some criteria to indicate what credential sources are acceptable to it.
/// For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-getAssertion
/// </summary>
/// <param name="assertCredentialParams">The parameters for the credential assertion operation</param>
/// <param name="extraParams">Extra parameters for the credential assertion operation</param>
/// <returns>The asserted credential</returns>
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams, Fido2ExtraAssertCredentialParams extraParams);
}
}

View File

@@ -1,20 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public struct Fido2GetAssertionUserInterfaceCredential
{
public string CipherId { get; set; }
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
}
public interface IFido2GetAssertionUserInterface : IFido2UserInterface
{
/// <summary>
/// Ask the user to pick a credential from a list of existing credentials.
/// </summary>
/// <param name="credentials">The credentials that the user can pick from, and if the user must be verified before completing the operation</param>
/// <returns>The ID of the cipher that contains the credentials the user picked, and if the user was verified before completing the operation</returns>
Task<(string CipherId, bool UserVerified)> PickCredentialAsync(Fido2GetAssertionUserInterfaceCredential[] credentials);
}
}

View File

@@ -1,66 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2MakeCredentialConfirmationUserInterface : IFido2MakeCredentialUserInterface
{
/// <summary>
/// Call this method after the user chose where to save the new Fido2 credential.
/// </summary>
/// <param name="cipherId">
/// Cipher ID where to save the new credential.
/// If <c>null</c> a new default passkey cipher item will be created
/// </param>
/// <param name="userVerified">
/// Whether the user has been verified or not.
/// If <c>null</c> verification has not taken place yet.
/// </param>
void Confirm(string cipherId, bool? userVerified);
/// <summary>
/// Call this method after the user chose where to save the new Fido2 credential.
/// </summary>
/// <param name="cipherId">
/// Cipher ID where to save the new credential.
/// If <c>null</c> a new default passkey cipher item will be created
/// </param>
/// <param name="alreadyHasFido2Credential">
/// If the cipher corresponding to the <paramref name="cipherId"/> already has a Fido2 credential.
/// </param>
/// <param name="userVerified">
/// Whether the user has been verified or not.
/// If <c>null</c> verification has not taken place yet.
/// </param>
Task ConfirmAsync(string cipherId, bool alreadyHasFido2Credential, bool? userVerified);
/// <summary>
/// Cancels the current flow to make a credential
/// </summary>
void Cancel();
/// <summary>
/// Call this if an exception needs to happen on the credential making process
/// </summary>
void OnConfirmationException(Exception ex);
/// <summary>
/// True if we are already confirming a new credential.
/// </summary>
bool IsConfirmingNewCredential { get; }
/// <summary>
/// Call this after the vault was unlocked so that Fido2 credential creation can proceed.
/// </summary>
void ConfirmVaultUnlocked();
/// <summary>
/// True if we are waiting for the vault to be unlocked.
/// </summary>
bool IsWaitingUnlockVault { get; }
Fido2UserVerificationOptions? GetCurrentUserVerificationOptions();
void SetCheckHasVaultBeenUnlockedInThisTransaction(Func<bool> checkHasVaultBeenUnlockedInThisTransaction);
}
}

View File

@@ -1,44 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public struct Fido2ConfirmNewCredentialParams
{
///<summary>
/// The name of the credential.
///</summary>
public string CredentialName { get; set; }
///<summary>
/// The name of the user.
///</summary>
public string UserName { get; set; }
/// <summary>
/// The preference to whether or not the user must be verified before completing the operation.
/// </summary>
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
/// <summary>
/// The relying party identifier
/// </summary>
public string RpId { get; set; }
}
public interface IFido2MakeCredentialUserInterface : IFido2UserInterface
{
/// <summary>
/// Inform the user that the operation was cancelled because their vault contains excluded credentials.
/// </summary>
/// <param name="existingCipherIds">The IDs of the excluded credentials.</param>
/// <returns>When user has confirmed the message</returns>
Task InformExcludedCredentialAsync(string[] existingCipherIds);
/// <summary>
/// Ask the user to confirm the creation of a new credential.
/// </summary>
/// <param name="confirmNewCredentialParams">The parameters to use when asking the user to confirm the creation of a new credential.</param>
/// <returns>The ID of the cipher where the new credential should be saved, and if the user was verified before completing the operation</returns>
Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams);
}
}

View File

@@ -1,14 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2MediatorService
{
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams, Fido2ExtraCreateCredentialParams extraParams);
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams, Fido2ExtraAssertCredentialParams extraParams);
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
}
}

View File

@@ -1,17 +0,0 @@
namespace Bit.Core.Abstractions
{
public interface IFido2UserInterface
{
/// <summary>
/// Whether the vault has been unlocked during this transaction
/// </summary>
bool HasVaultBeenUnlockedInThisTransaction { get; }
/// <summary>
/// Make sure that the vault is unlocked.
/// This should open a window and ask the user to login or unlock the vault if necessary.
/// </summary>
/// <returns>When vault has been unlocked.</returns>
Task EnsureUnlockedVaultAsync();
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
@@ -9,7 +10,5 @@ namespace Bit.App.Abstractions
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> ShouldByPassMasterPasswordRepromptAsync();
}
}

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
@@ -26,7 +29,7 @@ namespace Bit.Core.Abstractions
bool SupportsDuo();
Task<bool> SupportsBiometricAsync();
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false);
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false);
long GetActiveTime();
}
}

View File

@@ -186,9 +186,6 @@ namespace Bit.Core.Abstractions
Task<BwRegion?> GetActiveUserRegionAsync();
Task<BwRegion?> GetPreAuthRegionAsync();
Task SetPreAuthRegionAsync(BwRegion value);
Task ReloadStateAsync();
Task<bool> GetShouldCheckOrganizationUnassignedItemsAsync(string userId = null);
Task SetShouldCheckOrganizationUnassignedItemsAsync(bool shouldCheck, string userId = null);
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]

View File

@@ -1,12 +1,9 @@
using Bit.Core.Services;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface IUserPinService
{
Task<bool> IsPinLockEnabledAsync();
Task SetupPinAsync(string pin, bool requireMasterPasswordOnRestart);
Task<bool> VerifyPinAsync(string inputPin);
Task<bool> VerifyPinAsync(string inputPin, string email, KdfConfig kdfConfig, PinLockType pinLockType);
}
}

View File

@@ -1,28 +0,0 @@
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IUserVerificationMediatorService
{
Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options);
Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options);
Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options);
Task<CancellableResult<UVResult>> PerformOSUnlockAsync();
Task<CancellableResult<UVResult>> VerifyPinCodeAsync();
Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt);
public struct UVResult
{
public UVResult(bool canPerform, bool isVerified)
{
CanPerform = canPerform;
IsVerified = isVerified;
}
public bool CanPerform { get; set; }
public bool IsVerified { get; set; }
}
}
}

View File

@@ -1,11 +1,11 @@
using Bit.Core.Enums;
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.Core.Abstractions
{
public interface IUserVerificationService
{
Task<bool> VerifyUser(string secret, VerificationType verificationType);
Task<bool> VerifyMasterPasswordAsync(string masterPassword);
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
}
}

View File

@@ -9,12 +9,10 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Pages;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Bit.App
@@ -38,9 +36,6 @@ namespace Bit.App
private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService;
private readonly ILogger _logger;
#if ANDROID
private LazyResolve<IFido2MakeCredentialConfirmationUserInterface> _fido2MakeCredentialConfirmationUserInterface = new LazyResolve<IFido2MakeCredentialConfirmationUserInterface>();
#endif
private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
@@ -108,10 +103,7 @@ namespace Bit.App
Options.MyVaultTile = appOptions.MyVaultTile;
Options.GeneratorTile = appOptions.GeneratorTile;
Options.FromAutofillFramework = appOptions.FromAutofillFramework;
Options.FromFido2Framework = appOptions.FromFido2Framework;
Options.Fido2CredentialAction = appOptions.Fido2CredentialAction;
Options.CreateSend = appOptions.CreateSend;
Options.HasUnlockedInThisTransaction = appOptions.HasUnlockedInThisTransaction;
}
}
@@ -127,15 +119,6 @@ namespace Bit.App
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
}
//When executing from CredentialProviderSelectionActivity we don't have "Options" so we need to filter "manually"
//In the CredentialProviderSelectionActivity we don't need to show any Page, so we just create a "dummy" Window with a NavigationPage to avoid crashing.
if (activationState != null
&& activationState.State.ContainsKey("CREDENTIAL_DATA")
&& activationState.State.ContainsKey("credentialProviderCipherId"))
{
return new Window(new NavigationPage()); //No actual page needed. Only used for auto-filling the fields directly (externally)
}
_isResumed = true;
return new ResumeWindow(new NavigationPage(new AndroidNavigationRedirectPage(Options)));
}
@@ -184,182 +167,132 @@ namespace Bit.App
_accountsManager.Init(() => Options, this);
_broadcasterService.Subscribe(nameof(App), BroadcastServiceMessageCallbackAsync);
Bootstrap();
}
private async void BroadcastServiceMessageCallbackAsync(Message message)
{
try
{
ArgumentNullException.ThrowIfNull(message);
if (message.Command == "showDialog")
_broadcasterService.Subscribe(nameof(App), async (message) =>
{
try
{
var details = message.Data as DialogDetails;
ArgumentNullException.ThrowIfNull(details);
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(ShowDialogAction);
async Task ShowDialogAction()
if (message.Command == "showDialog")
{
if (!string.IsNullOrWhiteSpace(details.CancelText))
var details = message.Data as DialogDetails;
var confirmed = true;
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
AppResources.Ok : details.ConfirmText;
await MainThread.InvokeOnMainThreadAsync(async () =>
{
ArgumentNullException.ThrowIfNull(MainPage);
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await _deviceActionService.DisplayAlertAsync(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
if (!string.IsNullOrWhiteSpace(details.CancelText))
{
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
details.CancelText);
}
else
{
await MainPage.DisplayAlert(details.Title, details.Text, confirmText);
}
_messagingService.Send("showDialogResolve", new Tuple<int, bool>(details.DialogId, confirmed));
});
}
}
#if IOS
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
ResumedAsync().FireAndForget();
}
else if (message.Command == "slept")
{
await SleptAsync();
}
#endif
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
else if (message.Command == AppHelpers.RESUMED_MESSAGE_COMMAND)
{
Options.OtpData = new OtpData((string)message.Data);
ResumedAsync().FireAndForget();
}
await MainThread.InvokeOnMainThreadAsync(ExecuteNavigationAction);
async Task ExecuteNavigationAction()
else if (message.Command == "slept")
{
if (MainPage is TabsPage tabsPage)
await SleptAsync();
}
#endif
else if (message.Command == "migrated")
{
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
ArgumentNullException.ThrowIfNull(tabsPage.Navigation.ModalStack);
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
Options.OtpData = new OtpData((string)message.Data);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
await MainThread.InvokeOnMainThreadAsync(async () =>
{
if (MainPage is TabsPage tabsPage)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
ArgumentNullException.ThrowIfNull(tabsPage.Navigation);
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
while (tabsPage.Navigation.ModalStack.Count > 0)
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
}
});
}
else if (message.Command == "convertAccountToKeyConnector")
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
else if (message.Command == Constants.ForceUpdatePassword)
{
await MainThread.InvokeOnMainThreadAsync(async () =>
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == Constants.ForceSetPassword)
{
await MainThread.InvokeOnMainThreadAsync(() => MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data))));
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
}
}
else if (message.Command == Constants.CredentialNavigateToAutofillCipherMessageCommand && message.Data is Fido2ConfirmNewCredentialParams createParams)
catch (Exception ex)
{
ArgumentNullException.ThrowIfNull(MainPage);
ArgumentNullException.ThrowIfNull(Options);
await MainThread.InvokeOnMainThreadAsync(NavigateToCipherSelectionPageAction);
void NavigateToCipherSelectionPageAction()
{
Options.Uri = createParams.RpId;
Options.SaveUsername = createParams.UserName;
Options.SaveName = createParams.CredentialName;
MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
else if (message.Command == "convertAccountToKeyConnector")
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToRemoveMasterPasswordPageAction);
async Task NavigateToRemoveMasterPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new RemoveMasterPasswordPage()));
}
}
else if (message.Command == Constants.ForceUpdatePassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToUpdateTempPasswordPageAction);
async Task NavigateToUpdateTempPasswordPageAction()
{
await MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
}
}
else if (message.Command == Constants.ForceSetPassword)
{
ArgumentNullException.ThrowIfNull(MainPage);
await MainThread.InvokeOnMainThreadAsync(NavigateToSetPasswordPageAction);
void NavigateToSetPasswordPageAction()
{
MainPage.Navigation.PushModalAsync(
new NavigationPage(new SetPasswordPage(orgIdentifier: (string)message.Data)));
}
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
#if ANDROID
if (message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED && _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
_fido2MakeCredentialConfirmationUserInterface.Value.OnConfirmationException(new AccountSwitchedException());
}
#endif
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
}
else if (message.Command == Constants.NavigateToMessageCommand && message.Data is NavigationTarget navigationTarget)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
Navigate(navigationTarget, null);
});
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
}
private async Task CheckPasswordlessLoginRequestsAsync()
@@ -374,6 +307,7 @@ namespace Bit.App
{
return;
}
var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null)
{
@@ -725,15 +659,6 @@ namespace Bit.App
// If we are in background we add the Navigation Actions to a queue to execute when the app resumes.
// Links: https://github.com/dotnet/maui/issues/11501 and https://bitwarden.atlassian.net/wiki/spaces/NMME/pages/664862722/MainPage+Assignments+not+working+on+Android+on+Background+or+App+resume
#if ANDROID
if (_fido2MakeCredentialConfirmationUserInterface != null && _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
// if it's creating passkey
// and we have an active pending TaskCompletionSource
// then we let the Fido2 Authenticator flow manage the navigation to avoid issues
// like duplicated navigation to lock page.
return;
}
if (!_isResumed)
{
_onResumeActions.Enqueue(() => NavigateImpl(navTarget, navParams));

View File

@@ -46,11 +46,8 @@ namespace Bit.Core
public const string PreLoginEmailKey = "preLoginEmailKey";
public const string ConfigsKey = "configsKey";
public const string DisplayEuEnvironmentFlag = "display-eu-environment";
public const string UnassignedItemsBannerFlag = "unassigned-items-banner";
public const string RegionEnvironment = "regionEnvironment";
public const string DuoCallback = "bitwarden://duo-callback";
public const string NavigateToMessageCommand = "navigateTo";
public const string CredentialNavigateToAutofillCipherMessageCommand = "credentialNavigateToAutofillCipher";
/// <summary>
/// This key is used to store the value of "ShouldConnectToWatch" of the last user that had logged in
@@ -139,7 +136,6 @@ namespace Bit.Core
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
public static string ShouldCheckOrganizationUnassignedItemsKey(string userId) => $"shouldCheckOrganizationUnassignedItems_{userId}";
[Obsolete]
public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete]

View File

@@ -50,7 +50,7 @@
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
Text="{Binding ., Converter={StaticResource iconGlyphConverter}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<controls:BaseSettingItemView
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:core="clr-namespace:Bit.Core"
x:Class="Bit.App.Controls.ExternalLinkSubtitleItemView"
x:Name="_contentView"
ControlTemplate="{StaticResource SettingControlTemplate}">
<controls:BaseSettingItemView.GestureRecognizers>
<TapGestureRecognizer Tapped="ContentView_Tapped" />
</controls:BaseSettingItemView.GestureRecognizers>
<controls:IconLabel
Text="{Binding Source={x:Static core:BitwardenIcons.ExternalLink}}"
TextColor="{DynamicResource TextColor}"
FontSize="25"
Margin="6,0,7,0"
HorizontalOptions="End"
VerticalOptions="Center"
SemanticProperties.Description="{Binding Title, Mode=OneWay, Source={x:Reference _contentView}}" />
</controls:BaseSettingItemView>

View File

@@ -1,26 +0,0 @@
using System.Windows.Input;
namespace Bit.App.Controls
{
public partial class ExternalLinkSubtitleItemView : BaseSettingItemView
{
public static readonly BindableProperty GoToLinkCommandProperty = BindableProperty.Create(
nameof(GoToLinkCommand), typeof(ICommand), typeof(ExternalLinkSubtitleItemView));
public ExternalLinkSubtitleItemView()
{
InitializeComponent();
}
public ICommand GoToLinkCommand
{
get => GetValue(GoToLinkCommandProperty) as ICommand;
set => SetValue(GoToLinkCommandProperty, value);
}
void ContentView_Tapped(System.Object sender, System.EventArgs e)
{
GoToLinkCommand?.Execute(null);
}
}
}

View File

@@ -34,7 +34,6 @@
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="PCLCrypto" Version="2.1.40-alpha" />
<PackageReference Include="System.Formats.Cbor" Version="8.0.0" />
<PackageReference Include="zxcvbn-core" Version="7.0.92" />
<PackageReference Include="MessagePack.MSBuild.Tasks" Version="2.5.124">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -53,7 +52,6 @@
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.7.2.1" />
<PackageReference Include="Xamarin.AndroidX.Credentials" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android' AND !$(DefineConstants.Contains(FDROID))">
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet" Version="118.0.1.5" />
@@ -77,14 +75,14 @@
<Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
<Folder Include="Resources\Localization\" />
<Folder Include="Utilities\Fido2\" />
<Folder Include="Controls\Picker\" />
<Folder Include="Controls\Avatar\" />
<Folder Include="Services\UserVerification\" />
<Folder Include="Utilities\WebAuthenticatorMAUI\" />
<Folder Include="Resources\Images\" />
</ItemGroup>
<ItemGroup>
<MauiImage Include="Resources\Images\dotnet_bot.svg">
<BaseSize>168,208</BaseSize>
</MauiImage>
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>
@@ -93,9 +91,6 @@
<LastGenOutput>AppResources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<Compile Update="Controls\Settings\ExternalLinkSubtitleItemView.xaml.cs">
<DependentUpon>ExternalLinkSubtitleItemView.xaml</DependentUpon>
</Compile>
<Compile Update="Pages\AndroidNavigationRedirectPage.xaml.cs">
<DependentUpon>AndroidNavigationRedirectPage.xaml</DependentUpon>
</Compile>
@@ -106,25 +101,13 @@
</Compile>
</ItemGroup>
<ItemGroup>
<MauiXaml Update="Controls\Settings\ExternalLinkSubtitleItemView.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
<MauiXaml Update="Pages\AndroidNavigationRedirectPage.xaml">
<Generator>MSBuild:Compile</Generator>
</MauiXaml>
</ItemGroup>
<ItemGroup>
<None Remove="Utilities\Fido2\" />
<None Remove="Controls\Picker\" />
<None Remove="Controls\Avatar\" />
<None Remove="Services\UserVerification\" />
<None Remove="Utilities\WebAuthenticatorMAUI\" />
<None Remove="Resources\Images\" />
<None Remove="Resources\Images\empty_items_state_dark.svg" />
<None Remove="Resources\Images\empty_items_state.svg" />
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<MauiImage Include="Resources\Images\empty_items_state.svg" />
<MauiImage Include="Resources\Images\empty_items_state_dark.svg" />
</ItemGroup>
</Project>

View File

@@ -1,10 +0,0 @@
namespace Bit.Core.Exceptions
{
public class ValidationException : Exception
{
public ValidationException(string localizedMessage)
: base(localizedMessage)
{
}
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Models.Domain;
using System;
using Bit.Core.Models.Domain;
namespace Bit.Core.Models.Api
{
@@ -20,7 +21,6 @@ namespace Bit.Core.Models.Api
RpName = fido2Key.RpName?.EncryptedString;
UserHandle = fido2Key.UserHandle?.EncryptedString;
UserName = fido2Key.UserName?.EncryptedString;
UserDisplayName = fido2Key.UserDisplayName?.EncryptedString;
Counter = fido2Key.Counter?.EncryptedString;
CreationDate = fido2Key.CreationDate;
}
@@ -35,7 +35,6 @@ namespace Bit.Core.Models.Api
public string RpName { get; set; }
public string UserHandle { get; set; }
public string UserName { get; set; }
public string UserDisplayName { get; set; }
public string Counter { get; set; }
public DateTime CreationDate { get; set; }
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.App.Models
@@ -8,8 +9,6 @@ namespace Bit.App.Models
public bool MyVaultTile { get; set; }
public bool GeneratorTile { get; set; }
public bool FromAutofillFramework { get; set; }
public bool FromFido2Framework { get; set; }
public string Fido2CredentialAction { get; set; }
public CipherType? FillType { get; set; }
public string Uri { get; set; }
public CipherType? SaveType { get; set; }
@@ -26,8 +25,6 @@ namespace Bit.App.Models
public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public OtpData? OtpData { get; set; }
public bool HasUnlockedInThisTransaction { get; set; }
public bool HasJustLoggedInOrUnlocked { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -38,7 +35,6 @@ namespace Bit.App.Models
MyVaultTile = o.MyVaultTile;
GeneratorTile = o.GeneratorTile;
FromAutofillFramework = o.FromAutofillFramework;
Fido2CredentialAction = o.Fido2CredentialAction;
FillType = o.FillType;
Uri = o.Uri;
SaveType = o.SaveType;
@@ -55,7 +51,6 @@ namespace Bit.App.Models
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
OtpData = o.OtpData;
HasUnlockedInThisTransaction = o.HasUnlockedInThisTransaction;
}
}
}

View File

@@ -19,7 +19,6 @@ namespace Bit.Core.Models.Data
RpName = apiData.RpName;
UserHandle = apiData.UserHandle;
UserName = apiData.UserName;
UserDisplayName = apiData.UserDisplayName;
Counter = apiData.Counter;
CreationDate = apiData.CreationDate;
}
@@ -34,7 +33,6 @@ namespace Bit.Core.Models.Data
public string RpName { get; set; }
public string UserHandle { get; set; }
public string UserName { get; set; }
public string UserDisplayName { get; set; }
public string Counter { get; set; }
public DateTime CreationDate { get; set; }
}

View File

@@ -1,4 +1,8 @@
using Bit.Core.Models.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.View;
namespace Bit.Core.Models.Domain
@@ -17,7 +21,6 @@ namespace Bit.Core.Models.Domain
nameof(RpName),
nameof(UserHandle),
nameof(UserName),
nameof(UserDisplayName),
nameof(Counter)
};
@@ -45,7 +48,6 @@ namespace Bit.Core.Models.Domain
public EncString RpName { get; set; }
public EncString UserHandle { get; set; }
public EncString UserName { get; set; }
public EncString UserDisplayName { get; set; }
public EncString Counter { get; set; }
public DateTime CreationDate { get; set; }

View File

@@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Domain
{
@@ -8,7 +9,7 @@ namespace Bit.Core.Models.Domain
{
if (key == null)
{
throw new ArgumentKeyNullException(nameof(key));
throw new Exception("Must provide key.");
}
if (encType == null)
@@ -23,7 +24,7 @@ namespace Bit.Core.Models.Domain
}
else
{
throw new InvalidKeyOperationException("Unable to determine encType.");
throw new Exception("Unable to determine encType.");
}
}
@@ -47,7 +48,7 @@ namespace Bit.Core.Models.Domain
}
else
{
throw new InvalidKeyOperationException("Unsupported encType/key length.");
throw new Exception("Unsupported encType/key length.");
}
if (Key != null)
@@ -71,32 +72,6 @@ namespace Bit.Core.Models.Domain
public string KeyB64 { get; set; }
public string EncKeyB64 { get; set; }
public string MacKeyB64 { get; set; }
public class ArgumentKeyNullException : ArgumentNullException
{
public ArgumentKeyNullException(string paramName) : base(paramName)
{
}
public ArgumentKeyNullException(string message, Exception innerException) : base(message, innerException)
{
}
public ArgumentKeyNullException(string paramName, string message) : base(paramName, message)
{
}
}
public class InvalidKeyOperationException : InvalidOperationException
{
public InvalidKeyOperationException(string message) : base(message)
{
}
public InvalidKeyOperationException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
public class UserKey : SymmetricCryptoKey

View File

@@ -1,7 +1,8 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
namespace Bit.Core.Models.View
{
@@ -51,7 +52,7 @@ namespace Bit.Core.Models.View
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public CipherKey Key { get; set; }
public ItemView Item
{
get
@@ -121,14 +122,5 @@ namespace Bit.Core.Models.View
public bool IsClonable => OrganizationId is null;
public bool HasFido2Credential => Type == CipherType.Login && Login?.HasFido2Credentials == true;
public string GetMainFido2CredentialUsername()
{
return Login?.MainFido2Credential?.UserName
.FallbackOnNullOrWhiteSpace(Login?.MainFido2Credential?.UserDisplayName)
.FallbackOnNullOrWhiteSpace(Login?.Username)
.FallbackOnNullOrWhiteSpace(Name)
.FallbackOnNullOrWhiteSpace(AppResources.UnknownAccount);
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Text.Json.Serialization;
using System;
using System.Collections.Generic;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
namespace Bit.Core.Models.View
{
@@ -26,42 +26,13 @@ namespace Bit.Core.Models.View
public string RpName { get; set; }
public string UserHandle { get; set; }
public string UserName { get; set; }
public string UserDisplayName { get; set; }
public string Counter { get; set; }
public DateTime CreationDate { get; set; }
[JsonIgnore]
public int CounterValue {
get => int.TryParse(Counter, out var counter) ? counter : 0;
set => Counter = value.ToString();
}
[JsonIgnore]
public byte[] UserHandleValue {
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
}
[JsonIgnore]
public byte[] KeyBytes {
get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue);
set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value);
}
[JsonIgnore]
public bool DiscoverableValue {
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
set => Discoverable = value.ToString().ToLower();
}
[JsonIgnore]
public override string SubTitle => UserName;
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
[JsonIgnore]
public bool IsDiscoverable => !string.IsNullOrWhiteSpace(Discoverable);
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
[JsonIgnore]
public string LaunchUri => $"https://{RpId}";
public bool IsUniqueAgainst(Fido2CredentialView fido2View) => fido2View?.RpId != RpId || fido2View?.UserName != UserName;

View File

@@ -1,4 +1,7 @@
using Bit.Core.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
namespace Bit.Core.Models.View

View File

@@ -168,7 +168,7 @@ namespace Bit.App.Pages
var tasks = Task.Run(async () =>
{
await Task.Delay(50);
_vm.SubmitCommand.Execute(null);
MainThread.BeginInvokeOnMainThread(async () => await _vm.SubmitAsync());
});
}
}
@@ -233,10 +233,6 @@ namespace Bit.App.Pages
}
var previousPage = await AppHelpers.ClearPreviousPage();
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
App.MainPage = new TabsPage(_appOptions, previousPage);
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.Core.Resources.Localization;
@@ -74,10 +73,7 @@ namespace Bit.App.Pages
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = CreateDefaultAsyncRelayCommand(
() => MainThread.InvokeOnMainThreadAsync(SubmitAsync),
onException: _logger.Exception,
allowsMultipleExecutions: false);
SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel =
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
@@ -161,7 +157,7 @@ namespace Bit.App.Pages
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ICommand SubmitCommand { get; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
@@ -237,8 +233,8 @@ namespace Bit.App.Pages
}
BiometricButtonVisible = true;
BiometricButtonText = AppResources.UseBiometricsToUnlock;
if (DeviceInfo.Platform == DevicePlatform.iOS)
// TODO Xamarin.Forms.Device.RuntimePlatform is no longer supported. Use Microsoft.Maui.Devices.DeviceInfo.Platform instead. For more details see https://learn.microsoft.com/en-us/dotnet/maui/migration/forms-projects#device-changes
if (Device.RuntimePlatform == Device.iOS)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
@@ -285,8 +281,6 @@ namespace Bit.App.Pages
var failed = true;
try
{
await MainThread.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
EncString userKeyPin;
EncString oldPinProtected;
switch (_pinStatus)
@@ -335,26 +329,20 @@ namespace Bit.App.Pages
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetUserKeyAndContinueAsync(userKey, shouldHandleHideLoading: true);
await Task.Delay(150); //Workaround Delay to avoid "duplicate" execution of SubmitAsync on Android when invoked from the ReturnCommand
await SetUserKeyAndContinueAsync(userKey);
}
}
catch (LegacyUserException)
{
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
throw;
}
catch (Exception ex)
catch
{
_logger.Exception(ex);
failed = true;
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
@@ -430,7 +418,6 @@ namespace Bit.App.Pages
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetUserKeyAndContinueAsync(userKey);
await Task.Delay(150); //Workaround Delay to avoid "duplicate" execution of SubmitAsync on Android when invoked from the ReturnCommand
// Re-enable biometrics
if (BiometricEnabled & !BiometricIntegrityValid)
@@ -528,7 +515,7 @@ namespace Bit.App.Pages
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
!PinEnabled && !HasMasterPassword) ?? false;
!PinEnabled && !HasMasterPassword);
await _stateService.SetBiometricLockedAsync(!success);
if (success)
@@ -543,7 +530,7 @@ namespace Bit.App.Pages
}
}
private async Task SetUserKeyAndContinueAsync(UserKey key, bool shouldHandleHideLoading = false)
private async Task SetUserKeyAndContinueAsync(UserKey key)
{
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
@@ -551,18 +538,14 @@ namespace Bit.App.Pages
await _cryptoService.SetUserKeyAsync(key);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync(shouldHandleHideLoading);
await DoContinueAsync();
}
private async Task DoContinueAsync(bool shouldHandleHideLoading = false)
private async Task DoContinueAsync()
{
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
if (shouldHandleHideLoading)
{
await MainThread.InvokeOnMainThreadAsync(_deviceActionService.HideLoadingAsync);
}
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}

View File

@@ -35,11 +35,6 @@ namespace Bit.App.Pages
{
return;
}
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}

View File

@@ -195,11 +195,6 @@ namespace Bit.App.Pages
{
return;
}
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}

View File

@@ -55,11 +55,6 @@ namespace Bit.App.Pages
{
return;
}
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}

View File

@@ -125,7 +125,7 @@ namespace Bit.App.Pages
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage(_appOptions);
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -71,11 +71,6 @@ namespace Bit.App.Pages
{
return;
}
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}

View File

@@ -191,7 +191,7 @@ namespace Bit.App.Pages
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage(_appOptions);
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
@@ -206,11 +206,6 @@ namespace Bit.App.Pages
{
return;
}
if (_appOptions != null)
{
_appOptions.HasJustLoggedInOrUnlocked = true;
}
var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage);
}

View File

@@ -5,7 +5,7 @@
x:Class="Bit.App.Pages.AutofillPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
Title="{u:I18n SetUpAutofill}">
Title="{u:I18n PasswordAutofill}">
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
@@ -15,22 +15,26 @@
<StackLayout Spacing="5"
Padding="20, 20, 20, 30"
VerticalOptions="FillAndExpand">
<Label Text="{u:I18n GetInstantAccessToYourPasswordsAndPasskeys}"
<Label Text="{u:I18n ExtensionInstantAccess}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
StyleClass="text-lg"
Margin="0, 0, 0, 15" />
<Label Text="{u:I18n SetUpAutoFillDescriptionLong}"
<Label Text="{u:I18n AutofillTurnOn}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap"
Margin="0, 0, 0, 15" />
<Label Text="{u:I18n FirstDotGoToYourDeviceSettingsPasswordsPasswordOptions}"
<Label Text="{u:I18n AutofillTurnOn1}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n SecondDotTurnOnAutoFill}"
<Label Text="{u:I18n AutofillTurnOn2}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n ThirdDotSelectBitwardenToUseForPasswordsAndPasskeys}"
<Label Text="{u:I18n AutofillTurnOn3}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn4}"
LineBreakMode="WordWrap" />
<Label Text="{u:I18n AutofillTurnOn5}"
LineBreakMode="WordWrap" />
<Image Source="autofill-kb.png"
VerticalOptions="CenterAndExpand"

View File

@@ -38,15 +38,6 @@
StyleClass="settings-item-view"
HorizontalOptions="FillAndExpand" />
<controls:ExternalLinkSubtitleItemView
Title="{u:I18n PasskeyManagement}"
Subtitle="{u:I18n PasskeyManagementExplanationLong}"
IsVisible="{Binding SupportsCredentialProviderService}"
GoToLinkCommand="{Binding GoToCredentialProviderSettingsCommand}"
AutomationId="CredentialProviderServiceSwitch"
StyleClass="settings-item-view"
HorizontalOptions="FillAndExpand" />
<controls:SwitchItemView
Title="{u:I18n Accessibility}"
Subtitle="{Binding UseAccessibilityDescription}"

View File

@@ -12,8 +12,6 @@ namespace Bit.App.Pages
private bool _useDrawOver;
private bool _askToAddLogin;
public bool SupportsCredentialProviderService => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsCredentialProviderService();
public bool SupportsAndroidAutofillServices => DeviceInfo.Platform == DevicePlatform.Android && _deviceActionService.SupportsAutofillServices();
public bool UseAutofillServices
@@ -92,7 +90,6 @@ namespace Bit.App.Pages
public AsyncRelayCommand ToggleUseDrawOverCommand { get; private set; }
public AsyncRelayCommand ToggleAskToAddLoginCommand { get; private set; }
public ICommand GoToBlockAutofillUrisCommand { get; private set; }
public ICommand GoToCredentialProviderSettingsCommand { get; private set; }
private void InitAndroidCommands()
{
@@ -102,7 +99,6 @@ namespace Bit.App.Pages
ToggleUseDrawOverCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => ToggleDrawOver()), () => _inited, allowsMultipleExecutions: false);
ToggleAskToAddLoginCommand = CreateDefaultAsyncRelayCommand(ToggleAskToAddLoginAsync, () => _inited, allowsMultipleExecutions: false);
GoToBlockAutofillUrisCommand = CreateDefaultAsyncRelayCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()), allowsMultipleExecutions: false);
GoToCredentialProviderSettingsCommand = CreateDefaultAsyncRelayCommand(() => MainThread.InvokeOnMainThreadAsync(() => GoToCredentialProviderSettings()), () => _inited, allowsMultipleExecutions: false);
}
private async Task InitAndroidAutofillSettingsAsync()
@@ -134,17 +130,6 @@ namespace Bit.App.Pages
});
}
private async Task GoToCredentialProviderSettings()
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.SetBitwardenAsPasskeyManagerDescription, AppResources.ContinueToDeviceSettings,
AppResources.Continue,
AppResources.Cancel);
if (confirmed)
{
_deviceActionService.OpenCredentialProviderSettings();
}
}
private void ToggleUseAutofillServices()
{
if (UseAutofillServices)

View File

@@ -370,7 +370,7 @@ namespace Bit.App.Pages
if (!_supportsBiometric
||
await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null) != true)
!await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null))
{
_canUnlockWithBiometrics = false;
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics)));

View File

@@ -33,7 +33,7 @@ namespace Bit.App.Pages
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_stateService = ServiceContainer.Resolve<IStateService>();
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage, appOptions: appOptions))
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
{
Title = AppResources.MyVault,
IconImageSource = "lock.png"

View File

@@ -1,7 +1,6 @@
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
@@ -13,8 +12,6 @@ namespace Bit.App.Pages
public class AutofillCiphersPageViewModel : CipherSelectionPageViewModel
{
private CipherType? _fillType;
private AppOptions _appOptions;
private readonly LazyResolve<IFido2MakeCredentialConfirmationUserInterface> _fido2MakeCredentialConfirmationUserInterface = new LazyResolve<IFido2MakeCredentialConfirmationUserInterface>();
public string Uri { get; set; }
@@ -22,7 +19,6 @@ namespace Bit.App.Pages
{
Uri = appOptions?.Uri;
_fillType = appOptions.FillType;
_appOptions = appOptions;
string name = null;
if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false)
@@ -40,7 +36,6 @@ namespace Bit.App.Pages
Name = name;
PageTitle = string.Format(AppResources.ItemsForUri, Name ?? "--");
NoDataText = string.Format(AppResources.NoItemsForUri, Name ?? "--");
AddNewItemText = _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential ? AppResources.SavePasskeyAsNewLogin : AppResources.AddAnItem;
}
protected override async Task<List<GroupingsPageListGroup>> LoadGroupedItemsAsync()
@@ -48,11 +43,7 @@ namespace Bit.App.Pages
var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);
var matching = ciphers.Item1?.Select(c => new CipherItemViewModel(c, WebsiteIconsEnabled)
{
UsePasskeyIconAsPlaceholderFallback = _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential
}).ToList();
var matching = ciphers.Item1?.Select(c => new CipherItemViewModel(c, WebsiteIconsEnabled)).ToList();
var hasMatching = matching?.Any() ?? false;
if (matching?.Any() ?? false)
{
@@ -87,12 +78,6 @@ namespace Bit.App.Pages
return;
}
if (_fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
await _fido2MakeCredentialConfirmationUserInterface.Value.ConfirmAsync(cipher.Id, cipher.Login.HasFido2Credentials, null);
return;
}
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
return;
@@ -145,30 +130,8 @@ namespace Bit.App.Pages
}
}
protected override async Task AddFabCipherAsync()
{
//Scenario for creating a new Fido2 credential on Android but showing the Cipher Page
if (_fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
var pageForOther = new CipherAddEditPage(null, CipherType.Login, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForOther));
return;
}
else
{
await AddCipherAsync();
}
}
protected override async Task AddCipherAsync()
{
//Scenario for creating a new Fido2 credential on Android
if (_fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
_fido2MakeCredentialConfirmationUserInterface.Value.Confirm(null, null);
return;
}
if (_fillType.HasValue && _fillType != CipherType.Login)
{
var pageForOther = new CipherAddEditPage(type: _fillType, fromAutofill: true);
@@ -180,13 +143,5 @@ namespace Bit.App.Pages
fromAutofill: true);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
public void Cancel()
{
if (_fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
_fido2MakeCredentialConfirmationUserInterface.Value.Cancel();
}
}
}
}

View File

@@ -112,7 +112,7 @@
StyleClass="box-header, box-header-platform" />
</StackLayout>
<StackLayout StyleClass="box-row, box-row-input"
IsVisible="{Binding TypeEditMode, Converter={StaticResource inverseBool}}">
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
<Label
Text="{u:I18n Type}"
StyleClass="box-label" />
@@ -649,11 +649,9 @@
Grid.Row="1"
Grid.Column="0"
SemanticProperties.Description="{u:I18n URI}"
IsEnabled="{Binding BindingContext.IsFromFido2Framework, Source={x:Reference _page}, Converter={StaticResource inverseBool}}"
AutomationId="LoginUriEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
IsVisible="{Binding BindingContext.IsFromFido2Framework, Source={x:Reference _page}, Converter={StaticResource inverseBool}}"
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
Command="{Binding BindingContext.UriOptionsCommand, Source={x:Reference _page}}"
CommandParameter="{Binding .}"
@@ -667,7 +665,6 @@
</BindableLayout.ItemTemplate>
</StackLayout>
<Button Text="{u:I18n NewUri}" StyleClass="box-button-row"
IsVisible="{Binding IsFromFido2Framework, Converter={StaticResource inverseBool}}"
Clicked="NewUri_Clicked"
AutomationId="LoginAddNewUriButton"></Button>
</StackLayout>

View File

@@ -19,9 +19,6 @@ namespace Bit.App.Pages
private readonly IAutofillHandler _autofillHandler;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IUserVerificationService _userVerificationService;
#if ANDROID
private readonly LazyResolve<IFido2MakeCredentialConfirmationUserInterface> _fido2MakeCredentialConfirmationUserInterface = new LazyResolve<IFido2MakeCredentialConfirmationUserInterface>();
#endif
private CipherAddEditPageViewModel _vm;
private bool _fromAutofill;
@@ -48,9 +45,6 @@ namespace Bit.App.Pages
_appOptions = appOptions;
_fromAutofill = fromAutofill;
FromAutofillFramework = _appOptions?.FromAutofillFramework ?? false;
#if ANDROID
FromAndroidFido2Framework = _fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential;
#endif
InitializeComponent();
_vm = BindingContext as CipherAddEditPageViewModel;
_vm.Page = this;
@@ -150,7 +144,6 @@ namespace Bit.App.Pages
}
public bool FromAutofillFramework { get; set; }
public bool FromAndroidFido2Framework { get; set; }
public CipherAddEditPageViewModel ViewModel => _vm;
protected override async void OnAppearing()

View File

@@ -17,8 +17,6 @@ using Microsoft.Maui.Controls;
using Microsoft.Maui;
using Bit.App.Utilities;
using CommunityToolkit.Mvvm.Input;
using Bit.Core.Utilities.Fido2;
using Bit.Core.Services;
#nullable enable
@@ -39,9 +37,7 @@ namespace Bit.App.Pages
private readonly IAutofillHandler _autofillHandler;
private readonly IWatchDeviceService _watchDeviceService;
private readonly IAccountsManager _accountsManager;
private readonly IFido2MakeCredentialConfirmationUserInterface _fido2MakeCredentialConfirmationUserInterface;
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
private bool _showNotesSeparator;
private bool _showPassword;
private bool _showCardNumber;
@@ -96,11 +92,6 @@ namespace Bit.App.Pages
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
if (ServiceContainer.TryResolve<IFido2MakeCredentialConfirmationUserInterface>(out var fido2MakeService))
{
_fido2MakeCredentialConfirmationUserInterface = fido2MakeService;
}
_userVerificationMediatorService = ServiceContainer.Resolve<IUserVerificationMediatorService>();
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
@@ -301,9 +292,7 @@ namespace Bit.App.Pages
});
}
public bool ShowCollections => (!EditMode || CloneMode) && Cipher?.OrganizationId != null;
public bool IsFromFido2Framework { get; set; }
public bool EditMode => !string.IsNullOrWhiteSpace(CipherId);
public bool TypeEditMode => !string.IsNullOrWhiteSpace(CipherId) || IsFromFido2Framework;
public bool ShowOwnershipOptions => !EditMode || CloneMode;
public bool OwnershipPolicyInEffect => ShowOwnershipOptions && !AllowPersonal;
public bool CloneMode { get; set; }
@@ -335,7 +324,6 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(AppOptions appOptions = null)
{
_fromOtp = appOptions?.OtpData != null;
IsFromFido2Framework = _fido2MakeCredentialConfirmationUserInterface?.IsConfirmingNewCredential == true;
var myEmail = await _stateService.GetEmailAsync();
OwnershipOptions.Add(new KeyValuePair<string, string>(myEmail, null));
@@ -548,26 +536,6 @@ namespace Bit.App.Pages
}
try
{
bool isFido2UserVerified = false;
if (IsFromFido2Framework)
{
// Verify the user and prevent saving cipher if enforcing is needed and it's not verified.
var userVerification = await VerifyUserAsync();
if (userVerification.IsCancelled)
{
return false;
}
isFido2UserVerified = userVerification.Result;
var options = _fido2MakeCredentialConfirmationUserInterface.GetCurrentUserVerificationOptions();
if (!isFido2UserVerified && await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(options.Value))
{
await _platformUtilsService.ShowDialogAsync(AppResources.ErrorCreatingPasskey, AppResources.SavePasskey);
return false;
}
}
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
await _cipherService.SaveWithServerAsync(cipher);
@@ -586,11 +554,6 @@ namespace Bit.App.Pages
// Close and go back to app
_autofillHandler.CloseAutofill();
}
else if (IsFromFido2Framework)
{
_fido2MakeCredentialConfirmationUserInterface.Confirm(cipher.Id, isFido2UserVerified);
return true;
}
else if (_fromOtp)
{
await _accountsManager.StartDefaultNavigationFlowAsync(op => op.OtpData = null);
@@ -626,27 +589,6 @@ namespace Bit.App.Pages
return false;
}
private async Task<CancellableResult<bool>> VerifyUserAsync()
{
try
{
var options = _fido2MakeCredentialConfirmationUserInterface.GetCurrentUserVerificationOptions();
ArgumentNullException.ThrowIfNull(options);
if (options.Value.UserVerificationPreference == Fido2UserVerificationPreference.Discouraged)
{
return new CancellableResult<bool>(false);
}
return await _userVerificationMediatorService.VerifyUserForFido2Async(options.Value);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
return new CancellableResult<bool>(false);
}
}
public async Task<bool> DeleteAsync()
{
if (Microsoft.Maui.Networking.Connectivity.NetworkAccess == Microsoft.Maui.Networking.NetworkAccess.None)

View File

@@ -44,7 +44,5 @@ namespace Bit.App.Pages
/// This is useful to check when the cell is being reused.
/// </summary>
public bool IconImageSuccesfullyLoaded { get; set; }
public bool UsePasskeyIconAsPlaceholderFallback { get; set; }
}
}

View File

@@ -78,13 +78,12 @@
Spacing="20"
IsVisible="{Binding ShowNoData}">
<Image
x:Name="_emptyItemsPlaceholder"
Source="empty_items_state" />
<Label
Text="{Binding NoDataText}"
HorizontalTextAlignment="Center"></Label>
<Button
Text="{Binding AddNewItemText}"
Text="{u:I18n AddAnItem}"
Command="{Binding AddCipherCommand}" />
</StackLayout>
@@ -134,7 +133,7 @@
<Button
x:Name="_fab"
ImageSource="plus.png"
Command="{Binding AddFabCipherCommand}"
Command="{Binding AddCipherCommand}"
Style="{StaticResource btn-fab}"
IsVisible="{OnPlatform iOS=false, Android=true}"
AbsoluteLayout.LayoutFlags="PositionProportional"

View File

@@ -115,8 +115,6 @@ namespace Bit.App.Pages
await _vm.LoadAsync();
}
}, _mainContent);
UpdatePlaceholder();
}
protected override bool OnBackButtonPressed()
@@ -129,11 +127,6 @@ namespace Bit.App.Pages
#if ANDROID
_appOptions.Uri = null;
if (BindingContext is AutofillCiphersPageViewModel autofillVM)
{
autofillVM.Cancel();
}
#endif
return base.OnBackButtonPressed();
}
@@ -182,27 +175,7 @@ namespace Bit.App.Pages
if (DoOnce())
{
_accountsManager.StartDefaultNavigationFlowAsync(op => op.OtpData = null).FireAndForget();
if (BindingContext is AutofillCiphersPageViewModel autofillVM)
{
autofillVM.Cancel();
}
}
}
public override async Task UpdateOnThemeChanged()
{
await base.UpdateOnThemeChanged();
UpdatePlaceholder();
}
private void UpdatePlaceholder()
{
#if ANDROID
MainThread.BeginInvokeOnMainThread(() =>
_emptyItemsPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_items_state.png" : "empty_items_state_dark.png"));
#endif
}
}
}

View File

@@ -4,7 +4,6 @@ using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
namespace Bit.App.Pages
@@ -23,7 +22,6 @@ namespace Bit.App.Pages
protected bool _showNoData;
protected bool _showList;
protected string _noDataText;
protected string _addNewItemText;
protected bool _websiteIconsEnabled;
public CipherSelectionPageViewModel()
@@ -44,9 +42,6 @@ namespace Bit.App.Pages
SelectCipherCommand = CreateDefaultAsyncRelayCommand<IGroupingsPageListItem>(SelectCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddFabCipherCommand = CreateDefaultAsyncRelayCommand(AddFabCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
@@ -55,8 +50,6 @@ namespace Bit.App.Pages
{
AllowAddAccountRow = false
};
AddNewItemText = AppResources.AddAnItem;
}
public string Name { get; set; }
@@ -67,7 +60,6 @@ namespace Bit.App.Pages
public ICommand CipherOptionsCommand { get; set; }
public ICommand SelectCipherCommand { get; set; }
public ICommand AddCipherCommand { get; set; }
public ICommand AddFabCipherCommand { get; set; }
public bool ShowNoData
{
@@ -87,12 +79,6 @@ namespace Bit.App.Pages
set => SetProperty(ref _noDataText, value);
}
public string AddNewItemText
{
get => _addNewItemText;
set => SetProperty(ref _addNewItemText, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
@@ -167,6 +153,5 @@ namespace Bit.App.Pages
protected abstract Task SelectCipherAsync(IGroupingsPageListItem item);
protected abstract Task AddCipherAsync();
protected abstract Task AddFabCipherAsync();
}
}

View File

@@ -7,7 +7,6 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.App.Pages
{
@@ -22,10 +21,6 @@ namespace Bit.App.Pages
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
#if ANDROID
private readonly LazyResolve<IFido2MakeCredentialConfirmationUserInterface> _fido2MakeCredentialConfirmationUserInterface = new LazyResolve<IFido2MakeCredentialConfirmationUserInterface>();
#endif
private CancellationTokenSource _searchCancellationTokenSource;
private readonly ILogger _logger;
@@ -51,9 +46,6 @@ namespace Bit.App.Pages
CipherOptionsCommand = CreateDefaultAsyncRelayCommand<CipherView>(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddFabCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddCipherCommand = CreateDefaultAsyncRelayCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
@@ -61,7 +53,6 @@ namespace Bit.App.Pages
public ICommand CipherOptionsCommand { get; }
public ICommand AddCipherCommand { get; }
public ICommand AddFabCipherCommand { get; }
public ExtendedObservableCollection<CipherItemViewModel> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public string AutofillUrl { get; set; }
@@ -177,14 +168,6 @@ namespace Bit.App.Pages
public async Task SelectCipherAsync(CipherView cipher)
{
#if ANDROID
if (_fido2MakeCredentialConfirmationUserInterface.Value.IsConfirmingNewCredential)
{
await _fido2MakeCredentialConfirmationUserInterface.Value.ConfirmAsync(cipher.Id, cipher.Login.HasFido2Credentials, null);
return;
}
#endif
string selection = null;
if (!string.IsNullOrWhiteSpace(AutofillUrl))

View File

@@ -1,6 +1,5 @@
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -28,7 +27,7 @@ namespace Bit.App.Pages
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
PreviousPageInfo previousPage = null, bool deleted = false, bool showTotp = false, AppOptions appOptions = null)
PreviousPageInfo previousPage = null, bool deleted = false, bool showTotp = false)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
@@ -51,7 +50,6 @@ namespace Bit.App.Pages
_vm.CollectionId = collectionId;
_vm.Deleted = deleted;
_vm.ShowTotp = showTotp;
_vm.AppOptions = appOptions;
_previousPage = previousPage;
if (pageTitle != null)
{
@@ -162,8 +160,6 @@ namespace Bit.App.Pages
return;
}
await _vm.CheckOrganizationUnassignedItemsAsync();
// Push registration
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);

View File

@@ -1,7 +1,6 @@
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
@@ -46,8 +45,6 @@ namespace Bit.App.Pages
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService;
private readonly IConfigService _configService;
private readonly IEnvironmentService _environmentService;
private readonly ILogger _logger;
public GroupingsPageViewModel()
@@ -64,8 +61,6 @@ namespace Bit.App.Pages
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_configService = ServiceContainer.Resolve<IConfigService>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true;
@@ -109,7 +104,6 @@ namespace Bit.App.Pages
public List<Core.Models.View.CollectionView> Collections { get; set; }
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
public AppOptions AppOptions { get; internal set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService;
@@ -705,59 +699,5 @@ namespace Bit.App.Pages
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
return folders.Any() ? folders : null;
}
internal async Task CheckOrganizationUnassignedItemsAsync()
{
try
{
if (AppOptions?.HasJustLoggedInOrUnlocked != true)
{
return;
}
AppOptions.HasJustLoggedInOrUnlocked = false;
if (!await _configService.GetFeatureFlagBoolAsync(Core.Constants.UnassignedItemsBannerFlag)
||
!await _stateService.GetShouldCheckOrganizationUnassignedItemsAsync())
{
return;
}
var waitSyncTask = Task.Run(async () =>
{
while (_syncService.SyncInProgress)
{
await Task.Delay(100);
}
});
await waitSyncTask.WaitAsync(TimeSpan.FromMinutes(5));
if (!await _cipherService.VerifyOrganizationHasUnassignedItemsAsync())
{
return;
}
var message = _environmentService.SelectedRegion == Core.Enums.Region.SelfHosted
? AppResources.OrganizationUnassignedItemsMessageSelfHost041624DescriptionLong
: AppResources.OrganizationUnassignedItemsMessageUSEUDescriptionLong;
var response = await _deviceActionService.DisplayAlertAsync(AppResources.Notice,
message,
null,
AppResources.RemindMeLater,
AppResources.Ok);
if (response == AppResources.Ok)
{
await _stateService.SetShouldCheckOrganizationUnassignedItemsAsync(false);
}
}
catch (TimeoutException) { }
catch (Exception ex)
{
_logger.Exception(ex);
}
}
}
}

View File

@@ -70,10 +70,5 @@ namespace Bit.App.Pages
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, name: Name, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
protected override async Task AddFabCipherAsync()
{
await AddCipherAsync();
}
}
}

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